Skip to content
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

Generic defaults #6354

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
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
98 changes: 86 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2948,10 +2948,25 @@ namespace ts {
return getClassExtendsHeritageClauseElement(<ClassLikeDeclaration>type.symbol.valueDeclaration);
}

function getRequiredTypeArgumentCount(typeParameters: TypeParameter[]) {
if (typeParameters) {
for (let i = typeParameters.length - 1; i >= 0; i--) {
if (!typeParameters[i].default) {
return i + 1;
}
}
}
return 0;
}

function getMaximumTypeArgumentCount(typeParameters: TypeParameter[]) {
return typeParameters ? typeParameters.length : 0;
}

function getConstructorsForTypeArguments(type: ObjectType, typeArgumentNodes: TypeNode[]): Signature[] {
const typeArgCount = typeArgumentNodes ? typeArgumentNodes.length : 0;
return filter(getSignaturesOfType(type, SignatureKind.Construct),
sig => (sig.typeParameters ? sig.typeParameters.length : 0) === typeArgCount);
sig => typeArgCount >= getRequiredTypeArgumentCount(sig.typeParameters) && typeArgCount <= getMaximumTypeArgumentCount(sig.typeParameters));
}

function getInstantiatedConstructorsForTypeArguments(type: ObjectType, typeArgumentNodes: TypeNode[]): Signature[] {
Expand Down Expand Up @@ -3198,9 +3213,23 @@ namespace ts {
if (!links.declaredType) {
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
type.symbol = symbol;
if (!(<TypeParameterDeclaration>getDeclarationOfKind(symbol, SyntaxKind.TypeParameter)).constraint) {
const declaration = <TypeParameterDeclaration>getDeclarationOfKind(symbol, SyntaxKind.TypeParameter);
if (!declaration.constraint) {
type.constraint = noConstraintType;
}
const defaultType = forEach(symbol.declarations, decl => (decl.kind === SyntaxKind.TypeParameter) && (<TypeParameterDeclaration>decl).default);
if (defaultType) {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) {
type.default = unknownType;
}
else {
type.default = getTypeOfNode(defaultType);
}

if (!popTypeResolution()) {
error(symbol.declarations[0], Diagnostics.Type_parameter_0_has_a_circular_default, type.symbol.name);
}
}
links.declaredType = type;
}
return <TypeParameter>links.declaredType;
Expand Down Expand Up @@ -3426,13 +3455,17 @@ namespace ts {
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, 0, /*hasRestParameter*/ false, /*hasStringLiterals*/ false)];
}
const baseTypeNode = getBaseTypeNodeOfClass(classType);
const typeArguments = map(baseTypeNode.typeArguments, getTypeFromTypeNode);
let typeArguments = map(baseTypeNode.typeArguments, getTypeFromTypeNode);
const typeArgCount = typeArguments ? typeArguments.length : 0;
const result: Signature[] = [];
for (const baseSig of baseSignatures) {
const typeParamCount = baseSig.typeParameters ? baseSig.typeParameters.length : 0;
if (typeParamCount === typeArgCount) {
const sig = typeParamCount ? getSignatureInstantiation(baseSig, typeArguments) : cloneSignature(baseSig);
const minTypeParamCount = getRequiredTypeArgumentCount(baseSig.typeParameters);
const maxTypeParamCount = getMaximumTypeArgumentCount(baseSig.typeParameters);
if (typeArgCount >= minTypeParamCount && typeArgCount <= maxTypeParamCount) {
for (let i = typeArgCount; i < maxTypeParamCount; i++) {
(typeArguments = typeArguments || []).push(baseSig.typeParameters[i].default);
}
const sig = typeArgCount ? getSignatureInstantiation(baseSig, typeArguments) : cloneSignature(baseSig);
sig.typeParameters = classType.localTypeParameters;
sig.resolvedReturnType = classType;
result.push(sig);
Expand Down Expand Up @@ -4195,19 +4228,44 @@ namespace ts {
return type;
}

function checkTypeArgumentArity(typeParameters: TypeParameter[], typeArguments: NodeArray<TypeNode>): boolean {
if (typeArguments && (typeArguments.length > typeParameters.length)) {
return false;
}
else {
for (let i = (typeArguments && typeArguments.length) || 0; i < typeParameters.length; i++) {
if (!typeParameters[i].default) {
return false;
}
}
}
return true;
}

// Get type from reference to class or interface
function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type {
const type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
const typeParameters = type.localTypeParameters;
if (typeParameters) {
if (!node.typeArguments || node.typeArguments.length !== typeParameters.length) {
error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length);
if (!checkTypeArgumentArity(typeParameters, node.typeArguments)) {
const minArgCount = getRequiredTypeArgumentCount(typeParameters);
const maxArgCount = getMaximumTypeArgumentCount(typeParameters);
error(node,
minArgCount === maxArgCount ? Diagnostics.Generic_type_0_requires_1_type_argument_s : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType),
minArgCount,
maxArgCount);
return unknownType;
}
const typeArguments = concatenate(type.outerTypeParameters, map(node.typeArguments, getTypeFromTypeNode)) || [];
for (let i = (typeArguments && typeArguments.length) || 0; i < typeParameters.length; i++) {
typeArguments.push(typeParameters[i].default);
}

// In a type reference, the outer type parameters of the referenced class or interface are automatically
// supplied as type arguments and the type reference only specifies arguments for the local type parameters
// of the class or interface.
return createTypeReference(<GenericType>type, concatenate(type.outerTypeParameters, map(node.typeArguments, getTypeFromTypeNode)));
return createTypeReference(<GenericType>type, typeArguments);
}
if (node.typeArguments) {
error(node, Diagnostics.Type_0_is_not_generic, typeToString(type));
Expand All @@ -4224,7 +4282,7 @@ namespace ts {
const links = getSymbolLinks(symbol);
const typeParameters = links.typeParameters;
if (typeParameters) {
if (!node.typeArguments || node.typeArguments.length !== typeParameters.length) {
if (!checkTypeArgumentArity(typeParameters, node.typeArguments)) {
error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, symbolToString(symbol), typeParameters.length);
return unknownType;
}
Expand Down Expand Up @@ -6434,11 +6492,11 @@ namespace ts {
inferenceSucceeded = !!unionOrSuperType;
}
else {
// Infer the empty object type when no inferences were made. It is important to remember that
// Infer the default, or the empty object type, when no inferences were made. It is important to remember that
// in this case, inference still succeeds, meaning there is no error for not having inference
// candidates. An inference error only occurs when there are *conflicting* candidates, i.e.
// candidates with no common supertype.
inferredType = emptyObjectType;
inferredType = context.typeParameters[index].default || emptyObjectType;
inferenceSucceeded = true;
}
context.inferredTypes[index] = inferredType;
Expand Down Expand Up @@ -11083,6 +11141,10 @@ namespace ts {
}

checkSourceElement(node.constraint);
if (node.default) {
checkSourceElement(node.default);
}

getConstraintOfTypeParameter(getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)));
if (produceDiagnostics) {
checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0);
Expand Down Expand Up @@ -13468,13 +13530,25 @@ namespace ts {
}

// Check each type parameter and check that list has no duplicate type parameter declarations
// and no required parameters after optional parameters and no forward references to other
// type parameters in defaults
function checkTypeParameters(typeParameterDeclarations: TypeParameterDeclaration[]) {
if (typeParameterDeclarations) {
let seenDefault = false;
for (let i = 0, n = typeParameterDeclarations.length; i < n; i++) {
const node = typeParameterDeclarations[i];
checkTypeParameter(node);

if (produceDiagnostics) {
if (seenDefault) {
if (!node.default) {
error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters);
}
}
else {
seenDefault = !!node.default;
}

for (let j = 0; j < i; j++) {
if (typeParameterDeclarations[j].symbol === node.symbol) {
error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name));
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,18 @@
"category": "Error",
"code": 2660
},
"Required type parameters may not follow optional type parameters": {
"category": "Error",
"code": 2661
},
"Type parameter '{0}' has a circular default.": {
"category": "Error",
"code": 2662
},
"Generic type '{0}' requires between {1} and {2} type arguments": {
"category": "Error",
"code": 2663
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace ts {
case SyntaxKind.TypeParameter:
return visitNode(cbNode, (<TypeParameterDeclaration>node).name) ||
visitNode(cbNode, (<TypeParameterDeclaration>node).constraint) ||
visitNode(cbNode, (<TypeParameterDeclaration>node).default) ||
visitNode(cbNode, (<TypeParameterDeclaration>node).expression);
case SyntaxKind.ShorthandPropertyAssignment:
return visitNodes(cbNodes, node.decorators) ||
Expand Down Expand Up @@ -2005,6 +2006,10 @@ namespace ts {
}
}

if (parseOptional(SyntaxKind.EqualsToken)) {
node.default = parseType();
}

return finishNode(node);
}

Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ namespace ts {
export interface TypeParameterDeclaration extends Declaration {
name: Identifier;
constraint?: TypeNode;
default?: TypeNode;

// For error recovery purposes.
expression?: Expression;
Expand Down Expand Up @@ -2248,6 +2249,7 @@ namespace ts {
// Type parameters (TypeFlags.TypeParameter)
export interface TypeParameter extends Type {
constraint: Type; // Constraint
default?: Type;
/* @internal */
target?: TypeParameter; // Instantiation target
/* @internal */
Expand Down
99 changes: 99 additions & 0 deletions tests/baselines/reference/typeParameterCircularDefault.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(4,14): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(4,21): error TS2662: Type parameter 'U' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(7,14): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(10,14): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(16,14): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(16,21): error TS2662: Type parameter 'U' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(16,28): error TS2662: Type parameter 'V' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(28,10): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(28,17): error TS2662: Type parameter 'U' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(31,10): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(34,10): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(40,10): error TS2662: Type parameter 'T' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(40,17): error TS2662: Type parameter 'U' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(40,24): error TS2662: Type parameter 'V' has a circular default.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(43,5): error TS2502: 'p' is referenced directly or indirectly in its own type annotation.
tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts(45,10): error TS2662: Type parameter 'T' has a circular default.


==== tests/cases/conformance/types/typeParameters/typeParameterCircularDefault.ts (16 errors) ====
/** Interfaces **/

// Two-way circular
interface I1<T = U, U = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'U' has a circular default.

// Self-circular
interface I2<T = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.

// Wrapped circular
interface I3<T = Array<T>> { }
~~~~~~~~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.

// OK, I guess?
interface I4<T = U, U = number> { }

// Three-way circular
interface I5<T = U, U = V, V = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'U' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'V' has a circular default.

// Indirect through type query
var a: I6;
var b = p.x;
interface I6<T = typeof b> {
x: T;
}


/** Classes **/
// Two-way circular
class C1<T = U, U = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'U' has a circular default.

// Self-circular
class C2<T = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.

// Wrapped circular
class C3<T = Array<T>> { }
~~~~~~~~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.

// OK, I guess?
class C4<T = U, U = number> { }

// Three-way circular
class C5<T = U, U = V, V = T> { }
~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'U' has a circular default.
~~~~~
!!! error TS2662: Type parameter 'V' has a circular default.

// Indirect through type query
var p: C6;
~
!!! error TS2502: 'p' is referenced directly or indirectly in its own type annotation.
var n = p.x;
class C6<T = typeof n> {
~~~~~~~~~~~~
!!! error TS2662: Type parameter 'T' has a circular default.
x: T;
}

Loading