Skip to content

Commit ea38146

Browse files
committed
Error on union types that are too complex to represent
1 parent 4335f44 commit ea38146

File tree

2 files changed

+88
-57
lines changed

2 files changed

+88
-57
lines changed

src/compiler/checker.ts

Lines changed: 80 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ namespace ts {
6767
let enumCount = 0;
6868
let instantiationDepth = 0;
6969
let constraintDepth = 0;
70+
let currentNode: Node | undefined;
7071

7172
const emptySymbols = createSymbolTable();
7273
const identityMapper: (type: Type) => Type = identity;
@@ -7525,6 +7526,7 @@ namespace ts {
75257526
// very high likelyhood we're dealing with an infinite generic type that perpetually generates
75267527
// new type identities as we descend into it. We stop the recursion here and mark this type
75277528
// and the outer types as having circular constraints.
7529+
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
75287530
nonTerminating = true;
75297531
return t.immediateBaseConstraint = noConstraintType;
75307532
}
@@ -9240,18 +9242,6 @@ namespace ts {
92409242
return includes;
92419243
}
92429244

9243-
function isSubtypeOfAny(source: Type, targets: ReadonlyArray<Type>): boolean {
9244-
for (const target of targets) {
9245-
if (source !== target && isTypeSubtypeOf(source, target) && (
9246-
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
9247-
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
9248-
isTypeDerivedFrom(source, target))) {
9249-
return true;
9250-
}
9251-
}
9252-
return false;
9253-
}
9254-
92559245
function isSetOfLiteralsFromSameEnum(types: ReadonlyArray<Type>): boolean {
92569246
const first = types[0];
92579247
if (first.flags & TypeFlags.EnumLiteral) {
@@ -9268,17 +9258,40 @@ namespace ts {
92689258
return false;
92699259
}
92709260

9271-
function removeSubtypes(types: Type[]) {
9272-
if (types.length === 0 || isSetOfLiteralsFromSameEnum(types)) {
9273-
return;
9261+
function removeSubtypes(types: Type[]): boolean {
9262+
const len = types.length;
9263+
if (len === 0 || isSetOfLiteralsFromSameEnum(types)) {
9264+
return true;
92749265
}
9275-
let i = types.length;
9266+
let i = len;
9267+
let count = 0;
92769268
while (i > 0) {
92779269
i--;
9278-
if (isSubtypeOfAny(types[i], types)) {
9279-
orderedRemoveItemAt(types, i);
9270+
const source = types[i];
9271+
for (const target of types) {
9272+
if (source !== target) {
9273+
if (count === 10000) {
9274+
// After 10000 subtype checks we estimate the remaining amount of work by assuming the
9275+
// same ratio of checks to removals. If the estimated number of remaining type checks is
9276+
// greater than 1000000 we deem the union type too complex to represent.
9277+
const estimatedCount = (count / (len - i)) * len;
9278+
if (estimatedCount > 1000000) {
9279+
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
9280+
return false;
9281+
}
9282+
}
9283+
count++;
9284+
if (isTypeSubtypeOf(source, target) && (
9285+
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
9286+
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
9287+
isTypeDerivedFrom(source, target))) {
9288+
orderedRemoveItemAt(types, i);
9289+
break;
9290+
}
9291+
}
92809292
}
92819293
}
9294+
return true;
92829295
}
92839296

92849297
function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) {
@@ -9325,7 +9338,9 @@ namespace ts {
93259338
}
93269339
break;
93279340
case UnionReduction.Subtype:
9328-
removeSubtypes(typeSet);
9341+
if (!removeSubtypes(typeSet)) {
9342+
return errorType;
9343+
}
93299344
break;
93309345
}
93319346
if (typeSet.length === 0) {
@@ -10957,6 +10972,7 @@ namespace ts {
1095710972
// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
1095810973
// with a combination of infinite generic types that perpetually generate new type identities. We stop
1095910974
// the recursion here by yielding the error type.
10975+
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
1096010976
return errorType;
1096110977
}
1096210978
instantiationDepth++;
@@ -23063,7 +23079,7 @@ namespace ts {
2306323079
return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
2306423080
}
2306523081

23066-
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration, type: Type, checkMode?: CheckMode) {
23082+
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
2306723083
if (checkMode === CheckMode.Inferential) {
2306823084
const signature = getSingleCallSignature(type);
2306923085
if (signature && signature.typeParameters) {
@@ -23133,15 +23149,10 @@ namespace ts {
2313323149
// have the wildcard function type; this form of type check is used during overload resolution to exclude
2313423150
// contextually typed function and arrow expressions in the initial phase.
2313523151
function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type {
23136-
let type: Type;
23137-
if (node.kind === SyntaxKind.QualifiedName) {
23138-
type = checkQualifiedName(<QualifiedName>node);
23139-
}
23140-
else {
23141-
const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple);
23142-
type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
23143-
}
23144-
23152+
const saveCurrentNode = currentNode;
23153+
currentNode = node;
23154+
const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple);
23155+
const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
2314523156
if (isConstEnumObjectType(type)) {
2314623157
// enum object type for const enums are only permitted in:
2314723158
// - 'left' in property access
@@ -23157,6 +23168,7 @@ namespace ts {
2315723168
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query);
2315823169
}
2315923170
}
23171+
currentNode = saveCurrentNode;
2316023172
return type;
2316123173
}
2316223174

@@ -23168,7 +23180,7 @@ namespace ts {
2316823180
return checkExpression(node.expression, checkMode);
2316923181
}
2317023182

23171-
function checkExpressionWorker(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type {
23183+
function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type {
2317223184
switch (node.kind) {
2317323185
case SyntaxKind.Identifier:
2317423186
return checkIdentifier(<Identifier>node);
@@ -23201,6 +23213,8 @@ namespace ts {
2320123213
return checkObjectLiteral(<ObjectLiteralExpression>node, checkMode);
2320223214
case SyntaxKind.PropertyAccessExpression:
2320323215
return checkPropertyAccessExpression(<PropertyAccessExpression>node);
23216+
case SyntaxKind.QualifiedName:
23217+
return checkQualifiedName(<QualifiedName>node);
2320423218
case SyntaxKind.ElementAccessExpression:
2320523219
return checkIndexedAccess(<ElementAccessExpression>node);
2320623220
case SyntaxKind.CallExpression:
@@ -27882,10 +27896,15 @@ namespace ts {
2788227896
}
2788327897

2788427898
function checkSourceElement(node: Node | undefined): void {
27885-
if (!node) {
27886-
return;
27899+
if (node) {
27900+
const saveCurrentNode = currentNode;
27901+
currentNode = node;
27902+
checkSourceElementWorker(node);
27903+
currentNode = saveCurrentNode;
2788727904
}
27905+
}
2788827906

27907+
function checkSourceElementWorker(node: Node): void {
2788927908
if (isInJSFile(node)) {
2789027909
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
2789127910
}
@@ -28143,32 +28162,36 @@ namespace ts {
2814328162

2814428163
function checkDeferredNodes(context: SourceFile) {
2814528164
const links = getNodeLinks(context);
28146-
if (!links.deferredNodes) {
28147-
return;
28165+
if (links.deferredNodes) {
28166+
links.deferredNodes.forEach(checkDeferredNode);
2814828167
}
28149-
links.deferredNodes.forEach(node => {
28150-
switch (node.kind) {
28151-
case SyntaxKind.FunctionExpression:
28152-
case SyntaxKind.ArrowFunction:
28153-
case SyntaxKind.MethodDeclaration:
28154-
case SyntaxKind.MethodSignature:
28155-
checkFunctionExpressionOrObjectLiteralMethodDeferred(<FunctionExpression>node);
28156-
break;
28157-
case SyntaxKind.GetAccessor:
28158-
case SyntaxKind.SetAccessor:
28159-
checkAccessorDeclaration(<AccessorDeclaration>node);
28160-
break;
28161-
case SyntaxKind.ClassExpression:
28162-
checkClassExpressionDeferred(<ClassExpression>node);
28163-
break;
28164-
case SyntaxKind.JsxSelfClosingElement:
28165-
checkJsxSelfClosingElementDeferred(<JsxSelfClosingElement>node);
28166-
break;
28167-
case SyntaxKind.JsxElement:
28168-
checkJsxElementDeferred(<JsxElement>node);
28169-
break;
28170-
}
28171-
});
28168+
}
28169+
28170+
function checkDeferredNode(node: Node) {
28171+
const saveCurrentNode = currentNode;
28172+
currentNode = node;
28173+
switch (node.kind) {
28174+
case SyntaxKind.FunctionExpression:
28175+
case SyntaxKind.ArrowFunction:
28176+
case SyntaxKind.MethodDeclaration:
28177+
case SyntaxKind.MethodSignature:
28178+
checkFunctionExpressionOrObjectLiteralMethodDeferred(<FunctionExpression>node);
28179+
break;
28180+
case SyntaxKind.GetAccessor:
28181+
case SyntaxKind.SetAccessor:
28182+
checkAccessorDeclaration(<AccessorDeclaration>node);
28183+
break;
28184+
case SyntaxKind.ClassExpression:
28185+
checkClassExpressionDeferred(<ClassExpression>node);
28186+
break;
28187+
case SyntaxKind.JsxSelfClosingElement:
28188+
checkJsxSelfClosingElementDeferred(<JsxSelfClosingElement>node);
28189+
break;
28190+
case SyntaxKind.JsxElement:
28191+
checkJsxElementDeferred(<JsxElement>node);
28192+
break;
28193+
}
28194+
currentNode = saveCurrentNode;
2817228195
}
2817328196

2817428197
function checkSourceFile(node: SourceFile) {

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,14 @@
21322132
"category": "Error",
21332133
"code": 2588
21342134
},
2135+
"Type instantiation is excessively deep and possibly infinite.": {
2136+
"category": "Error",
2137+
"code": 2589
2138+
},
2139+
"Expression produces a union type that is too complex to represent.": {
2140+
"category": "Error",
2141+
"code": 2590
2142+
},
21352143
"JSX element attributes type '{0}' may not be a union type.": {
21362144
"category": "Error",
21372145
"code": 2600

0 commit comments

Comments
 (0)