From 6650496e85a6538d5382cf58ca7404a7e098cf97 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 24 Sep 2020 14:18:16 -0700 Subject: [PATCH 1/2] Enforce a size limit in getSpreadType When a union is spread into a union, the sizes are multiplied, potentially resulting in an enormous union (especially if there are repeated spreads). This check detects cases that used to run out of memory. Fixes #40754 --- src/compiler/checker.ts | 15 +- src/compiler/diagnosticMessages.json | 4 + .../objectSpreadRepeatedComplexity.errors.txt | 170 +++++++++ .../objectSpreadRepeatedComplexity.js | 160 +++++++++ .../objectSpreadRepeatedComplexity.symbols | 203 +++++++++++ .../objectSpreadRepeatedComplexity.types | 339 ++++++++++++++++++ .../spread/objectSpreadRepeatedComplexity.ts | 85 +++++ 7 files changed, 974 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt create mode 100644 tests/baselines/reference/objectSpreadRepeatedComplexity.js create mode 100644 tests/baselines/reference/objectSpreadRepeatedComplexity.symbols create mode 100644 tests/baselines/reference/objectSpreadRepeatedComplexity.types create mode 100644 tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7750d8af73e51..398724fca930e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14458,14 +14458,14 @@ namespace ts { if (merged) { return getSpreadType(merged, right, symbol, objectFlags, readonly); } - return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)); + return errorTypeIfTooLarge() ?? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)); } if (right.flags & TypeFlags.Union) { const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly); if (merged) { return getSpreadType(left, merged, symbol, objectFlags, readonly); } - return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)); + return errorTypeIfTooLarge() ?? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)); } if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { return left; @@ -14544,6 +14544,17 @@ namespace ts { getIndexInfoWithReadonly(numberIndexInfo, readonly)); spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; return spread; + + function errorTypeIfTooLarge(): Type | undefined { + if (left.flags & right.flags & TypeFlags.Union) { + const resultSize = (left as UnionType).types.length * (right as UnionType).types.length; + if (resultSize > 100000) { + tracing.instant(tracing.Phase.Check, "getSpreadType_DepthLimit", { leftId: left.id, rightId: right.id }); + error(currentNode, Diagnostics.Spread_expression_produces_a_union_type_that_is_too_complex_to_represent); + return errorType; + } + } + } } /** We approximate own properties as non-methods plus methods that are inside the object literal */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f2ea2fc68d6d5..c7255c40ba4e1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3048,6 +3048,10 @@ "category": "Error", "code": 2795 }, + "Spread expression produces a union type that is too complex to represent.": { + "category": "Error", + "code": 2796 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt b/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt new file mode 100644 index 0000000000000..e9ad041f28dd7 --- /dev/null +++ b/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt @@ -0,0 +1,170 @@ +tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts(3,12): error TS2796: Spread expression produces a union type that is too complex to represent. + + +==== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts (1 errors) ==== + function f(cnd: Record){ + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return { + ~ + // Without this one, it collapses to {} ? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ...(cnd[1] && + ~~~~~~~~~~~~~~~~~~~~~ + cnd[2] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop0: 0, + ~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + + + // With one prop each, it collapses to a single object (#34853?) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ...(cnd[3] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop3a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop3b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[4] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop4a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop4b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[5] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop5a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop5b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[6] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop6a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop6b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[7] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop7a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop7b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[8] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop8a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop8b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[9] && { + ~~~~~~~~~~~~~~~~~~~~~~~ + prop9a: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + prop9b: 1, + ~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[10] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop10a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop10b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[11] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop11a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop11b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[12] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop12a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop12b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[13] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop13a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop13b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[14] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop14a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop14b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[15] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop15a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop15b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[16] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop16a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop16b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[17] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop17a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop17b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[18] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop18a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop18b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[19] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop19a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop19b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + ...(cnd[20] && { + ~~~~~~~~~~~~~~~~~~~~~~~~ + prop20a: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + prop20b: 1, + ~~~~~~~~~~~~~~~~~~~~~~~ + }), + ~~~~~~~~~~~ + }; + ~~~~~ +!!! error TS2796: Spread expression produces a union type that is too complex to represent. + } \ No newline at end of file diff --git a/tests/baselines/reference/objectSpreadRepeatedComplexity.js b/tests/baselines/reference/objectSpreadRepeatedComplexity.js new file mode 100644 index 0000000000000..8d860377a2575 --- /dev/null +++ b/tests/baselines/reference/objectSpreadRepeatedComplexity.js @@ -0,0 +1,160 @@ +//// [objectSpreadRepeatedComplexity.ts] +function f(cnd: Record){ + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return { + // Without this one, it collapses to {} ? + ...(cnd[1] && + cnd[2] && { + prop0: 0, + }), + + // With one prop each, it collapses to a single object (#34853?) + ...(cnd[3] && { + prop3a: 1, + prop3b: 1, + }), + ...(cnd[4] && { + prop4a: 1, + prop4b: 1, + }), + ...(cnd[5] && { + prop5a: 1, + prop5b: 1, + }), + ...(cnd[6] && { + prop6a: 1, + prop6b: 1, + }), + ...(cnd[7] && { + prop7a: 1, + prop7b: 1, + }), + ...(cnd[8] && { + prop8a: 1, + prop8b: 1, + }), + ...(cnd[9] && { + prop9a: 1, + prop9b: 1, + }), + ...(cnd[10] && { + prop10a: 1, + prop10b: 1, + }), + ...(cnd[11] && { + prop11a: 1, + prop11b: 1, + }), + ...(cnd[12] && { + prop12a: 1, + prop12b: 1, + }), + ...(cnd[13] && { + prop13a: 1, + prop13b: 1, + }), + ...(cnd[14] && { + prop14a: 1, + prop14b: 1, + }), + ...(cnd[15] && { + prop15a: 1, + prop15b: 1, + }), + ...(cnd[16] && { + prop16a: 1, + prop16b: 1, + }), + ...(cnd[17] && { + prop17a: 1, + prop17b: 1, + }), + ...(cnd[18] && { + prop18a: 1, + prop18b: 1, + }), + ...(cnd[19] && { + prop19a: 1, + prop19b: 1, + }), + ...(cnd[20] && { + prop20a: 1, + prop20b: 1, + }), + }; +} + +//// [objectSpreadRepeatedComplexity.js] +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +function f(cnd) { + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({}, (cnd[1] && + cnd[2] && { + prop0: 0 + })), (cnd[3] && { + prop3a: 1, + prop3b: 1 + })), (cnd[4] && { + prop4a: 1, + prop4b: 1 + })), (cnd[5] && { + prop5a: 1, + prop5b: 1 + })), (cnd[6] && { + prop6a: 1, + prop6b: 1 + })), (cnd[7] && { + prop7a: 1, + prop7b: 1 + })), (cnd[8] && { + prop8a: 1, + prop8b: 1 + })), (cnd[9] && { + prop9a: 1, + prop9b: 1 + })), (cnd[10] && { + prop10a: 1, + prop10b: 1 + })), (cnd[11] && { + prop11a: 1, + prop11b: 1 + })), (cnd[12] && { + prop12a: 1, + prop12b: 1 + })), (cnd[13] && { + prop13a: 1, + prop13b: 1 + })), (cnd[14] && { + prop14a: 1, + prop14b: 1 + })), (cnd[15] && { + prop15a: 1, + prop15b: 1 + })), (cnd[16] && { + prop16a: 1, + prop16b: 1 + })), (cnd[17] && { + prop17a: 1, + prop17b: 1 + })), (cnd[18] && { + prop18a: 1, + prop18b: 1 + })), (cnd[19] && { + prop19a: 1, + prop19b: 1 + })), (cnd[20] && { + prop20a: 1, + prop20b: 1 + })); +} diff --git a/tests/baselines/reference/objectSpreadRepeatedComplexity.symbols b/tests/baselines/reference/objectSpreadRepeatedComplexity.symbols new file mode 100644 index 0000000000000..58877c3c2b7ba --- /dev/null +++ b/tests/baselines/reference/objectSpreadRepeatedComplexity.symbols @@ -0,0 +1,203 @@ +=== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts === +function f(cnd: Record){ +>f : Symbol(f, Decl(objectSpreadRepeatedComplexity.ts, 0, 0)) +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return { + // Without this one, it collapses to {} ? + ...(cnd[1] && +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + cnd[2] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop0: 0, +>prop0 : Symbol(prop0, Decl(objectSpreadRepeatedComplexity.ts, 5, 23)) + + }), + + // With one prop each, it collapses to a single object (#34853?) + ...(cnd[3] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop3a: 1, +>prop3a : Symbol(prop3a, Decl(objectSpreadRepeatedComplexity.ts, 10, 23)) + + prop3b: 1, +>prop3b : Symbol(prop3b, Decl(objectSpreadRepeatedComplexity.ts, 11, 22)) + + }), + ...(cnd[4] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop4a: 1, +>prop4a : Symbol(prop4a, Decl(objectSpreadRepeatedComplexity.ts, 14, 23)) + + prop4b: 1, +>prop4b : Symbol(prop4b, Decl(objectSpreadRepeatedComplexity.ts, 15, 22)) + + }), + ...(cnd[5] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop5a: 1, +>prop5a : Symbol(prop5a, Decl(objectSpreadRepeatedComplexity.ts, 18, 23)) + + prop5b: 1, +>prop5b : Symbol(prop5b, Decl(objectSpreadRepeatedComplexity.ts, 19, 22)) + + }), + ...(cnd[6] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop6a: 1, +>prop6a : Symbol(prop6a, Decl(objectSpreadRepeatedComplexity.ts, 22, 23)) + + prop6b: 1, +>prop6b : Symbol(prop6b, Decl(objectSpreadRepeatedComplexity.ts, 23, 22)) + + }), + ...(cnd[7] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop7a: 1, +>prop7a : Symbol(prop7a, Decl(objectSpreadRepeatedComplexity.ts, 26, 23)) + + prop7b: 1, +>prop7b : Symbol(prop7b, Decl(objectSpreadRepeatedComplexity.ts, 27, 22)) + + }), + ...(cnd[8] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop8a: 1, +>prop8a : Symbol(prop8a, Decl(objectSpreadRepeatedComplexity.ts, 30, 23)) + + prop8b: 1, +>prop8b : Symbol(prop8b, Decl(objectSpreadRepeatedComplexity.ts, 31, 22)) + + }), + ...(cnd[9] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop9a: 1, +>prop9a : Symbol(prop9a, Decl(objectSpreadRepeatedComplexity.ts, 34, 23)) + + prop9b: 1, +>prop9b : Symbol(prop9b, Decl(objectSpreadRepeatedComplexity.ts, 35, 22)) + + }), + ...(cnd[10] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop10a: 1, +>prop10a : Symbol(prop10a, Decl(objectSpreadRepeatedComplexity.ts, 38, 24)) + + prop10b: 1, +>prop10b : Symbol(prop10b, Decl(objectSpreadRepeatedComplexity.ts, 39, 23)) + + }), + ...(cnd[11] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop11a: 1, +>prop11a : Symbol(prop11a, Decl(objectSpreadRepeatedComplexity.ts, 42, 24)) + + prop11b: 1, +>prop11b : Symbol(prop11b, Decl(objectSpreadRepeatedComplexity.ts, 43, 23)) + + }), + ...(cnd[12] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop12a: 1, +>prop12a : Symbol(prop12a, Decl(objectSpreadRepeatedComplexity.ts, 46, 24)) + + prop12b: 1, +>prop12b : Symbol(prop12b, Decl(objectSpreadRepeatedComplexity.ts, 47, 23)) + + }), + ...(cnd[13] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop13a: 1, +>prop13a : Symbol(prop13a, Decl(objectSpreadRepeatedComplexity.ts, 50, 24)) + + prop13b: 1, +>prop13b : Symbol(prop13b, Decl(objectSpreadRepeatedComplexity.ts, 51, 23)) + + }), + ...(cnd[14] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop14a: 1, +>prop14a : Symbol(prop14a, Decl(objectSpreadRepeatedComplexity.ts, 54, 24)) + + prop14b: 1, +>prop14b : Symbol(prop14b, Decl(objectSpreadRepeatedComplexity.ts, 55, 23)) + + }), + ...(cnd[15] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop15a: 1, +>prop15a : Symbol(prop15a, Decl(objectSpreadRepeatedComplexity.ts, 58, 24)) + + prop15b: 1, +>prop15b : Symbol(prop15b, Decl(objectSpreadRepeatedComplexity.ts, 59, 23)) + + }), + ...(cnd[16] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop16a: 1, +>prop16a : Symbol(prop16a, Decl(objectSpreadRepeatedComplexity.ts, 62, 24)) + + prop16b: 1, +>prop16b : Symbol(prop16b, Decl(objectSpreadRepeatedComplexity.ts, 63, 23)) + + }), + ...(cnd[17] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop17a: 1, +>prop17a : Symbol(prop17a, Decl(objectSpreadRepeatedComplexity.ts, 66, 24)) + + prop17b: 1, +>prop17b : Symbol(prop17b, Decl(objectSpreadRepeatedComplexity.ts, 67, 23)) + + }), + ...(cnd[18] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop18a: 1, +>prop18a : Symbol(prop18a, Decl(objectSpreadRepeatedComplexity.ts, 70, 24)) + + prop18b: 1, +>prop18b : Symbol(prop18b, Decl(objectSpreadRepeatedComplexity.ts, 71, 23)) + + }), + ...(cnd[19] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop19a: 1, +>prop19a : Symbol(prop19a, Decl(objectSpreadRepeatedComplexity.ts, 74, 24)) + + prop19b: 1, +>prop19b : Symbol(prop19b, Decl(objectSpreadRepeatedComplexity.ts, 75, 23)) + + }), + ...(cnd[20] && { +>cnd : Symbol(cnd, Decl(objectSpreadRepeatedComplexity.ts, 0, 11)) + + prop20a: 1, +>prop20a : Symbol(prop20a, Decl(objectSpreadRepeatedComplexity.ts, 78, 24)) + + prop20b: 1, +>prop20b : Symbol(prop20b, Decl(objectSpreadRepeatedComplexity.ts, 79, 23)) + + }), + }; +} diff --git a/tests/baselines/reference/objectSpreadRepeatedComplexity.types b/tests/baselines/reference/objectSpreadRepeatedComplexity.types new file mode 100644 index 0000000000000..c19d985acc6e9 --- /dev/null +++ b/tests/baselines/reference/objectSpreadRepeatedComplexity.types @@ -0,0 +1,339 @@ +=== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts === +function f(cnd: Record){ +>f : (cnd: Record) => any +>cnd : Record + + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return { +>{ // Without this one, it collapses to {} ? ...(cnd[1] && cnd[2] && { prop0: 0, }), // With one prop each, it collapses to a single object (#34853?) ...(cnd[3] && { prop3a: 1, prop3b: 1, }), ...(cnd[4] && { prop4a: 1, prop4b: 1, }), ...(cnd[5] && { prop5a: 1, prop5b: 1, }), ...(cnd[6] && { prop6a: 1, prop6b: 1, }), ...(cnd[7] && { prop7a: 1, prop7b: 1, }), ...(cnd[8] && { prop8a: 1, prop8b: 1, }), ...(cnd[9] && { prop9a: 1, prop9b: 1, }), ...(cnd[10] && { prop10a: 1, prop10b: 1, }), ...(cnd[11] && { prop11a: 1, prop11b: 1, }), ...(cnd[12] && { prop12a: 1, prop12b: 1, }), ...(cnd[13] && { prop13a: 1, prop13b: 1, }), ...(cnd[14] && { prop14a: 1, prop14b: 1, }), ...(cnd[15] && { prop15a: 1, prop15b: 1, }), ...(cnd[16] && { prop16a: 1, prop16b: 1, }), ...(cnd[17] && { prop17a: 1, prop17b: 1, }), ...(cnd[18] && { prop18a: 1, prop18b: 1, }), ...(cnd[19] && { prop19a: 1, prop19b: 1, }), ...(cnd[20] && { prop20a: 1, prop20b: 1, }), } : any + + // Without this one, it collapses to {} ? + ...(cnd[1] && +>(cnd[1] && cnd[2] && { prop0: 0, }) : false | { prop0: number; } +>cnd[1] && cnd[2] && { prop0: 0, } : false | { prop0: number; } +>cnd[1] && cnd[2] : boolean +>cnd[1] : boolean +>cnd : Record +>1 : 1 + + cnd[2] && { +>cnd[2] : boolean +>cnd : Record +>2 : 2 +>{ prop0: 0, } : { prop0: number; } + + prop0: 0, +>prop0 : number +>0 : 0 + + }), + + // With one prop each, it collapses to a single object (#34853?) + ...(cnd[3] && { +>(cnd[3] && { prop3a: 1, prop3b: 1, }) : false | { prop3a: number; prop3b: number; } +>cnd[3] && { prop3a: 1, prop3b: 1, } : false | { prop3a: number; prop3b: number; } +>cnd[3] : boolean +>cnd : Record +>3 : 3 +>{ prop3a: 1, prop3b: 1, } : { prop3a: number; prop3b: number; } + + prop3a: 1, +>prop3a : number +>1 : 1 + + prop3b: 1, +>prop3b : number +>1 : 1 + + }), + ...(cnd[4] && { +>(cnd[4] && { prop4a: 1, prop4b: 1, }) : false | { prop4a: number; prop4b: number; } +>cnd[4] && { prop4a: 1, prop4b: 1, } : false | { prop4a: number; prop4b: number; } +>cnd[4] : boolean +>cnd : Record +>4 : 4 +>{ prop4a: 1, prop4b: 1, } : { prop4a: number; prop4b: number; } + + prop4a: 1, +>prop4a : number +>1 : 1 + + prop4b: 1, +>prop4b : number +>1 : 1 + + }), + ...(cnd[5] && { +>(cnd[5] && { prop5a: 1, prop5b: 1, }) : false | { prop5a: number; prop5b: number; } +>cnd[5] && { prop5a: 1, prop5b: 1, } : false | { prop5a: number; prop5b: number; } +>cnd[5] : boolean +>cnd : Record +>5 : 5 +>{ prop5a: 1, prop5b: 1, } : { prop5a: number; prop5b: number; } + + prop5a: 1, +>prop5a : number +>1 : 1 + + prop5b: 1, +>prop5b : number +>1 : 1 + + }), + ...(cnd[6] && { +>(cnd[6] && { prop6a: 1, prop6b: 1, }) : false | { prop6a: number; prop6b: number; } +>cnd[6] && { prop6a: 1, prop6b: 1, } : false | { prop6a: number; prop6b: number; } +>cnd[6] : boolean +>cnd : Record +>6 : 6 +>{ prop6a: 1, prop6b: 1, } : { prop6a: number; prop6b: number; } + + prop6a: 1, +>prop6a : number +>1 : 1 + + prop6b: 1, +>prop6b : number +>1 : 1 + + }), + ...(cnd[7] && { +>(cnd[7] && { prop7a: 1, prop7b: 1, }) : false | { prop7a: number; prop7b: number; } +>cnd[7] && { prop7a: 1, prop7b: 1, } : false | { prop7a: number; prop7b: number; } +>cnd[7] : boolean +>cnd : Record +>7 : 7 +>{ prop7a: 1, prop7b: 1, } : { prop7a: number; prop7b: number; } + + prop7a: 1, +>prop7a : number +>1 : 1 + + prop7b: 1, +>prop7b : number +>1 : 1 + + }), + ...(cnd[8] && { +>(cnd[8] && { prop8a: 1, prop8b: 1, }) : false | { prop8a: number; prop8b: number; } +>cnd[8] && { prop8a: 1, prop8b: 1, } : false | { prop8a: number; prop8b: number; } +>cnd[8] : boolean +>cnd : Record +>8 : 8 +>{ prop8a: 1, prop8b: 1, } : { prop8a: number; prop8b: number; } + + prop8a: 1, +>prop8a : number +>1 : 1 + + prop8b: 1, +>prop8b : number +>1 : 1 + + }), + ...(cnd[9] && { +>(cnd[9] && { prop9a: 1, prop9b: 1, }) : false | { prop9a: number; prop9b: number; } +>cnd[9] && { prop9a: 1, prop9b: 1, } : false | { prop9a: number; prop9b: number; } +>cnd[9] : boolean +>cnd : Record +>9 : 9 +>{ prop9a: 1, prop9b: 1, } : { prop9a: number; prop9b: number; } + + prop9a: 1, +>prop9a : number +>1 : 1 + + prop9b: 1, +>prop9b : number +>1 : 1 + + }), + ...(cnd[10] && { +>(cnd[10] && { prop10a: 1, prop10b: 1, }) : false | { prop10a: number; prop10b: number; } +>cnd[10] && { prop10a: 1, prop10b: 1, } : false | { prop10a: number; prop10b: number; } +>cnd[10] : boolean +>cnd : Record +>10 : 10 +>{ prop10a: 1, prop10b: 1, } : { prop10a: number; prop10b: number; } + + prop10a: 1, +>prop10a : number +>1 : 1 + + prop10b: 1, +>prop10b : number +>1 : 1 + + }), + ...(cnd[11] && { +>(cnd[11] && { prop11a: 1, prop11b: 1, }) : false | { prop11a: number; prop11b: number; } +>cnd[11] && { prop11a: 1, prop11b: 1, } : false | { prop11a: number; prop11b: number; } +>cnd[11] : boolean +>cnd : Record +>11 : 11 +>{ prop11a: 1, prop11b: 1, } : { prop11a: number; prop11b: number; } + + prop11a: 1, +>prop11a : number +>1 : 1 + + prop11b: 1, +>prop11b : number +>1 : 1 + + }), + ...(cnd[12] && { +>(cnd[12] && { prop12a: 1, prop12b: 1, }) : false | { prop12a: number; prop12b: number; } +>cnd[12] && { prop12a: 1, prop12b: 1, } : false | { prop12a: number; prop12b: number; } +>cnd[12] : boolean +>cnd : Record +>12 : 12 +>{ prop12a: 1, prop12b: 1, } : { prop12a: number; prop12b: number; } + + prop12a: 1, +>prop12a : number +>1 : 1 + + prop12b: 1, +>prop12b : number +>1 : 1 + + }), + ...(cnd[13] && { +>(cnd[13] && { prop13a: 1, prop13b: 1, }) : false | { prop13a: number; prop13b: number; } +>cnd[13] && { prop13a: 1, prop13b: 1, } : false | { prop13a: number; prop13b: number; } +>cnd[13] : boolean +>cnd : Record +>13 : 13 +>{ prop13a: 1, prop13b: 1, } : { prop13a: number; prop13b: number; } + + prop13a: 1, +>prop13a : number +>1 : 1 + + prop13b: 1, +>prop13b : number +>1 : 1 + + }), + ...(cnd[14] && { +>(cnd[14] && { prop14a: 1, prop14b: 1, }) : false | { prop14a: number; prop14b: number; } +>cnd[14] && { prop14a: 1, prop14b: 1, } : false | { prop14a: number; prop14b: number; } +>cnd[14] : boolean +>cnd : Record +>14 : 14 +>{ prop14a: 1, prop14b: 1, } : { prop14a: number; prop14b: number; } + + prop14a: 1, +>prop14a : number +>1 : 1 + + prop14b: 1, +>prop14b : number +>1 : 1 + + }), + ...(cnd[15] && { +>(cnd[15] && { prop15a: 1, prop15b: 1, }) : false | { prop15a: number; prop15b: number; } +>cnd[15] && { prop15a: 1, prop15b: 1, } : false | { prop15a: number; prop15b: number; } +>cnd[15] : boolean +>cnd : Record +>15 : 15 +>{ prop15a: 1, prop15b: 1, } : { prop15a: number; prop15b: number; } + + prop15a: 1, +>prop15a : number +>1 : 1 + + prop15b: 1, +>prop15b : number +>1 : 1 + + }), + ...(cnd[16] && { +>(cnd[16] && { prop16a: 1, prop16b: 1, }) : false | { prop16a: number; prop16b: number; } +>cnd[16] && { prop16a: 1, prop16b: 1, } : false | { prop16a: number; prop16b: number; } +>cnd[16] : boolean +>cnd : Record +>16 : 16 +>{ prop16a: 1, prop16b: 1, } : { prop16a: number; prop16b: number; } + + prop16a: 1, +>prop16a : number +>1 : 1 + + prop16b: 1, +>prop16b : number +>1 : 1 + + }), + ...(cnd[17] && { +>(cnd[17] && { prop17a: 1, prop17b: 1, }) : false | { prop17a: number; prop17b: number; } +>cnd[17] && { prop17a: 1, prop17b: 1, } : false | { prop17a: number; prop17b: number; } +>cnd[17] : boolean +>cnd : Record +>17 : 17 +>{ prop17a: 1, prop17b: 1, } : { prop17a: number; prop17b: number; } + + prop17a: 1, +>prop17a : number +>1 : 1 + + prop17b: 1, +>prop17b : number +>1 : 1 + + }), + ...(cnd[18] && { +>(cnd[18] && { prop18a: 1, prop18b: 1, }) : false | { prop18a: number; prop18b: number; } +>cnd[18] && { prop18a: 1, prop18b: 1, } : false | { prop18a: number; prop18b: number; } +>cnd[18] : boolean +>cnd : Record +>18 : 18 +>{ prop18a: 1, prop18b: 1, } : { prop18a: number; prop18b: number; } + + prop18a: 1, +>prop18a : number +>1 : 1 + + prop18b: 1, +>prop18b : number +>1 : 1 + + }), + ...(cnd[19] && { +>(cnd[19] && { prop19a: 1, prop19b: 1, }) : false | { prop19a: number; prop19b: number; } +>cnd[19] && { prop19a: 1, prop19b: 1, } : false | { prop19a: number; prop19b: number; } +>cnd[19] : boolean +>cnd : Record +>19 : 19 +>{ prop19a: 1, prop19b: 1, } : { prop19a: number; prop19b: number; } + + prop19a: 1, +>prop19a : number +>1 : 1 + + prop19b: 1, +>prop19b : number +>1 : 1 + + }), + ...(cnd[20] && { +>(cnd[20] && { prop20a: 1, prop20b: 1, }) : false | { prop20a: number; prop20b: number; } +>cnd[20] && { prop20a: 1, prop20b: 1, } : false | { prop20a: number; prop20b: number; } +>cnd[20] : boolean +>cnd : Record +>20 : 20 +>{ prop20a: 1, prop20b: 1, } : { prop20a: number; prop20b: number; } + + prop20a: 1, +>prop20a : number +>1 : 1 + + prop20b: 1, +>prop20b : number +>1 : 1 + + }), + }; +} diff --git a/tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts b/tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts new file mode 100644 index 0000000000000..4dbc31b0ba982 --- /dev/null +++ b/tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts @@ -0,0 +1,85 @@ +// @strict: true +function f(cnd: Record){ + // Type is a union of 2^(n-1) members, where n is the number of spread objects + return { + // Without this one, it collapses to {} ? + ...(cnd[1] && + cnd[2] && { + prop0: 0, + }), + + // With one prop each, it collapses to a single object (#34853?) + ...(cnd[3] && { + prop3a: 1, + prop3b: 1, + }), + ...(cnd[4] && { + prop4a: 1, + prop4b: 1, + }), + ...(cnd[5] && { + prop5a: 1, + prop5b: 1, + }), + ...(cnd[6] && { + prop6a: 1, + prop6b: 1, + }), + ...(cnd[7] && { + prop7a: 1, + prop7b: 1, + }), + ...(cnd[8] && { + prop8a: 1, + prop8b: 1, + }), + ...(cnd[9] && { + prop9a: 1, + prop9b: 1, + }), + ...(cnd[10] && { + prop10a: 1, + prop10b: 1, + }), + ...(cnd[11] && { + prop11a: 1, + prop11b: 1, + }), + ...(cnd[12] && { + prop12a: 1, + prop12b: 1, + }), + ...(cnd[13] && { + prop13a: 1, + prop13b: 1, + }), + ...(cnd[14] && { + prop14a: 1, + prop14b: 1, + }), + ...(cnd[15] && { + prop15a: 1, + prop15b: 1, + }), + ...(cnd[16] && { + prop16a: 1, + prop16b: 1, + }), + ...(cnd[17] && { + prop17a: 1, + prop17b: 1, + }), + ...(cnd[18] && { + prop18a: 1, + prop18b: 1, + }), + ...(cnd[19] && { + prop19a: 1, + prop19b: 1, + }), + ...(cnd[20] && { + prop20a: 1, + prop20b: 1, + }), + }; +} \ No newline at end of file From 9f5310fd8da9fca91298572e5c19eb56c782d191 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 25 Sep 2020 13:37:54 -0700 Subject: [PATCH 2/2] Use the existing checkCrossProductUnion helper --- src/compiler/checker.ts | 20 +++++++------------ src/compiler/diagnosticMessages.json | 4 ---- .../objectSpreadRepeatedComplexity.errors.txt | 4 ++-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 398724fca930e..1830eafd1f45a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13394,6 +13394,7 @@ namespace ts { function checkCrossProductUnion(types: readonly Type[]) { const size = reduceLeft(types, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : t.flags & TypeFlags.Never ? 0 : 1), 1); if (size >= 100000) { + tracing.instant(tracing.Phase.Check, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); return false; } @@ -14458,14 +14459,18 @@ namespace ts { if (merged) { return getSpreadType(merged, right, symbol, objectFlags, readonly); } - return errorTypeIfTooLarge() ?? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)); + return checkCrossProductUnion([left, right]) + ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) + : errorType; } if (right.flags & TypeFlags.Union) { const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly); if (merged) { return getSpreadType(left, merged, symbol, objectFlags, readonly); } - return errorTypeIfTooLarge() ?? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)); + return checkCrossProductUnion([left, right]) + ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) + : errorType; } if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { return left; @@ -14544,17 +14549,6 @@ namespace ts { getIndexInfoWithReadonly(numberIndexInfo, readonly)); spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; return spread; - - function errorTypeIfTooLarge(): Type | undefined { - if (left.flags & right.flags & TypeFlags.Union) { - const resultSize = (left as UnionType).types.length * (right as UnionType).types.length; - if (resultSize > 100000) { - tracing.instant(tracing.Phase.Check, "getSpreadType_DepthLimit", { leftId: left.id, rightId: right.id }); - error(currentNode, Diagnostics.Spread_expression_produces_a_union_type_that_is_too_complex_to_represent); - return errorType; - } - } - } } /** We approximate own properties as non-methods plus methods that are inside the object literal */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c7255c40ba4e1..f2ea2fc68d6d5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3048,10 +3048,6 @@ "category": "Error", "code": 2795 }, - "Spread expression produces a union type that is too complex to represent.": { - "category": "Error", - "code": 2796 - }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt b/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt index e9ad041f28dd7..d411c319605b8 100644 --- a/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt +++ b/tests/baselines/reference/objectSpreadRepeatedComplexity.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts(3,12): error TS2796: Spread expression produces a union type that is too complex to represent. +tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts(3,12): error TS2590: Expression produces a union type that is too complex to represent. ==== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts (1 errors) ==== @@ -166,5 +166,5 @@ tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts(3,12): er ~~~~~~~~~~~ }; ~~~~~ -!!! error TS2796: Spread expression produces a union type that is too complex to represent. +!!! error TS2590: Expression produces a union type that is too complex to represent. } \ No newline at end of file