From ede6b9a5cbf2f2b3c44c706c0a78adfb2326cffc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Feb 2019 12:39:01 -0800 Subject: [PATCH 1/4] Issue errors for all circular type parameter constraints --- src/compiler/checker.ts | 19 +++++++++++++++---- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 958ceae0395..890f8f4beb8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7577,7 +7577,19 @@ namespace ts { constraintDepth++; let result = computeBaseConstraint(getSimplifiedType(t)); constraintDepth--; - if (!popTypeResolution() || nonTerminating) { + if (!popTypeResolution()) { + if (t.flags & TypeFlags.TypeParameter) { + const errorNode = getConstraintDeclaration(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; } t.immediateBaseConstraint = result || noConstraintType; @@ -23475,9 +23487,8 @@ namespace ts { checkSourceElement(node.constraint); checkSourceElement(node.default); const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); - if (!hasNonCircularBaseConstraint(typeParameter)) { - error(getEffectiveConstraintOfTypeParameter(node), Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter)); - } + // Resolve base constraint to reveal circularity errors + getBaseConstraintOfType(typeParameter); if (!hasNonCircularTypeParameterDefault(typeParameter)) { error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 09b0f721292..5ae3a44b4b9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2589,6 +2589,10 @@ "category": "Error", "code": 2750 }, + "Circularity originates in type at this location.": { + "category": "Error", + "code": 2751 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", From 5270b49bcc7d4e490c606dccd23fb96a3866e626 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Feb 2019 12:39:14 -0800 Subject: [PATCH 2/4] Accept new baselines --- tests/baselines/reference/recursiveMappedTypes.errors.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index f2f59052a90..c003204cd78 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -32,6 +32,7 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS258 [K in keyof Recurse1]: Recurse1[K] ~~~~~~~~~~~~~~ !!! 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 From 2212f4777a90830aa689574821153d41d06ae70c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Feb 2019 12:44:12 -0800 Subject: [PATCH 3/4] Add regression test --- .../types/mapped/recursiveMappedTypes.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts index 69a0c1ca597..d5a5d63515f 100644 --- a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts +++ b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts @@ -61,3 +61,21 @@ type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; type a = Remap1; // string[] type b = Remap2; // string[] + +// Repro from #29992 + +type NonOptionalKeys = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T]; +type Child = { [P in NonOptionalKeys]: T[P] } + +export interface ListWidget { + "type": "list", + "minimum_count": number, + "maximum_count": number, + "collapsable"?: boolean, //default to false, means all expanded + "each": Child; +} + +type ListChild = Child + +declare let x: ListChild; +x.type; From ecebc9ffeb02e4cd023c30a33e5cf70636124048 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Feb 2019 12:44:18 -0800 Subject: [PATCH 4/4] Accept new baselines --- .../reference/recursiveMappedTypes.errors.txt | 24 ++++++++- .../reference/recursiveMappedTypes.js | 33 ++++++++++++ .../reference/recursiveMappedTypes.symbols | 54 +++++++++++++++++++ .../reference/recursiveMappedTypes.types | 36 +++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index c003204cd78..43a0e736d25 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -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(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(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 type Recurse = { @@ -84,4 +85,25 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS258 type a = Remap1; // string[] type b = Remap2; // string[] + + // Repro from #29992 + + type NonOptionalKeys = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T]; + type Child = { [P in NonOptionalKeys]: 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; + } + + type ListChild = Child + + declare let x: ListChild; + x.type; \ No newline at end of file diff --git a/tests/baselines/reference/recursiveMappedTypes.js b/tests/baselines/reference/recursiveMappedTypes.js index e9e44a1df41..2cda37a8d45 100644 --- a/tests/baselines/reference/recursiveMappedTypes.js +++ b/tests/baselines/reference/recursiveMappedTypes.js @@ -60,6 +60,24 @@ type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; type a = Remap1; // string[] type b = Remap2; // string[] + +// Repro from #29992 + +type NonOptionalKeys = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T]; +type Child = { [P in NonOptionalKeys]: T[P] } + +export interface ListWidget { + "type": "list", + "minimum_count": number, + "maximum_count": number, + "collapsable"?: boolean, //default to false, means all expanded + "each": Child; +} + +type ListChild = Child + +declare let x: ListChild; +x.type; //// [recursiveMappedTypes.js] @@ -70,9 +88,24 @@ function foo(arg) { return arg; } product.users; // (Transform | Transform)[] +x.type; //// [recursiveMappedTypes.d.ts] export declare type Circular = { [P in keyof T]: Circular; }; +declare type NonOptionalKeys = { + [P in keyof T]: undefined extends T[P] ? never : P; +}[keyof T]; +declare type Child = { + [P in NonOptionalKeys]: T[P]; +}; +export interface ListWidget { + "type": "list"; + "minimum_count": number; + "maximum_count": number; + "collapsable"?: boolean; + "each": Child; +} +export {}; diff --git a/tests/baselines/reference/recursiveMappedTypes.symbols b/tests/baselines/reference/recursiveMappedTypes.symbols index 1638dc03f56..777a6a722fa 100644 --- a/tests/baselines/reference/recursiveMappedTypes.symbols +++ b/tests/baselines/reference/recursiveMappedTypes.symbols @@ -165,3 +165,57 @@ type b = Remap2; // string[] >b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26)) >Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51)) +// Repro from #29992 + +type NonOptionalKeys = { [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 = { [P in NonOptionalKeys]: 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; +>"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 +>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)) + diff --git a/tests/baselines/reference/recursiveMappedTypes.types b/tests/baselines/reference/recursiveMappedTypes.types index 126d1e1d740..34cfd2d6100 100644 --- a/tests/baselines/reference/recursiveMappedTypes.types +++ b/tests/baselines/reference/recursiveMappedTypes.types @@ -97,3 +97,39 @@ type a = Remap1; // string[] type b = Remap2; // string[] >b : string[] +// Repro from #29992 + +type NonOptionalKeys = { [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 = { [P in NonOptionalKeys]: T[P] } +>Child : Child + +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; +>"each" : Child +} + +type ListChild = Child +>ListChild : Child + +declare let x: ListChild; +>x : Child + +x.type; +>x.type : any +>x : Child +>type : any +