Skip to content

Fix recursive reference in type parameter default #19056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 11, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -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 = <TypeParameter>createType(TypeFlags.TypeParameter);
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
@@ -6055,27 +6056,51 @@ namespace ts {
return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type));
}

/**
* 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.
*/
function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined {
if (!typeParameter.default) {
if (typeParameter.target) {
const targetDefault = getDefaultFromTypeParameter(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);
typeParameter.default = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType;
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;
}
}
}
return typeParameter.default === noConstraintType ? undefined : typeParameter.default;
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 or the default is
* circular, `undefined` is returned.
*/
function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
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) {
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
@@ -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",
7 changes: 6 additions & 1 deletion tests/baselines/reference/genericDefaults.js
Original file line number Diff line number Diff line change
@@ -487,7 +487,10 @@ const t03c00 = (<t03<number>>x).a;
const t03c01 = (<t03<1>>x).a;
const t03c02 = (<t03<number, number>>x).a;
const t03c03 = (<t03<1, 1>>x).a;
const t03c04 = (<t03<number, 1>>x).a;
const t03c04 = (<t03<number, 1>>x).a;

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference<string>> {}

//// [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<T = SelfReference<string>> {
}
6 changes: 6 additions & 0 deletions tests/baselines/reference/genericDefaults.symbols
Original file line number Diff line number Diff line change
@@ -2291,3 +2291,9 @@ const t03c04 = (<t03<number, 1>>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<T = SelfReference<string>> {}
>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37))
>T : Symbol(T, Decl(genericDefaults.ts, 491, 24))
>SelfReference : Symbol(SelfReference, Decl(genericDefaults.ts, 488, 37))

6 changes: 6 additions & 0 deletions tests/baselines/reference/genericDefaults.types
Original file line number Diff line number Diff line change
@@ -2643,3 +2643,9 @@ const t03c04 = (<t03<number, 1>>x).a;
>x : any
>a : [number, 1]

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference<string>> {}
>SelfReference : SelfReference<T>
>T : T
>SelfReference : SelfReference<T>

10 changes: 8 additions & 2 deletions tests/baselines/reference/genericDefaultsErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -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<T, U, V>' 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 TS2716: 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<T extends string = number>(): 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<T = number> {}
interface i10<T = number> {}

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference> {}
~~~~~~~~~~~~~
!!! error TS2716: Type parameter 'T' has a circular default.
5 changes: 4 additions & 1 deletion tests/baselines/reference/genericDefaultsErrors.js
Original file line number Diff line number Diff line change
@@ -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<T = number> {}
interface i10<T = number> {}

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference> {}

//// [genericDefaultsErrors.js]
f11(); // ok
6 changes: 6 additions & 0 deletions tests/baselines/reference/genericDefaultsErrors.symbols
Original file line number Diff line number Diff line change
@@ -136,3 +136,9 @@ interface i10<T = number> {}
>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<T = 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))

6 changes: 6 additions & 0 deletions tests/baselines/reference/genericDefaultsErrors.types
Original file line number Diff line number Diff line change
@@ -145,3 +145,9 @@ interface i10<T = number> {}
>i10 : i10<T>
>T : T

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference> {}
>SelfReference : SelfReference<T>
>T : T
>SelfReference : SelfReference<T>

5 changes: 4 additions & 1 deletion tests/cases/compiler/genericDefaults.ts
Original file line number Diff line number Diff line change
@@ -487,4 +487,7 @@ const t03c00 = (<t03<number>>x).a;
const t03c01 = (<t03<1>>x).a;
const t03c02 = (<t03<number, number>>x).a;
const t03c03 = (<t03<1, 1>>x).a;
const t03c04 = (<t03<number, 1>>x).a;
const t03c04 = (<t03<number, 1>>x).a;

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference<string>> {}
5 changes: 4 additions & 1 deletion tests/cases/compiler/genericDefaultsErrors.ts
Original file line number Diff line number Diff line change
@@ -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<T = number> {}
interface i10<T = number> {}

// https://github.com/Microsoft/TypeScript/issues/16221
interface SelfReference<T = SelfReference> {}