From 53b1572ed60283c765fabf32830608f44b1f4458 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Jan 2018 07:59:46 -0800 Subject: [PATCH] Revert to extends check being part of conditional type --- src/compiler/checker.ts | 93 +++++++++++++++--------------- src/compiler/declarationEmitter.ts | 4 +- src/compiler/emitter.ts | 4 +- src/compiler/factory.ts | 12 ++-- src/compiler/parser.ts | 23 +++----- src/compiler/types.ts | 8 ++- src/compiler/visitor.ts | 3 +- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 328f3710029..de3666b0ad5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2643,10 +2643,11 @@ namespace ts { return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } if (type.flags & TypeFlags.Conditional) { - const conditionTypeNode = typeToTypeNodeHelper((type).conditionType, context); + const checkTypeNode = typeToTypeNodeHelper((type).checkType, context); + const extendsTypeNode = typeToTypeNodeHelper((type).extendsType, context); const trueTypeNode = typeToTypeNodeHelper((type).trueType, context); const falseTypeNode = typeToTypeNodeHelper((type).falseType, context); - return createConditionalTypeNode(conditionTypeNode, trueTypeNode, falseTypeNode); + return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } if (type.flags & TypeFlags.Extends) { const leftTypeNode = typeToTypeNodeHelper((type).checkType, context); @@ -3422,7 +3423,11 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseBracketToken); } else if (type.flags & TypeFlags.Conditional) { - writeType((type).conditionType, TypeFormatFlags.InElementType); + writeType((type).checkType, TypeFormatFlags.InElementType); + writeSpace(writer); + writer.writeKeyword("extends"); + writeSpace(writer); + writeType((type).extendsType, TypeFormatFlags.InElementType); writeSpace(writer); writePunctuation(writer, SyntaxKind.QuestionToken); writeSpace(writer); @@ -6396,14 +6401,11 @@ namespace ts { // with its constraint. We do this because if the constraint is a union type it will be distributed // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' // removes 'undefined' from T. - const conditionType = type.conditionType; - if (conditionType.flags & TypeFlags.Extends) { - const checkType = (conditionType).checkType; - if (checkType.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintOfTypeParameter(checkType); - if (constraint) { - return instantiateType(type, createTypeMapper([checkType], [constraint])); - } + const checkType = type.checkType; + if (checkType.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintOfTypeParameter(checkType); + if (constraint) { + return instantiateType(type, createTypeMapper([checkType], [constraint])); } } return undefined; @@ -8387,33 +8389,38 @@ namespace ts { return links.resolvedType; } - function isGenericConditionType(type: Type) { - return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Extends); - } - - function createConditionalType(conditionType: Type, whenTrueType: Type, whenFalseType: Type, aliasSymbol: Symbol, aliasTypeArguments: Type[]) { + function createConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, aliasSymbol: Symbol, aliasTypeArguments: Type[]) { const type = createType(TypeFlags.Conditional); - type.conditionType = conditionType; - type.trueType = whenTrueType; - type.falseType = whenFalseType; + type.checkType = checkType; + type.extendsType = extendsType; + type.trueType = trueType; + type.falseType = falseType; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; return type; } - function getConditionalType(condition: Type, whenTrue: Type, whenFalse: Type, aliasSymbol: Symbol, aliasTypeArguments: Type[], mapper: TypeMapper): Type { - if (!isGenericConditionType(condition)) { - return condition.flags & TypeFlags.Never ? neverType : getUnionType([ - typeMaybeAssignableTo(condition, trueType) ? instantiateType(whenTrue, mapper) : neverType, - typeMaybeAssignableTo(condition, falseType) ? instantiateType(whenFalse, mapper) : neverType]); + function getConditionalType(checkType: Type, extendsType: Type, mapper: TypeMapper, trueType: Type, falseType: Type, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + // Distribute union types over conditional types + if (checkType.flags & TypeFlags.Union) { + return getUnionType(map((checkType).types, t => getConditionalType(t, extendsType, mapper, trueType, falseType))); } - const resultTrueType = instantiateType(whenTrue, mapper); - const resultFalseType = instantiateType(whenFalse, mapper); - const resultTypeArguments = instantiateTypes(aliasTypeArguments, mapper); - const id = condition.id + "," + resultTrueType.id + "," + resultFalseType.id; + // Return trueType for a definitely true extends check + if (isTypeAssignableTo(checkType, extendsType)) { + return instantiateType(trueType, mapper); + } + // Return falseType for a definitely false extends check + if (!isTypeAssignableTo(instantiateType(checkType, anyMapper), instantiateType(extendsType, constraintMapper))) { + return instantiateType(falseType, mapper); + } + // Otherwise return a deferred conditional type + 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; let type = conditionalTypes.get(id); if (!type) { - conditionalTypes.set(id, type = createConditionalType(condition, resultTrueType, resultFalseType, aliasSymbol, resultTypeArguments)); + conditionalTypes.set(id, type = createConditionalType(checkType, extendsType, resTrueType, resFalseType, aliasSymbol, resTypeArguments)); } return type; } @@ -8421,9 +8428,9 @@ namespace ts { function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getConditionalType(getTypeFromTypeNode(node.conditionType), - getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType), - getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node), identityMapper); + links.resolvedType = getConditionalType(getTypeFromTypeNode(node.checkType), getTypeFromTypeNode(node.extendsType), + identityMapper, getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType), + getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node)); } return links.resolvedType; } @@ -9039,23 +9046,19 @@ namespace ts { // Check if we have a conditional type of the form T extends U ? X : Y, where T is a type parameter. // If so, the conditional type is distributive over a union type and when T is instantiated to a union // type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y). - const conditionType = type.conditionType; - if (conditionType.flags & TypeFlags.Extends) { - const checkType = (conditionType).checkType; - if (checkType.flags & TypeFlags.TypeParameter) { - const instantiatedType = mapper(checkType); - if (checkType !== instantiatedType && instantiatedType.flags & TypeFlags.Union) { - return mapType(instantiatedType, t => instantiateConditionalType(type, createReplacementMapper(checkType, t, mapper))); - } + const checkType = type.checkType; + if (checkType.flags & TypeFlags.TypeParameter) { + const instantiatedType = mapper(checkType); + if (checkType !== instantiatedType && instantiatedType.flags & TypeFlags.Union) { + return mapType(instantiatedType, t => instantiateConditionalType(type, createReplacementMapper(checkType, t, mapper))); } } return instantiateConditionalType(type, mapper); } function instantiateConditionalType(type: ConditionalType, mapper: TypeMapper): Type { - return getConditionalType(instantiateType((type).conditionType, mapper), - (type).trueType, (type).falseType, - type.aliasSymbol, type.aliasTypeArguments, mapper); + return getConditionalType(instantiateType(type.checkType, mapper), instantiateType(type.extendsType, mapper), + mapper, type.trueType, type.falseType, type.aliasSymbol, type.aliasTypeArguments); } function instantiateType(type: Type, mapper: TypeMapper): Type { @@ -11644,7 +11647,8 @@ namespace ts { inferFromTypes((source).indexType, (target).indexType); } else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) { - inferFromTypes((source).conditionType, (target).conditionType); + inferFromTypes((source).checkType, (target).checkType); + inferFromTypes((source).extendsType, (target).extendsType); inferFromTypes((source).trueType, (target).trueType); inferFromTypes((source).falseType, (target).falseType); } @@ -20358,7 +20362,6 @@ namespace ts { function checkConditionalType(node: ConditionalTypeNode) { forEachChild(node, checkSourceElement); - checkTypeAssignableTo(getTypeFromTypeNode(node.conditionType), booleanType, node.conditionType); } function isPrivateWithinAmbient(node: Node): boolean { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index f4266278c75..73a910f18d4 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -550,7 +550,9 @@ namespace ts { } function emitConditionalType(node: ConditionalTypeNode) { - emitType(node.conditionType); + emitType(node.checkType); + write(" extends "); + emitType(node.extendsType); write(" ? "); emitType(node.trueType); write(" : "); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6b7ae73411c..05166ee539c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1134,7 +1134,9 @@ namespace ts { } function emitConditionalType(node: ConditionalTypeNode) { - emit(node.conditionType); + emit(node.checkType); + write(" extends "); + emit(node.extendsType); write(" ? "); emit(node.trueType); write(" : "); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index e1cf0d00660..86af989f38c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -720,19 +720,21 @@ namespace ts { : node; } - export function createConditionalTypeNode(conditionType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode; - node.conditionType = parenthesizeConditionalTypeMember(conditionType); + node.checkType = parenthesizeConditionalTypeMember(checkType); + node.extendsType = parenthesizeConditionalTypeMember(extendsType); node.trueType = trueType; node.falseType = falseType; return node; } - export function updateConditionalTypeNode(node: ConditionalTypeNode, conditionType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - return node.conditionType !== conditionType + export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + return node.checkType !== checkType + || node.extendsType !== extendsType || node.trueType !== trueType || node.falseType !== falseType - ? updateNode(createConditionalTypeNode(conditionType, trueType, falseType), node) + ? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) : node; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9e130f290f8..4fbf491d266 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -176,7 +176,8 @@ namespace ts { case SyntaxKind.IntersectionType: return visitNodes(cbNode, cbNodes, (node).types); case SyntaxKind.ConditionalType: - return visitNode(cbNode, (node).conditionType) || + return visitNode(cbNode, (node).checkType) || + visitNode(cbNode, (node).extendsType) || visitNode(cbNode, (node).trueType) || visitNode(cbNode, (node).falseType); case SyntaxKind.ParenthesizedType: @@ -2883,23 +2884,13 @@ namespace ts { return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken); } - function parseBinaryTypeOrHigher(): TypeNode { - let type = parseUnionTypeOrHigher(); - while (parseOptional(SyntaxKind.ExtendsKeyword)) { - const node = createNode(SyntaxKind.BinaryType, type.pos); - node.left = type; - node.operator = SyntaxKind.ExtendsKeyword; - node.right = parseUnionTypeOrHigher(); - type = finishNode(node); - } - return type; - } - function parseConditionalTypeOrHigher(): TypeNode { - const type = parseBinaryTypeOrHigher(); - if (parseOptional(SyntaxKind.QuestionToken)) { + const type = parseUnionTypeOrHigher(); + if (parseOptional(SyntaxKind.ExtendsKeyword)) { const node = createNode(SyntaxKind.ConditionalType, type.pos); - node.conditionType = type; + node.checkType = type; + node.extendsType = parseUnionTypeOrHigher(); + parseExpected(SyntaxKind.QuestionToken); node.trueType = parseConditionalTypeOrHigher(); parseExpected(SyntaxKind.ColonToken); node.falseType = parseConditionalTypeOrHigher(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 10ae9bcc7a2..8861d557294 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1099,7 +1099,8 @@ namespace ts { export interface ConditionalTypeNode extends TypeNode { kind: SyntaxKind.ConditionalType; - conditionType: TypeNode; + checkType: TypeNode; + extendsType: TypeNode; trueType: TypeNode; falseType: TypeNode; } @@ -3407,7 +3408,7 @@ namespace ts { Intersection = 1 << 18, // Intersection (T & U) Index = 1 << 19, // keyof T IndexedAccess = 1 << 20, // T[K] - Conditional = 1 << 21, // C ? T : U + Conditional = 1 << 21, // T extends U ? X : Y Extends = 1 << 22, // T extends U /* @internal */ FreshLiteral = 1 << 23, // Fresh literal or unique type @@ -3702,7 +3703,8 @@ namespace ts { } export interface ConditionalType extends InstantiableType { - conditionType: Type; + checkType: Type; + extendsType: Type; trueType: Type; falseType: Type; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 81ae6917037..d9d7058cdf4 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -387,7 +387,8 @@ namespace ts { case SyntaxKind.ConditionalType: return updateConditionalTypeNode(node, - visitNode((node).conditionType, visitor, isTypeNode), + visitNode((node).checkType, visitor, isTypeNode), + visitNode((node).extendsType, visitor, isTypeNode), visitNode((node).trueType, visitor, isTypeNode), visitNode((node).falseType, visitor, isTypeNode));