Skip to content

Commit 6a777ff

Browse files
authored
Fix crash when serializing the return type of a generic call to Array.prototype.flat (#38904)
* Add declaration emit error and checking for circularly referential unions produced by recursive conditionals * Allow indexed accesses to produce alias symbols on types * Add test that still triggers the declaration emit error * Fix spelling
1 parent f7bca49 commit 6a777ff

File tree

47 files changed

+503
-222
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+503
-222
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4420,8 +4420,8 @@ namespace ts {
44204420
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
44214421
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
44224422
context.inferTypeParameters = saveInferTypeParameters;
4423-
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
4424-
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
4423+
const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(<ConditionalType>type));
4424+
const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(<ConditionalType>type));
44254425
context.approximateLength += 15;
44264426
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
44274427
}
@@ -4431,6 +4431,21 @@ namespace ts {
44314431

44324432
return Debug.fail("Should be unreachable.");
44334433

4434+
4435+
function typeToTypeNodeOrCircularityElision(type: Type) {
4436+
if (type.flags & TypeFlags.Union) {
4437+
if (context.visitedTypes && context.visitedTypes.has("" + getTypeId(type))) {
4438+
if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) {
4439+
context.encounteredError = true;
4440+
context.tracker?.reportCyclicStructureError?.();
4441+
}
4442+
return createElidedInformationPlaceholder(context);
4443+
}
4444+
return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context));
4445+
}
4446+
return typeToTypeNodeHelper(type, context);
4447+
}
4448+
44344449
function createMappedTypeNodeFromType(type: MappedType) {
44354450
Debug.assert(!!(type.flags & TypeFlags.Object));
44364451
const readonlyToken = type.declaration.readonlyToken ? <ReadonlyToken | PlusToken | MinusToken>createToken(type.declaration.readonlyToken.kind) : undefined;
@@ -12978,10 +12993,12 @@ namespace ts {
1297812993
return links.resolvedType;
1297912994
}
1298012995

12981-
function createIndexedAccessType(objectType: Type, indexType: Type) {
12996+
function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
1298212997
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
1298312998
type.objectType = objectType;
1298412999
type.indexType = indexType;
13000+
type.aliasSymbol = aliasSymbol;
13001+
type.aliasTypeArguments = aliasTypeArguments;
1298513002
return type;
1298613003
}
1298713004

@@ -13316,11 +13333,11 @@ namespace ts {
1331613333
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
1331713334
}
1331813335

13319-
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type {
13320-
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType);
13336+
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
13337+
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType);
1332113338
}
1332213339

13323-
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined {
13340+
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined {
1332413341
if (objectType === wildcardType || indexType === wildcardType) {
1332513342
return wildcardType;
1332613343
}
@@ -13342,7 +13359,7 @@ namespace ts {
1334213359
const id = objectType.id + "," + indexType.id;
1334313360
let type = indexedAccessTypes.get(id);
1334413361
if (!type) {
13345-
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType));
13362+
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments));
1334613363
}
1334713364
return type;
1334813365
}
@@ -13370,7 +13387,7 @@ namespace ts {
1337013387
if (wasMissingProp) {
1337113388
return undefined;
1337213389
}
13373-
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
13390+
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
1337413391
}
1337513392
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol);
1337613393
}
@@ -13380,7 +13397,8 @@ namespace ts {
1338013397
if (!links.resolvedType) {
1338113398
const objectType = getTypeFromTypeNode(node.objectType);
1338213399
const indexType = getTypeFromTypeNode(node.indexType);
13383-
const resolved = getIndexedAccessType(objectType, indexType, node);
13400+
const potentialAlias = getAliasSymbolForTypeNode(node);
13401+
const resolved = getIndexedAccessType(objectType, indexType, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias));
1338413402
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
1338513403
(<IndexedAccessType>resolved).objectType === objectType &&
1338613404
(<IndexedAccessType>resolved).indexType === indexType ?
@@ -14531,7 +14549,7 @@ namespace ts {
1453114549
return getIndexType(instantiateType((<IndexType>type).type, mapper));
1453214550
}
1453314551
if (flags & TypeFlags.IndexedAccess) {
14534-
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
14552+
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper), /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
1453514553
}
1453614554
if (flags & TypeFlags.Conditional) {
1453714555
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3533,6 +3533,10 @@
35333533
"category": "Error",
35343534
"code": 5087
35353535
},
3536+
"The inferred type of '{0}' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.": {
3537+
"category": "Error",
3538+
"code": 5088
3539+
},
35363540

