Merge pull request #30109 from Microsoft/circularConstraintErrors
Consistently error on circular constraints
This commit is contained in:
commit
237c33b444
7 changed files with 184 additions and 5 deletions
|
@ -7584,7 +7584,19 @@ namespace ts {
|
||||||
constraintDepth++;
|
constraintDepth++;
|
||||||
let result = computeBaseConstraint(getSimplifiedType(t));
|
let result = computeBaseConstraint(getSimplifiedType(t));
|
||||||
constraintDepth--;
|
constraintDepth--;
|
||||||
if (!popTypeResolution() || nonTerminating) {
|
if (!popTypeResolution()) {
|
||||||
|
if (t.flags & TypeFlags.TypeParameter) {
|
||||||
|
const errorNode = getConstraintDeclaration(<TypeParameter>t);
|
||||||
|
if (errorNode) {
|
||||||
|
const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t));
|
||||||
|
if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) {
|
||||||
|
addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = circularConstraintType;
|
||||||
|
}
|
||||||
|
if (nonTerminating) {
|
||||||
result = circularConstraintType;
|
result = circularConstraintType;
|
||||||
}
|
}
|
||||||
t.immediateBaseConstraint = result || noConstraintType;
|
t.immediateBaseConstraint = result || noConstraintType;
|
||||||
|
@ -23541,9 +23553,8 @@ namespace ts {
|
||||||
checkSourceElement(node.constraint);
|
checkSourceElement(node.constraint);
|
||||||
checkSourceElement(node.default);
|
checkSourceElement(node.default);
|
||||||
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
|
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
|
||||||
if (!hasNonCircularBaseConstraint(typeParameter)) {
|
// Resolve base constraint to reveal circularity errors
|
||||||
error(getEffectiveConstraintOfTypeParameter(node), Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
|
getBaseConstraintOfType(typeParameter);
|
||||||
}
|
|
||||||
if (!hasNonCircularTypeParameterDefault(typeParameter)) {
|
if (!hasNonCircularTypeParameterDefault(typeParameter)) {
|
||||||
error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter));
|
error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2589,6 +2589,10 @@
|
||||||
"category": "Error",
|
"category": "Error",
|
||||||
"code": 2750
|
"code": 2750
|
||||||
},
|
},
|
||||||
|
"Circularity originates in type at this location.": {
|
||||||
|
"category": "Error",
|
||||||
|
"code": 2751
|
||||||
|
},
|
||||||
|
|
||||||
"Import declaration '{0}' is using private name '{1}'.": {
|
"Import declaration '{0}' is using private name '{1}'.": {
|
||||||
"category": "Error",
|
"category": "Error",
|
||||||
|
|
|
@ -5,9 +5,10 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313
|
||||||
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself.
|
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself.
|
||||||
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint.
|
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint.
|
||||||
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite.
|
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite.
|
||||||
|
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(66,25): error TS2313: Type parameter 'P' has a circular constraint.
|
||||||
|
|
||||||
|
|
||||||
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (7 errors) ====
|
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (8 errors) ====
|
||||||
// Recursive mapped types simply appear empty
|
// Recursive mapped types simply appear empty
|
||||||
|
|
||||||
type Recurse = {
|
type Recurse = {
|
||||||
|
@ -32,6 +33,7 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS258
|
||||||
[K in keyof Recurse1]: Recurse1[K]
|
[K in keyof Recurse1]: Recurse1[K]
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
!!! error TS2313: Type parameter 'K' has a circular constraint.
|
!!! error TS2313: Type parameter 'K' has a circular constraint.
|
||||||
|
!!! related TS2751 tests/cases/conformance/types/mapped/recursiveMappedTypes.ts:8:17: Circularity originates in type at this location.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repro from #27881
|
// Repro from #27881
|
||||||
|
@ -84,3 +86,24 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS258
|
||||||
type a = Remap1<string[]>; // string[]
|
type a = Remap1<string[]>; // string[]
|
||||||
type b = Remap2<string[]>; // string[]
|
type b = Remap2<string[]>; // string[]
|
||||||
|
|
||||||
|
// Repro from #29992
|
||||||
|
|
||||||
|
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
|
||||||
|
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
!!! error TS2313: Type parameter 'P' has a circular constraint.
|
||||||
|
!!! related TS2751 tests/cases/conformance/types/mapped/recursiveMappedTypes.ts:79:1: Circularity originates in type at this location.
|
||||||
|
|
||||||
|
export interface ListWidget {
|
||||||
|
"type": "list",
|
||||||
|
"minimum_count": number,
|
||||||
|
"maximum_count": number,
|
||||||
|
"collapsable"?: boolean, //default to false, means all expanded
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChild = Child<ListWidget>
|
||||||
|
|
||||||
|
declare let x: ListChild;
|
||||||
|
x.type;
|
||||||
|
|
|
@ -61,6 +61,24 @@ type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
|
||||||
type a = Remap1<string[]>; // string[]
|
type a = Remap1<string[]>; // string[]
|
||||||
type b = Remap2<string[]>; // string[]
|
type b = Remap2<string[]>; // string[]
|
||||||
|
|
||||||
|
// Repro from #29992
|
||||||
|
|
||||||
|
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
|
||||||
|
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
|
||||||
|
|
||||||
|
export interface ListWidget {
|
||||||
|
"type": "list",
|
||||||
|
"minimum_count": number,
|
||||||
|
"maximum_count": number,
|
||||||
|
"collapsable"?: boolean, //default to false, means all expanded
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChild = Child<ListWidget>
|
||||||
|
|
||||||
|
declare let x: ListChild;
|
||||||
|
x.type;
|
||||||
|
|
||||||
|
|
||||||
//// [recursiveMappedTypes.js]
|
//// [recursiveMappedTypes.js]
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -70,9 +88,24 @@ function foo(arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
product.users; // (Transform<User> | Transform<Guest>)[]
|
product.users; // (Transform<User> | Transform<Guest>)[]
|
||||||
|
x.type;
|
||||||
|
|
||||||
|
|
||||||
//// [recursiveMappedTypes.d.ts]
|
//// [recursiveMappedTypes.d.ts]
|
||||||
export declare type Circular<T> = {
|
export declare type Circular<T> = {
|
||||||
[P in keyof T]: Circular<T>;
|
[P in keyof T]: Circular<T>;
|
||||||
};
|
};
|
||||||
|
declare type NonOptionalKeys<T> = {
|
||||||
|
[P in keyof T]: undefined extends T[P] ? never : P;
|
||||||
|
}[keyof T];
|
||||||
|
declare type Child<T> = {
|
||||||
|
[P in NonOptionalKeys<T>]: T[P];
|
||||||
|
};
|
||||||
|
export interface ListWidget {
|
||||||
|
"type": "list";
|
||||||
|
"minimum_count": number;
|
||||||
|
"maximum_count": number;
|
||||||
|
"collapsable"?: boolean;
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
|
|
|
@ -165,3 +165,57 @@ type b = Remap2<string[]>; // string[]
|
||||||
>b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26))
|
>b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26))
|
||||||
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
|
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
|
||||||
|
|
||||||
|
// Repro from #29992
|
||||||
|
|
||||||
|
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
|
||||||
|
>NonOptionalKeys : Symbol(NonOptionalKeys, Decl(recursiveMappedTypes.ts, 60, 26))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 64, 21))
|
||||||
|
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 64, 29))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 64, 21))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 64, 21))
|
||||||
|
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 64, 29))
|
||||||
|
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 64, 29))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 64, 21))
|
||||||
|
|
||||||
|
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
|
||||||
|
>Child : Symbol(Child, Decl(recursiveMappedTypes.ts, 64, 90))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 65, 11))
|
||||||
|
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 65, 19))
|
||||||
|
>NonOptionalKeys : Symbol(NonOptionalKeys, Decl(recursiveMappedTypes.ts, 60, 26))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 65, 11))
|
||||||
|
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 65, 11))
|
||||||
|
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 65, 19))
|
||||||
|
|
||||||
|
export interface ListWidget {
|
||||||
|
>ListWidget : Symbol(ListWidget, Decl(recursiveMappedTypes.ts, 65, 51))
|
||||||
|
|
||||||
|
"type": "list",
|
||||||
|
>"type" : Symbol(ListWidget["type"], Decl(recursiveMappedTypes.ts, 67, 29))
|
||||||
|
|
||||||
|
"minimum_count": number,
|
||||||
|
>"minimum_count" : Symbol(ListWidget["minimum_count"], Decl(recursiveMappedTypes.ts, 68, 19))
|
||||||
|
|
||||||
|
"maximum_count": number,
|
||||||
|
>"maximum_count" : Symbol(ListWidget["maximum_count"], Decl(recursiveMappedTypes.ts, 69, 28))
|
||||||
|
|
||||||
|
"collapsable"?: boolean, //default to false, means all expanded
|
||||||
|
>"collapsable" : Symbol(ListWidget["collapsable"], Decl(recursiveMappedTypes.ts, 70, 28))
|
||||||
|
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
>"each" : Symbol(ListWidget["each"], Decl(recursiveMappedTypes.ts, 71, 28))
|
||||||
|
>Child : Symbol(Child, Decl(recursiveMappedTypes.ts, 64, 90))
|
||||||
|
>ListWidget : Symbol(ListWidget, Decl(recursiveMappedTypes.ts, 65, 51))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChild = Child<ListWidget>
|
||||||
|
>ListChild : Symbol(ListChild, Decl(recursiveMappedTypes.ts, 73, 1))
|
||||||
|
>Child : Symbol(Child, Decl(recursiveMappedTypes.ts, 64, 90))
|
||||||
|
>ListWidget : Symbol(ListWidget, Decl(recursiveMappedTypes.ts, 65, 51))
|
||||||
|
|
||||||
|
declare let x: ListChild;
|
||||||
|
>x : Symbol(x, Decl(recursiveMappedTypes.ts, 77, 11))
|
||||||
|
>ListChild : Symbol(ListChild, Decl(recursiveMappedTypes.ts, 73, 1))
|
||||||
|
|
||||||
|
x.type;
|
||||||
|
>x : Symbol(x, Decl(recursiveMappedTypes.ts, 77, 11))
|
||||||
|
|
||||||
|
|
|
@ -97,3 +97,39 @@ type a = Remap1<string[]>; // string[]
|
||||||
type b = Remap2<string[]>; // string[]
|
type b = Remap2<string[]>; // string[]
|
||||||
>b : string[]
|
>b : string[]
|
||||||
|
|
||||||
|
// Repro from #29992
|
||||||
|
|
||||||
|
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
|
||||||
|
>NonOptionalKeys : { [P in keyof T]: undefined extends T[P] ? never : P; }[keyof T]
|
||||||
|
|
||||||
|
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
|
||||||
|
>Child : Child<T>
|
||||||
|
|
||||||
|
export interface ListWidget {
|
||||||
|
"type": "list",
|
||||||
|
>"type" : "list"
|
||||||
|
|
||||||
|
"minimum_count": number,
|
||||||
|
>"minimum_count" : number
|
||||||
|
|
||||||
|
"maximum_count": number,
|
||||||
|
>"maximum_count" : number
|
||||||
|
|
||||||
|
"collapsable"?: boolean, //default to false, means all expanded
|
||||||
|
>"collapsable" : boolean
|
||||||
|
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
>"each" : Child<ListWidget>
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChild = Child<ListWidget>
|
||||||
|
>ListChild : Child<ListWidget>
|
||||||
|
|
||||||
|
declare let x: ListChild;
|
||||||
|
>x : Child<ListWidget>
|
||||||
|
|
||||||
|
x.type;
|
||||||
|
>x.type : any
|
||||||
|
>x : Child<ListWidget>
|
||||||
|
>type : any
|
||||||
|
|
||||||
|
|
|
@ -61,3 +61,21 @@ type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
|
||||||
|
|
||||||
type a = Remap1<string[]>; // string[]
|
type a = Remap1<string[]>; // string[]
|
||||||
type b = Remap2<string[]>; // string[]
|
type b = Remap2<string[]>; // string[]
|
||||||
|
|
||||||
|
// Repro from #29992
|
||||||
|
|
||||||
|
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
|
||||||
|
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
|
||||||
|
|
||||||
|
export interface ListWidget {
|
||||||
|
"type": "list",
|
||||||
|
"minimum_count": number,
|
||||||
|
"maximum_count": number,
|
||||||
|
"collapsable"?: boolean, //default to false, means all expanded
|
||||||
|
"each": Child<ListWidget>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChild = Child<ListWidget>
|
||||||
|
|
||||||
|
declare let x: ListChild;
|
||||||
|
x.type;
|
||||||
|
|
Loading…
Reference in a new issue