Infer constraints for 'infer T' declarations nested in type references

This commit is contained in:
Anders Hejlsberg
2018-02-06 13:24:53 -08:00
parent c4c9a00a9f
commit 8885d7941c

View File

@@ -6963,6 +6963,29 @@ namespace ts {
return type.symbol && getDeclarationOfKind<TypeParameterDeclaration>(type.symbol, SyntaxKind.TypeParameter).constraint;
}
function getInferredTypeParameterConstraint(typeParameter: TypeParameter) {
let constraints: 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) {
constraints = append(constraints, getBaseConstraintOfType(typeParameters[index]));
}
}
}
}
}
return constraints && getIntersectionType(constraints);
}
function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type {
if (!typeParameter.constraint) {
if (typeParameter.target) {
@@ -6971,7 +6994,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 +7102,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 +7140,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 +7181,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 +7248,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;
}
}
}
@@ -8781,7 +8814,7 @@ namespace ts {
}
function isMappableType(type: Type) {
return type.flags & (TypeFlags.Any | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.Intersection | TypeFlags.IndexedAccess);
return type.flags & (TypeFlags.Any | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
}
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
@@ -20182,6 +20215,23 @@ namespace ts {
return result;
}
function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) {
const type = getTypeFromTypeReference(node);
if (type !== unknownType) {
const symbol = getNodeLinks(node).resolvedSymbol;
if (symbol) {
const typeAliasParameters = symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters;
if (typeAliasParameters) {
return typeAliasParameters;
}
if (getObjectFlags(type) & ObjectFlags.Reference) {
return (<TypeReference>type).target.localTypeParameters;
}
}
}
return undefined;
}
function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) {
checkGrammarTypeArguments(node, node.typeArguments);
if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJavaScriptFile(node) && !isInJSDoc(node)) {
@@ -20194,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(typeParameters, node.typeArguments);
}
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) {