Initial implementation of conditional type operator

This commit is contained in:
Anders Hejlsberg 2017-12-05 14:18:11 -08:00
parent 334bf4eaf7
commit 57ca7680c9
10 changed files with 165 additions and 13 deletions

View File

@ -3416,6 +3416,7 @@ namespace ts {
case SyntaxKind.TupleType:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ConditionalType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:

View File

@ -2620,6 +2620,13 @@ namespace ts {
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.Conditional) {
const checkTypeNode = typeToTypeNodeHelper((<ConditionalType>type).checkType, context);
const extendskTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
return createConditionalTypeNode(checkTypeNode, extendskTypeNode, trueTypeNode, falseTypeNode);
}
Debug.fail("Should be unreachable.");
@ -3388,6 +3395,15 @@ namespace ts {
writeType((<IndexedAccessType>type).indexType, TypeFormatFlags.None);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else if (type.flags & TypeFlags.Conditional) {
writeType((<ConditionalType>type).checkType, TypeFormatFlags.InElementType);
writer.writeKeyword("extends");
writeType((<ConditionalType>type).extendsType, TypeFormatFlags.InElementType);
writePunctuation(writer, SyntaxKind.QuestionToken);
writeType((<ConditionalType>type).trueType, TypeFormatFlags.InElementType);
writePunctuation(writer, SyntaxKind.ColonToken);
writeType((<ConditionalType>type).falseType, TypeFormatFlags.InElementType);
}
else {
// Should never get here
// { ... }
@ -8189,6 +8205,35 @@ namespace ts {
return links.resolvedType;
}
function getConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
if (checkType.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>checkType).types, t => getConditionalType(t, extendsType, trueType, falseType)),
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
}
if (isTypeAssignableTo(checkType, extendsType)) {
return trueType;
}
if (!isGenericObjectType(checkType) && !isGenericObjectType(extendsType)) {
return falseType;
}
const type = <ConditionalType>createType(TypeFlags.Conditional);
type.checkType = checkType;
type.extendsType = extendsType;
type.trueType = trueType;
type.falseType = falseType;
return type;
}
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getConditionalType(getTypeFromTypeNode(node.checkType), getTypeFromTypeNode(node.extendsType),
getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType),
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
}
return links.resolvedType;
}
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
@ -8481,6 +8526,8 @@ namespace ts {
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
case SyntaxKind.MappedType:
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
case SyntaxKind.ConditionalType:
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
// This function assumes that an identifier or qualified name is a type expression
// Callers should first ensure this by calling isTypeNode
case SyntaxKind.Identifier:
@ -8778,6 +8825,11 @@ namespace ts {
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
if (type.flags & TypeFlags.Conditional) {
return getConditionalType(instantiateType((<ConditionalType>type).checkType, mapper), instantiateType((<ConditionalType>type).extendsType, mapper),
instantiateType((<ConditionalType>type).trueType, mapper), instantiateType((<ConditionalType>type).falseType, mapper),
type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
}
return type;
}

View File

@ -450,6 +450,8 @@ namespace ts {
return emitUnionType(<UnionTypeNode>type);
case SyntaxKind.IntersectionType:
return emitIntersectionType(<IntersectionTypeNode>type);
case SyntaxKind.ConditionalType:
return emitConditionalType(<ConditionalTypeNode>type);
case SyntaxKind.ParenthesizedType:
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.TypeOperator:
@ -545,7 +547,17 @@ namespace ts {
emitSeparatedList(type.types, " & ", emitType);
}
function emitParenType(type: ParenthesizedTypeNode) {
function emitConditionalType(node: ConditionalTypeNode) {
emitType(node.checkType);
write(" extends ");
emitType(node.extendsType);
write(" ? ");
emitType(node.trueType);
write(" : ");
emitType(node.falseType);
}
function emitParenType(type: ParenthesizedTypeNode) {
write("(");
emitType(type.type);
write(")");

View File

@ -562,6 +562,8 @@ namespace ts {
return emitUnionType(<UnionTypeNode>node);
case SyntaxKind.IntersectionType:
return emitIntersectionType(<IntersectionTypeNode>node);
case SyntaxKind.ConditionalType:
return emitConditionalType(<ConditionalTypeNode>node);
case SyntaxKind.ParenthesizedType:
return emitParenthesizedType(<ParenthesizedTypeNode>node);
case SyntaxKind.ExpressionWithTypeArguments:
@ -1129,6 +1131,16 @@ namespace ts {
emitList(node, node.types, ListFormat.IntersectionTypeConstituents);
}
function emitConditionalType(node: ConditionalTypeNode) {
emit(node.checkType);
write(" extends ");
emit(node.extendsType);
write(" ? ");
emit(node.trueType);
write(" : ");
emit(node.falseType);
}
function emitParenthesizedType(node: ParenthesizedTypeNode) {
write("(");
emit(node.type);

View File

@ -720,6 +720,24 @@ namespace ts {
: node;
}
export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode;
node.checkType = parenthesizeConditionalTypeMember(checkType);
node.extendsType = parenthesizeConditionalTypeMember(extendsType);
node.trueType = trueType;
node.falseType = falseType;
return node;
}
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(checkType, extendsType, trueType, falseType), node)
: node;
}
export function createParenthesizedType(type: TypeNode) {
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
node.type = type;
@ -4081,6 +4099,10 @@ namespace ts {
return expression;
}
export function parenthesizeConditionalTypeMember(member: TypeNode) {
return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member;
}
export function parenthesizeElementTypeMember(member: TypeNode) {
switch (member.kind) {
case SyntaxKind.UnionType:
@ -4089,7 +4111,7 @@ namespace ts {
case SyntaxKind.ConstructorType:
return createParenthesizedType(member);
}
return member;
return parenthesizeConditionalTypeMember(member);
}
export function parenthesizeArrayTypeMember(member: TypeNode) {

View File

@ -147,6 +147,11 @@ namespace ts {
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
return visitNodes(cbNode, cbNodes, (<UnionOrIntersectionTypeNode>node).types);
case SyntaxKind.ConditionalType:
return visitNode(cbNode, (<ConditionalTypeNode>node).checkType) ||
visitNode(cbNode, (<ConditionalTypeNode>node).extendsType) ||
visitNode(cbNode, (<ConditionalTypeNode>node).trueType) ||
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
@ -2760,6 +2765,10 @@ namespace ts {
type = createJSDocPostfixType(SyntaxKind.JSDocNonNullableType, type);
break;
case SyntaxKind.QuestionToken:
// only parse postfix ? inside jsdoc, otherwise it is a conditional type
if (!(contextFlags & NodeFlags.JSDoc)) {
return type;
}
type = createJSDocPostfixType(SyntaxKind.JSDocNullableType, type);
break;
case SyntaxKind.OpenBracketToken:
@ -2839,6 +2848,21 @@ namespace ts {
return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken);
}
function parseConditionalTypeOrHigher(): TypeNode {
const type = parseUnionTypeOrHigher();
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
const node = <ConditionalTypeNode>createNode(SyntaxKind.ConditionalType, type.pos);
node.checkType = type;
node.extendsType = parseUnionTypeOrHigher();
parseExpected(SyntaxKind.QuestionToken);
node.trueType = parseConditionalTypeOrHigher();
parseExpected(SyntaxKind.ColonToken);
node.falseType = parseConditionalTypeOrHigher();
return finishNode(node);
}
return type;
}
function isStartOfFunctionType(): boolean {
if (token() === SyntaxKind.LessThanToken) {
return true;
@ -2928,7 +2952,7 @@ namespace ts {
if (token() === SyntaxKind.NewKeyword) {
return parseFunctionOrConstructorType(SyntaxKind.ConstructorType);
}
return parseUnionTypeOrHigher();
return parseConditionalTypeOrHigher();
}
function parseTypeAnnotation(): TypeNode {

View File

@ -385,6 +385,7 @@ namespace ts {
case SyntaxKind.TypeReference:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ConditionalType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:

View File

@ -248,6 +248,7 @@ namespace ts {
TupleType,
UnionType,
IntersectionType,
ConditionalType,
ParenthesizedType,
ThisType,
TypeOperator,
@ -1065,6 +1066,14 @@ namespace ts {
types: NodeArray<TypeNode>;
}
export interface ConditionalTypeNode extends TypeNode {
kind: SyntaxKind.ConditionalType;
checkType: TypeNode;
extendsType: TypeNode;
trueType: TypeNode;
falseType: TypeNode;
}
export interface ParenthesizedTypeNode extends TypeNode {
kind: SyntaxKind.ParenthesizedType;
type: TypeNode;
@ -3339,18 +3348,19 @@ namespace ts {
Intersection = 1 << 18, // Intersection (T & U)
Index = 1 << 19, // keyof T
IndexedAccess = 1 << 20, // T[K]
Conditional = 1 << 21, // A extends B ? T : U
/* @internal */
FreshLiteral = 1 << 21, // Fresh literal or unique type
FreshLiteral = 1 << 22, // Fresh literal or unique type
/* @internal */
ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type
ContainsWideningType = 1 << 23, // Type is or contains undefined or null widening type
/* @internal */
ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type
ContainsObjectLiteral = 1 << 24, // Type is or contains object literal type
/* @internal */
ContainsAnyFunctionType = 1 << 24, // Type is or contains the anyFunctionType
NonPrimitive = 1 << 25, // intrinsic object type
ContainsAnyFunctionType = 1 << 25, // Type is or contains the anyFunctionType
NonPrimitive = 1 << 26, // intrinsic object type
/* @internal */
JsxAttributes = 1 << 26, // Jsx attributes type
MarkerType = 1 << 27, // Marker type used for variance probing
JsxAttributes = 1 << 27, // Jsx attributes type
MarkerType = 1 << 28, // Marker type used for variance probing
/* @internal */
Nullable = Undefined | Null,
@ -3373,12 +3383,12 @@ namespace ts {
ESSymbolLike = ESSymbol | UniqueESSymbol,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess,
TypeVariable = TypeParameter | IndexedAccess,
TypeVariable = TypeParameter | IndexedAccess | Conditional,
StructuredOrTypeVariable = StructuredType | TypeVariable | Index,
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
Narrowable = Any | StructuredOrTypeVariable | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
@ -3626,6 +3636,13 @@ namespace ts {
type: TypeVariable | UnionOrIntersectionType;
}
export interface ConditionalType extends TypeVariable {
checkType: Type;
extendsType: Type;
trueType: Type;
falseType: Type;
}
export const enum SignatureKind {
Call,
Construct,

View File

@ -4527,6 +4527,10 @@ namespace ts {
return node.kind === SyntaxKind.IntersectionType;
}
export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode {
return node.kind === SyntaxKind.ConditionalType;
}
export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
return node.kind === SyntaxKind.ParenthesizedType;
}

View File

@ -385,6 +385,13 @@ namespace ts {
return updateIntersectionTypeNode(<IntersectionTypeNode>node,
nodesVisitor((<IntersectionTypeNode>node).types, visitor, isTypeNode));
case SyntaxKind.ConditionalType:
return updateConditionalTypeNode(<ConditionalTypeNode>node,
visitNode((<ConditionalTypeNode>node).checkType, visitor, isTypeNode),
visitNode((<ConditionalTypeNode>node).extendsType, visitor, isTypeNode),
visitNode((<ConditionalTypeNode>node).trueType, visitor, isTypeNode),
visitNode((<ConditionalTypeNode>node).falseType, visitor, isTypeNode));
case SyntaxKind.ParenthesizedType:
return updateParenthesizedType(<ParenthesizedTypeNode>node,
visitNode((<ParenthesizedTypeNode>node).type, visitor, isTypeNode));