Self-referencing causes non-portable inferred types (TS2742) false positive
🔎 Search Terms
- The inferred type of '...' cannot be named without a reference to '...'. This is likely not portable. A type annotation is necessary.
- TS2742
- Project reference redirect
- Self-reference
- Build mode
- Watcher
🕗 Version & Regression Information
Versions 4.7 - 5.9.3.
I've reproduced this in TypeScript 4.7.2, which is when self-referencing was first announced as a feature. This bug appears on the latest version of TypeScript at the time of writing (i.e. 5.9.3).
⏯ Playground Link
This issue requires multiple NPM packages. Small repro at https://github.com/gluxon/typescript-portability-error-false-positive-due-to-self-import
💻 Code
I've set up a repro at https://github.com/gluxon/typescript-portability-error-false-positive-due-to-self-import.
The code below is from the repro above and should match exactly. The TypeScript for the repro itself is only ~16 lines of code. In these packages, package a depends on b + c, and package b depends on c.
graph LR
a --> b
a --> c
b --> c
// packages/a/src/index.ts
import { B } from "b";
const b: B = {
c: { foo: "bar" },
};
export const c = b.c;
// packages/b/src/index.ts
import { C } from "c";
export interface B {
readonly c: C;
}
// packages/c/src/index.ts
export type { C } from "./C";
export type { C2 as C2 } from "./C2";
// packages/c/src/C.ts
export interface C {
readonly foo: "bar";
}
// packages/c/src/C2.ts
// 🚨 This self-reference causes the non-portable type false positive. 🚨
// Importing from "./C" instead of "c" fixes the issue.
import { C } from "c";
export type C2 = C;
🙁 Actual behavior
When running tsc with build mode, a false positive error is shown.
tsc --build --verbose packages/a/tsconfig.json
[8:40:22 PM] Building project '/Volumes/git/typescript-false-positive-non-portable-watcher-error/packages/a/tsconfig.json'...
packages/a/src/index.ts:7:14 - error TS2742: The inferred type of 'c' cannot be named without a reference to '../node_modules/c/src'. This is likely not portable. A type annotation is necessary.
7 export const c = b.c;
~
The error above should not happen since:
- Running
tscwithout the build flag does not show this issue. (See "Expected Behavior" below.) - The portability error is non-sensical. It's looking for a reference to package
c's source code in../node_modules/c/srcrather than its built.d.tsfiles. I've narrowed this to a bug with project reference redirects. - The portability error happens even when
cis a dependency ofa.
🙂 Expected behavior
Running tsc without the --build flag results in a successful compilation of package a.
# Compile manually in topological order.
tsc -p packages/c/tsconfig.json
tsc -p packages/b/tsconfig.json
tsc -p packages/a/tsconfig.json
Additional information about the issue
No response
Adding a bit of usage context — It isn't a high priority for this issue to be fixed from my side.
It used to be a very pressing issue since my team was getting 15+ of these errors from the TypeScript watcher running from tsc --build --watch, which added noise and made it harder to discover real errors. We've since rolled out a lint rule to prevent self-referencing, which eliminated this problem in our codebases.
I wanted to file this issue regardless since other TypeScript developers may benefit from the information in this report. I spent several days debugging the TypeScript compiler to see what was causing the false positives only in tsc --build-watch before realizing it was the self-reference.
Hope this issue report is helpful!
Adding a bit more debugging notes from when I was looking deep into this. I think there's two potential issues going on:
- Self-references in built files resolve to original files instead of built files
- Discrepancies in
--buildmode when resolving symbol chains
1. Self-references in built files resolve to original files instead of built files
When running --traceResolution in the minimal repro
tsc -p packages/a/tsconfig.json --traceResolution
The self-reference in C2.d.ts was resolving to packages/c/src/index.ts.
======== Resolving module 'c' from '/Volumes/git/typescript-false-positive-non-portable-watcher-error/packages/c/dist/C2.d.ts'. ========
Using compiler options of project reference redirect '/Volumes/git/typescript-false-positive-non-portable-watcher-error/packages/c/tsconfig.json'.
Module resolution kind is not specified, using 'NodeNext'.
======== Module name 'c' was successfully resolved to '/Volumes/git/typescript-false-positive-non-portable-watcher-error/packages/c/src/index.ts'. ========
I would have expected it to resolve to packages/c/dist/index.d.ts.
2. Discrepancies in --build mode when resolving symbol chains
When attempting to serialize the problematic inferred type in --build mode:
packages/a/src/index.ts:7:14 - error TS2742: The inferred type of 'c' cannot be named without a reference to '../node_modules/c/src'. This is likely not portable. A type annotation is necessary.
7 export const c = b.c;
~
TypeScript iterates through these parentSpecifiers to find a potential import specifier candidate. This is computed in checker.ts#L8405-L8411
parentSpecifiers = parents!.map(symbol =>
some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)
? getSpecifierForModuleSymbol(symbol, context)
: undefined
);
Build Mode
In --build mode, the parentSpecifiers evaluate to:
❯ parentSpecifiers
0 = '../node_modules/c/src/C'
1 = '../node_modules/c/src/C'
2 = '../node_modules/c/src/C'
3 = '../node_modules/c/src'
4 = '../node_modules/c/src'
CLI
However, without build mode, the parentSpecifiers show:
❯ parentSpecifiers
0 = '../node_modules/c/src/C'
1 = '../node_modules/c/src/C'
2 = '../node_modules/c/src'
3 = 'c'
It seems in --build mode, the option for just c as the module specifier is not available. The --build mode only knows about the non-portable specifiers in ../node_modules/c/src.
No self-reference
For completeness, if you remove the self-reference, the parentSpecifiers look correct and don't reference c/src ever.
❯ parentSpecifiers
0 = '../node_modules/c/dist/C'
1 = '../node_modules/c/dist/C'
2 = 'c'
🤖 Thank you for your issue! I've done some analysis to help get you started. This response is automatically generated; feel free to 👍 or 👎 this comment according to its usefulness.
Similar Issues
Here are the most similar issues I found
- (69%) microsoft/typescript#39117: With project references, some cross-package imports of inferred types are emitted with relative paths
- (69%) microsoft/typescript#58914: "The inferred type of X cannot be named without a reference to Y" (TS2742) still happens, when working with npm link to link packages manually
- (69%) microsoft/typescript#56260: Invalid synthesized import specifiers on infered types in declarations
- (69%) microsoft/typescript#58710: 5.5.0-dev.20240524 regression on "The inferred type of 'X' cannot be named without a reference"
- (68%) microsoft/typescript#26863: Relative reference in generated .d.ts file in a monorepo
- (68%) microsoft/typescript#50270: Self-reference / import of the current package doesn't work in an mjs file, but does in a ts file in src
- (68%) microsoft/typescript#49171: Portability error issued when reachable source file is redirected to an unreachable target
- (68%) microsoft/typescript#32970: Type resolution in linked packages
- (67%) microsoft/typescript#52616: Duplicated files do not check unused @ts-expect-error properly
- (67%) microsoft/typescript#60913: `./*.d.ts` required in `exports` to avoid `Cannot be named without a reference to... not portable...`
- (67%) microsoft/typescript#26867: Compilation fails when using project references with tsc -b
- (67%) microsoft/typescript#46175: Unrecognized self-referencing @scope/package
- (67%) microsoft/typescript#36773: Package dependencies and The inferred type of ... cannot be named without a reference to ...
- (67%) microsoft/typescript#46153: tsc -b incorrectly succeeds when there is a problem introduced by a transitive dependency
- (67%) microsoft/typescript#27887: tsc --build incorrectly reports TS6307 'not in project file list' when a leaf module exports a type
If your issue is a duplicate of one of these, feel free to close this issue. Otherwise, no action is needed.