fix(resolution): resolve calls to object-literal namespace members (#1573) - #1597
Open
colbymchenry wants to merge 1 commit into
Open
fix(resolution): resolve calls to object-literal namespace members (#1573)#1597colbymchenry wants to merge 1 commit into
colbymchenry wants to merge 1 commit into
Conversation
…1573) `export const api = { call() {…}, get: () => {…} }` used as a module's API surface is a common TypeScript shape, and no call of the form `api.call()` ever linked to the member: the members are extracted as plain functions with BARE qualified names inside the constant's extent (there is no `api::call`), so the `Container::member` lookup the class-shaped kinds use (#825) bails on kind `constant`, the declared-type inference for singleton instances (#1292) finds no type in a literal, and the same-file strategies only consider classes and `method` kinds. The result was an edge to the constant through an import and no edge at all in the defining file — `codegraph callers` / impact reported zero for methods called from everywhere. Resolve the member by CONTAINMENT instead: a node named `member` whose source range lies inside the value's range, in the value's own file. One helper, used from both halves: - import path: when the imported value is a constant/variable, try the literal member before the #1292 instance inference; - same-file path: a same-file constant/variable receiver (TS/JS family only) is checked before the class-name strategies. Precision rules: calls accept callable kinds only; a declaration nested inside another member's body is not a member; nothing outside the value's range can donate a match (a same-named top-level function, a method returned by a factory the value merely holds). Class statics and non-literal values keep their existing paths byte-for-byte. Tests: the issue's repro (same-file + cross-file callers of `m`, a decoy `m` in a third file untouched, the `C.s()` static control unchanged, no edge to the constant), arrow + method members with a nested declaration skipped, and a non-literal value receiver left on its existing path. The two positive tests fail on main; the control passes both ways. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxZj6W6Y1SHXwvpT3uwJpK
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.
Fixes #1573. Thanks @IAliceBobI — the report had the root cause exactly right, and the fix sits one layer up from the suggested spot (resolution rather than the container-kind set), for the reason below.
What was wrong
Methods of an exported object-literal constant —
export const api = { call() {…}, get: () => {…} }used as a module's API surface — never received a call edge fromapi.call(), same-file or through an import. The members are extracted as plain functions with bare qualified names (call, notapi::call) sitting inside the constant's source extent, so:Container::memberlookup the class-shaped kinds use (Cross-file static method calls (ClassName.staticMethod()) are dropped: calls edge mis-promoted to instantiates #825) bails on kindconstant, and even withconstantadded to that set there is noapi::callto find;methodkinds, so the call resolved to nothing at all.Net effect:
callers/ impact reported zero for methods called from everywhere, with no boundary warning because nothing aboutobj.method()looks dynamic.What this does
Adds one helper that resolves a member by containment — a node named
memberwhose source range lies inside the value's range, in the value's own file — and uses it from both halves:Container::memberlookup and before the TypeScript: calls through an imported singleton instance resolve to the exported constant instead of the class method #1292 instance inference, so the cross-file edge lands on the method instead of the constant.Precision rules, all tested: calls accept callable kinds only; a declaration nested inside another member's body is not a member; nothing outside the value's range can donate a match — a same-named top-level function, or a method returned by a factory the value merely holds — so those cases keep today's behavior rather than guessing. Class statics (
C.s()) and non-literal values are untouched.Extraction and qualified names are deliberately left alone: changing how literal members are named would have to be mirrored in the native kernel byte-for-byte, and the resolver-side lookup is contained and language-gated.
Tests
sameFileCallersandcrossFileCallerare both callers ofm; a decoymin a third file gets none; theC.s()static control resolves exactly as before;crossFileCallerno longer has acallsedge to the constant.function call()nested insideget's body is never taken forapi.call().const obj = makeObj()) with a same-named top-levelmin the file: no false attribution, existing behavior kept.main; the control passes both ways, as a guard should.With the built CLI on the issue's
a.ts/b.ts:codegraph callers m→ 2 callers (sameFileCallers,crossFileCaller);callers sunchanged; edgessameFileCallers -> m(0.85) andcrossFileCaller -> m(import, 0.9), none toobj.🤖 Generated with Claude Code
https://claude.ai/code/session_01LxZj6W6Y1SHXwvpT3uwJpK