Skip to content

TypeScript: a local initialized from a library call takes the one-method guess (follow-up to #2589) #2947

Open

Description

@inth3shadows

Follow-up to #2589. A method call on an untyped local initialized from a library call still links to the project's only method with that name. #2811 fixed this for a class component's render() and lifecycle methods, but an ordinary class method is still guessed.

// app.ts
export class App { render(): void {} translate(x: number): void {} close(): void {} }

// use.ts
import { createRoot } from 'react-dom/client';
import * as net from 'net';
import { createServer } from 'net';
export function a(el: HTMLElement) { const root = createRoot(el); root.render(null); }
export function b() { const s = net.createServer(); s.close(); }
export function c() { const s = createServer(); s.close(); }
export function d(canvas: HTMLCanvasElement) { const ctx = canvas.getContext('2d')!; ctx.translate(1); }
export function e(canvas: HTMLCanvasElement) { canvas.getContext('2d')!.translate(1); }
function edge on main expected
a App.render (instance-method) none
b App.close none
c App.close none
d App.translate none
e (chained, no local) none none

Renaming the locals doesn't change anything, so this isn't the variable-name guess in #2946. The chained form e already links nothing; only the assigned local takes the guess. In each case the local's value comes from something the project doesn't declare: a package import, a node: namespace, or a method on a DOM-typed parameter. That fits #2169's "an outside type is not a project type" rule, but the type is never written down, so the rule doesn't fire.

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). On excalidraw (14bb42e), 63 instance-method edges land on project code where tsc says the method is a library one. For example, every context.translate(…) on a canvas.getContext("2d")! local, as in packages/element/src/renderElement.ts, links to App.translate in packages/excalidraw/components/App.viewport.ts. On this repo there are 58. For example, server.close() on net.createServer(…) in __tests__/cli-unlock.test.ts links to the close() of the viewer's server interface in src/ui-server/index.ts. The interface method gets callers it never has.

Possible direction (not verified). When the local's initializer is a call whose callee resolves outside the project (an import from a package or node:, or a member of a receiver already typed as an outside type), treat the local as outside-typed: the same "no guess" outcome as an annotated const r: Root = createRoot(el) in #2589. #2152 has PHP's version of this ("an untyped receiver keeps a unique method only when named for it").

Related: #2589, #2811, #2169, #2946.

Edited 2026-11-04: re-checked on main at f72c9d2 (with CODEGRAPH_KERNEL=0 and with the native kernel, Node 22.22.2): still reproduces. Related: #3036 (closed as covered by this issue and #2948). Nothing else changed.

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