diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d47a77a744..4c6973f304 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -281,6 +281,7 @@ namespace ts { const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const markerSuperType = createType(TypeFlags.TypeParameter); const markerSubType = createType(TypeFlags.TypeParameter); @@ -6055,27 +6056,51 @@ namespace ts { return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type)); } + function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { + if (!typeParameter.default) { + if (typeParameter.target) { + const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); + typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; + } + else { + // To block recursion, set the initial value to the resolvingDefaultType. + typeParameter.default = resolvingDefaultType; + const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); + const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; + if (typeParameter.default === resolvingDefaultType) { + // If we have not been called recursively, set the correct default type. + typeParameter.default = defaultType; + } + } + } + else if (typeParameter.default === resolvingDefaultType) { + // If we are called recursively for this type parameter, mark the default as circular. + typeParameter.default = circularConstraintType; + } + return typeParameter.default; + } + /** * Gets the default type for a type parameter. * * If the type parameter is the result of an instantiation, this gets the instantiated - * default type of its target. If the type parameter has no default type, `undefined` - * is returned. - * - * This function *does not* perform a circularity check. + * default type of its target. If the type parameter has no default type or the default is + * circular, `undefined` is returned. */ function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.default) { - if (typeParameter.target) { - const targetDefault = getDefaultFromTypeParameter(typeParameter.target); - typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; - } - else { - const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); - typeParameter.default = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; - } - } - return typeParameter.default === noConstraintType ? undefined : typeParameter.default; + const defaultType = getResolvedTypeParameterDefault(typeParameter); + return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + } + + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } + + /** + * Indicates whether the declaration of a typeParameter has a default type. + */ + function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { + return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); } /** @@ -6361,7 +6386,7 @@ namespace ts { let minTypeArgumentCount = 0; if (typeParameters) { for (let i = 0; i < typeParameters.length; i++) { - if (!getDefaultFromTypeParameter(typeParameters[i])) { + if (!hasTypeParameterDefault(typeParameters[i])) { minTypeArgumentCount = i + 1; } } @@ -18478,6 +18503,9 @@ namespace ts { if (!hasNonCircularBaseConstraint(typeParameter)) { error(node.constraint, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter)); } + if (!hasNonCircularTypeParameterDefault(typeParameter)) { + error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); + } const constraintType = getConstraintOfTypeParameter(typeParameter); const defaultType = getDefaultFromTypeParameter(typeParameter); if (constraintType && defaultType) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 91ad9e52bf..3389bc1063 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2224,6 +2224,10 @@ "category": "Error", "code": 2715 }, + "Type parameter '{0}' has a circular default.": { + "category": "Error", + "code": 2716 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/genericDefaults.js b/tests/baselines/reference/genericDefaults.js index 06503b89d5..be6b76ea43 100644 --- a/tests/baselines/reference/genericDefaults.js +++ b/tests/baselines/reference/genericDefaults.js @@ -487,7 +487,10 @@ const t03c00 = (>x).a; const t03c01 = (>x).a; const t03c02 = (>x).a; const t03c03 = (>x).a; -const t03c04 = (>x).a; +const t03c04 = (>x).a; + +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference> {} //// [genericDefaults.js] // no inference @@ -1024,3 +1027,5 @@ declare const t03c01: [1, 1]; declare const t03c02: [number, number]; declare const t03c03: [1, 1]; declare const t03c04: [number, 1]; +interface SelfReference> { +} diff --git a/tests/baselines/reference/genericDefaults.symbols b/tests/baselines/reference/genericDefaults.symbols index 6c9e97b265..755a34c7a6 100644 --- a/tests/baselines/reference/genericDefaults.symbols +++ b/tests/baselines/reference/genericDefaults.symbols @@ -2291,3 +2291,9 @@ const t03c04 = (>x).a; >x : Symbol(x, Decl(genericDefaults.ts, 13, 13)) >a : Symbol(a, Decl(genericDefaults.ts, 483, 47)) +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference> {} +>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37)) +>T : Symbol(T, Decl(genericDefaults.ts, 491, 24)) +>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37)) + diff --git a/tests/baselines/reference/genericDefaults.types b/tests/baselines/reference/genericDefaults.types index 739588badb..0013daefd8 100644 --- a/tests/baselines/reference/genericDefaults.types +++ b/tests/baselines/reference/genericDefaults.types @@ -2643,3 +2643,9 @@ const t03c04 = (>x).a; >x : any >a : [number, 1] +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference> {} +>SelfReference : SelfReference +>T : T +>SelfReference : SelfReference + diff --git a/tests/baselines/reference/genericDefaultsErrors.errors.txt b/tests/baselines/reference/genericDefaultsErrors.errors.txt index 6200046c49..762bb92535 100644 --- a/tests/baselines/reference/genericDefaultsErrors.errors.txt +++ b/tests/baselines/reference/genericDefaultsErrors.errors.txt @@ -21,9 +21,10 @@ tests/cases/compiler/genericDefaultsErrors.ts(33,15): error TS2707: Generic type tests/cases/compiler/genericDefaultsErrors.ts(36,15): error TS2707: Generic type 'i09' requires between 2 and 3 type arguments. tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS2304: Cannot find name 'T'. tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS4033: Property 'x' of exported interface has or is using private name 'T'. +tests/cases/compiler/genericDefaultsErrors.ts(42,29): error TS2715: Type parameter 'T' has a circular default. -==== tests/cases/compiler/genericDefaultsErrors.ts (21 errors) ==== +==== tests/cases/compiler/genericDefaultsErrors.ts (22 errors) ==== declare const x: any; declare function f03(): void; // error @@ -106,4 +107,9 @@ tests/cases/compiler/genericDefaultsErrors.ts(38,20): error TS4033: Property 'x' !!! error TS2304: Cannot find name 'T'. ~ !!! error TS4033: Property 'x' of exported interface has or is using private name 'T'. - interface i10 {} \ No newline at end of file + interface i10 {} + + // https://github.com/Microsoft/TypeScript/issues/16221 + interface SelfReference {} + ~~~~~~~~~~~~~ +!!! error TS2715: Type parameter 'T' has a circular default. \ No newline at end of file diff --git a/tests/baselines/reference/genericDefaultsErrors.js b/tests/baselines/reference/genericDefaultsErrors.js index c737644e99..19201172b2 100644 --- a/tests/baselines/reference/genericDefaultsErrors.js +++ b/tests/baselines/reference/genericDefaultsErrors.js @@ -37,7 +37,10 @@ type i09t03 = i09<1, 2, 3>; // ok type i09t04 = i09<1, 2, 3, 4>; // error interface i10 { x: T; } // error -interface i10 {} +interface i10 {} + +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference {} //// [genericDefaultsErrors.js] f11(); // ok diff --git a/tests/baselines/reference/genericDefaultsErrors.symbols b/tests/baselines/reference/genericDefaultsErrors.symbols index 495b56ea25..e6e5cb8606 100644 --- a/tests/baselines/reference/genericDefaultsErrors.symbols +++ b/tests/baselines/reference/genericDefaultsErrors.symbols @@ -136,3 +136,9 @@ interface i10 {} >i10 : Symbol(i10, Decl(genericDefaultsErrors.ts, 35, 30), Decl(genericDefaultsErrors.ts, 37, 23)) >T : Symbol(T, Decl(genericDefaultsErrors.ts, 38, 14)) +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference {} +>SelfReference : Symbol(SelfReference, Decl(genericDefaultsErrors.ts, 38, 28)) +>T : Symbol(T, Decl(genericDefaultsErrors.ts, 41, 24)) +>SelfReference : Symbol(SelfReference, Decl(genericDefaultsErrors.ts, 38, 28)) + diff --git a/tests/baselines/reference/genericDefaultsErrors.types b/tests/baselines/reference/genericDefaultsErrors.types index 46bae67bc2..87e9af0bb0 100644 --- a/tests/baselines/reference/genericDefaultsErrors.types +++ b/tests/baselines/reference/genericDefaultsErrors.types @@ -145,3 +145,9 @@ interface i10 {} >i10 : i10 >T : T +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference {} +>SelfReference : SelfReference +>T : T +>SelfReference : SelfReference + diff --git a/tests/cases/compiler/genericDefaults.ts b/tests/cases/compiler/genericDefaults.ts index 624b44c082..e7b9c95ede 100644 --- a/tests/cases/compiler/genericDefaults.ts +++ b/tests/cases/compiler/genericDefaults.ts @@ -487,4 +487,7 @@ const t03c00 = (>x).a; const t03c01 = (>x).a; const t03c02 = (>x).a; const t03c03 = (>x).a; -const t03c04 = (>x).a; \ No newline at end of file +const t03c04 = (>x).a; + +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference> {} \ No newline at end of file diff --git a/tests/cases/compiler/genericDefaultsErrors.ts b/tests/cases/compiler/genericDefaultsErrors.ts index 4ea42beb3d..9cdba88832 100644 --- a/tests/cases/compiler/genericDefaultsErrors.ts +++ b/tests/cases/compiler/genericDefaultsErrors.ts @@ -38,4 +38,7 @@ type i09t03 = i09<1, 2, 3>; // ok type i09t04 = i09<1, 2, 3, 4>; // error interface i10 { x: T; } // error -interface i10 {} \ No newline at end of file +interface i10 {} + +// https://github.com/Microsoft/TypeScript/issues/16221 +interface SelfReference {} \ No newline at end of file