Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 60 additions & 40 deletions tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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{
Expand Down
Original file line number Diff line number Diff line change
@@ -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) ====
Expand Down Expand Up @@ -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.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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.

Expand Down Expand Up @@ -31,4 +32,43 @@ 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;
};
};

==== 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> = T extends object ? _HelperObjectDeep<T> : T;
export type _HelperObjectDeep<T> = { readonly [K in keyof T]: HelperDeep<T[K]> };
export type InlineDeep<T> = T extends object ? { readonly [K in keyof T]: InlineDeep<T[K]> } : T;
export type PublicMapped<T> = { readonly [K in keyof T]: T[K] };
export declare function mapped<T>(value: T): _HelperObjectDeep<T>;

==== src/mapped.ts (0 errors) ====
import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types";

type Tree = { value: string; children: Tree[] };
declare function helper<T>(value: T): HelperDeep<T>;
declare function inline<T>(value: T): InlineDeep<T>;
declare function publicMapped<T>(value: T): PublicMapped<T>;

export const recursive = helper({} as Tree);
export const finite = helper({ value: "text" });
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 = <T>(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.
Original file line number Diff line number Diff line change
Expand Up @@ -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> = T extends object ? _HelperObjectDeep<T> : T;
export type _HelperObjectDeep<T> = { readonly [K in keyof T]: HelperDeep<T[K]> };
export type InlineDeep<T> = T extends object ? { readonly [K in keyof T]: InlineDeep<T[K]> } : T;
export type PublicMapped<T> = { readonly [K in keyof T]: T[K] };
export declare function mapped<T>(value: T): _HelperObjectDeep<T>;

//// [mapped.ts]
import type { HelperDeep, InlineDeep, PublicMapped } from "deep-types";

type Tree = { value: string; children: Tree[] };
declare function helper<T>(value: T): HelperDeep<T>;
declare function inline<T>(value: T): InlineDeep<T>;
declare function publicMapped<T>(value: T): PublicMapped<T>;

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 = <T>(value: T) => mapped(value);

//// [index.js]
"use strict";
Expand All @@ -39,3 +76,36 @@ 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;


//// [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;
}>;
Original file line number Diff line number Diff line change
Expand Up @@ -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> = T extends object ? _HelperObjectDeep<T> : 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<T> = { readonly [K in keyof T]: HelperDeep<T[K]> };
>_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> = T extends object ? { readonly [K in keyof T]: InlineDeep<T[K]> } : 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<T> = { 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<T>(value: T): _HelperObjectDeep<T>;
>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<T>(value: T): HelperDeep<T>;
>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<T>(value: T): InlineDeep<T>;
>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<T>(value: T): PublicMapped<T>;
>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 = <T>(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))

Loading
Loading