diff --git a/gitnexus/bench/receiver-resolution/baseline.json b/gitnexus/bench/receiver-resolution/baseline.json index f5264af5e..32191bd8b 100644 --- a/gitnexus/bench/receiver-resolution/baseline.json +++ b/gitnexus/bench/receiver-resolution/baseline.json @@ -29,9 +29,9 @@ "plainDeepChain": "RESOLVES", "optionalChain": "RESOLVES", "nonNullAssert": "RESOLVES", - "awaitParen": "VISIBLE-GAP", + "awaitParen": "RESOLVES", "explicitTypeArgs": "RESOLVES", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fourHopChain": "RESOLVES", "fieldReceiverCall": "RESOLVES", "decoratedReceiverBase": "N/A", @@ -60,7 +60,7 @@ "nonNullAssert": "N/A", "awaitParen": "N/A", "explicitTypeArgs": "VISIBLE-GAP", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fieldReceiverCall": "INVISIBLE-GAP", "decoratedReceiverBase": "N/A", "decoratedFieldType": "INVISIBLE-GAP" @@ -72,7 +72,7 @@ "nonNullAssert": "N/A", "awaitParen": "N/A", "explicitTypeArgs": "N/A", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fieldReceiverCall": "RESOLVES", "decoratedReceiverBase": "RESOLVES", "decoratedFieldType": "RESOLVES" @@ -94,9 +94,9 @@ "plainDeepChain": "RESOLVES", "optionalChain": "N/A", "nonNullAssert": "N/A", - "awaitParen": "VISIBLE-GAP", + "awaitParen": "RESOLVES", "explicitTypeArgs": "N/A", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fieldReceiverCall": "RESOLVES", "decoratedReceiverBase": "N/A", "decoratedFieldType": "RESOLVES" @@ -118,7 +118,7 @@ "plainDeepChain": "RESOLVES", "optionalChain": "INVISIBLE-GAP", "nonNullAssert": "VISIBLE-GAP", - "awaitParen": "VISIBLE-GAP", + "awaitParen": "RESOLVES", "explicitTypeArgs": "VISIBLE-GAP", "indexElement": "VISIBLE-GAP", "fieldReceiverCall": "RESOLVES", @@ -144,7 +144,7 @@ "nonNullAssert": "N/A", "awaitParen": "N/A", "explicitTypeArgs": "INVISIBLE-GAP", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fieldReceiverCall": "RESOLVES", "decoratedReceiverBase": "RESOLVES", "decoratedFieldType": "INVISIBLE-GAP" @@ -168,7 +168,7 @@ "nonNullAssert": "VISIBLE-GAP", "awaitParen": "RESOLVES", "explicitTypeArgs": "RESOLVES", - "indexElement": "VISIBLE-GAP", + "indexElement": "RESOLVES", "fieldReceiverCall": "RESOLVES", "decoratedReceiverBase": "N/A", "decoratedFieldType": "RESOLVES" diff --git a/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts b/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts index 007b06d34..6096476c8 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/passes/compound-receiver.ts @@ -479,7 +479,15 @@ export function foldReceiverChain( // first. That turned a correct edge into a WRONG one (Ruby // `Factory.new.run` → `Product.run`), which is the failure mode this whole // line of work exists to avoid. Decline and let the cascade answer. - if (options.constructionSyntax?.selector === step.name) return undefined; + // `step.name !== undefined` is load-bearing, not defensive. The name-free + // step kinds (`await`, `index`) carry no name, and a language with no + // `constructionSyntax` has no selector — so the bare equality below was + // `undefined === undefined`, which matched EVERY name-free step and vetoed + // the whole fold before it ran. That is why subscript and await receivers + // minted a chain, fired the gate, and still produced no edge. + if (step.name !== undefined && options.constructionSyntax?.selector === step.name) { + return undefined; + } // A position whose declared type named no class can only be advanced by an // unwrapping step. Folding an ordinary member off it would look the member diff --git a/gitnexus/test/integration/resolvers/typescript.test.ts b/gitnexus/test/integration/resolvers/typescript.test.ts index d01f68609..82a5b6b6e 100644 --- a/gitnexus/test/integration/resolvers/typescript.test.ts +++ b/gitnexus/test/integration/resolvers/typescript.test.ts @@ -3344,12 +3344,17 @@ export class Service { `, 'main.ts': `import { Service } from './models'; -export async function droppedCall(svc: Service): Promise { - // An await-parenthesized receiver. Structural typing does NOT cover this - // shape — \`extractMixedChain\` reaches \`await …\`, which is not a chain node, - // so no chain is minted and the site still reaches the drop recorder. The - // \`!\` spelling used to serve here until structural typing resolved it. - (await svc.getUserAsync()).save(); +export async function droppedCall(svc): Promise { + // An UNANNOTATED parameter. The chain mints fine, but the base has no type + // binding to resolve against, so the site reaches the drop recorder. + // + // Third fixture for this case: \`!\` served until structural typing resolved + // it, then the await-parenthesized form served until name-free step kinds + // resolved that too. Both were shapes the resolver merely did not SUPPORT + // yet, so each fix moved the goalposts. An untyped receiver carries no type + // information at all, so no amount of resolver work can type it — which is + // what makes it a stable choice rather than the next one to be fixed. + svc.getUser().save(); } export function droppedWrite(svc: Service | null): void {