35373541
"Generates a sourcemap for each corresponding '.d.ts' file.": {
35383542
"category": "Message",

src/compiler/transformers/declarations.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ namespace ts {
7272
trackSymbol,
7373
reportInaccessibleThisError,
7474
reportInaccessibleUniqueSymbolError,
75+
reportCyclicStructureError,
7576
reportPrivateInBaseOfClassExpression,
7677
reportLikelyUnsafeImportRequiredError,
7778
moduleResolverHost: host,
@@ -175,6 +176,13 @@ namespace ts {
175176
}
176177
}
177178

179+
function reportCyclicStructureError() {
180+
if (errorNameNode) {
181+
context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary,
182+
declarationNameToString(errorNameNode)));
183+
}
184+
}
185+
178186
function reportInaccessibleThisError() {
179187
if (errorNameNode) {
180188
context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary,

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6495,6 +6495,7 @@ namespace ts {
64956495
reportInaccessibleThisError?(): void;
64966496
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
64976497
reportInaccessibleUniqueSymbolError?(): void;
6498+
reportCyclicStructureError?(): void;
64986499
reportLikelyUnsafeImportRequiredError?(specifier: string): void;
64996500
moduleResolverHost?: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string };
65006501
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
tests/cases/compiler/arrayFakeFlatNoCrashInferenceDeclarations.ts(13,10): error TS5088: The inferred type of 'foo' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
2+
3+
4+
==== tests/cases/compiler/arrayFakeFlatNoCrashInferenceDeclarations.ts (1 errors) ====
5+
type BadFlatArray<Arr, Depth extends number> = {obj: {
6+
"done": Arr,
7+
"recur": Arr extends ReadonlyArray<infer InnerArr>
8+
? BadFlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
9+
: Arr
10+
}[Depth extends -1 ? "done" : "recur"]}["obj"];
11+
12+
declare function flat<A, D extends number = 1>(
13+
arr: A,
14+
depth?: D
15+
): BadFlatArray<A, D>[]
16+
17+
function foo<T>(arr: T[], depth: number) {
18+
~~~
19+
!!! error TS5088: The inferred type of 'foo' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
20+
return flat(arr, depth);
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [arrayFakeFlatNoCrashInferenceDeclarations.ts]
2+
type BadFlatArray<Arr, Depth extends number> = {obj: {
3+
"done": Arr,
4+
"recur": Arr extends ReadonlyArray<infer InnerArr>
5+
? BadFlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
6+
: Arr
7+
}[Depth extends -1 ? "done" : "recur"]}["obj"];
8+
9+
declare function flat<A, D extends number = 1>(
10+
arr: A,
11+
depth?: D
12+
): BadFlatArray<A, D>[]
13+
14+
function foo<T>(arr: T[], depth: number) {
15+
return flat(arr, depth);
16+
}
17+
18+
//// [arrayFakeFlatNoCrashInferenceDeclarations.js]
19+
"use strict";
20+
function foo(arr, depth) {
21+
return flat(arr, depth);
22+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
=== tests/cases/compiler/arrayFakeFlatNoCrashInferenceDeclarations.ts ===
2+
type BadFlatArray<Arr, Depth extends number> = {obj: {
3+
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
4+
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
5+
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))
6+
>obj : Symbol(obj, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 48))
7+
8+
"done": Arr,
9+
>"done" : Symbol("done", Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 54))
10+
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
11+
12+
"recur": Arr extends ReadonlyArray<infer InnerArr>
13+
>"recur" : Symbol("recur", Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 1, 16))
14+
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
15+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2016.array.include.d.ts, --, --), Decl(lib.es2019.array.d.ts, --, --))
16+
>InnerArr : Symbol(InnerArr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 2, 44))
17+
18+
? BadFlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
19+
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
20+
>InnerArr : Symbol(InnerArr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 2, 44))
21+
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))
22+
23+
: Arr
24+
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
25+
26+
}[Depth extends -1 ? "done" : "recur"]}["obj"];
27+
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))
28+
29+
declare function flat<A, D extends number = 1>(
30+
>flat : Symbol(flat, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 5, 47))
31+
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))
32+
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))
33+
34+
arr: A,
35+
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 47))
36+
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))
37+
38+
depth?: D
39+
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 8, 11))
40+
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))
41+
42+
): BadFlatArray<A, D>[]
43+
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
44+
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))
45+
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))
46+
47+
function foo<T>(arr: T[], depth: number) {
48+
>foo : Symbol(foo, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 10, 23))
49+
>T : Symbol(T, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 13))
50+
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 16))
51+
>T : Symbol(T, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 13))
52+
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 25))
53+
54+
return flat(arr, depth);
55+
>flat : Symbol(flat, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 5, 47))
56+
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 16))
57+
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 25))
58+
}

0 commit comments

Comments
 (0)