fix(ts-lsp): type imported class receivers for cross-file method calls (#1354) - #2407
Merged
Merged
Conversation
#1354) A method call on an instance of a class defined in another file produced no CALLS edge, while the identical code in the class's own file resolved via lsp_ts_method. The type-aware tier typed the receiver wrongly in two places, so the method lookup in the shared TS registry missed and the call fell to method_not_in_registry: 1. `new C()` always qualified a bare constructor name against the CURRENT module. `const t = new ImportedClass(); t.m()` and `new ImportedClass().m()` were typed as a phantom `<this module>.ImportedClass`. The constructor type now follows TS scoping: a class declared in this module, else the class an unambiguous import binding names, else the old spelling. 2. ts_import_symbol_qn (and the copy of its heuristic in resolve_type_with_imports) treated an import value ending in ".<name>" as an already-qualified symbol QN. For the dominant TS convention of a file named after its class (`Foo.ts` exports `class Foo`, module `x.Foo`, class `x.Foo.Foo`), the receiver was typed as the MODULE, for `new`, annotations and parameters alike. The registry now decides the ambiguous case: a registered `module.name` wins, else the value is the symbol QN as before. Both are resolved through the existing shared cross-file registry; no name-based fallback is reintroduced. Cost is one or two hashed lookups on the finalized overlay / sealed shared registry, per `new` expression or per ambiguous import spelling during the walk; nothing is looked up during registration and nothing is written to the shared registry. typeorm (3,593 .ts files, shallow f279fd13): lsp_ts_method 2,551 -> 3,130, all 579 new edges cross-file (cross-file lsp_ts_method 696 -> 1,275), 0 edges lost, other strategies unchanged, index time unchanged. 20 sampled new edges checked against source: 20 true positives. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData
force-pushed
the
fix/issue-1354
branch
from
September 30, 2026 22:34
c02dff1 to
3984795
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A method call on an instance of a class imported from another file got no CALLS edge. The same code in the class's own file resolved. There were two receiver-typing defects in
internal/cbm/lsp/ts_lsp.c:new C()used the wrong module. It qualified a bare class name against the current module. It now follows TS scoping: a local class first, then an unambiguous import binding, then the old spelling..<name>was read as an already-qualified symbol QN. When a file is named after its class (Foo.tsexportsFoo), the receiver was typed as the module instead of the class. The registry now decides the ambiguous case.Both resolve through the existing shared cross-file registry. No name-based fallback is added. The only added cost is hashed lookups during the walk: none during registration, no tail-scan, and the shared registry is never written.
Tests:
ts_lsp:tslsp_crossfile_new_expression_receiver_issue1354andtslsp_crossfile_same_name_file_receiver_issue1354, on the production shared-registry path.pipeline:pipeline_ts_crossfile_new_instance_method_call_issue1354, end to end, with a same-file control.make -f Makefile.cbm lint-ciis clean.Proof on typeorm/typeorm (3,593 .ts files):
lsp_ts_methodedgesA sample of 20 new edges, each checked against the declared receiver type in the source, was 20/20 correct.
No overlap with #2305 (unresolved-call coverage signal). Constructor parameter properties, unannotated field initializers and tsconfig
pathsalias imports are separate gaps and are not covered here.Thanks to @artaommahe for the sharp same-file control, @braggintime for the 0.8.1 → 0.9.0 edge census, and @chonorov for confirming on v0.10.8.
Fixes #1354