mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-21 00:21:30 +00:00
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(scope-resolution): make interface dispatch generic-instantiation aware (#2912) Interface-dispatch fan-out walked the subtype closure with generic arguments erased, so `IValidator<string>` and `IValidator<int>` — one declaration, one subtype list — were indistinguishable and a call through the first reached `IntValidator.Check(int)`, a target no runtime dispatch can produce. The arguments were already in the capture, unread: every language anchors `@reference.inherits` on the whole base node while `@reference.name` keeps the erased base. `ReferenceSite.typeArguments` is therefore derived generically in `scope-extractor.ts` from the anchor's own spelling — no per-language query changed — covering C#, Java, TypeScript, Kotlin, Go (`Base[int]` embedding), Python (`Base[User]`) and Swift; Rust and Dart anchor on the bare name and get nothing, which reads as "unknown". `preEmitInheritanceEdges` is the only code that pairs a heritage site with a resolved (subtype, supertype), so it records the instantiation there and hands it to the dispatch pass. The closure is then walked carrying a substitution, as a type checker would: `Wrapper<T> : IValidator<T>` binds T to the receiver's argument and stays reachable from every instantiation, while its own subtypes are matched against that binding. An incompatible hop is skipped without being marked seen, so a type reachable by a second, compatible path still gets its edge, and without descending, since its subtypes inherit the mismatch. Pruning happens only on positive evidence that two instantiations differ. Unknown arguments on either side, an arity that does not line up, an unresolved qualified spelling of the same simple name, or an argument that might be a type variable the language never captured all keep the target. Telling an uncaptured type VARIABLE from a concrete type is the crux: `typeParameters` is absent both for a non-generic declaration and for every declaration in a language whose query omits `@declaration.type-parameters`, so the pass reads the evidence in front of it — one run resolves one language, so a single generic declaration anywhere in it proves the captures record parameters. A language recording neither arguments nor parameters keeps exactly its pre-#2912 fan-out. Type arguments are compared as resolved declarations rather than spellings, so `Models.User` and an imported `User` are one type; the new optional `ScopeResolver.normalizeTypeArgument` hook canonicalizes a language's predefined aliases, implemented for C# (`string` ≡ `String`) where mixing the spellings would otherwise delete a real implementor. Fan-out cap, skipped-target reporting, overload selection and non-generic closure behaviour are unchanged. SCHEMA_BUMP 60 -> 64: the heritage arguments are a parse-time capture, so a warm cache would replay pre-fix sites and leave the filter silently inert on unchanged files (61/62/63 are claimed by open PRs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StNKYi7Qxv5DnSURZuFBef * fix(scope-resolution): close the two generic-dispatch gaps (#2912) The first commit left two shapes on the pre-#2912 fan-out. Both are now covered, and the second one turned out to need a route the pipeline did not have at all. **Folded receivers (Cases 0 and 3b).** `this._validator.Check(x)` is typed by the compound fold, and the fold answers with a CLASS — which is exactly what loses the instantiation, since `IValidator<string>` and `IValidator<int>` fold to one declaration. The fold now reports the SPELLING it typed each receiver position from, through a pure side channel (`recordReceiverType`) added to the one helper every declared-type route already shares plus the two return-type routes; resolution is unchanged whether or not a caller passes it. The reader keeps the last report and uses it only when it names the class the fold returned, so an intermediate position cannot lend its arguments to another class. This covers the dependency-injection shape the issue is really about — a field-held generic interface — and multi-hop chains, where it is the last hop's spelling that types the receiver. **Rust and Dart heritage.** Neither recorded arguments, for two different reasons, so both routes exist now: - Rust's `@reference.inherits` anchor is the trait identifier INSIDE a `generic_type`. Widening the anchor would move the site's range, and that range is part of every inheritance edge's id, so the arguments arrive through a new `@reference.type-arguments` sub-tag instead. - Dart's `implements` / `with` never become reference sites at all: they travel as heritage MARKERS and their edges are emitted by the language hook. The arguments ride the marker payload as an optional fourth field (dropped, not encoded, when the spelling contains the marker delimiter), and `ScopeResolver.emitHeritageEdges` now receives the same sink `preEmitInheritanceEdges` writes to, so whichever pass emits an edge records that edge's instantiation. Dart also gained the `@declaration.type-parameters` capture, without which its own type VARIABLES are indistinguishable from concrete arguments and `class Box<T> implements Validator<T>` would be pruned from every instantiation. Note this makes Rust and Dart record their instantiations; it does not make them fan out. Interface dispatch still fires only for a receiver whose folded type is an `Interface` symbol, so a Rust `Trait` or a Dart abstract `Class` receiver has no secondary targets to filter. Widening that gate emits new edges for several languages and belongs to its own issue. **Two matcher rules the wider coverage exposed.** A WILDCARD names a set of types rather than one — `Repo<? extends User>` holds a `Repo<User>`, and Kotlin's `Repo<*>` / `Repo<out User>` say the same — so a position with one on either side is unknown; nullable spellings trip the same test, which costs a little precision in the safe direction. And insignificant whitespace inside a nested spelling (`Map<string, User>` vs `Map<string,User>`) is no longer a difference. One expectation changed in the #2833 field-receiver matrix: a `Repo<Repo<User>>` receiver no longer reaches `UserRepo implements Repo<User>`. That edge is precisely the false positive this issue is about, and the primary edge to the interface's own declaration — which is what the matrix row exists to prove — is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StNKYi7Qxv5DnSURZuFBef * refactor(scope-resolution): apply the quality pass to the #2912 change Three cleanups, no behaviour change. **One balanced-list scanner, not two.** `erasedTypeApplication` and `typeApplicationArguments` each carried a copy of the same fiddly scan — one bracket list, balanced, closing on the last character, non-empty — differing only in what they did with the result. Both now call `balancedTailList`; the rule that rejects `User[][]` and `Repo<User>?` lives in one place instead of being free to drift between two. **The receiver's arguments are parsed after the gates, not before them.** `emitInterfaceDispatchFor` takes the receiver's declared SPELLING and parses it itself, once the owner is known to be an Interface with subtypes. Every one of the five cases calls it unconditionally and the overwhelming majority of receivers are concrete classes that return at the first line, so the parse was running per resolved receiver site to be discarded immediately. Case 4 and Case 6 now hand over the string they already hold, and the folded-receiver helper returns the recorded spelling rather than parsing it. **One question gates the whole instantiation apparatus.** Inside the closure walk, the graph-id lookups now hang off "is the supertype's instantiation known?" — false for every non-generic receiver and for every language that captures no heritage arguments, which is what makes those walks cost exactly what they cost before #2912. Also lifted the argument-route choice in `pass5CollectReferences` out of a nested ternary into a named `heritageTypeArguments`, where the reason the explicit sub-tag wins over the anchor text can be stated once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StNKYi7Qxv5DnSURZuFBef * test(scope-resolution): cover generic interface dispatch in Kotlin and Go (#2912) Extends the #2912 dispatch coverage past C#/Java/TypeScript. No production code changes — the derivation is language-agnostic by construction (`heritageTypeArguments` reads the heritage anchor's own spelling), so the question was only which languages actually reach the filter. Kotlin rides the shared heritage pre-pass; Go reaches the same filter from the other side, matching implementors structurally while the receiver's `Validator[string]` spelling carries the instantiation. Both are confirmed to prune the mismatched implementor. Each language gets a NON-GENERIC control asserting the fan-out still reaches every implementor. Without it the `not.toContain` assertion passes just as well when a language emits no dispatch edge at all — which is what Dart, Python and Rust were measured doing for this receiver shape, generic or not. They are deliberately not asserted on here: a "filtered correctly" test over a path that never fans out measures nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * test(bench): re-baseline the Rust and Dart capture fingerprints for #2912 The Rust trait-impl and Dart heritage capture changes this branch makes are additive TEXT on existing matches — each carries the instantiation the clause was written with — so they drift the scope-capture digest without adding or removing a match. The baselines were never re-measured when those captures landed, which left `measure.mjs --check` red on this branch independently of the merge. Re-measured rather than hand-edited. Rust's capture_groups_fp (3556) and fixture_count (202) are unchanged across the move, which is the evidence that this is digest drift and not a capture-set regression. The other 13 languages are byte-identical; 15/15 pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * refactor(scope-resolution): quality pass over the #2912 change Cleanup only — no behavior change. Findings from a four-angle review (reuse, simplification, efficiency, altitude), applied where they were verified. Reuse / duplication: * `stripTrailingCallSuffix` was a second copy of `matchingOpenParen`'s backward balanced-paren scan. Both now live in `template-arguments.ts` beside `balancedTailList`, for the reason that helper was shared in the first place: two copies of a scan this fiddly are free to disagree. * The two call-return arms of the compound fold repeated the same four-part expression character for character; they share `classOfReturnType` now, the return-type twin of `classOfDeclaredType`, which keeps the "look up by rawName, report the erased application" pairing in one place. * `pipeline/run.ts` implemented first-writer-wins twice — once in the pre-pass and once in the provider sink. One store, one sink, one rule; the pass keeps its `Set<string>` return and the callable-flow-only arm stops building an empty map to satisfy a widened return shape. Simplification: * `subtypeParametersComplete` dropped a disjunct that could never decide: every `subDef` reaching it comes out of the same loop that sets `languageCapturesTypeParameters`, from exactly those defs. * The heritage-argument lookup asked "is the supertype's instantiation known?" three times; `superGraphId` now gates the block once. * `TypeArgumentResolver` and `HeritageInstantiationResult` un-exported — no consumer outside their module. Efficiency (all on the per-site dispatch walk): * `resolveSupertypeArgument` captures only the site, so it is built once per site instead of once per subtype visited; the subtype's scope id is looked up once per subtype instead of once per argument position. * `erasedTypeApplication` no longer runs on every fold hop through a call — the spelling is built only once the lookup has found a class, since it is discarded otherwise. * `normalize`+`compact` computed once per side rather than twice. * Regex literals and the identity `normalize` fallback hoisted to module scope. * C# `System.` prefix stripped with `startsWith`/`slice` instead of a regex. Verified: tsc clean, build clean, 1994 scope-resolution unit tests, 171 generic-dispatch + generic-field-receiver integration tests, 15/15 capture bench fingerprints unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * style: apply Prettier to the two files the quality pass reformatted Whitespace only — `quality / format` (npx prettier --check .) was red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * fix(scope-resolution): close the generic-dispatch review findings (#2912) Addresses the gitnexus-check review on #2939. A repeated type variable was rebound rather than unified: `class C<T> : Pair<T, T>` accepted a `Pair<string, int>` receiver, with `T = int` silently replacing `T = string` and the bogus substitution carried to the next hop. It now unifies, and prunes only on the same positive evidence the concrete path demands — an undecidable repeat keeps the target with no binding. A type PARAMETER of the declaration enclosing either side is now recognised and never compared. `subtypeParametersComplete` is evidence about the SUBTYPE's parameter list and says nothing about a `T` written at the call site, so `void Run<T>(IValidator<T> v) { v.Check(x); }` pruned every implementor: unbounded, `T` grounds to nothing; bounded, it grounds to its BOUND. Both read as a difference of type. That is the missing-edge failure this filter is built to avoid, and it is the common dependency-injection shape in C#, Java and Kotlin. Making that recognition reliable is why generic METHODS now capture `@declaration.type-parameters` in C#, Java and Kotlin — TypeScript already did, which is why its generic functions never had the defect. The capture feeds the existing `bindsTypeParameter` guard, so a method-level `T` also stops resolving to a same-named class in every other lookup. C# alias normalization additionally strips the `global::` qualifier, which `import-decomposer` already unwraps elsewhere: `global::System.String` read as unequal to `string` and pruned a live implementor. The C# captures golden fixture is regenerated for the new capture; the extractor reads `@declaration.type-parameters` generically, so no reader changed. SCHEMA_BUMP 64 already covers these capture changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * fix(scope-resolution): close the two remaining gitnexus-check findings (#2912) `balancedTailList` counted ONE bracket family, so a crossed pair slipped through: scanning `Foo<Bar]>` it never sees the `]`, reaches the final `>` at depth zero, and reports `Bar]` as a balanced argument list — which `typeApplicationArguments` then splits and `erasedTypeApplication` rebuilds a spelling from. It now tracks a stack of expected closers, so every closer must match the opener it actually closes and a crossed pair declines to `undefined`, the "unknown" both callers already fail open on. Well-formed mixed nesting (`List<Dict[a, b]>`) is unaffected. C# `normalizeTypeArgument` stripped `System.` from every qualified spelling, so `System.Custom` answered `Custom` and compared equal to an unrelated `Custom` elsewhere in the workspace. The strip is now earned: a keyword answers from the alias table first, and the qualifier is dropped only when what remains IS a predefined type. `System.Custom` is returned as written and goes to the identity comparison instead — the step that can actually tell two declarations apart. `global::System.String` still meets `string`. Both are pinned by unit tests, including the well-formed mixed nesting and the `global::`-qualified ordinary type that must keep its qualifier. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * docs(csharp): record why a shadowed `String` keeps its implementor (#2912) Answers a review finding rather than changing behavior. A workspace may declare its own type named `String`, shadowing the BCL simple name, and the alias table then reads `IValidator<String>` as the `string` instantiation and keeps that implementor. That is the SAFE direction, not an oversight: pruning instead would rest on the belief that two spellings differ, which is the missing-edge failure `generic-instantiation.ts` exists to avoid. Resolving rather than normalizing cannot settle it either — the identity comparison needs a `definitionId` from both sides, and a built-in name carries none, so "built-in versus workspace-declared implies different" would be a new prune with no positive evidence behind it. The cost is one surplus edge for that pair, which is exactly the pre-#2912 fan-out and no worse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB * refactor(scope-resolution): pair the receiver spelling with the class structurally (#2912) The fan-out needs the spelling a receiver position was typed from, because the class the fold returns has lost the generic arguments. That was carried by a PASS-LEVEL mutable holder, written by every declared-type lookup anywhere in the fold and read back through a def-id coincidence check, with the holder cleared by hand before each call site. Three things were load-bearing and none were enforced: * the reset had to be remembered at every call site. It was not: the Case 3b retry (`rawName` then `rawName + '()'`) reset once, BEFORE the first attempt, so a spelling reported by the attempt that failed could be attributed to the one that succeeded. * the holder outlived every resolution, so a site that resolved through a route reporting nothing could read the previous site's spelling if the def ids happened to line up. * the pairing itself was inferred from "whichever lookup reported last", not from the fold's own bookkeeping — losing branches (an MRO walk that moved on, a step later folded past) report too. `foldReceiverChain` already had the answer and threw it away: its final `FoldState` holds `def` and `declaredType` produced by the SAME step. It now reports that pairing last, so the structural route is the one that stands. `resolveCompoundReceiverTyped` returns `{def, declaredSpelling}` and owns a sink created and read within the single call, which is what removes the reset discipline — a local cannot be forgotten, and each of the two retry attempts carries its own. The def-id guard stays as the check that a report names the class actually returned. Behavior is unchanged: 1975 scope-resolution unit tests, 177 generic-dispatch and generic-field-receiver integration tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Px638Zyqa9CJMUU7DsJoB --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
238 lines
12 KiB
TypeScript
238 lines
12 KiB
TypeScript
/**
|
|
* `ReferenceSite` — a pre-resolution usage fact collected by `ScopeExtractor`
|
|
* (RFC §3.2 Phase 1; Ring 2 PKG #919).
|
|
*
|
|
* One record per `@reference.*` capture. The extractor records:
|
|
* - the name being referenced (method/field/class name),
|
|
* - the source range,
|
|
* - the innermost lexical scope containing the reference,
|
|
* - the reference kind (call, read, write, inherits, etc.),
|
|
* - optional call-form classification from `provider.classifyCallForm`,
|
|
* - optional explicit-receiver hint for dotted calls (`user.save()`),
|
|
* - optional arity for call sites.
|
|
*
|
|
* Reference sites are consumed by the resolution phase (RFC §3.2 Phase 4)
|
|
* which routes each through `Registry.lookup` / `resolveTypeRef` and
|
|
* emits the final `Reference` record into `ReferenceIndex`.
|
|
*
|
|
* **Pre-resolution only.** `ReferenceSite` intentionally carries no
|
|
* `toDef`, `confidence`, or `evidence`. Those are populated by the
|
|
* resolution step that reads this record and produces a `Reference`
|
|
* (defined in `./types.ts`).
|
|
*/
|
|
|
|
import type { ParameterTypeClass } from './symbol-definition.js';
|
|
import type { Range, ScopeId } from './types.js';
|
|
|
|
/**
|
|
* What kind of usage this reference represents — the graph-edge kind
|
|
* emitted after resolution (`CALLS`, `READS`, `WRITES`, etc.).
|
|
*
|
|
* Matches the `kind` field on `Reference` in `./types.ts` so the
|
|
* resolution phase can pass it through without re-classification.
|
|
*/
|
|
export type ReferenceKind =
|
|
| 'call'
|
|
| 'read'
|
|
| 'write'
|
|
| 'type-reference'
|
|
| 'inherits'
|
|
| 'import-use'
|
|
// An identifier in object-literal property-value position
|
|
// (`{ emitScopeCaptures: emitCppScopeCaptures }`, shorthand `{ hook }`).
|
|
// Resolution is owned entirely by the post-finalize property-dispatch pass
|
|
// (`emitPropertyDispatchCalls` via the callable-gated finalized-bindings
|
|
// walker `findCallableBindingInScope`; `resolveReferenceSites` skips these
|
|
// sites), so a non-function value never produces a reference. Emitted as a `USES`
|
|
// reference edge — NOT `CALLS` (a registration is not an invocation;
|
|
// Kythe `ref` / Joern `METHOD_REF` precedent). The invocation side is
|
|
// recovered separately by the property-dispatch pass, which uses
|
|
// `propertyKey` to synthesize CALLS at member-call sites (#2437).
|
|
| 'value-ref'
|
|
// A macro invocation (`log!(...)` / `vec![...]`). Resolved against
|
|
// `Macro`-labeled definitions ONLY (see `MacroRegistry`) so a macro
|
|
// never aliases a same-named free function — macros and functions are
|
|
// disjoint namespaces. Emitted as a `USES` edge, not `CALLS`.
|
|
| 'macro';
|
|
|
|
/**
|
|
* How a call site binds its target. Informs `Registry.lookup` Step 2
|
|
* (type-binding path):
|
|
* - `'free'` — bare call (no receiver); resolution via lexical chain.
|
|
* - `'member'` — dotted call (`x.foo()`); resolution via receiver type.
|
|
* - `'constructor'` — `new Foo()`; receiver is the class itself.
|
|
* - `'index'` — index expression (`arr[0]`); rare as a dispatch site.
|
|
*
|
|
* Only meaningful for `kind === 'call'`; ignored for reads/writes.
|
|
*/
|
|
export type CallForm = 'free' | 'member' | 'constructor' | 'index';
|
|
|
|
export interface ReferenceSite {
|
|
/** The name being referenced (e.g., `'save'`, `'User'`, `'count'`). */
|
|
readonly name: string;
|
|
/**
|
|
* Optional raw, qualified form of the referenced name when the source wrote
|
|
* a qualified path (e.g. a C++ base `struct D : Other::Inner` yields
|
|
* `'Other::Inner'`). `name` keeps the simple tail (`'Inner'`) for the existing
|
|
* scope-chain contract; resolution normalizes this via `normalizeQualifiedName`
|
|
* and resolves it against the full-path `QualifiedNameIndex` BEFORE the
|
|
* simple-tail walk, so a same-tail nested base resolves to the correct
|
|
* sibling instead of the first-inserted one (issue #1982). Populated only by
|
|
* per-language captures that emit `@reference.qualified-name`; absent
|
|
* otherwise, in which case resolution is unchanged.
|
|
*/
|
|
readonly rawQualifiedName?: string;
|
|
/**
|
|
* Top-level generic/template arguments the source wrote ON this reference —
|
|
* `class UserValidator : IValidator<string>` yields `['string']` on the
|
|
* `inherits` site whose `name` is `IValidator`.
|
|
*
|
|
* `name` is the BASE name and stays that way: every lookup in resolution is
|
|
* keyed by it, and one declaration answers for every instantiation of itself.
|
|
* This records what the erasure threw away, so a consumer that needs the
|
|
* INSTANTIATION — receiver-bound interface dispatch, which must not fan a
|
|
* `IValidator<string>` receiver out to an `IValidator<int>` implementor
|
|
* (#2912) — can ask for it without re-parsing the source.
|
|
*
|
|
* Derived generically from the anchor capture's own text (see
|
|
* `collectReferenceSites`), so no language query change is needed: an emitter
|
|
* whose `@reference.inherits` anchor spans the whole base gets this for free,
|
|
* and one whose anchor is the bare name simply leaves it absent.
|
|
*
|
|
* ABSENT MEANS UNKNOWN, never "not generic" — the two are indistinguishable
|
|
* here, and only the first is safe to act on. Consumers must fail OPEN on
|
|
* absence (keep the target), matching `SymbolDefinition.typeParameters`.
|
|
*/
|
|
readonly typeArguments?: readonly string[];
|
|
/** Source-text range of this reference. */
|
|
readonly atRange: Range;
|
|
/**
|
|
* Innermost lexical scope that contains `atRange`. Resolved by the
|
|
* extractor via position lookup and frozen here so the resolution
|
|
* phase doesn't re-compute it per call.
|
|
*/
|
|
readonly inScope: ScopeId;
|
|
readonly kind: ReferenceKind;
|
|
/** Set when `kind === 'call'`. */
|
|
readonly callForm?: CallForm;
|
|
/**
|
|
* Explicit receiver for dotted calls (`user.save()` → `{ name: 'user' }`).
|
|
* Passed through to `Registry.lookup.explicitReceiver`.
|
|
*/
|
|
readonly explicitReceiver?: { readonly name: string };
|
|
/** Argument count at the call site; used by `provider.arityCompatibility`. */
|
|
readonly arity?: number;
|
|
/**
|
|
* Object-literal key under which a `value-ref` site registers its value
|
|
* (`{ emitScopeCaptures: emitHook }` → `'emitScopeCaptures'`; shorthand
|
|
* `{ emitHook }` → `'emitHook'`). Consumed by the property-dispatch pass
|
|
* to connect member-call sites (`x.emitScopeCaptures()`) to registered
|
|
* functions (#2437). Only set for `kind === 'value-ref'`.
|
|
*/
|
|
readonly propertyKey?: string;
|
|
/**
|
|
* Inferred argument types at the call site, one per argument. An
|
|
* empty-string entry means "unknown" — consumers narrowing overload
|
|
* candidates treat unknown as any-match. Populated by languages
|
|
* that can derive types from literals / constructor expressions
|
|
* (C#: `42` → `'int'`, `"alice"` → `'string'`).
|
|
*/
|
|
readonly argumentTypes?: readonly string[];
|
|
/**
|
|
* Optional per-argument type-shape sidecar for languages that need
|
|
* cv/ref/pointer distinctions during constraint filtering. This is
|
|
* intentionally separate from `argumentTypes`, which stays normalized
|
|
* for existing overload narrowing and conversion-rank logic.
|
|
*/
|
|
readonly argumentTypeClasses?: readonly ParameterTypeClass[];
|
|
/**
|
|
* Compact encoding of a receiver that is itself an expression, so resolution
|
|
* can type it by folding over structure instead of re-parsing the receiver's
|
|
* source text.
|
|
*
|
|
* Format and the reason it is a string rather than `MixedChainStep[]` live in
|
|
* `receiver-chain-codec.ts` — briefly, the store's interning reviver re-shares
|
|
* objects only when they carry `nodeId` + `filePath`, which a chain step does
|
|
* not, so an object encoding would survive every warm load as fresh
|
|
* allocations.
|
|
*
|
|
* Absent whenever the receiver is a bare name, which is the overwhelming
|
|
* majority of sites — the field costs nothing where it is not needed.
|
|
*/
|
|
readonly receiverChain?: string;
|
|
/**
|
|
* This site sits in CALLEE position: it is the expression being invoked by an
|
|
* enclosing call, not a value the program otherwise consumes. Only ever set on
|
|
* `kind: 'read'` sites, and only by languages whose member-read capture also
|
|
* matches the callee of a member call (`obj.f()` yields both a `call` site on
|
|
* `f` and a `read` site on `obj.f`).
|
|
*
|
|
* It is a POSITION FACT, not a decision. Whether that read is redundant
|
|
* depends on what the tail resolves to, which the capture layer cannot know:
|
|
*
|
|
* - tail is a METHOD → the read duplicates the call's own edge and must be
|
|
* suppressed (an `ACCESSES → m` beside a `CALLS → m`
|
|
* at the same position is a phantom).
|
|
* - tail is a FIELD → the read is GENUINE. `h.dep.Work()` where
|
|
* `Work func() error` selects a func-typed field and
|
|
* then calls the value it holds; deleting the read
|
|
* erases the only evidence that the field was used
|
|
* (callback/hook structs, hand-rolled mocks).
|
|
*
|
|
* The suppression is therefore applied at edge emission, where the resolved
|
|
* target's kind is known — see `tryEmitEdge`. Absent on every site that is not
|
|
* in callee position, so nothing changes for languages that never set it.
|
|
*/
|
|
readonly inCalleePosition?: boolean;
|
|
/**
|
|
* This `inherits` site describes an embedded field written as a POINTER
|
|
* (`struct S { *T }`) rather than as a value (`struct S { T }`).
|
|
*
|
|
* Go's method-set rules make the two forms genuinely different, so the
|
|
* distinction cannot be normalized away without producing wrong answers
|
|
* (go.dev/ref/spec#Struct_types):
|
|
*
|
|
* - `S` embeds `T` → `MS(S)` and `MS(*S)` get promoted methods with
|
|
* receiver `T`; only `MS(*S)` also gets those with
|
|
* receiver `*T`.
|
|
* - `S` embeds `*T` → `MS(S)` AND `MS(*S)` get promoted methods with
|
|
* receiver `T` **or** `*T`.
|
|
*
|
|
* So with `func (t *T) Ping()`, `S{T}` does not implement a `Ping` interface
|
|
* by value while `S{*T}` does. Collapsing the forms makes both answers the
|
|
* same, and one of them is then wrong.
|
|
*
|
|
* A POSITION FACT, like `inCalleePosition`: the capture layer records how the
|
|
* field was spelled and resolution decides what it means. Set only by
|
|
* languages with pointer-embedding semantics (Go today); absent everywhere
|
|
* else, so every other language's sites stay byte-identical.
|
|
*/
|
|
readonly embeddedAsPointer?: boolean;
|
|
}
|
|
|
|
/**
|
|
* One step in a mixed receiver chain — the decoded form of a receiver that is
|
|
* itself an expression rather than a bare name.
|
|
*
|
|
* For `svc.getUser().address.save()`, the receiver of `save` decodes to
|
|
* `[{ kind: 'call', name: 'getUser' }, { kind: 'field', name: 'address' }]`
|
|
* over a base receiver of `svc`.
|
|
*
|
|
* Lives here rather than beside its producer because it is part of the
|
|
* ScopeExtractor output contract that this package owns: the producer
|
|
* (`extractMixedChain`) walks a tree-sitter AST and so must stay in the
|
|
* analyzer, but the shape it yields crosses into resolution.
|
|
*/
|
|
/**
|
|
* One hop in a receiver chain.
|
|
*
|
|
* `field` and `call` carry the member name they reach. `await` and `index` are
|
|
* NAME-FREE: the call step already holds the method name for an awaited call,
|
|
* and a subscript has no member name at all — an index expression's key is a
|
|
* value, not an identifier the resolver could look up. The codec encodes them
|
|
* as a bare sigil and rejects any trailing characters, so the encoder's
|
|
* non-empty-name guard stays live for exactly the two kinds it was written for.
|
|
*/
|
|
export type MixedChainStep =
|
|
| { kind: 'field' | 'call'; name: string }
|
|
| { kind: 'await' | 'index'; name?: undefined };
|