Skip to content

fix(resolution): constrain inheritance/import reference target kinds (#1536, #1537) - #1796

Merged
colbymchenry merged 3 commits into
mainfrom
forge/fix-1537-1536-reference-target-kind
Sep 8, 2026
Merged

colbymchenry merged 3 commits into
mainfrom
forge/fix-1537-1536-reference-target-kind

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Fixes #1536. Fixes #1537.

An external supertype such as Rust's std::error::Error could bind to a local enum variant or type alias named Error; an import such as node:path could bind to an unrelated class property named path. Candidate ranking treated node kind as optional and did not establish whether an imported supertype existed in the repository.

Lands and supersedes #1538 by @ctype-lab: cherry-picked b55ad79f, f010a09e, and 7f2035e1 onto main at 040ba388, retaining upstream authorship. Applies target-kind eligibility before name ranking and a gate across resolution strategies, checks external Rust/ES-module supertype bindings, and classifies Svelte/Vue/Astro script imports consistently. Conflict resolution preserves main's visibility, module isolation, lexical shadowing, PHP import-alias handling, and deferred-reference lifecycle. Existing changelog entries are preserved; the two fix notes are under Unreleased. Re-index after upgrading to clear existing false edges.

Linux x64 verification, Node 22.19.0, real node:sqlite databases (CODEGRAPH_KERNEL=0 for identical WASM extraction in both worktrees):

  • Copied the unchanged upstream __tests__/reference-target-kind.test.ts into an isolated main worktree at 040ba388: 7 failed, 5 passed, including both issue repros and all three SFC variants.
  • Same test on the Forge tip 9ad9a113: 12 passed; valid in-repo traits, re-exported workspace traits, class/interface/type-alias supertypes, and relative SFC imports remain connected.
  • Broader resolution slice: 344 passed across 14 files, including resolution, framework integration, visibility, lexical reachability, PHP aliases, CFML inheritance, ArkTS, Erlang, emitted import specifiers, and value references.
  • npm run build: passed, including TypeScript, viewer build, and copied grammar/viewer asset checks.

Reproduction command in each worktree with Node 22 on PATH:

CODEGRAPH_KERNEL=0 node node_modules/vitest/vitest.mjs run __tests__/reference-target-kind.test.ts --project engine --maxWorkers=2 --minWorkers=1 --reporter=verbose

Verification used this native Linux workspace; the full suite and agent A/B were not run.

ctype-lab and others added 3 commits September 8, 2026 20:37
An inheritance reference bound to whatever local symbol shared its name.
The name-matcher scores node kind as a bonus, never a filter, and awards
no bonus at all for inheritance refs, so `use std::error::Error;` +
`impl Error for MapperError {}` resolved to the local `MapperError::Error`
VARIANT — an implementation relationship absent from the source.

Two changes, both needed. Filtering by kind alone was measured and it
only RELOCATES the false edge: with enum members excluded, the same 7
refs moved onto an unrelated local `type Error` alias, which is a legal
supertype kind and therefore harder for a consumer to reject.

1. Eligibility before ranking. `matchByExactName` restricts its candidate
   pool to kinds that can BE a supertype, so a legitimate trait outranks
   a same-named variant instead of merely losing its edge. `resolveOne`
   is wrapped by a gate that applies the same set to every other strategy
   at one seam — filtering inside the name-matcher would have missed the
   framework, import, chain and CFML paths.
2. Locality. A name imported from outside the repository has no in-repo
   referent at all, so no candidate is correct. Only oracles that cannot
   be wrong are consulted: Rust `use` paths rooted at a stdlib crate, and
   `isExternalImport` for ES modules. Generalizing the Rust side to "the
   module path doesn't resolve to a file" was tried and reverted — a
   crate re-exporting a sibling's modules (`pub use pupil_core::ports;`)
   has no directory to walk, and that version deleted 13 real trait
   implementations.

Measured on a Rust/Tauri project (2,682 nodes): the 11 false inheritance
edges are gone, all 59 real trait relationships are preserved, and node
count is unchanged. On this repository as a control, the only edge
removed is a class recorded as extending a function. Synthesized-edge
counts are identical in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`import * as path from 'node:path'` is unresolvable — the module is
external — so the name-matcher fell back to finding any node called
`path`, and a common word like path/url/join/get matches a class property
or interface method somewhere in almost any repo. Nothing in any
supported language lets an import bind to a member that only exists
inside a type; you import the type.

Same shape as the inheritance gate that precedes it: eligibility applied
to the candidate pool before ranking, plus the resolveOne gate as the
backstop for every other strategy.

On this repository as a control: 19 imports pointing at methods and 4 at
properties are gone (all of them coincidences — `Walker::join`,
`Telemetry::events`), 3 refs now find the module constant they actually
name, node count unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`isExternalImport` had a TS/JS branch listing typescript/tsx/javascript/jsx/
arkts, so for Svelte, Vue and Astro it fell through every branch and returned
false — "not external" — for `import { Foo } from 'some-npm-pkg'`.

An SFC imports inside its `<script>` block (Astro: the `---` frontmatter) with
ordinary ES module syntax; `extractImportMappings` already routes all three
through the same `extractJSImports`. So the classifier disagreed with the
extractor about what those imports are.

Effect on the preceding commit: its locality check asks `isExternalImport`, so
it silently did nothing for SFCs. A class in a `.svelte`/`.vue`/`.astro` file
implementing a type imported from an npm package still bound to whatever local
class shared that name — verified against this branch before the fix, all three
languages.

The language set is now one constant used by both the classifier and the
locality check, so they cannot drift apart again. Relative and aliased
specifiers are unaffected: the branch returns "not external" for `./…`,
workspace members, tsconfig alias prefixes, `@/`, `~/` and `src/` exactly as it
does for `.ts`.

No edge changes on this repository as a control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 374b3b4 into main Sep 8, 2026
@colbymchenry
colbymchenry deleted the forge/fix-1537-1536-reference-target-kind branch September 8, 2026 20:49
colbymchenry added a commit that referenced this pull request Sep 27, 2026
…e pair (#2055)

The inheritance target-kind gate (#1796) rejected any extends/implements
resolution whose target cannot be a supertype. VS Code declares every
service twice under one name, `export const IFoo = createDecorator<IFoo>(…)`
beside `export interface IFoo`, and the import resolver takes the first
export of that name, which is the value. The gate then dropped the edge
instead of taking the interface the same file declares.

Found by the pre-release v1.6.0 vs main comparison: 864 `implements` edges
lost on vscode (AccessibilityService -> IAccessibilityService and ~860 like
it), plus the ~5.8k interface-dispatch call edges synthesized from them.
The gate now moves a TypeScript constant/variable target to the one
same-named supertype in its file; with no such sibling it still drops the
edge. vscode: implements 4,139 -> 5,003 (v1.6.0: 5,436, 254 of them on
non-type targets; now 0), node count unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants