From 3f7a9a906b91660afd655976064ea4ac4b1f3294 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 27 Dec 2018 13:45:13 -1000 Subject: [PATCH] Type parameter defaults can only reference previously declared type parameters --- src/compiler/checker.ts | 32 ++++++++++++++++++++-------- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 23c5568f2e5..c63a5dce46f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7928,22 +7928,17 @@ namespace ts { const numTypeArguments = length(typeArguments); if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { const result = typeArguments ? typeArguments.slice() : []; - - // Map an unsatisfied type parameter with a default type. - // If a type parameter does not have a default type, or if the default type - // is a forward reference, the empty object type is used. - const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); - const circularityMapper = createTypeMapper(typeParameters!, map(typeParameters!, () => baseDefaultType)); + // Map invalid forward references in default types to the error type for (let i = numTypeArguments; i < numTypeParameters; i++) { - result[i] = instantiateType(getConstraintFromTypeParameter(typeParameters![i]) || baseDefaultType, circularityMapper); + result[i] = errorType; } + const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); for (let i = numTypeArguments; i < numTypeParameters; i++) { - const mapper = createTypeMapper(typeParameters!, result); let defaultType = getDefaultFromTypeParameter(typeParameters![i]); if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) { defaultType = anyType; } - result[i] = defaultType ? instantiateType(defaultType, mapper) : baseDefaultType; + result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; } result.length = typeParameters!.length; return result; @@ -26465,6 +26460,7 @@ namespace ts { if (produceDiagnostics) { if (node.default) { seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); } else if (seenDefault) { error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); @@ -26479,6 +26475,24 @@ namespace ts { } } + /** Check that type parameter defaults only reference previously declared type parameters */ + function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: ReadonlyArray, index: number) { + visit(root); + function visit(node: Node) { + if (node.kind === SyntaxKind.TypeReference) { + const type = getTypeFromTypeReference(node); + if (type.flags & TypeFlags.TypeParameter) { + for (let i = index; i < typeParameters.length; i++) { + if (type.symbol === getSymbolOfNode(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + } + } + } + } + forEachChild(node, visit); + } + } + /** Check that type parameter lists are identical across multiple declarations */ function checkTypeParameterListsIdentical(symbol: Symbol) { if (symbol.declarations.length === 1) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b0dfb85ce6d..57f4c4c5611 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2537,6 +2537,10 @@ "category": "Error", "code": 2743 }, + "Type parameter defaults can only reference previously declared type parameters.": { + "category": "Error", + "code": 2744 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error",