Skip to content

TypeScript: a local that shadows a project import is ignored from a nested arrow; the call links to the import #2949

Open

Description

@inth3shadows

If a function declares a local that shadows a name the file imports from a project file, a call to that name from a nested arrow links to the import instead of the local. The same code importing from a package resolves correctly in every shape, thanks to #2855.

// lib.ts
export function skipString(j: number): number { return j + 1; }

// use.ts
import { skipString } from './lib';
export function scanA(s: string) {
  const skipString = (j: number): number => j + 2;
  return skipString(0);                                          // line 4
}
export function scanB(s: string) {
  function skipString(j: number): number { return j + 3; }
  return skipString(0);                                          // line 8
}
export function scanC(s: string) { return skipString(0); }       // line 10
export function scanD(s: string) {
  const skipString = (j: number): number => j + 2;
  const skipValue = (j: number): number => skipString(j);        // line 13
  return skipValue(0);
}
export function scanE(s: string) {
  function skipString(j: number): number { return j + 3; }
  const skipValue = (j: number): number => skipString(j);        // line 18
  return skipValue(0);
}
call from './lib' (project) from 'some-pkg' (package) expected
line 4 (scanA, own local) no edge local at line 3 local
line 8 (scanB, own local function) no edge local at line 7 local
line 10 (scanC, no shadow) lib.ts skipString none (package) the import
line 13 (nested arrow, const shadow) lib.ts skipString (resolvedBy: import) ✗ local at line 12 local
line 18 (nested arrow, function shadow) lib.ts skipString (resolvedBy: import) ✗ local at line 17 local

The two marked rows are wrong edges. The two "no edge" rows for the project import are missing edges rather than wrong ones. The import resolver appears to check for a shadow only in the calling node's own scope, not in the functions around it, and only the package-import path (#2855) prefers the local.

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

In this repo. src/resolution/frameworks/react.ts has the same shape. readObjectValue declares const skipString (lines 392–396), and the nested skipValue arrow calls it at line 401. That call links to skipString in object-literal.ts, which react.ts imports. The same shape occurs again at line 441, against a second local skipString declared at line 432. I found these by checking every TS calls edge against the TypeScript checker.

Expected. Apply the same lexical shadow check to a name imported from a project file as to one imported from a package: the innermost binding in scope at the call wins, including bindings in enclosing functions.

Related: #2855, #1715.

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. Added: scanA and scanB get no edge at all, to the local or anywhere, which is a separate gap. 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