Skip to content

TypeScript: method calls on a receiver typed by inference (const x = new Foo()) produce no CALLS edge #1974

Closed

Description

@dazaiio

Version

codebase-memory-mcp 0.10.8

Platform

macOS (Apple Silicon)

Install channel

GitHub release archive / install.sh / install.ps1

Binary variant

standard

What happened, and what did you expect?

A method call whose receiver is a const initialised with new Foo() is not resolved: no CALLS edge is created from the calling function to the method. The identical call resolves correctly when the receiver carries an explicit type annotation (const x: Foo = new Foo()).

Expected: both forms produce a CALLS edge. const x = new Foo() is the ordinary way to write this, and the README states that Hybrid LSP performs "parameter binding, return-type inference, generic substitution" so that "the resulting graph mirrors what an IDE 'Go to Definition' would resolve". tsc and tsserver resolve both forms identically.

Impact: on a 5.5k-symbol TypeScript codebase this leaves 752 of 1087 non-test Method nodes (69%) with zero incoming CALLS, versus 36% for Function nodes. That makes trace_path, impact analysis and any "is this dead code" question untrustworthy in one direction — a silent graph is indistinguishable from a genuinely uncalled method.

Reproduction

  1. Code — two dummy files under /tmp/repro/src:

src/store.ts

export class WidgetStore {
  async removeById(id: string): Promise<void> {
    void id
  }
  async findById(id: string): Promise<string> {
    return id
  }
}

export function helperFn(id: string): string {
  return id
}

src/service.ts

import { WidgetStore, helperFn } from './store'

const inferred = new WidgetStore()
const annotated: WidgetStore = new WidgetStore()

export async function run(id: string) {
  helperFn(id)                    // A: plain function     -> expected CALLS
  await inferred.findById(id)     // B: inferred receiver  -> expected CALLS
  await annotated.removeById(id)  // C: annotated receiver -> expected CALLS
}
  1. Commands:

codebase-memory-mcp cli index_repository '{"repo_path":"/tmp/repro","mode":"full","name":"repro"}'
codebase-memory-mcp cli query_graph '{"project":"repro","query":"MATCH (a)-[:CALLS]->(b) RETURN a.name AS caller, b.name AS callee"}'

  1. Result — 3 rows:

src/service.ts WidgetStore
run helperFn
run removeById <- C, annotated receiver: resolved

Expected 4 rows. run -> findById (B, inferred receiver) is absent.

The discriminator is the receiver, not the method: swapping which method each receiver calls swaps which edge disappears. Indexing reports parse_partial: 0, so this is call resolution, not parsing. There is also no incoming edge of any other type into the unresolved method — only DEFINES from its file and class — so USAGE / CALL_REFERENCE do not act as a fallback.

Additional observation, NOT reduced to a minimal case: the same codebase also loses CALLS edges for object-literal repositories — export const fooRepo = { async bar() {…} } called as fooRepo.bar(…) from another module. Minimal reproductions of that shape resolve correctly, so something about the real files (~700 lines, 33 members) is involved and I could not isolate it. Mentioning it only as context for the aggregate: 1482 of 4138 non-test Function nodes have zero incoming CALLS, and an audit of a random sample of 60 found a genuine call-shaped reference elsewhere in the tree for 72% of them.

Logs


Diagnostics trajectory (memory / performance / leak issues)


Project scale (if relevant)

27,757 nodes / 86,358 edges / ~1,900 files

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Activity

  1. added
    language-requestRequest for new language support
    parsing/qualityGraph extraction bugs, false positives, missing edges
    windowsWindows-specific issues
    on Aug 31, 2026
  2. added
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Sep 5, 2026
  3. DeusData commented on Oct 17, 2026

    @DeusData
    Owner

    Thank you @dazaiio for the precise report. Putting the inferred and the annotated receiver side by side made the cause easy to pin down.

    This is fixed on main by 3984795 (#1354, merged in PR #2407) and released in v0.12.0: new ImportedClass() now types the instance through its import binding, so a receiver typed by inference resolves exactly like an annotated one.

    Verified on current main (9f85f10) with your exact two-file repro (src/store.ts + src/service.ts):

    • run -> helperFn (lsp_ts_import)
    • run -> findById (lsp_ts_method), through the inferred const inferred = new WidgetStore()
    • run -> removeById (lsp_ts_method), through the annotated receiver

    #1999 will add your module-level shape as a regression test. The object-literal repository observation from your report is a separate shape and is not covered by this fix; we are looking at it separately.

    Closing as completed. If you still see a missing edge on v0.12.0 or later, please reopen with the snippet.

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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions