Skip to content

TS: !f<T>(x) / typeof f<T>(x) get no calls edge; the reference is recorded as "!f" #2594

Closed

Description

@inth3shadows

Summary

In TypeScript, a call with type arguments behind a prefix operator gets a reference named after the operator plus the function: !isUser<User>(x) is recorded as !isUser, typeof make<Foo>(1) as typeof make. Those names match nothing, so the call gets no calls edge. The same call without type arguments, !isUser(x), is recorded as isUser and resolves. if (!isFoo<T>(x)) is a common way to call a generic type guard, so its callers go missing from the graph.

This is the same grammar quirk as #2586 (await f<T>(x)), with the other prefix operators.

Reproduction (verified)

On main at c29caf2, from source, Linux (WSL2), Node 22, WASM extractor (CODEGRAPH_KERNEL=0). The same result for .ts and .tsx:

export function guard(x: unknown) { if (!isUser<User>(x)) return; }
export function plainGuard(x: unknown) { if (!isUser(x)) return; }
export function kind() { return typeof make<Foo>(1); }
export function neg() { return -count<number>(1); }
export function v() { void run<Foo>(); }

extractFromSource gives these calls references:

L1 guard      -> "!isUser"
L2 plainGuard -> "isUser"
L3 kind       -> "typeof make"
L4 neg        -> "-count"
L5 v          -> "void run"

Expected: isUser, isUser, make, count, run.

Likely cause

tree-sitter-typescript gives a generic call lower precedence than a prefix operator, so it parses !isUser<User>(x) as a call whose callee is !isUser: (call_expression function: (unary_expression (identifier)) type_arguments: … arguments: …). extractCall reads the callee text as is (tree-sitter.ts#L5314, #L5649). The native kernel reads the same field (extractors.rs#L1401). That code path is inferred from reading the code; the reference names above are verified.

A fix could take the operand of a unary_expression callee when the call has type_arguments, in both the wasm extractor and the kernel. The PR for #2586 adds a helper for the await case that this could extend.

Not checked

  • The native kernel arm (CODEGRAPH_KERNEL unset). It reads the same field, so it is likely the same.
  • ArkTS (.ets).

Activity

  1. colbymchenry commented on Oct 22, 2026

    @colbymchenry
    Owner

    Thanks for the precise report. This is fixed on main by #2742: a call with type arguments behind !, typeof, - or void, like !isUser<T>(x), now links to the function it calls in .ts, .tsx and .ets files, and no !f or typeof f names are recorded anymore. We checked all five shapes from your report. It's merged but not released yet, so it ships in the next release after 1.6.2.

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