Skip to content

fix(resolution): don't guess project methods for untyped JS built-in method calls - #1941

Closed
danusha2345 wants to merge 1 commit into
colbymchenry:mainfrom
danusha2345:fix/js-builtin-method-fallback
Closed

danusha2345 wants to merge 1 commit into
colbymchenry:mainfrom
danusha2345:fix/js-builtin-method-fallback

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Problem

When a JS/TS member call's receiver has no inferred type, matchMethodCall's strategy 3 guesses the target from the method name alone. It binds to the only project method with that name (0.7), or to one whose class shares a word with the receiver (0.65).

For built-in prototype methods, the guess is almost always wrong: lines.map(...), seen.has(k), acc.push(x), re.exec(s), params.set(...). Whichever project class happens to declare the only map or has gets every such call as a caller. A parameter declared as an array isn't protected either:

// src/adapter.ts
export class GraphAdapter { async map(req?: string) { return req ?? ''; } async filter() { return 1; } }
// src/use.ts
export function names(items: { name: string }[]): string[] {
  const lines = 'a\nb'.split('\n');
  const upper = lines.map((l) => l.toUpperCase());          // -> GraphAdapter::map (0.7)
  return items.filter((i) => i.name).map((i) => i.name).concat(upper); // -> GraphAdapter::filter (0.7)
}

The effect on this repo's own index: GraphAdapter::map has 684 callers, all Array#map. LRUCache::has and LRUCache::set have 362 and 140, mostly Set/Map/URLSearchParams. The Node::toString callers are Buffer#toString. callers and impact for these methods are mostly noise.

Fix

A curated list, JS_BUILTIN_METHOD_NAMES in js-builtins.ts, holds the common methods of Array, Map/Set, String, Promise, RegExp and Object. In strategy 3 only, a JS/TS-family call to one of these names now needs receiver evidence: the receiver's camel-case words must name a candidate's class. For example, treeCache.get can still reach LRUCache::get. Without that evidence, the call stays unresolved.

These paths are unchanged:

  • typed receivers (private cache: LRUCache, new LRUCache() locals, typed parameters);
  • this.field calls;
  • Class.method matches.

This is the JS counterpart of the existing PYTHON_BUILT_IN_METHODS guard.

Measured

Each corpus was indexed with main (ba3c21e) and with this branch. Node counts are identical, no edges were gained, and all lost edges are calls:

corpus lost main targets
codegraph 1,216 GraphAdapter::map 684, LRUCache::has 362, LRUCache::set 140, Node::toString 26
vite @8492422 47 MemoryFiles::has 38, Clients::delete 9
excalidraw 282 History::push 201, AppCursor::set 60, AppBucketFill::fill 8

I read about 40 random lost edges, plus every toString/exec one, and all were wrong. I also matched every variable declared as a target class (: X or = new X) against the lost receivers and found 0 hits, so no true edge was lost. Examples of removed edges: acc.push → History::push, embeddedLinkCache.set on a module-level new Map() → AppCursor::set, and context.fill() on a canvas context → AppBucketFill::fill.

Not fixed here: word-overlap evidence still lets a few wrong edges through, such as srcCache.get on a real Map → LRUCache::get at 0.65. Tightening that would also drop true *Cache edges.

Tests

The new __tests__/js-builtin-method-calls.test.ts has 8 cases:

  • Must drop the edge, each failing on main: the repro, Set#has, and Promise#then against project methods with those names.
  • Controls that must keep the edge: a typed field, a new local, a typed parameter, name evidence (treeCache.get), and a lone method with a non-builtin name.

The full suite passes.

🤖 Generated with Claude Code

…thods

The name-only method fallback bound an untyped `lines.map()`, `seen.has(k)`
or `p.then()` to whichever project class declared the only `map`/`has`/`then`
(confidence 0.7), or to a same-named method via receiver/qualified-name word
overlap (0.65). On codegraph's own index that fabricated ~1,200 call edges,
most of them into GraphAdapter::map and LRUCache::has/set.

For TS/JS-family refs whose method name is a common Array/Map/Set/String/
Promise/RegExp/Object prototype method, the fallback now requires the
receiver's words to name a candidate's class (`treeCache.get` -> LRUCache
keeps its edge) and otherwise declines. Typed receivers, `this.field` calls
and `Class.method` matches resolve before this fallback and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345

Copy link
Copy Markdown
Contributor Author

Closing in favour of #2028. It is the same receiver-evidence guard, applied earlier and more strictly. One small note: this PR also listed exec and test (RegExp) and hasOwnProperty, which the upstream list does not have. Worth adding if you revisit it. Thanks for landing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant