Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track type recusion and symbol instantiation depth seperately in createAnonymousTypeNode #28490

Merged
Merged
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
35 changes: 27 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3199,7 +3199,8 @@ namespace ts {
getCurrentDirectory: host.getCurrentDirectory && (() => host.getCurrentDirectory!())
} : undefined },
encounteredError: false,
visitedSymbols: undefined,
visitedTypes: undefined,
symbolDepth: undefined,
inferTypeParameters: undefined,
approximateLength: 0
};
Expand Down Expand Up @@ -3430,6 +3431,7 @@ namespace ts {
}

function createAnonymousTypeNode(type: ObjectType): TypeNode {
const typeId = "" + type.id;
const symbol = type.symbol;
let id: string;
if (symbol) {
Expand All @@ -3446,7 +3448,7 @@ namespace ts {
shouldWriteTypeOfFunctionSymbol()) {
return symbolToTypeNode(symbol, context, SymbolFlags.Value);
}
else if (context.visitedSymbols && context.visitedSymbols.has(id)) {
else if (context.visitedTypes && context.visitedTypes.has(typeId)) {
// If type is an anonymous type literal in a type alias declaration, use type alias name
const typeAlias = getTypeAliasForTypeLiteral(type);
if (typeAlias) {
Expand All @@ -3455,19 +3457,35 @@ namespace ts {
}
else {
context.approximateLength += 3;
if (!(context.flags & NodeBuilderFlags.NoTruncation)) {
return createTypeReferenceNode(createIdentifier("..."), /*typeArguments*/ undefined);
}
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
}
else {
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
// of types allows us to catch circular references to instantiations of the same anonymous type
if (!context.visitedSymbols) {
context.visitedSymbols = createMap<true>();
if (!context.visitedTypes) {
context.visitedTypes = createMap<true>();
}
if (!context.symbolDepth) {
context.symbolDepth = createMap<number>();
}

context.visitedSymbols.set(id, true);
const depth = context.symbolDepth.get(id) || 0;
if (depth > 10) {
context.approximateLength += 3;
if (!(context.flags & NodeBuilderFlags.NoTruncation)) {
return createTypeReferenceNode(createIdentifier("..."), /*typeArguments*/ undefined);
weswigham marked this conversation as resolved.
Show resolved Hide resolved
}
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
context.symbolDepth.set(id, depth + 1);
context.visitedTypes.set(typeId, true);
const result = createTypeNodeFromObjectType(type);
context.visitedSymbols.delete(id);
context.visitedTypes.delete(typeId);
context.symbolDepth.set(id, depth);
return result;
}
}
Expand All @@ -3484,7 +3502,7 @@ namespace ts {
declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock));
if (isStaticMethodSymbol || isNonLocalFunctionSymbol) {
// typeof is allowed only for static/non local functions
return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedSymbols && context.visitedSymbols.has(id))) && // it is type of the symbol uses itself recursively
return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes && context.visitedTypes.has(typeId))) && // it is type of the symbol uses itself recursively
(!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration!)); // TODO: GH#18217 // And the build is going to succeed without visibility error or there is no structural fallback allowed
}
}
Expand Down Expand Up @@ -4308,7 +4326,8 @@ namespace ts {

// State
encounteredError: boolean;
visitedSymbols: Map<true> | undefined;
visitedTypes: Map<true> | undefined;
symbolDepth: Map<number> | undefined;
inferTypeParameters: TypeParameter[] | undefined;
approximateLength: number;
truncating?: boolean;
Expand Down
54 changes: 27 additions & 27 deletions tests/baselines/reference/cyclicGenericTypeInstantiation.types
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
=== tests/cases/compiler/cyclicGenericTypeInstantiation.ts ===
function foo<T>() {
>foo : <T>() => { y2: any; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var z = foo<typeof y>();
>z : { y2: any; }
>foo<typeof y>() : { y2: any; }
>foo : <T>() => { y2: any; }
>y : { y2: any; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo<typeof y>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var y: {
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

y2: typeof z
>y2 : { y2: any; }
>z : { y2: any; }
>y2 : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

};
return y;
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}


function bar<T>() {
>bar : <T>() => { y2: any; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var z = bar<typeof y>();
>z : { y2: any; }
>bar<typeof y>() : { y2: any; }
>bar : <T>() => { y2: any; }
>y : { y2: any; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar<typeof y>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var y: {
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

y2: typeof z;
>y2 : { y2: any; }
>z : { y2: any; }
>y2 : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}
return y;
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}

var a = foo<number>();
>a : { y2: any; }
>foo<number>() : { y2: any; }
>foo : <T>() => { y2: any; }
>a : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo<number>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var b = bar<number>();
>b : { y2: any; }
>bar<number>() : { y2: any; }
>bar : <T>() => { y2: any; }
>b : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar<number>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

a = b;
>a = b : { y2: any; }
>a : { y2: any; }
>b : { y2: any; }
>a = b : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>a : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>b : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
=== tests/cases/compiler/cyclicGenericTypeInstantiationInference.ts ===
function foo<T>() {
>foo : <T>() => { y2: any; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var z = foo<typeof y>();
>z : { y2: any; }
>foo<typeof y>() : { y2: any; }
>foo : <T>() => { y2: any; }
>y : { y2: any; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo<typeof y>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var y: {
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

y2: typeof z
>y2 : { y2: any; }
>z : { y2: any; }
>y2 : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

};
return y;
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}


function bar<T>() {
>bar : <T>() => { y2: any; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var z = bar<typeof y>();
>z : { y2: any; }
>bar<typeof y>() : { y2: any; }
>bar : <T>() => { y2: any; }
>y : { y2: any; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar<typeof y>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var y: {
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

y2: typeof z;
>y2 : { y2: any; }
>z : { y2: any; }
>y2 : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}
return y;
>y : { y2: any; }
>y : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
}

var a = foo<number>();
>a : { y2: any; }
>foo<number>() : { y2: any; }
>foo : <T>() => { y2: any; }
>a : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo<number>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>foo : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

var b = bar<number>();
>b : { y2: any; }
>bar<number>() : { y2: any; }
>bar : <T>() => { y2: any; }
>b : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar<number>() : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>bar : <T>() => { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

function test<T>(x: typeof a): void { }
>test : <T>(x: { y2: any; }) => void
>x : { y2: any; }
>a : { y2: any; }
>test : <T>(x: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }) => void
>x : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }
>a : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

test(b);
>test(b) : void
>test : <T>(x: { y2: any; }) => void
>b : { y2: any; }
>test : <T>(x: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }) => void
>b : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ var r2 = foo({ bar: 1, baz: 1 }); // T = number
>1 : 1

var r3 = foo({ bar: foo, baz: foo }); // T = typeof foo
>r3 : { bar: <T>(x: any) => any; baz: <T>(x: any) => any; }
>foo({ bar: foo, baz: foo }) : { bar: <T>(x: any) => any; baz: <T>(x: any) => any; }
>r3 : { bar: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; baz: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; }
>foo({ bar: foo, baz: foo }) : { bar: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; baz: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; }
>foo : <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }
>{ bar: foo, baz: foo } : { bar: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; baz: <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }; }
>bar : <T>(x: { bar: T; baz: T; }) => { bar: T; baz: T; }
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/limitDeepInstantiations.types
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Repro from #14837

type Foo<T extends "true", B> = { "true": Foo<T, Foo<T, B>> }[T];
>Foo : { "true": any[T]; }[T]
>"true" : { "true": any[T]; }[T]
>Foo : { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": any[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]
>"true" : { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": { "true": any[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]; }[T]

let f1: Foo<"true", {}>;
>f1 : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ var axios = {}
>{} : {}

module.exports = axios // both assignments should be ok
>module.exports = axios : { default: any; }
>module.exports : { default: any; }
>module : { "tests/cases/conformance/salsa/axios": { default: any; }; }
>exports : { default: any; }
>module.exports = axios : { default: { default: any; }; }
>module.exports : { default: { default: any; }; }
>module : { "tests/cases/conformance/salsa/axios": { default: { default: any; }; }; }
>exports : { default: { default: any; }; }
>axios : { default: { default: any; }; }

module.exports.default = axios
>module.exports.default = axios : { default: { default: any; }; }
>module.exports.default : { default: any; }
>module.exports : { default: any; }
>module : { "tests/cases/conformance/salsa/axios": { default: any; }; }
>exports : { default: any; }
>module.exports : { default: { default: any; }; }
>module : { "tests/cases/conformance/salsa/axios": { default: { default: any; }; }; }
>exports : { default: { default: any; }; }
>default : { default: any; }
>axios : { default: { default: any; }; }

2 changes: 1 addition & 1 deletion tests/baselines/reference/recursiveLetConst.types
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ let z1 = function () { return z1; }

let z2 = { f() { return z2;}}
>z2 : { f(): any; }
>{ f() { return z2;}} : { f(): any; }
>{ f() { return z2;}} : { f(): { f(): any; }; }
>f : () => { f(): any; }
>z2 : { f(): any; }

Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
/////// <reference path="file1.ts" />
////foo();

verify.singleReferenceGroup("(local function) foo(a?: void, b?: () => (a?: void, b?: any) => void): void");
verify.singleReferenceGroup("(local function) foo(a?: void, b?: () => (a?: void, b?: ...) => void): void");
18 changes: 18 additions & 0 deletions tests/cases/fourslash/quickInforForSucessiveInferencesIsNotAny.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />

////declare function schema<T> (value : T) : {field : T};
////
////declare const b: boolean;
////const obj/*1*/ = schema(b);
////const actualTypeOfNested/*2*/ = schema(obj);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. This is not circular generic type though so this shouldn't run into the circularity print in the first place. So to me it feels like we are missing something else that we can account in ID (eg. instantiations of the type as well?)

Copy link
Member Author

@weswigham weswigham Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so we track how many times we visit the same symbol, since we don't know if the symbol is generative or not (ie, does each member produce a type whose member is a new instantiation of the type) and we have tests to that effect. In this case, we do produce a type with a member of the same symbol as the overall type, but it's finite - just 2 levels deep. The explicit depth check helps distinguish these cases (rather than assuming that all recursive instantiations could be circular and printing as any).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why you are tracking depth but what i meant was why not add (symbol.links.instatiations.typeId) to determine if we are recuring into symbol + type combination. Thus we would still not have to print something like z : { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: { y2: any; }; }; }; }; }; }; }; }; }; }; }

Copy link
Member Author

@weswigham weswigham Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every y2 in that type is within a differently id'd object literal type backed by the same (well, 1 of 2, the test is mutual recursion) symbol. No symbol + type id pair will ever repeat because it generates new type ids infinitely as it recurs. Which is why we just have to track symbol depth, similarly to how we do in instantiation itself.


verify.quickInfos({
1: `const obj: {
field: boolean;
}`,
2: `const actualTypeOfNested: {
field: {
field: boolean;
};
}`
});