Skip to content

Commit fd3af78

Browse files
authored
Merge pull request #29179 from Microsoft/typeParameterDefaultForwardReference
No self or forward references in type parameter defaults
2 parents 8e14031 + 8a72a19 commit fd3af78

12 files changed

+689
-61
lines changed

src/compiler/checker.ts

+23-9
Original file line numberDiff line numberDiff line change
@@ -7928,22 +7928,17 @@ namespace ts {
79287928
const numTypeArguments = length(typeArguments);
79297929
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
79307930
const result = typeArguments ? typeArguments.slice() : [];
7931-
7932-
// Map an unsatisfied type parameter with a default type.
7933-
// If a type parameter does not have a default type, or if the default type
7934-
// is a forward reference, the empty object type is used.
7935-
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
7936-
const circularityMapper = createTypeMapper(typeParameters!, map(typeParameters!, () => baseDefaultType));
7931+
// Map invalid forward references in default types to the error type
79377932
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7938-
result[i] = instantiateType(getConstraintFromTypeParameter(typeParameters![i]) || baseDefaultType, circularityMapper);
7933+
result[i] = errorType;
79397934
}
7935+
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
79407936
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7941-
const mapper = createTypeMapper(typeParameters!, result);
79427937
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
79437938
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
79447939
defaultType = anyType;
79457940
}
7946-
result[i] = defaultType ? instantiateType(defaultType, mapper) : baseDefaultType;
7941+
result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType;
79477942
}
79487943
result.length = typeParameters!.length;
79497944
return result;
@@ -26466,6 +26461,7 @@ namespace ts {
2646626461
if (produceDiagnostics) {
2646726462
if (node.default) {
2646826463
seenDefault = true;
26464+
checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i);
2646926465
}
2647026466
else if (seenDefault) {
2647126467
error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters);
@@ -26480,6 +26476,24 @@ namespace ts {
2648026476
}
2648126477
}
2648226478

26479+
/** Check that type parameter defaults only reference previously declared type parameters */
26480+
function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: ReadonlyArray<TypeParameterDeclaration>, index: number) {
26481+
visit(root);
26482+
function visit(node: Node) {
26483+
if (node.kind === SyntaxKind.TypeReference) {
26484+
const type = getTypeFromTypeReference(<TypeReferenceNode>node);
26485+
if (type.flags & TypeFlags.TypeParameter) {
26486+
for (let i = index; i < typeParameters.length; i++) {
26487+
if (type.symbol === getSymbolOfNode(typeParameters[i])) {
26488+
error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters);
26489+
}
26490+
}
26491+
}
26492+
}
26493+
forEachChild(node, visit);
26494+
}
26495+
}
26496+
2648326497
/** Check that type parameter lists are identical across multiple declarations */
2648426498
function checkTypeParameterListsIdentical(symbol: Symbol) {
2648526499
if (symbol.declarations.length === 1) {

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -2537,6 +2537,10 @@
25372537
"category": "Error",
25382538
"code": 2743
25392539
},
2540+
"Type parameter defaults can only reference previously declared type parameters.": {
2541+
"category": "Error",
2542+
"code": 2744
2543+
},
25402544

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

0 commit comments

Comments
 (0)