mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-09 22:33:39 +00:00
fix(resolution): decouple the wrapper peel bound; leave the chain cap at 3 (#2766)
Two halves, and the headline one is a negative result. DECOUPLED (the real fix). `unwrapTransparentReceiver` used `MAX_CHAIN_DEPTH` as its iteration bound. The two answer unrelated questions — how many chain hops do we type, versus how many redundant parens might someone write — so raising the chain cap would silently widen the paren peel as a side effect, and the shared name reads as intentional enough to hide it. The coupling got worse when the await/subscript work added a peel call at loop entry. Now `MAX_TRANSPARENT_WRAPPER_DEPTH`, its own constant. The parse-worker comment that hardcoded "(3)" no longer restates the number. NOT RAISED, on measurement. The premise was that a chain deeper than the cap is discarded whole rather than truncated, so a 4-hop builder chain "contributes nothing at all". The first half is true. The second is not. `fourHopChain` was added to the TypeScript corpus as a declared extra to make the question answerable at all — without a chain longer than the cap, raising the cap measures nothing: cap 3: NO chain minted (confirmed by probing the emitter) -> RESOLVES cap 4: chain minted -> RESOLVES It resolves at both. At 3 the TEXT CASCADE answers, because it owns the fallback path and runs to its own `COMPOUND_RECEIVER_MAX_DEPTH` of 8. So the cap bounds which chains are typed STRUCTURALLY, not which calls resolve — raising it moves work from the cascade to the fold and changes no edge. Verified across the whole matrix: totals identical at 3 and 4, callDrops 102 at both. Left at 3. The fixture is committed so the next person to reach for this number inherits the measurement rather than the intuition. (An earlier version of that fixture was three steps, not four — `root.getSvc().getUser().address` counts hops in the source text, not steps in the receiver — so it fit inside the old cap and measured nothing. Corrected before drawing any conclusion.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
59c054338d
commit
a4b75ffdb2
5 changed files with 112 additions and 4 deletions
|
|
@ -1,5 +1,46 @@
|
|||
# Receiver-resolution baseline
|
||||
|
||||
## U6 — the depth cap does NOT limit resolution. Measured, not raised.
|
||||
|
||||
The premise was that a chain deeper than `MAX_CHAIN_DEPTH` (3) is discarded
|
||||
whole rather than truncated, so a 4-hop builder chain "contributes nothing at
|
||||
all". The first half is true; the second is not.
|
||||
|
||||
`fourHopChain` was added to the TypeScript corpus as a declared extra
|
||||
specifically to make the question answerable — without a chain longer than the
|
||||
cap, raising the cap measures nothing:
|
||||
|
||||
```ts
|
||||
root.getSvc().getUser().address.getCity().save();
|
||||
// ^step1 ^step2 ^step3 ^step4 receiver of `save` = 4 steps
|
||||
```
|
||||
|
||||
| Cap | Chain minted? | Cell state |
|
||||
|---|---|---|
|
||||
| 3 | **none** (confirmed by probing the emitter directly) | **RESOLVES** |
|
||||
| 4 | `2\|root\|cgetSvc\|cgetUser\|faddress\|cgetCity` | RESOLVES |
|
||||
|
||||
The site resolves at BOTH depths. At 3 it resolves through the text cascade,
|
||||
which owns the fallback path and runs to its own
|
||||
`COMPOUND_RECEIVER_MAX_DEPTH` of 8.
|
||||
|
||||
**So the cap bounds which chains are typed structurally, not which calls
|
||||
resolve.** Raising it moves work from the cascade to the fold without changing a
|
||||
single edge — measured across the whole matrix: totals identical at 3 and 4,
|
||||
`callDrops` 102 at both.
|
||||
|
||||
Left at 3. The fixture is committed so the next person to reach for this number
|
||||
inherits the measurement instead of the intuition.
|
||||
|
||||
What DID need fixing: `unwrapTransparentReceiver` shared `MAX_CHAIN_DEPTH` as
|
||||
its iteration bound. The two answer unrelated questions — how many chain hops do
|
||||
we type, versus how many redundant parens might someone write — so raising the
|
||||
chain cap would have silently widened the paren peel as a side effect. That
|
||||
coupling got worse when the await/subscript work added a peel call at loop
|
||||
entry. Now `MAX_TRANSPARENT_WRAPPER_DEPTH`, its own constant.
|
||||
|
||||
---
|
||||
|
||||
## U9 — the epistemic hedge has TWO producers, and only one is a defect
|
||||
|
||||
`impact` reports `epistemic: 'lower-bound'` for two independent reasons that were
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
"awaitParen": "VISIBLE-GAP",
|
||||
"explicitTypeArgs": "RESOLVES",
|
||||
"indexElement": "VISIBLE-GAP",
|
||||
"fourHopChain": "RESOLVES",
|
||||
"fieldReceiverCall": "RESOLVES",
|
||||
"decoratedReceiverBase": "N/A",
|
||||
"decoratedFieldType": "RESOLVES"
|
||||
|
|
|
|||
|
|
@ -140,11 +140,22 @@ const CORPORA = [
|
|||
{
|
||||
lang: 'typescript',
|
||||
ext: '.ts',
|
||||
// `fourHopChain` exists to make MAX_CHAIN_DEPTH answerable. Without a chain
|
||||
// longer than the cap, raising the cap measures nothing and the question
|
||||
// "what does depth N buy?" has no instrument behind it.
|
||||
extraShapeIds: ['fourHopChain'],
|
||||
support: {
|
||||
'models.ts': `export class Address {
|
||||
'models.ts': `export class City {
|
||||
save(): void {}
|
||||
}
|
||||
|
||||
export class Address {
|
||||
save(): void {}
|
||||
getCity(): City {
|
||||
return new City();
|
||||
}
|
||||
}
|
||||
|
||||
export class User {
|
||||
name: string = '';
|
||||
address: Address = new Address();
|
||||
|
|
@ -185,6 +196,26 @@ export class Service {
|
|||
},
|
||||
{ id: 'explicitTypeArgs', member: 'save', body: 'svc.getTyped<User>().save();', note: 'PF4' },
|
||||
{ id: 'indexElement', member: 'save', body: 'repos[0].save();', note: 'PF5' },
|
||||
{
|
||||
// FOUR STEPS in the receiver of `save`: getSvc, getUser, address,
|
||||
// getCity. One more than the pre-U6 cap of 3, so the chain is DISCARDED
|
||||
// WHOLE at depth 3 and types end to end at depth 4 — the whole basis
|
||||
// for choosing the number. (An earlier version of this fixture had only
|
||||
// three steps and therefore resolved at both depths, measuring nothing.)
|
||||
id: 'fourHopChain',
|
||||
member: 'save',
|
||||
body: 'root.getSvc().getUser().address.getCity().save();',
|
||||
raw: `class Root {
|
||||
getSvc(): Service {
|
||||
return new Service();
|
||||
}
|
||||
}
|
||||
|
||||
export function fourHopChain(root: Root): void {
|
||||
root.getSvc().getUser().address.getCity().save();
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fieldReceiverCall',
|
||||
member: 'save',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,24 @@ export const CALL_EXPRESSION_TYPES = new Set([
|
|||
/**
|
||||
* Hard limit on chain depth to prevent runaway recursion.
|
||||
* For `a.b().c().d()`, the chain has depth 2 (b and c before d).
|
||||
*
|
||||
* A chain deeper than this is DISCARDED WHOLE, not truncated:
|
||||
* `extractMixedChain` returns an undefined base and the encoder refuses to mint
|
||||
* a partial chain, because a base-side prefix decodes cleanly as a shorter,
|
||||
* complete-looking chain and would type the receiver against the wrong member.
|
||||
* Correct, but it means a builder chain one hop too long contributes nothing at
|
||||
* all rather than degrading.
|
||||
*
|
||||
* DELIBERATELY NOT RAISED. Measured (see bench/receiver-resolution/BASELINE.md,
|
||||
* `fourHopChain`): a 4-step chain mints NOTHING at this cap — confirmed by
|
||||
* probing the emitter directly — and the site still RESOLVES, because the text
|
||||
* cascade that owns the fallback path runs to `COMPOUND_RECEIVER_MAX_DEPTH` (8)
|
||||
* and answers where the structural fold declined.
|
||||
*
|
||||
* So the cap bounds which chains are typed STRUCTURALLY, not which calls
|
||||
* resolve. Raising it moves work from the cascade to the fold without changing
|
||||
* any edge, and the fixture that proves it is committed so the next person to
|
||||
* reach for this number has the measurement rather than the intuition.
|
||||
*/
|
||||
export const MAX_CHAIN_DEPTH = 3;
|
||||
|
||||
|
|
@ -522,11 +540,25 @@ const TRANSPARENT_RECEIVER_WRAPPERS = new Set([
|
|||
'parenthesized_expression', // `(svc)`
|
||||
]);
|
||||
|
||||
/**
|
||||
* Iteration bound for the wrapper peel. Its OWN constant, not `MAX_CHAIN_DEPTH`.
|
||||
*
|
||||
* The two answer unrelated questions — "how many chain hops do we type?" versus
|
||||
* "how many redundant parens might someone write?" — and sharing one number
|
||||
* meant raising the chain cap silently widened this loop as a side effect. That
|
||||
* coupling is easy to miss precisely because the shared name reads as
|
||||
* intentional. `((x))` nests twice; nothing real nests deeply.
|
||||
*/
|
||||
const MAX_TRANSPARENT_WRAPPER_DEPTH = 3;
|
||||
|
||||
/** Peel transparent wrappers off a base receiver node. */
|
||||
function unwrapTransparentReceiver(node: SyntaxNode): SyntaxNode {
|
||||
let current = node;
|
||||
// Bounded: `((x))` nests twice; nothing real nests deeply.
|
||||
for (let i = 0; i < MAX_CHAIN_DEPTH && TRANSPARENT_RECEIVER_WRAPPERS.has(current.type); i++) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < MAX_TRANSPARENT_WRAPPER_DEPTH && TRANSPARENT_RECEIVER_WRAPPERS.has(current.type);
|
||||
i++
|
||||
) {
|
||||
const inner = current.namedChildren?.find((c) => c !== null);
|
||||
if (inner === undefined || inner === null) break;
|
||||
current = inner;
|
||||
|
|
|
|||
|
|
@ -283,7 +283,10 @@ export interface ExtractedCall {
|
|||
* `svc.getUser().save()` → chain=[{kind:'call',name:'getUser'}], receiverName='svc'
|
||||
* `user.address.save()` → chain=[{kind:'field',name:'address'}], receiverName='user'
|
||||
* `svc.getUser().address.save()` → chain=[{kind:'call',name:'getUser'},{kind:'field',name:'address'}]
|
||||
* Length is capped at MAX_CHAIN_DEPTH (3).
|
||||
* Length is capped at MAX_CHAIN_DEPTH. Deliberately NOT restating the number
|
||||
* here: this comment previously hardcoded `(3)` and would have drifted the
|
||||
* moment the cap moved, which is exactly the kind of stale doc that reads as
|
||||
* authoritative.
|
||||
*/
|
||||
receiverMixedChain?: MixedChainStep[];
|
||||
argTypes?: (string | undefined)[];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue