Skip to content

TypeScript: this.a.b.m() resolves by bare name, onto an unrelated method or a property #2948

Open

Description

@inth3shadows

A call two fields deep from this (this.a.b.m()) is matched by the bare name m. It links to any project symbol with that name: an unrelated class's method, or even a non-callable interface field.

// heap.ts
export class BinaryHeap { push(x: number): void {} }
export interface Conf { includes: string[]; }

// use.ts
export class Scene {
  cache = { list: [] as number[], names: [] as string[] };
  a() { this.cache.list.push(1); }
  b(t: string) { return this.cache.names.includes(t); }
}
export function c(o: { data: number[] }) { o.data.push(1); }
export function d(w: { dbg?: { data: number[] } }) { w.dbg?.data.push(1); }
export function e(list: number[]) { list.push(1); }
call edge on main expected
this.cache.list.push(1) BinaryHeap.push (exact-match) none
this.cache.names.includes(t) Conf.includes, a string[] field (exact-match) none
o.data.push(1) none none
w.dbg?.data.push(1) none none
list.push(1) none none

Only the this-rooted chain falls through to name matching. The same depth on a parameter correctly links nothing, so the receiver-evidence rule for built-in method names (#2028) seems to be skipped for this shape. A calls edge to a property is wrong whatever the receiver.

Reproduced on main at 31ec908 (6162d872 since then only touches the watcher), with CODEGRAPH_KERNEL=0. Linux/WSL2, Node 22.23.2.

Scale (found by checking every TS calls edge against the TypeScript checker). 44 edges on excalidraw (14bb42e) and 15 on this repo. Examples:

  • this.extractor?.functionTypes.includes(nodeType) in src/extraction/tree-sitter.ts:1132 links to the includes: string[] field in src/extraction/kernel/loader.ts.
  • this.collab.excalidrawAPI.getSceneElementsIncludingDeleted() in excalidraw's excalidraw-app/collab/Portal.tsx links to Collab's own same-named method rather than the API's.

Expected. Type the chain through the field declarations where they are known. Failing that, give the call the same treatment as o.data.push(): no edge for a built-in name, and never an edge to a property. Rust had the same shape in #2836, fixed by #2846 (keep the whole receiver chain). #920 (open) covers the one-level this.<field>.m().

Related: #2836, #2846, #920, #2028, and #1566 (closed: built-in Map.get/set landing on project methods), the general case this shape still escapes.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions