Skip to content

Commit 73d06f7

Browse files
authored
Merge pull request #19056 from Microsoft/fix16221
Fix recursive reference in type parameter default
2 parents bce77fd + 26290a8 commit 73d06f7

11 files changed

+96
-20
lines changed

src/compiler/checker.ts

+42-14
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ namespace ts {
281281

282282
const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
283283
const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
284+
const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
284285

285286
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
286287
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
@@ -6055,27 +6056,51 @@ namespace ts {
60556056
return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type));
60566057
}
60576058

6058-
/**
6059-
* Gets the default type for a type parameter.
6060-
*
6061-
* If the type parameter is the result of an instantiation, this gets the instantiated
6062-
* default type of its target. If the type parameter has no default type, `undefined`
6063-
* is returned.
6064-
*
6065-
* This function *does not* perform a circularity check.
6066-
*/
6067-
function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
6059+
function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined {
60686060
if (!typeParameter.default) {
60696061
if (typeParameter.target) {
6070-
const targetDefault = getDefaultFromTypeParameter(typeParameter.target);
6062+
const targetDefault = getResolvedTypeParameterDefault(typeParameter.target);
60716063
typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType;
60726064
}
60736065
else {
6066+
// To block recursion, set the initial value to the resolvingDefaultType.
6067+
typeParameter.default = resolvingDefaultType;
60746068
const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default);
6075-
typeParameter.default = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType;
6069+
const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType;
6070+
if (typeParameter.default === resolvingDefaultType) {
6071+
// If we have not been called recursively, set the correct default type.
6072+
typeParameter.default = defaultType;
6073+
}
60766074
}
60776075
}
6078-
return typeParameter.default === noConstraintType ? undefined : typeParameter.default;
6076+
else if (typeParameter.default === resolvingDefaultType) {
6077+
// If we are called recursively for this type parameter, mark the default as circular.
6078+
typeParameter.default = circularConstraintType;
6079+
}
6080+
return typeParameter.default;
6081+
}
6082+
6083+
/**
6084+
* Gets the default type for a type parameter.
6085+
*
6086+
* If the type parameter is the result of an instantiation, this gets the instantiated
6087+
* default type of its target. If the type parameter has no default type or the default is
6088+
* circular, `undefined` is returned.
6089+
*/
6090+
function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
6091+
const defaultType = getResolvedTypeParameterDefault(typeParameter);
6092+
return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined;
6093+
}
6094+
6095+
function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) {
6096+
return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType;
6097+
}
6098+
6099+
/**
6100+
* Indicates whether the declaration of a typeParameter has a default type.
6101+
*/
6102+
function hasTypeParameterDefault(typeParameter: TypeParameter): boolean {
6103+
return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default));
60796104
}
60806105

60816106
/**
@@ -6361,7 +6386,7 @@ namespace ts {
63616386
let minTypeArgumentCount = 0;
63626387
if (typeParameters) {
63636388
for (let i = 0; i < typeParameters.length; i++) {
6364-
if (!getDefaultFromTypeParameter(typeParameters[i])) {
6389+
if (!hasTypeParameterDefault(typeParameters[i])) {
63656390
minTypeArgumentCount = i + 1;
63666391
}
63676392
}
@@ -18478,6 +18503,9 @@ namespace ts {
1847818503
if (!hasNonCircularBaseConstraint(typeParameter)) {
1847918504
error(node.constraint, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
1848018505
}
18506+
if (!hasNonCircularTypeParameterDefault(typeParameter)) {
18507+
error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter));
18508+
}
1848118509
const constraintType = getConstraintOfTypeParameter(typeParameter);
1848218510
const defaultType = getDefaultFromTypeParameter(typeParameter);
1848318511
if (constraintType && defaultType) {

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -2224,6 +2224,10 @@
22242224
"category": "Error",
22252225
"code": 2715
22262226
},
2227+
"Type parameter '{0}' has a circular default.": {
2228+
"category": "Error",
2229+
"code": 2716
2230+
},
22272231

22282232
"Import declaration '{0}' is using private name '{1}'.": {
22292233
"category": "Error",

tests/baselines/reference/genericDefaults.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,10 @@ const t03c00 = (<t03<number>>x).a;
487487
const t03c01 = (<t03<1>>x).a;
488488
const t03c02 = (<t03<number, number>>x).a;
489489
const t03c03 = (<t03<1, 1>>x).a;
490-
const t03c04 = (<t03<number, 1>>x).a;
490+
const t03c04 = (<t03<number, 1>>x).a;
491+
492+
// https://github.com/Microsoft/TypeScript/issues/16221
493+
interface SelfReference<T = SelfReference<string>> {}
491494

492495
//// [genericDefaults.js]
493496
// no inference
@@ -1024,3 +1027,5 @@ declare const t03c01: [1, 1];
10241027
declare const t03c02: [number, number];
10251028
declare const t03c03: [1, 1];
10261029
declare const t03c04: [number, 1];
1030+
interface SelfReference<T = SelfReference<string>> {
1031+
}

tests/baselines/reference/genericDefaults.symbols

+6
Original file line numberDiff line numberDiff line change
@@ -2291,3 +2291,9 @@ const t03c04 = (<t03<number, 1>>x).a;
22912291
>x : Symbol(x, Decl(genericDefaults.ts, 13, 13))
22922292
>a : Symbol(a, Decl(genericDefaults.ts, 483, 47))
22932293

2294+
// https://github.com/Microsoft/TypeScript/issues/16221
2295+
interface SelfReference<T = SelfReference<string>> {}
2296+
>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37))
2297+
>T : Symbol(T, Decl(genericDefaults.ts, 491, 24))
2298+
>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37))
2299+

tests/baselines/reference/genericDefaults.types

+6
Original file line numberDiff line numberDiff line change
@@ -2643,3 +2643,9 @@ const t03c04 = (<t03<number, 1>>x).a;
26432643
>x : any
26442644
>a : [number, 1]
26452645

2646+
// https://github.com/Microsoft/TypeScript/issues/16221
2647+
interface SelfReference<T = SelfReference<string>> {}
2648+
>SelfReference : SelfReference<T>
2649+
>T : T
2650+
>SelfReference : SelfReference<T>
2651+

tests/baselines/reference/genericDefaultsErrors.errors.txt

+8-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ tests/cases/compiler/genericDefaultsErrors.ts(33,15): error TS2707: Generic type
2121
tests/cases/compiler/genericDefaultsErrors.ts(36,15): error TS2707: Generic type 'i09<T, U, V>' requires between 2 and 3 type arguments.
2222
tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS2304: Cannot find name 'T'.
2323
tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS4033: Property 'x' of exported interface has or is using private name 'T'.
24+
tests/cases/compiler/genericDefaultsErrors.ts(42,29): error TS2716: Type parameter 'T' has a circular default.
2425

2526

26-
==== tests/cases/compiler/genericDefaultsErrors.ts (21 errors) ====
27+
==== tests/cases/compiler/genericDefaultsErrors.ts (22 errors) ====
2728
declare const x: any;
2829

2930
declare function f03<T extends string = number>(): void; // error
@@ -106,4 +107,9 @@ tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS4033: Property 'x'
106107
!!! error TS2304: Cannot find name 'T'.
107108
~
108109
!!! error TS4033: Property 'x' of exported interface has or is using private name 'T'.
109-
interface i10<T = number> {}
110+
interface i10<T = number> {}
111+
112+
// https://github.com/Microsoft/TypeScript/issues/16221
113+
interface SelfReference<T = SelfReference> {}
114+
~~~~~~~~~~~~~
115+
!!! error TS2716: Type parameter 'T' has a circular default.

tests/baselines/reference/genericDefaultsErrors.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ type i09t03 = i09<1, 2, 3>; // ok
3737
type i09t04 = i09<1, 2, 3, 4>; // error
3838

3939
interface i10 { x: T; } // error
40-
interface i10<T = number> {}
40+
interface i10<T = number> {}
41+
42+
// https://github.com/Microsoft/TypeScript/issues/16221
43+
interface SelfReference<T = SelfReference> {}
4144

4245
//// [genericDefaultsErrors.js]
4346
f11(); // ok

tests/baselines/reference/genericDefaultsErrors.symbols

+6
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,9 @@ interface i10<T = number> {}
136136
>i10 : Symbol(i10, Decl(genericDefaultsErrors.ts, 35, 30), Decl(genericDefaultsErrors.ts, 37, 23))
137137
>T : Symbol(T, Decl(genericDefaultsErrors.ts, 38, 14))
138138

139+
// https://github.com/Microsoft/TypeScript/issues/16221
140+
interface SelfReference<T = SelfReference> {}
141+
>SelfReference : Symbol(SelfReference, Decl(genericDefaultsErrors.ts, 38, 28))
142+
>T : Symbol(T, Decl(genericDefaultsErrors.ts, 41, 24))
143+
>SelfReference : Symbol(SelfReference, Decl(genericDefaultsErrors.ts, 38, 28))
144+

tests/baselines/reference/genericDefaultsErrors.types

+6
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,9 @@ interface i10<T = number> {}
145145
>i10 : i10<T>
146146
>T : T
147147

148+
// https://github.com/Microsoft/TypeScript/issues/16221
149+
interface SelfReference<T = SelfReference> {}
150+
>SelfReference : SelfReference<T>
151+
>T : T
152+
>SelfReference : SelfReference<T>
153+

tests/cases/compiler/genericDefaults.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -487,4 +487,7 @@ const t03c00 = (<t03<number>>x).a;
487487
const t03c01 = (<t03<1>>x).a;
488488
const t03c02 = (<t03<number, number>>x).a;
489489
const t03c03 = (<t03<1, 1>>x).a;
490-
const t03c04 = (<t03<number, 1>>x).a;
490+
const t03c04 = (<t03<number, 1>>x).a;
491+
492+
// https://github.com/Microsoft/TypeScript/issues/16221
493+
interface SelfReference<T = SelfReference<string>> {}

tests/cases/compiler/genericDefaultsErrors.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@ type i09t03 = i09<1, 2, 3>; // ok
3838
type i09t04 = i09<1, 2, 3, 4>; // error
3939

4040
interface i10 { x: T; } // error
41-
interface i10<T = number> {}
41+
interface i10<T = number> {}
42+
43+
// https://github.com/Microsoft/TypeScript/issues/16221
44+
interface SelfReference<T = SelfReference> {}

0 commit comments

Comments
 (0)