From 8885d7941ca2a2d0face91a3e848653f03ba58ae Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 6 Feb 2018 13:24:53 -0800 Subject: [PATCH] Infer constraints for 'infer T' declarations nested in type references --- src/compiler/checker.ts | 126 ++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a93189c81f2..83369c5cc3d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6963,6 +6963,29 @@ namespace ts { return type.symbol && getDeclarationOfKind(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'), 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 = declaration.parent.parent; + const typeParameters = getTypeParametersForTypeReference(typeReference); + if (typeParameters) { + const index = typeReference.typeArguments.indexOf(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(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(res, node) : res; + if (res) { + return checkNoTypeArguments(node, symbol) ? + res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(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((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 (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 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 = (type).target.localTypeParameters; - } - checkTypeArgumentConstraints(typeParameters, node.typeArguments); } } if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol.flags & SymbolFlags.EnumMember) {