From 39895b268b5badc4611b3675af33afaec68fa7e8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:09:20 -0800 Subject: [PATCH 1/2] Reproduce inaccessible concrete mapped declaration types Resolved conditional types can expose a mapped helper that the package does not export. Declaration emit rejects even concrete specializations whose structure can be represented without that helper. Cover finite and recursive results, alongside a reachable mapped alias and an unresolved generic that must retain its diagnostic. For 60913 --- .../declarationEmitUsingTypeAlias1.errors.txt | 48 ++++++- .../declarationEmitUsingTypeAlias1.js | 54 +++++++- .../declarationEmitUsingTypeAlias1.symbols | 123 ++++++++++++++++++ .../declarationEmitUsingTypeAlias1.types | 92 +++++++++++++ .../declarationEmitUsingTypeAlias1.ts | 39 +++++- 5 files changed, 353 insertions(+), 3 deletions(-) diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt index b243748856549..30219772c2b35 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt @@ -1,5 +1,8 @@ +src/genericMapped.ts(3,14): error TS2883: The inferred type of 'generic' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. src/index.ts(3,14): error TS2883: The inferred type of 'foo' cannot be named without a reference to 'SomeType' from '../node_modules/some-dep/dist/inner'. This is likely not portable. A type annotation is necessary. src/index.ts(7,14): error TS2883: The inferred type of 'bar' cannot be named without a reference to 'Other' from '../node_modules/some-dep/dist/inner'. This is likely not portable. A type annotation is necessary. +src/mapped.ts(8,14): error TS2883: The inferred type of 'recursive' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. +src/mapped.ts(9,14): error TS2883: The inferred type of 'finite' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. ==== node_modules/some-dep/dist/inner.d.ts (0 errors) ==== @@ -31,4 +34,47 @@ src/index.ts(7,14): error TS2883: The inferred type of 'bar' cannot be named wit ~~~ !!! error TS2883: The inferred type of 'bar' cannot be named without a reference to 'Other' from '../node_modules/some-dep/dist/inner'. This is likely not portable. A type annotation is necessary. return thing.arg; - }; \ No newline at end of file + }; + +==== node_modules/deep-types/package.json (0 errors) ==== + { + "name": "deep-types", + "exports": { + ".": { "types": "./index.d.ts" } + } + } + +==== node_modules/deep-types/index.d.ts (0 errors) ==== + export type { HelperDeep, InlineDeep, PublicMapped } from "./source.js"; + export { mapped } from "./source.js"; + +==== node_modules/deep-types/source.d.ts (0 errors) ==== + export type HelperDeep = T extends object ? _HelperObjectDeep : T; + export type _HelperObjectDeep = { readonly [K in keyof T]: HelperDeep }; + export type InlineDeep = T extends object ? { readonly [K in keyof T]: InlineDeep } : T; + export type PublicMapped = { readonly [K in keyof T]: T[K] }; + export declare function mapped(value: T): _HelperObjectDeep; + +==== src/mapped.ts (2 errors) ==== + import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; + + type Tree = { value: string; children: Tree[] }; + declare function helper(value: T): HelperDeep; + declare function inline(value: T): InlineDeep; + declare function publicMapped(value: T): PublicMapped; + + export const recursive = helper({} as Tree); + ~~~~~~~~~ +!!! error TS2883: The inferred type of 'recursive' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. + export const finite = helper({ value: "text" }); + ~~~~~~ +!!! error TS2883: The inferred type of 'finite' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. + export const inlined = inline({} as Tree); + export const named = publicMapped({ value: "text" }); + +==== src/genericMapped.ts (1 errors) ==== + import { mapped } from "deep-types"; + + export const generic = (value: T) => mapped(value); + ~~~~~~~ +!!! error TS2883: The inferred type of 'generic' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js index c243d425a035e..a51921647bd53 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js @@ -25,7 +25,44 @@ export const foo = (thing: SomeType) => { export const bar = (thing: SomeType) => { return thing.arg; -}; +}; + +//// [package.json] +{ + "name": "deep-types", + "exports": { + ".": { "types": "./index.d.ts" } + } +} + +//// [index.d.ts] +export type { HelperDeep, InlineDeep, PublicMapped } from "./source.js"; +export { mapped } from "./source.js"; + +//// [source.d.ts] +export type HelperDeep = T extends object ? _HelperObjectDeep : T; +export type _HelperObjectDeep = { readonly [K in keyof T]: HelperDeep }; +export type InlineDeep = T extends object ? { readonly [K in keyof T]: InlineDeep } : T; +export type PublicMapped = { readonly [K in keyof T]: T[K] }; +export declare function mapped(value: T): _HelperObjectDeep; + +//// [mapped.ts] +import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; + +type Tree = { value: string; children: Tree[] }; +declare function helper(value: T): HelperDeep; +declare function inline(value: T): InlineDeep; +declare function publicMapped(value: T): PublicMapped; + +export const recursive = helper({} as Tree); +export const finite = helper({ value: "text" }); +export const inlined = inline({} as Tree); +export const named = publicMapped({ value: "text" }); + +//// [genericMapped.ts] +import { mapped } from "deep-types"; + +export const generic = (value: T) => mapped(value); //// [index.js] "use strict"; @@ -39,3 +76,18 @@ const bar = (thing) => { return thing.arg; }; exports.bar = bar; +//// [mapped.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.named = exports.inlined = exports.finite = exports.recursive = void 0; +exports.recursive = helper({}); +exports.finite = helper({ value: "text" }); +exports.inlined = inline({}); +exports.named = publicMapped({ value: "text" }); +//// [genericMapped.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generic = void 0; +const deep_types_1 = require("deep-types"); +const generic = (value) => (0, deep_types_1.mapped)(value); +exports.generic = generic; diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.symbols b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.symbols index 55573f5c9aff2..187ac75d5e9f1 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.symbols +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.symbols @@ -44,3 +44,126 @@ export const bar = (thing: SomeType) => { >arg : Symbol(arg, Decl(inner.d.ts, 1, 32)) }; + +=== node_modules/deep-types/index.d.ts === +export type { HelperDeep, InlineDeep, PublicMapped } from "./source.js"; +>HelperDeep : Symbol(HelperDeep, Decl(index.d.ts, 0, 13)) +>InlineDeep : Symbol(InlineDeep, Decl(index.d.ts, 0, 25)) +>PublicMapped : Symbol(PublicMapped, Decl(index.d.ts, 0, 37)) + +export { mapped } from "./source.js"; +>mapped : Symbol(mapped, Decl(index.d.ts, 1, 8)) + +=== node_modules/deep-types/source.d.ts === +export type HelperDeep = T extends object ? _HelperObjectDeep : T; +>HelperDeep : Symbol(HelperDeep, Decl(source.d.ts, 0, 0)) +>T : Symbol(T, Decl(source.d.ts, 0, 23)) +>T : Symbol(T, Decl(source.d.ts, 0, 23)) +>_HelperObjectDeep : Symbol(_HelperObjectDeep, Decl(source.d.ts, 0, 72)) +>T : Symbol(T, Decl(source.d.ts, 0, 23)) +>T : Symbol(T, Decl(source.d.ts, 0, 23)) + +export type _HelperObjectDeep = { readonly [K in keyof T]: HelperDeep }; +>_HelperObjectDeep : Symbol(_HelperObjectDeep, Decl(source.d.ts, 0, 72)) +>T : Symbol(T, Decl(source.d.ts, 1, 30)) +>K : Symbol(K, Decl(source.d.ts, 1, 47)) +>T : Symbol(T, Decl(source.d.ts, 1, 30)) +>HelperDeep : Symbol(HelperDeep, Decl(source.d.ts, 0, 0)) +>T : Symbol(T, Decl(source.d.ts, 1, 30)) +>K : Symbol(K, Decl(source.d.ts, 1, 47)) + +export type InlineDeep = T extends object ? { readonly [K in keyof T]: InlineDeep } : T; +>InlineDeep : Symbol(InlineDeep, Decl(source.d.ts, 1, 81)) +>T : Symbol(T, Decl(source.d.ts, 2, 23)) +>T : Symbol(T, Decl(source.d.ts, 2, 23)) +>K : Symbol(K, Decl(source.d.ts, 2, 59)) +>T : Symbol(T, Decl(source.d.ts, 2, 23)) +>InlineDeep : Symbol(InlineDeep, Decl(source.d.ts, 1, 81)) +>T : Symbol(T, Decl(source.d.ts, 2, 23)) +>K : Symbol(K, Decl(source.d.ts, 2, 59)) +>T : Symbol(T, Decl(source.d.ts, 2, 23)) + +export type PublicMapped = { readonly [K in keyof T]: T[K] }; +>PublicMapped : Symbol(PublicMapped, Decl(source.d.ts, 2, 97)) +>T : Symbol(T, Decl(source.d.ts, 3, 25)) +>K : Symbol(K, Decl(source.d.ts, 3, 42)) +>T : Symbol(T, Decl(source.d.ts, 3, 25)) +>T : Symbol(T, Decl(source.d.ts, 3, 25)) +>K : Symbol(K, Decl(source.d.ts, 3, 42)) + +export declare function mapped(value: T): _HelperObjectDeep; +>mapped : Symbol(mapped, Decl(source.d.ts, 3, 64)) +>T : Symbol(T, Decl(source.d.ts, 4, 31)) +>value : Symbol(value, Decl(source.d.ts, 4, 34)) +>T : Symbol(T, Decl(source.d.ts, 4, 31)) +>_HelperObjectDeep : Symbol(_HelperObjectDeep, Decl(source.d.ts, 0, 72)) +>T : Symbol(T, Decl(source.d.ts, 4, 31)) + +=== src/mapped.ts === +import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; +>HelperDeep : Symbol(HelperDeep, Decl(mapped.ts, 0, 13)) +>InlineDeep : Symbol(InlineDeep, Decl(mapped.ts, 0, 25)) +>PublicMapped : Symbol(PublicMapped, Decl(mapped.ts, 0, 37)) + +type Tree = { value: string; children: Tree[] }; +>Tree : Symbol(Tree, Decl(mapped.ts, 0, 71)) +>value : Symbol(value, Decl(mapped.ts, 2, 13)) +>children : Symbol(children, Decl(mapped.ts, 2, 28)) +>Tree : Symbol(Tree, Decl(mapped.ts, 0, 71)) + +declare function helper(value: T): HelperDeep; +>helper : Symbol(helper, Decl(mapped.ts, 2, 48)) +>T : Symbol(T, Decl(mapped.ts, 3, 24)) +>value : Symbol(value, Decl(mapped.ts, 3, 27)) +>T : Symbol(T, Decl(mapped.ts, 3, 24)) +>HelperDeep : Symbol(HelperDeep, Decl(mapped.ts, 0, 13)) +>T : Symbol(T, Decl(mapped.ts, 3, 24)) + +declare function inline(value: T): InlineDeep; +>inline : Symbol(inline, Decl(mapped.ts, 3, 52)) +>T : Symbol(T, Decl(mapped.ts, 4, 24)) +>value : Symbol(value, Decl(mapped.ts, 4, 27)) +>T : Symbol(T, Decl(mapped.ts, 4, 24)) +>InlineDeep : Symbol(InlineDeep, Decl(mapped.ts, 0, 25)) +>T : Symbol(T, Decl(mapped.ts, 4, 24)) + +declare function publicMapped(value: T): PublicMapped; +>publicMapped : Symbol(publicMapped, Decl(mapped.ts, 4, 52)) +>T : Symbol(T, Decl(mapped.ts, 5, 30)) +>value : Symbol(value, Decl(mapped.ts, 5, 33)) +>T : Symbol(T, Decl(mapped.ts, 5, 30)) +>PublicMapped : Symbol(PublicMapped, Decl(mapped.ts, 0, 37)) +>T : Symbol(T, Decl(mapped.ts, 5, 30)) + +export const recursive = helper({} as Tree); +>recursive : Symbol(recursive, Decl(mapped.ts, 7, 12)) +>helper : Symbol(helper, Decl(mapped.ts, 2, 48)) +>Tree : Symbol(Tree, Decl(mapped.ts, 0, 71)) + +export const finite = helper({ value: "text" }); +>finite : Symbol(finite, Decl(mapped.ts, 8, 12)) +>helper : Symbol(helper, Decl(mapped.ts, 2, 48)) +>value : Symbol(value, Decl(mapped.ts, 8, 30)) + +export const inlined = inline({} as Tree); +>inlined : Symbol(inlined, Decl(mapped.ts, 9, 12)) +>inline : Symbol(inline, Decl(mapped.ts, 3, 52)) +>Tree : Symbol(Tree, Decl(mapped.ts, 0, 71)) + +export const named = publicMapped({ value: "text" }); +>named : Symbol(named, Decl(mapped.ts, 10, 12)) +>publicMapped : Symbol(publicMapped, Decl(mapped.ts, 4, 52)) +>value : Symbol(value, Decl(mapped.ts, 10, 35)) + +=== src/genericMapped.ts === +import { mapped } from "deep-types"; +>mapped : Symbol(mapped, Decl(genericMapped.ts, 0, 8)) + +export const generic = (value: T) => mapped(value); +>generic : Symbol(generic, Decl(genericMapped.ts, 2, 12)) +>T : Symbol(T, Decl(genericMapped.ts, 2, 24)) +>value : Symbol(value, Decl(genericMapped.ts, 2, 27)) +>T : Symbol(T, Decl(genericMapped.ts, 2, 24)) +>mapped : Symbol(mapped, Decl(genericMapped.ts, 0, 8)) +>value : Symbol(value, Decl(genericMapped.ts, 2, 27)) + diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.types b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.types index e11ca9402f6c5..2243ed850506d 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.types +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.types @@ -41,3 +41,95 @@ export const bar = (thing: SomeType) => { >arg : import("../node_modules/some-dep/dist/inner").Other }; + +=== node_modules/deep-types/index.d.ts === +export type { HelperDeep, InlineDeep, PublicMapped } from "./source.js"; +>HelperDeep : import("./source.js").HelperDeep +>InlineDeep : import("./source.js").InlineDeep +>PublicMapped : import("./source.js").PublicMapped + +export { mapped } from "./source.js"; +>mapped : (value: T) => import("./source.js")._HelperObjectDeep + +=== node_modules/deep-types/source.d.ts === +export type HelperDeep = T extends object ? _HelperObjectDeep : T; +>HelperDeep : HelperDeep + +export type _HelperObjectDeep = { readonly [K in keyof T]: HelperDeep }; +>_HelperObjectDeep : _HelperObjectDeep + +export type InlineDeep = T extends object ? { readonly [K in keyof T]: InlineDeep } : T; +>InlineDeep : InlineDeep + +export type PublicMapped = { readonly [K in keyof T]: T[K] }; +>PublicMapped : PublicMapped + +export declare function mapped(value: T): _HelperObjectDeep; +>mapped : (value: T) => _HelperObjectDeep +>value : T + +=== src/mapped.ts === +import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; +>HelperDeep : HelperDeep +>InlineDeep : InlineDeep +>PublicMapped : PublicMapped + +type Tree = { value: string; children: Tree[] }; +>Tree : Tree +>value : string +>children : Tree[] + +declare function helper(value: T): HelperDeep; +>helper : (value: T) => HelperDeep +>value : T + +declare function inline(value: T): InlineDeep; +>inline : (value: T) => InlineDeep +>value : T + +declare function publicMapped(value: T): PublicMapped; +>publicMapped : (value: T) => PublicMapped +>value : T + +export const recursive = helper({} as Tree); +>recursive : import("../node_modules/deep-types/source")._HelperObjectDeep +>helper({} as Tree) : import("../node_modules/deep-types/source")._HelperObjectDeep +>helper : (value: T) => HelperDeep +>{} as Tree : Tree +>{} : {} + +export const finite = helper({ value: "text" }); +>finite : import("../node_modules/deep-types/source")._HelperObjectDeep<{ value: string; }> +>helper({ value: "text" }) : import("../node_modules/deep-types/source")._HelperObjectDeep<{ value: string; }> +>helper : (value: T) => HelperDeep +>{ value: "text" } : { value: string; } +>value : string +>"text" : "text" + +export const inlined = inline({} as Tree); +>inlined : { readonly value: string; readonly children: readonly any[]; } +>inline({} as Tree) : { readonly value: string; readonly children: readonly any[]; } +>inline : (value: T) => InlineDeep +>{} as Tree : Tree +>{} : {} + +export const named = publicMapped({ value: "text" }); +>named : PublicMapped<{ value: string; }> +>publicMapped({ value: "text" }) : PublicMapped<{ value: string; }> +>publicMapped : (value: T) => PublicMapped +>{ value: "text" } : { value: string; } +>value : string +>"text" : "text" + +=== src/genericMapped.ts === +import { mapped } from "deep-types"; +>mapped : (value: T) => import("../node_modules/deep-types/source")._HelperObjectDeep + +export const generic = (value: T) => mapped(value); +>generic : (value: T) => import("../node_modules/deep-types/source")._HelperObjectDeep +>(value: T) => mapped(value) : (value: T) => import("../node_modules/deep-types/source")._HelperObjectDeep +>value : T +>mapped(value) : import("../node_modules/deep-types/source")._HelperObjectDeep +>mapped : (value: T_1) => import("../node_modules/deep-types/source")._HelperObjectDeep +>value : T + diff --git a/tsc/testdata/tests/cases/compiler/declarationEmitUsingTypeAlias1.ts b/tsc/testdata/tests/cases/compiler/declarationEmitUsingTypeAlias1.ts index fddae5fc65df2..380b92c879a64 100644 --- a/tsc/testdata/tests/cases/compiler/declarationEmitUsingTypeAlias1.ts +++ b/tsc/testdata/tests/cases/compiler/declarationEmitUsingTypeAlias1.ts @@ -28,4 +28,41 @@ export const foo = (thing: SomeType) => { export const bar = (thing: SomeType) => { return thing.arg; -}; \ No newline at end of file +}; + +// @filename: node_modules/deep-types/package.json +{ + "name": "deep-types", + "exports": { + ".": { "types": "./index.d.ts" } + } +} + +// @filename: node_modules/deep-types/index.d.ts +export type { HelperDeep, InlineDeep, PublicMapped } from "./source.js"; +export { mapped } from "./source.js"; + +// @filename: node_modules/deep-types/source.d.ts +export type HelperDeep = T extends object ? _HelperObjectDeep : T; +export type _HelperObjectDeep = { readonly [K in keyof T]: HelperDeep }; +export type InlineDeep = T extends object ? { readonly [K in keyof T]: InlineDeep } : T; +export type PublicMapped = { readonly [K in keyof T]: T[K] }; +export declare function mapped(value: T): _HelperObjectDeep; + +// @filename: src/mapped.ts +import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; + +type Tree = { value: string; children: Tree[] }; +declare function helper(value: T): HelperDeep; +declare function inline(value: T): InlineDeep; +declare function publicMapped(value: T): PublicMapped; + +export const recursive = helper({} as Tree); +export const finite = helper({ value: "text" }); +export const inlined = inline({} as Tree); +export const named = publicMapped({ value: "text" }); + +// @filename: src/genericMapped.ts +import { mapped } from "deep-types"; + +export const generic = (value: T) => mapped(value); \ No newline at end of file From 7b932cc8cd93d25686df7abb25fb75064787fcf6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:14:26 -0800 Subject: [PATCH 2/2] Serialize unreachable concrete mapped types structurally A resolved conditional can expose a mapped helper that is hidden by package exports. Rejecting that name blocks declaration emit even when the complete concrete object shape is available. Concrete mapped results can be represented structurally without naming the helper. Generic mapped results still need their mapping semantics, and reachable aliases should keep their existing public spelling. For 60913 --- tsc/internal/checker/nodebuilderimpl.go | 100 +++++++++++------- ...EmitObjectAssignedDefaultExport.errors.txt | 4 +- .../declarationEmitUsingTypeAlias1.errors.txt | 8 +- .../declarationEmitUsingTypeAlias1.js | 18 ++++ 4 files changed, 81 insertions(+), 49 deletions(-) diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 128dd968c79e5..1f63b9ee9a5de 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -702,45 +702,12 @@ func (b *NodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl if typeParameterNodes == nil { typeParameterNodes = b.lookupTypeParameterNodes(chain, 0) } - contextFile := ast.GetSourceFileOfNode(b.e.MostOriginal(b.ctx.enclosingDeclaration)) // TODO: Just use b.ctx.enclosingFile ? Or is the delayed lookup important for context moves? - targetFile := ast.GetSourceFileOfModule(chain[0]) - var specifierResult moduleSpecifierResult - var importModeOverride core.ResolutionMode - if b.ch.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNode16 || b.ch.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNodeNext { - // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion - if targetFile != nil && contextFile != nil && b.ch.program.GetEmitModuleFormatOfFile(targetFile) == core.ModuleKindESNext && b.ch.program.GetEmitModuleFormatOfFile(targetFile) != b.ch.program.GetEmitModuleFormatOfFile(contextFile) { - specifierResult = b.getSpecifierForModuleSymbol(chain[0], core.ModuleKindESNext) - importModeOverride = core.ModuleKindESNext - } - } - if len(specifierResult.specifier) == 0 { - specifierResult = b.getSpecifierForModuleSymbol(chain[0], core.ResolutionModeNone) - } - if (b.ctx.flags&nodebuilder.FlagsAllowNodeModulesRelativePaths == 0) /* && b.ch.compilerOptions.GetModuleResolutionKind() != core.ModuleResolutionKindClassic */ && strings.Contains(specifierResult.specifier.AsString(), "/node_modules/") { - oldSpecifierResult := specifierResult - - if b.ch.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNode16 || b.ch.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNodeNext { - // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set - swappedMode := core.ModuleKindESNext - if b.ch.program.GetEmitModuleFormatOfFile(contextFile) == core.ModuleKindESNext { - swappedMode = core.ModuleKindCommonJS - } - specifierResult = b.getSpecifierForModuleSymbol(chain[0], swappedMode) - - if strings.Contains(specifierResult.specifier.AsString(), "/node_modules/") { - // Still unreachable :( - specifierResult = oldSpecifierResult - } else { - importModeOverride = swappedMode - } - } - - if importModeOverride == core.ResolutionModeNone { - // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error - // since declaration files with these kinds of references are liable to fail when published :( - b.ctx.encounteredError = true - b.ctx.tracker.ReportLikelyUnsafeImportRequiredError(oldSpecifierResult.specifier.AsString(), symbol.Name()) - } + specifierResult, importModeOverride, portable := b.getImportTypeSpecifier(chain[0]) + if !portable { + // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error + // since declaration files with these kinds of references are liable to fail when published :( + b.ctx.encounteredError = true + b.ctx.tracker.ReportLikelyUnsafeImportRequiredError(specifierResult.specifier.AsString(), symbol.Name()) } attributes := b.createImportAttributesForModuleSpecifier(specifierResult, importModeOverride) @@ -781,6 +748,58 @@ func (b *NodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl return entityName } +func (b *NodeBuilderImpl) getImportTypeSpecifier(symbol *ast.Symbol) (moduleSpecifierResult, core.ResolutionMode, bool) { + contextFile := ast.GetSourceFileOfNode(b.e.MostOriginal(b.ctx.enclosingDeclaration)) // TODO: Just use b.ctx.enclosingFile ? Or is the delayed lookup important for context moves? + targetFile := ast.GetSourceFileOfModule(symbol) + var specifierResult moduleSpecifierResult + var importModeOverride core.ResolutionMode + resolutionKind := b.ch.compilerOptions.GetModuleResolutionKind() + if resolutionKind == core.ModuleResolutionKindNode16 || resolutionKind == core.ModuleResolutionKindNodeNext { + // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion + if targetFile != nil && contextFile != nil && b.ch.program.GetEmitModuleFormatOfFile(targetFile) == core.ModuleKindESNext && b.ch.program.GetEmitModuleFormatOfFile(targetFile) != b.ch.program.GetEmitModuleFormatOfFile(contextFile) { + specifierResult = b.getSpecifierForModuleSymbol(symbol, core.ModuleKindESNext) + importModeOverride = core.ModuleKindESNext + } + } + if len(specifierResult.specifier) == 0 { + specifierResult = b.getSpecifierForModuleSymbol(symbol, core.ResolutionModeNone) + } + if (b.ctx.flags&nodebuilder.FlagsAllowNodeModulesRelativePaths == 0) /* && b.ch.compilerOptions.GetModuleResolutionKind() != core.ModuleResolutionKindClassic */ && strings.Contains(specifierResult.specifier.AsString(), "/node_modules/") { + oldSpecifierResult := specifierResult + + if resolutionKind == core.ModuleResolutionKindNode16 || resolutionKind == core.ModuleResolutionKindNodeNext { + // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set + swappedMode := core.ModuleKindESNext + if b.ch.program.GetEmitModuleFormatOfFile(contextFile) == core.ModuleKindESNext { + swappedMode = core.ModuleKindCommonJS + } + specifierResult = b.getSpecifierForModuleSymbol(symbol, swappedMode) + + if strings.Contains(specifierResult.specifier.AsString(), "/node_modules/") { + // Still unreachable :( + specifierResult = oldSpecifierResult + } else { + importModeOverride = swappedMode + } + } + return specifierResult, importModeOverride, importModeOverride != core.ResolutionModeNone + } + return specifierResult, importModeOverride, true +} + +func (b *NodeBuilderImpl) shouldExpandUnreachableMappedType(t *Type) bool { + if b.ctx.flags&nodebuilder.FlagsUseStructuralFallback == 0 || + t.objectFlags&ObjectFlagsMapped == 0 || b.ch.isGenericMappedType(t) { + return false + } + chain := b.lookupSymbolChainWorker(t.alias.Symbol(), ast.SymbolFlagsType, b.ctx.flags&nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope == 0) + if !core.Some(chain[0].Declarations(), hasNonGlobalAugmentationExternalModuleSymbol) { + return false + } + _, _, portable := b.getImportTypeSpecifier(chain[0]) + return !portable +} + func getTopmostIndexedAccessType(node *ast.IndexedAccessTypeNode) *ast.IndexedAccessTypeNode { if ast.IsIndexedAccessTypeNode(node.ObjectType) { return getTopmostIndexedAccessType(node.ObjectType.AsIndexedAccessTypeNode()) @@ -3565,7 +3584,8 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode { return b.f.NewThisTypeNode() } - if inTypeAlias == 0 && t.alias != nil && (b.ctx.flags&nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope != 0 || b.ch.IsTypeSymbolAccessible(t.alias.Symbol(), b.ctx.enclosingDeclaration)) { + if inTypeAlias == 0 && t.alias != nil && (b.ctx.flags&nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope != 0 || b.ch.IsTypeSymbolAccessible(t.alias.Symbol(), b.ctx.enclosingDeclaration)) && + !b.shouldExpandUnreachableMappedType(t) { // If we should expand this type alias, skip the alias and fall through to expand the underlying type if !b.shouldExpandType(t, true /*isAlias*/) { return b.serializationTypeNameToNode(serializationTypeName{ diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitObjectAssignedDefaultExport.errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitObjectAssignedDefaultExport.errors.txt index 8396aee5921c5..bb2e89196f8ff 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitObjectAssignedDefaultExport.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitObjectAssignedDefaultExport.errors.txt @@ -1,4 +1,4 @@ -index.ts(7,1): error TS2883: The inferred type of 'default' cannot be named without a reference to 'NonReactStatics' from 'styled-components/node_modules/hoist-non-react-statics'. This is likely not portable. A type annotation is necessary. +index.ts(7,1): error TS4118: The type of this node cannot be serialized because its property '[iterator]' cannot be serialized. ==== node_modules/styled-components/node_modules/hoist-non-react-statics/index.d.ts (0 errors) ==== @@ -43,5 +43,5 @@ index.ts(7,1): error TS2883: The inferred type of 'default' cannot be named with ~~~~~ }); ~~~ -!!! error TS2883: The inferred type of 'default' cannot be named without a reference to 'NonReactStatics' from 'styled-components/node_modules/hoist-non-react-statics'. This is likely not portable. A type annotation is necessary. +!!! error TS4118: The type of this node cannot be serialized because its property '[iterator]' cannot be serialized. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt index 30219772c2b35..a4be57afd276e 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.errors.txt @@ -1,8 +1,6 @@ src/genericMapped.ts(3,14): error TS2883: The inferred type of 'generic' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. src/index.ts(3,14): error TS2883: The inferred type of 'foo' cannot be named without a reference to 'SomeType' from '../node_modules/some-dep/dist/inner'. This is likely not portable. A type annotation is necessary. src/index.ts(7,14): error TS2883: The inferred type of 'bar' cannot be named without a reference to 'Other' from '../node_modules/some-dep/dist/inner'. This is likely not portable. A type annotation is necessary. -src/mapped.ts(8,14): error TS2883: The inferred type of 'recursive' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. -src/mapped.ts(9,14): error TS2883: The inferred type of 'finite' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. ==== node_modules/some-dep/dist/inner.d.ts (0 errors) ==== @@ -55,7 +53,7 @@ src/mapped.ts(9,14): error TS2883: The inferred type of 'finite' cannot be named export type PublicMapped = { readonly [K in keyof T]: T[K] }; export declare function mapped(value: T): _HelperObjectDeep; -==== src/mapped.ts (2 errors) ==== +==== src/mapped.ts (0 errors) ==== import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types"; type Tree = { value: string; children: Tree[] }; @@ -64,11 +62,7 @@ src/mapped.ts(9,14): error TS2883: The inferred type of 'finite' cannot be named declare function publicMapped(value: T): PublicMapped; export const recursive = helper({} as Tree); - ~~~~~~~~~ -!!! error TS2883: The inferred type of 'recursive' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. export const finite = helper({ value: "text" }); - ~~~~~~ -!!! error TS2883: The inferred type of 'finite' cannot be named without a reference to '_HelperObjectDeep' from '../node_modules/deep-types/source'. This is likely not portable. A type annotation is necessary. export const inlined = inline({} as Tree); export const named = publicMapped({ value: "text" }); diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js index a51921647bd53..ec86238eb29cc 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitUsingTypeAlias1.js @@ -91,3 +91,21 @@ exports.generic = void 0; const deep_types_1 = require("deep-types"); const generic = (value) => (0, deep_types_1.mapped)(value); exports.generic = generic; + + +//// [mapped.d.ts] +import type { PublicMapped } from "deep-types"; +export declare const recursive: { + readonly value: string; + readonly children: readonly (typeof recursive)[]; +}; +export declare const finite: { + readonly value: string; +}; +export declare const inlined: { + readonly value: string; + readonly children: readonly (typeof inlined)[]; +}; +export declare const named: PublicMapped<{ + value: string; +}>;