Error on excessive relation complexity (#55851)

This commit is contained in:
Anders Hejlsberg 2023-09-26 15:57:57 -07:00 committed by GitHub
parent fbe426e7d3
commit c052bc7c72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 159 additions and 1 deletions

View File

@ -20778,6 +20778,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid
let lastSkippedInfo: [Type, Type] | undefined;
let incompatibleStack: DiagnosticAndArguments[] | undefined;
// In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation
// of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity.
let relationCount = (16_000_000 - relation.size) >> 3;
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
@ -20786,8 +20789,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
reportIncompatibleStack();
}
if (overflow) {
// Record this relation as having failed such that we don't attempt the overflowing operation again.
const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false);
relation.set(id, RelationComparisonResult.Reported | RelationComparisonResult.Failed);
tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
const message = relationCount <= 0 ?
Diagnostics.Excessive_complexity_comparing_types_0_and_1 :
Diagnostics.Excessive_stack_depth_comparing_types_0_and_1;
const diag = error(errorNode || currentNode, message, typeToString(source), typeToString(target));
if (errorOutputContainer) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
}
@ -21420,6 +21429,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// we need to deconstruct unions before intersections (because unions are always at the top),
// and we need to handle "each" relations before "some" relations for the same kind of type.
if (source.flags & TypeFlags.Union) {
if (target.flags & TypeFlags.Union) {
// Intersections of union types are normalized into unions of intersection types, and such normalized
// unions can get very large and expensive to relate. The following fast path checks if the source union
// originated in an intersection. If so, and if that intersection contains the target type, then we know
// the result to be true (for any two types A and B, A & B is related to both A and B).
const sourceOrigin = (source as UnionType).origin;
if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) {
return Ternary.True;
}
// Similarly, in unions of unions the we preserve the original list of unions. This original list is often
// much shorter than the normalized result, so we scan it in the following fast path.
const targetOrigin = (target as UnionType).origin;
if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) {
return Ternary.True;
}
}
return relation === comparableRelation ?
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState);
@ -21671,6 +21696,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
}
if (relationCount <= 0) {
overflow = true;
return Ternary.False;
}
if (!maybeKeys) {
maybeKeys = [];
maybeKeysSet = new Set();
@ -21768,6 +21797,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// A false result goes straight into global cache (when something is false under
// assumptions it will also be false without assumptions)
relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
relationCount--;
resetMaybeStack(/*markAllAsSucceeded*/ false);
}
return result;
@ -21777,6 +21807,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
maybeKeysSet.delete(maybeKeys[i]);
if (markAllAsSucceeded) {
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
relationCount--;
}
}
maybeCount = maybeStart;

View File

@ -3687,6 +3687,10 @@
"category": "Error",
"code": 2858
},
"Excessive complexity comparing types '{0}' and '{1}'.": {
"category": "Error",
"code": 2859
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",

View File

@ -0,0 +1,23 @@
relationComplexityError.ts(12,5): error TS2322: Type 'T1 & T2' is not assignable to type 'T1 | null'.
relationComplexityError.ts(12,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'.
==== relationComplexityError.ts (2 errors) ====
// Repro from #55630
type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type T1 = `${Digits}${Digits}${Digits}${Digits}` | undefined;
type T2 = { a: string } | { b: number };
function f1(x: T1, y: T1 & T2) {
x = y;
}
function f2(x: T1 | null, y: T1 & T2) {
x = y; // Complexity error
~
!!! error TS2322: Type 'T1 & T2' is not assignable to type 'T1 | null'.
~~~~~
!!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'.
}

View File

@ -0,0 +1,46 @@
//// [tests/cases/compiler/relationComplexityError.ts] ////
=== relationComplexityError.ts ===
// Repro from #55630
type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
>Digits : Symbol(Digits, Decl(relationComplexityError.ts, 0, 0))
type T1 = `${Digits}${Digits}${Digits}${Digits}` | undefined;
>T1 : Symbol(T1, Decl(relationComplexityError.ts, 2, 72))
>Digits : Symbol(Digits, Decl(relationComplexityError.ts, 0, 0))
>Digits : Symbol(Digits, Decl(relationComplexityError.ts, 0, 0))
>Digits : Symbol(Digits, Decl(relationComplexityError.ts, 0, 0))
>Digits : Symbol(Digits, Decl(relationComplexityError.ts, 0, 0))
type T2 = { a: string } | { b: number };
>T2 : Symbol(T2, Decl(relationComplexityError.ts, 3, 61))
>a : Symbol(a, Decl(relationComplexityError.ts, 4, 11))
>b : Symbol(b, Decl(relationComplexityError.ts, 4, 27))
function f1(x: T1, y: T1 & T2) {
>f1 : Symbol(f1, Decl(relationComplexityError.ts, 4, 40))
>x : Symbol(x, Decl(relationComplexityError.ts, 6, 12))
>T1 : Symbol(T1, Decl(relationComplexityError.ts, 2, 72))
>y : Symbol(y, Decl(relationComplexityError.ts, 6, 18))
>T1 : Symbol(T1, Decl(relationComplexityError.ts, 2, 72))
>T2 : Symbol(T2, Decl(relationComplexityError.ts, 3, 61))
x = y;
>x : Symbol(x, Decl(relationComplexityError.ts, 6, 12))
>y : Symbol(y, Decl(relationComplexityError.ts, 6, 18))
}
function f2(x: T1 | null, y: T1 & T2) {
>f2 : Symbol(f2, Decl(relationComplexityError.ts, 8, 1))
>x : Symbol(x, Decl(relationComplexityError.ts, 10, 12))
>T1 : Symbol(T1, Decl(relationComplexityError.ts, 2, 72))
>y : Symbol(y, Decl(relationComplexityError.ts, 10, 25))
>T1 : Symbol(T1, Decl(relationComplexityError.ts, 2, 72))
>T2 : Symbol(T2, Decl(relationComplexityError.ts, 3, 61))
x = y; // Complexity error
>x : Symbol(x, Decl(relationComplexityError.ts, 10, 12))
>y : Symbol(y, Decl(relationComplexityError.ts, 10, 25))
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
// @strict: true
// @noEmit: true
// Repro from #55630
type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type T1 = `${Digits}${Digits}${Digits}${Digits}` | undefined;
type T2 = { a: string } | { b: number };
function f1(x: T1, y: T1 & T2) {
x = y;
}
function f2(x: T1 | null, y: T1 & T2) {
x = y; // Complexity error
}