mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 07:13:45 -05:00
Merge pull request #21709 from Microsoft/inferredTypeParameterConstraints
Inferred type parameter constraints
This commit is contained in:
@@ -6963,6 +6963,42 @@ namespace ts {
|
||||
return type.symbol && getDeclarationOfKind<TypeParameterDeclaration>(type.symbol, SyntaxKind.TypeParameter).constraint;
|
||||
}
|
||||
|
||||
function getInferredTypeParameterConstraint(typeParameter: TypeParameter) {
|
||||
let inferences: Type[];
|
||||
if (typeParameter.symbol) {
|
||||
for (const declaration of typeParameter.symbol.declarations) {
|
||||
// When an 'infer T' declaration is immediately contained in a type reference node
|
||||
// (such as 'Foo<infer T>'), T's constraint is inferred from the constraint of the
|
||||
// corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are
|
||||
// present, we form an intersection of the inferred constraint types.
|
||||
if (declaration.parent.kind === SyntaxKind.InferType && declaration.parent.parent.kind === SyntaxKind.TypeReference) {
|
||||
const typeReference = <TypeReferenceNode>declaration.parent.parent;
|
||||
const typeParameters = getTypeParametersForTypeReference(typeReference);
|
||||
if (typeParameters) {
|
||||
const index = typeReference.typeArguments.indexOf(<TypeNode>declaration.parent);
|
||||
if (index < typeParameters.length) {
|
||||
const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]);
|
||||
if (declaredConstraint) {
|
||||
// Type parameter constraints can reference other type parameters so
|
||||
// constraints need to be instantiated. If instantiation produces the
|
||||
// type parameter itself, we discard that inference. For example, in
|
||||
// type Foo<T extends string, U extends T> = [T, U];
|
||||
// type Bar<T> = T extends Foo<infer X, infer X> ? Foo<X, X> : T;
|
||||
// the instantiated constraint for U is X, so we discard that inference.
|
||||
const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters));
|
||||
const constraint = instantiateType(declaredConstraint, mapper);
|
||||
if (constraint !== typeParameter) {
|
||||
inferences = append(inferences, constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inferences && getIntersectionType(inferences);
|
||||
}
|
||||
|
||||
function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type {
|
||||
if (!typeParameter.constraint) {
|
||||
if (typeParameter.target) {
|
||||
@@ -6971,7 +7007,8 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
const constraintDeclaration = getConstraintDeclaration(typeParameter);
|
||||
typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) : noConstraintType;
|
||||
typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) :
|
||||
getInferredTypeParameterConstraint(typeParameter) || noConstraintType;
|
||||
}
|
||||
}
|
||||
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
|
||||
@@ -7078,12 +7115,8 @@ namespace ts {
|
||||
const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgs, typeParameters, minTypeArgumentCount, isJs));
|
||||
return createTypeReference(<GenericType>type, typeArguments);
|
||||
}
|
||||
if (node.typeArguments) {
|
||||
error(node, Diagnostics.Type_0_is_not_generic, typeToString(type));
|
||||
return unknownType;
|
||||
return checkNoTypeArguments(node, symbol) ? type : unknownType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[]): Type {
|
||||
const type = getDeclaredTypeOfSymbol(symbol);
|
||||
@@ -7120,12 +7153,8 @@ namespace ts {
|
||||
}
|
||||
return getTypeAliasInstantiation(symbol, typeArguments);
|
||||
}
|
||||
if (node.typeArguments) {
|
||||
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
|
||||
return unknownType;
|
||||
return checkNoTypeArguments(node, symbol) ? type : unknownType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined {
|
||||
switch (node.kind) {
|
||||
@@ -7165,12 +7194,10 @@ namespace ts {
|
||||
|
||||
// Get type from reference to named type that cannot be generic (enum or type parameter)
|
||||
const res = tryGetDeclaredTypeOfSymbol(symbol);
|
||||
if (res !== undefined) {
|
||||
if (typeArguments) {
|
||||
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
|
||||
return unknownType;
|
||||
}
|
||||
return res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(<TypeParameter>res, node) : res;
|
||||
if (res) {
|
||||
return checkNoTypeArguments(node, symbol) ?
|
||||
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(<TypeParameter>res, node) : res :
|
||||
unknownType;
|
||||
}
|
||||
|
||||
if (!(symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node))) {
|
||||
@@ -7234,39 +7261,58 @@ namespace ts {
|
||||
return node.flags & NodeFlags.JSDoc && node.kind === SyntaxKind.TypeReference;
|
||||
}
|
||||
|
||||
function checkNoTypeArguments(node: TypeReferenceType, symbol?: Symbol) {
|
||||
if (node.typeArguments) {
|
||||
error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : declarationNameToString((<TypeReferenceNode>node).typeName));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type {
|
||||
if (isIdentifier(node.typeName)) {
|
||||
if (node.typeName.escapedText === "Object") {
|
||||
if (isJSDocIndexSignature(node)) {
|
||||
const indexed = getTypeFromTypeNode(node.typeArguments[0]);
|
||||
const target = getTypeFromTypeNode(node.typeArguments[1]);
|
||||
const index = createIndexInfo(target, /*isReadonly*/ false);
|
||||
return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType && index, indexed === numberType && index);
|
||||
}
|
||||
return anyType;
|
||||
}
|
||||
const typeArgs = node.typeArguments;
|
||||
switch (node.typeName.escapedText) {
|
||||
case "String":
|
||||
checkNoTypeArguments(node);
|
||||
return stringType;
|
||||
case "Number":
|
||||
checkNoTypeArguments(node);
|
||||
return numberType;
|
||||
case "Boolean":
|
||||
checkNoTypeArguments(node);
|
||||
return booleanType;
|
||||
case "Void":
|
||||
checkNoTypeArguments(node);
|
||||
return voidType;
|
||||
case "Undefined":
|
||||
checkNoTypeArguments(node);
|
||||
return undefinedType;
|
||||
case "Null":
|
||||
checkNoTypeArguments(node);
|
||||
return nullType;
|
||||
case "Function":
|
||||
case "function":
|
||||
checkNoTypeArguments(node);
|
||||
return globalFunctionType;
|
||||
case "Array":
|
||||
case "array":
|
||||
return !node.typeArguments || !node.typeArguments.length ? anyArrayType : undefined;
|
||||
return !typeArgs || !typeArgs.length ? anyArrayType : undefined;
|
||||
case "Promise":
|
||||
case "promise":
|
||||
return !node.typeArguments || !node.typeArguments.length ? createPromiseType(anyType) : undefined;
|
||||
return !typeArgs || !typeArgs.length ? createPromiseType(anyType) : undefined;
|
||||
case "Object":
|
||||
if (typeArgs && typeArgs.length === 2) {
|
||||
if (isJSDocIndexSignature(node)) {
|
||||
const indexed = getTypeFromTypeNode(typeArgs[0]);
|
||||
const target = getTypeFromTypeNode(typeArgs[1]);
|
||||
const index = createIndexInfo(target, /*isReadonly*/ false);
|
||||
return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType && index, indexed === numberType && index);
|
||||
}
|
||||
return anyType;
|
||||
}
|
||||
checkNoTypeArguments(node);
|
||||
return anyType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20148,8 +20194,12 @@ namespace ts {
|
||||
checkDecorators(node);
|
||||
}
|
||||
|
||||
function checkTypeArgumentConstraints(typeParameters: TypeParameter[], typeArgumentNodes: ReadonlyArray<TypeNode>): boolean {
|
||||
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
|
||||
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: TypeParameter[]) {
|
||||
return fillMissingTypeArguments(map(node.typeArguments, getTypeFromTypeNode), typeParameters,
|
||||
getMinTypeArgumentCount(typeParameters), isInJavaScriptFile(node));
|
||||
}
|
||||
|
||||
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: TypeParameter[]): boolean {
|
||||
let typeArguments: Type[];
|
||||
let mapper: TypeMapper;
|
||||
let result = true;
|
||||
@@ -20157,25 +20207,36 @@ namespace ts {
|
||||
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
|
||||
if (constraint) {
|
||||
if (!typeArguments) {
|
||||
typeArguments = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, minTypeArgumentCount, isInJavaScriptFile(typeArgumentNodes[i]));
|
||||
typeArguments = getEffectiveTypeArguments(node, typeParameters);
|
||||
mapper = createTypeMapper(typeParameters, typeArguments);
|
||||
}
|
||||
const typeArgument = typeArguments[i];
|
||||
result = result && checkTypeAssignableTo(
|
||||
typeArgument,
|
||||
instantiateType(constraint, mapper),
|
||||
typeArgumentNodes[i],
|
||||
node.typeArguments[i],
|
||||
Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) {
|
||||
const type = getTypeFromTypeReference(node);
|
||||
if (type !== unknownType) {
|
||||
const symbol = getNodeLinks(node).resolvedSymbol;
|
||||
if (symbol) {
|
||||
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
|
||||
(getObjectFlags(type) & ObjectFlags.Reference ? (<TypeReference>type).target.localTypeParameters : undefined);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) {
|
||||
checkGrammarTypeArguments(node, node.typeArguments);
|
||||
if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJavaScriptFile(node) && !isInJSDoc(node)) {
|
||||
grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
|
||||
|
||||
}
|
||||
const type = getTypeFromTypeReference(node);
|
||||
if (type !== unknownType) {
|
||||
@@ -20183,22 +20244,10 @@ namespace ts {
|
||||
// Do type argument local checks only if referenced type is successfully resolved
|
||||
forEach(node.typeArguments, checkSourceElement);
|
||||
if (produceDiagnostics) {
|
||||
const symbol = getNodeLinks(node).resolvedSymbol;
|
||||
if (!symbol) {
|
||||
// There is no resolved symbol cached if the type resolved to a builtin
|
||||
// via JSDoc type reference resolution (eg, Boolean became boolean), none
|
||||
// of which are generic when they have no associated symbol
|
||||
// (additionally, JSDoc's index signature syntax, Object<string, T> actually uses generic syntax without being generic)
|
||||
if (!isJSDocIndexSignature(node)) {
|
||||
error(node, Diagnostics.Type_0_is_not_generic, typeToString(type));
|
||||
}
|
||||
return;
|
||||
const typeParameters = getTypeParametersForTypeReference(node);
|
||||
if (typeParameters) {
|
||||
checkTypeArgumentConstraints(node, typeParameters);
|
||||
}
|
||||
let typeParameters = symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters;
|
||||
if (!typeParameters && getObjectFlags(type) & ObjectFlags.Reference) {
|
||||
typeParameters = (<TypeReference>type).target.localTypeParameters;
|
||||
}
|
||||
checkTypeArgumentConstraints(typeParameters, node.typeArguments);
|
||||
}
|
||||
}
|
||||
if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol.flags & SymbolFlags.EnumMember) {
|
||||
@@ -22895,7 +22944,7 @@ namespace ts {
|
||||
if (some(baseTypeNode.typeArguments)) {
|
||||
forEach(baseTypeNode.typeArguments, checkSourceElement);
|
||||
for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) {
|
||||
if (!checkTypeArgumentConstraints(constructor.typeParameters, baseTypeNode.typeArguments)) {
|
||||
if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user