Skip to content

Commit

Permalink
Fix crash when serializing the return type of a generic call to Array…
Browse files Browse the repository at this point in the history
….prototype.flat (#38904) (#39079)

* 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
  • Loading branch information
weswigham committed Jun 15, 2020
1 parent 9ec5fc5 commit 986e9dd
Show file tree
Hide file tree
Showing 47 changed files with 503 additions and 222 deletions.
38 changes: 28 additions & 10 deletions src/compiler/checker.ts
Expand Up @@ -4397,8 +4397,8 @@ namespace ts {
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
context.inferTypeParameters = saveInferTypeParameters;
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(<ConditionalType>type));
const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(<ConditionalType>type));
context.approximateLength += 15;
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}
Expand All @@ -4408,6 +4408,21 @@ namespace ts {

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


function typeToTypeNodeOrCircularityElision(type: Type) {
if (type.flags & TypeFlags.Union) {
if (context.visitedTypes && context.visitedTypes.has("" + getTypeId(type))) {
if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) {
context.encounteredError = true;
context.tracker?.reportCyclicStructureError?.();
}
return createElidedInformationPlaceholder(context);
}
return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context));
}
return typeToTypeNodeHelper(type, context);
}

function createMappedTypeNodeFromType(type: MappedType) {
Debug.assert(!!(type.flags & TypeFlags.Object));
const readonlyToken = type.declaration.readonlyToken ? <ReadonlyToken | PlusToken | MinusToken>createToken(type.declaration.readonlyToken.kind) : undefined;
Expand Down Expand Up @@ -12794,10 +12809,12 @@ namespace ts {
return links.resolvedType;
}

function createIndexedAccessType(objectType: Type, indexType: Type) {
function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
type.objectType = objectType;
type.indexType = indexType;
type.aliasSymbol = aliasSymbol;
type.aliasTypeArguments = aliasTypeArguments;
return type;
}

Expand Down Expand Up @@ -13129,11 +13146,11 @@ namespace ts {
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
}

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

function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined {
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined {
if (objectType === wildcardType || indexType === wildcardType) {
return wildcardType;
}
Expand All @@ -13155,7 +13172,7 @@ namespace ts {
const id = objectType.id + "," + indexType.id;
let type = indexedAccessTypes.get(id);
if (!type) {
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType));
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments));
}
return type;
}
Expand Down Expand Up @@ -13183,7 +13200,7 @@ namespace ts {
if (wasMissingProp) {
return undefined;
}
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol);
}
Expand All @@ -13193,7 +13210,8 @@ namespace ts {
if (!links.resolvedType) {
const objectType = getTypeFromTypeNode(node.objectType);
const indexType = getTypeFromTypeNode(node.indexType);
const resolved = getIndexedAccessType(objectType, indexType, node);
const potentialAlias = getAliasSymbolForTypeNode(node);
const resolved = getIndexedAccessType(objectType, indexType, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias));
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
(<IndexedAccessType>resolved).objectType === objectType &&
(<IndexedAccessType>resolved).indexType === indexType ?
Expand Down Expand Up @@ -14341,7 +14359,7 @@ namespace ts {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper), /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if (flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -3509,6 +3509,10 @@
"category": "Error",
"code": 5083
},
"The inferred type of '{0}' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.": {
"category": "Error",
"code": 5088
},

"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/transformers/declarations.ts
Expand Up @@ -72,6 +72,7 @@ namespace ts {
trackSymbol,
reportInaccessibleThisError,
reportInaccessibleUniqueSymbolError,
reportCyclicStructureError,
reportPrivateInBaseOfClassExpression,
reportLikelyUnsafeImportRequiredError,
moduleResolverHost: host,
Expand Down Expand Up @@ -175,6 +176,13 @@ namespace ts {
}
}

function reportCyclicStructureError() {
if (errorNameNode) {
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,
declarationNameToString(errorNameNode)));
}
}

function reportInaccessibleThisError() {
if (errorNameNode) {
context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -6479,6 +6479,7 @@ namespace ts {
reportInaccessibleThisError?(): void;
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
reportCyclicStructureError?(): void;
reportLikelyUnsafeImportRequiredError?(specifier: string): void;
moduleResolverHost?: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string };
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
Expand Down
@@ -0,0 +1,21 @@
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.


==== tests/cases/compiler/arrayFakeFlatNoCrashInferenceDeclarations.ts (1 errors) ====
type BadFlatArray<Arr, Depth extends number> = {obj: {
"done": Arr,
"recur": Arr extends ReadonlyArray<infer InnerArr>
? 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]>
: Arr
}[Depth extends -1 ? "done" : "recur"]}["obj"];

declare function flat<A, D extends number = 1>(
arr: A,
depth?: D
): BadFlatArray<A, D>[]

function foo<T>(arr: T[], depth: number) {
~~~
!!! error TS5088: The inferred type of 'foo' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
return flat(arr, depth);
}
@@ -0,0 +1,22 @@
//// [arrayFakeFlatNoCrashInferenceDeclarations.ts]
type BadFlatArray<Arr, Depth extends number> = {obj: {
"done": Arr,
"recur": Arr extends ReadonlyArray<infer InnerArr>
? 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]>
: Arr
}[Depth extends -1 ? "done" : "recur"]}["obj"];

declare function flat<A, D extends number = 1>(
arr: A,
depth?: D
): BadFlatArray<A, D>[]

function foo<T>(arr: T[], depth: number) {
return flat(arr, depth);
}

//// [arrayFakeFlatNoCrashInferenceDeclarations.js]
"use strict";
function foo(arr, depth) {
return flat(arr, depth);
}
@@ -0,0 +1,58 @@
=== tests/cases/compiler/arrayFakeFlatNoCrashInferenceDeclarations.ts ===
type BadFlatArray<Arr, Depth extends number> = {obj: {
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))
>obj : Symbol(obj, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 48))

"done": Arr,
>"done" : Symbol("done", Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 54))
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))

"recur": Arr extends ReadonlyArray<infer InnerArr>
>"recur" : Symbol("recur", Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 1, 16))
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))
>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, --, --))
>InnerArr : Symbol(InnerArr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 2, 44))

? 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]>
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
>InnerArr : Symbol(InnerArr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 2, 44))
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))

: Arr
>Arr : Symbol(Arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 18))

}[Depth extends -1 ? "done" : "recur"]}["obj"];
>Depth : Symbol(Depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 22))

declare function flat<A, D extends number = 1>(
>flat : Symbol(flat, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 5, 47))
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))

arr: A,
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 47))
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))

depth?: D
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 8, 11))
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))

): BadFlatArray<A, D>[]
>BadFlatArray : Symbol(BadFlatArray, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 0, 0))
>A : Symbol(A, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 22))
>D : Symbol(D, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 7, 24))

function foo<T>(arr: T[], depth: number) {
>foo : Symbol(foo, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 10, 23))
>T : Symbol(T, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 13))
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 16))
>T : Symbol(T, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 13))
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 25))

return flat(arr, depth);
>flat : Symbol(flat, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 5, 47))
>arr : Symbol(arr, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 16))
>depth : Symbol(depth, Decl(arrayFakeFlatNoCrashInferenceDeclarations.ts, 12, 25))
}

0 comments on commit 986e9dd

Please sign in to comment.