Skip to content

TypeScript: a local assigned from a static factory is typed by its variable name, not the factory's return type #2946

Open

Description

@inth3shadows

A method call on a local assigned from a static factory with a declared return type links to an unrelated class, picked by the variable's name.

// db.ts
export class DatabaseConnection {
  static initialize(p: string): DatabaseConnection { return new DatabaseConnection(); }
  close(): void {}
}
// writer.ts
export class StoreWriter { close(): void {} }
// use.ts
import { DatabaseConnection } from './db';
export function run() {
  const writer = DatabaseConnection.initialize('x');
  writer.close();
}
variant run → close
as above (writer) StoreWriter.close (resolvedBy: instance-method) ✗
same, plus an unrelated class Socket { close() {} } StoreWriter.close ✗
variable renamed to conn no edge
const writer: DatabaseConnection = … DatabaseConnection.close ✓
const writer = new DatabaseConnection() DatabaseConnection.close ✓
only DatabaseConnection has close DatabaseConnection.close ✓

The return type of initialize names the class, but the call result isn't used to type the local. Without that, the receiver-name guess matches writer to StoreWriter. This is a wrong edge rather than a missing one: the neutral name gives no edge, which is the safe outcome.

Reproduced on main at 31ec908 with CODEGRAPH_KERNEL=0, and on 69af19a with the native kernel too (Linux/WSL2, Node 22.23.2). This repo has examples: __tests__/concurrent-locking.test.ts:73 (writer = DatabaseConnection.initialize(dbPath); writer.close()) links to src/extraction/store-writer.ts's close.

Expected. Type the local from the declared return type of a static method it is assigned from, as the explicit annotation and new already are. Failing that, don't take the name guess when the initializer is a call on a project class. The Go side has the same rule in #2664 ("never to one it shares a word with"). Neither is verified: I haven't traced where the TS receiver typing should read the return type.

Related: #2175 (limits on the receiver-name guess) and #750 (chained Foo.create().bar() for other languages; the chained TS form DatabaseConnection.initialize('x').close() links nothing here). I searched open issues and PRs for static factories, declared return types and receiver-name guesses, and found nothing that covers the TS assigned-local form.

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: #3038, the Python twin (PR #3120 covers Python only and doesn't fix this TypeScript form). 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