diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9c23c97a2a6..41717d4f0f7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2648,6 +2648,9 @@ namespace ts { const falseTypeNode = typeToTypeNodeHelper((type).falseType, context); return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } + if (type.flags & TypeFlags.Substitution) { + return typeToTypeNodeHelper((type).typeParameter, context); + } Debug.fail("Should be unreachable."); @@ -3431,6 +3434,9 @@ namespace ts { writeSpace(writer); writeType((type).falseType, TypeFormatFlags.InElementType); } + else if (type.flags & TypeFlags.Substitution) { + writeType((type).typeParameter, TypeFormatFlags.None); + } else { // Should never get here // { ... } @@ -6480,6 +6486,9 @@ namespace ts { if (t.flags & TypeFlags.Conditional) { return getBaseConstraint(getConstraintOfConditionalType(t)); } + if (t.flags & TypeFlags.Substitution) { + return getBaseConstraint((t).substitute); + } if (isGenericMappedType(t)) { return emptyObjectType; } @@ -7458,7 +7467,7 @@ namespace ts { error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); return unknownType; } - return res; + return res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(res, node) : res; } if (!(symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node))) { @@ -7497,6 +7506,26 @@ namespace ts { } } + function getConstrainedTypeParameter(typeParameter: TypeParameter, node: Node) { + let constraints: Type[]; + while (isTypeNode(node)) { + const parent = node.parent; + if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { + if (getTypeFromTypeNode((parent).checkType) === typeParameter) { + constraints = append(constraints, getTypeFromTypeNode((parent).extendsType)); + } + } + node = parent; + } + if (constraints) { + const result = createType(TypeFlags.Substitution); + result.typeParameter = typeParameter; + result.substitute = getIntersectionType(append(constraints, typeParameter)); + return result; + } + return typeParameter; + } + function isJSDocTypeReference(node: TypeReferenceType): node is TypeReferenceNode { return node.flags & NodeFlags.JSDoc && node.kind === SyntaxKind.TypeReference; } @@ -8395,13 +8424,14 @@ namespace ts { return instantiateType(falseType, mapper); } // Otherwise return a deferred conditional type + const resCheckType = checkType.flags & TypeFlags.Substitution ? (checkType).typeParameter : checkType; const resTrueType = instantiateType(trueType, mapper); const resFalseType = instantiateType(falseType, mapper); const resTypeArguments = instantiateTypes(aliasTypeArguments, mapper); - const id = checkType.id + "," + extendsType.id + "," + resTrueType.id + "," + resFalseType.id; + const id = resCheckType.id + "," + extendsType.id + "," + resTrueType.id + "," + resFalseType.id; let type = conditionalTypes.get(id); if (!type) { - conditionalTypes.set(id, type = createConditionalType(checkType, extendsType, resTrueType, resFalseType, aliasSymbol, resTypeArguments)); + conditionalTypes.set(id, type = createConditionalType(resCheckType, extendsType, resTrueType, resFalseType, aliasSymbol, resTypeArguments)); } return type; } @@ -9034,6 +9064,9 @@ namespace ts { if (type.flags & TypeFlags.Conditional) { return getConditionalTypeInstantiation(type, mapper); } + if (type.flags & TypeFlags.Substitution) { + return instantiateType((type).typeParameter, mapper); + } } return type; } @@ -9619,6 +9652,13 @@ namespace ts { if (target.flags & TypeFlags.StringOrNumberLiteral && target.flags & TypeFlags.FreshLiteral) { target = (target).regularType; } + if (source.flags & TypeFlags.Substitution) { + source = (source).substitute; + } + if (target.flags & TypeFlags.Substitution) { + target = (target).typeParameter; + } + // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases if (source === target) return Ternary.True; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5acee864e1b..f426370002b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3401,15 +3401,16 @@ namespace ts { Index = 1 << 19, // keyof T IndexedAccess = 1 << 20, // T[K] Conditional = 1 << 21, // T extends U ? X : Y + Substitution = 1 << 22, // Type parameter substitution /* @internal */ - FreshLiteral = 1 << 22, // Fresh literal or unique type + FreshLiteral = 1 << 23, // Fresh literal or unique type /* @internal */ - ContainsWideningType = 1 << 23, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 24, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 24, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 25, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 25, // Type is or contains the anyFunctionType - NonPrimitive = 1 << 26, // intrinsic object type + ContainsAnyFunctionType = 1 << 26, // Type is or contains the anyFunctionType + NonPrimitive = 1 << 27, // intrinsic object type /* @internal */ GenericMappedType = 1 << 29, // Flag used by maybeTypeOfKind @@ -3435,7 +3436,7 @@ namespace ts { UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection, TypeVariable = TypeParameter | IndexedAccess, - InstantiableNonPrimitive = TypeVariable | Conditional, + InstantiableNonPrimitive = TypeVariable | Conditional | Substitution, InstantiablePrimitive = Index, Instantiable = InstantiableNonPrimitive | InstantiablePrimitive, StructuredOrInstantiable = StructuredType | Instantiable, @@ -3693,6 +3694,7 @@ namespace ts { type: InstantiableType | UnionOrIntersectionType; } + // T extends U ? X : Y (TypeFlags.Conditional) export interface ConditionalType extends InstantiableType { checkType: Type; extendsType: Type; @@ -3700,6 +3702,12 @@ namespace ts { falseType: Type; } + // Type parameter substitution (TypeFlags.Substitution) + export interface SubstitutionType extends InstantiableType { + typeParameter: TypeParameter; // Target type parameter + substitute: Type; // Type to substitute for type parameter + } + export const enum SignatureKind { Call, Construct,