Skip to content

Commit 57ca768

Browse files
committed
Initial implementation of conditional type operator
1 parent 334bf4e commit 57ca768

10 files changed

Lines changed: 165 additions & 13 deletions

File tree

src/compiler/binder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3416,6 +3416,7 @@ namespace ts {
34163416
case SyntaxKind.TupleType:
34173417
case SyntaxKind.UnionType:
34183418
case SyntaxKind.IntersectionType:
3419+
case SyntaxKind.ConditionalType:
34193420
case SyntaxKind.ParenthesizedType:
34203421
case SyntaxKind.InterfaceDeclaration:
34213422
case SyntaxKind.TypeAliasDeclaration:

src/compiler/checker.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,13 @@ namespace ts {
26202620
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
26212621
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
26222622
}
2623+
if (type.flags & TypeFlags.Conditional) {
2624+
const checkTypeNode = typeToTypeNodeHelper((<ConditionalType>type).checkType, context);
2625+
const extendskTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
2626+
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
2627+
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
2628+
return createConditionalTypeNode(checkTypeNode, extendskTypeNode, trueTypeNode, falseTypeNode);
2629+
}
26232630

26242631
Debug.fail("Should be unreachable.");
26252632

@@ -3388,6 +3395,15 @@ namespace ts {
33883395
writeType((<IndexedAccessType>type).indexType, TypeFormatFlags.None);
33893396
writePunctuation(writer, SyntaxKind.CloseBracketToken);
33903397
}
3398+
else if (type.flags & TypeFlags.Conditional) {
3399+
writeType((<ConditionalType>type).checkType, TypeFormatFlags.InElementType);
3400+
writer.writeKeyword("extends");
3401+
writeType((<ConditionalType>type).extendsType, TypeFormatFlags.InElementType);
3402+
writePunctuation(writer, SyntaxKind.QuestionToken);
3403+
writeType((<ConditionalType>type).trueType, TypeFormatFlags.InElementType);
3404+
writePunctuation(writer, SyntaxKind.ColonToken);
3405+
writeType((<ConditionalType>type).falseType, TypeFormatFlags.InElementType);
3406+
}
33913407
else {
33923408
// Should never get here
33933409
// { ... }
@@ -8189,6 +8205,35 @@ namespace ts {
81898205
return links.resolvedType;
81908206
}
81918207

8208+
function getConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
8209+
if (checkType.flags & TypeFlags.Union) {
8210+
return getUnionType(map((<UnionType>checkType).types, t => getConditionalType(t, extendsType, trueType, falseType)),
8211+
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
8212+
}
8213+
if (isTypeAssignableTo(checkType, extendsType)) {
8214+
return trueType;
8215+
}
8216+
if (!isGenericObjectType(checkType) && !isGenericObjectType(extendsType)) {
8217+
return falseType;
8218+
}
8219+
const type = <ConditionalType>createType(TypeFlags.Conditional);
8220+
type.checkType = checkType;
8221+
type.extendsType = extendsType;
8222+
type.trueType = trueType;
8223+
type.falseType = falseType;
8224+
return type;
8225+
}
8226+
8227+
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
8228+
const links = getNodeLinks(node);
8229+
if (!links.resolvedType) {
8230+
links.resolvedType = getConditionalType(getTypeFromTypeNode(node.checkType), getTypeFromTypeNode(node.extendsType),
8231+
getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType),
8232+
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
8233+
}
8234+
return links.resolvedType;
8235+
}
8236+
81928237
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type {
81938238
const links = getNodeLinks(node);
81948239
if (!links.resolvedType) {
@@ -8481,6 +8526,8 @@ namespace ts {
84818526
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
84828527
case SyntaxKind.MappedType:
84838528
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
8529+
case SyntaxKind.ConditionalType:
8530+
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
84848531
// This function assumes that an identifier or qualified name is a type expression
84858532
// Callers should first ensure this by calling isTypeNode
84868533
case SyntaxKind.Identifier:
@@ -8778,6 +8825,11 @@ namespace ts {
87788825
if (type.flags & TypeFlags.IndexedAccess) {
87798826
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
87808827
}
8828+
if (type.flags & TypeFlags.Conditional) {
8829+
return getConditionalType(instantiateType((<ConditionalType>type).checkType, mapper), instantiateType((<ConditionalType>type).extendsType, mapper),
8830+
instantiateType((<ConditionalType>type).trueType, mapper), instantiateType((<ConditionalType>type).falseType, mapper),
8831+
type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
8832+
}
87818833
}
87828834
return type;
87838835
}

src/compiler/declarationEmitter.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ namespace ts {
450450
return emitUnionType(<UnionTypeNode>type);
451451
case SyntaxKind.IntersectionType:
452452
return emitIntersectionType(<IntersectionTypeNode>type);
453+
case SyntaxKind.ConditionalType:
454+
return emitConditionalType(<ConditionalTypeNode>type);
453455
case SyntaxKind.ParenthesizedType:
454456
return emitParenType(<ParenthesizedTypeNode>type);
455457
case SyntaxKind.TypeOperator:
@@ -545,7 +547,17 @@ namespace ts {
545547
emitSeparatedList(type.types, " & ", emitType);
546548
}
547549

548-
function emitParenType(type: ParenthesizedTypeNode) {
550+
function emitConditionalType(node: ConditionalTypeNode) {
551+
emitType(node.checkType);
552+
write(" extends ");
553+
emitType(node.extendsType);
554+
write(" ? ");
555+
emitType(node.trueType);
556+
write(" : ");
557+
emitType(node.falseType);
558+
}
559+
560+
function emitParenType(type: ParenthesizedTypeNode) {
549561
write("(");
550562
emitType(type.type);
551563
write(")");

src/compiler/emitter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ namespace ts {
562562
return emitUnionType(<UnionTypeNode>node);
563563
case SyntaxKind.IntersectionType:
564564
return emitIntersectionType(<IntersectionTypeNode>node);
565+
case SyntaxKind.ConditionalType:
566+
return emitConditionalType(<ConditionalTypeNode>node);
565567
case SyntaxKind.ParenthesizedType:
566568
return emitParenthesizedType(<ParenthesizedTypeNode>node);
567569
case SyntaxKind.ExpressionWithTypeArguments:
@@ -1129,6 +1131,16 @@ namespace ts {
11291131
emitList(node, node.types, ListFormat.IntersectionTypeConstituents);
11301132
}
11311133

1134+
function emitConditionalType(node: ConditionalTypeNode) {
1135+
emit(node.checkType);
1136+
write(" extends ");
1137+
emit(node.extendsType);
1138+
write(" ? ");
1139+
emit(node.trueType);
1140+
write(" : ");
1141+
emit(node.falseType);
1142+
}
1143+
11321144
function emitParenthesizedType(node: ParenthesizedTypeNode) {
11331145
write("(");
11341146
emit(node.type);

src/compiler/factory.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,24 @@ namespace ts {
720720
: node;
721721
}
722722

723+
export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
724+
const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode;
725+
node.checkType = parenthesizeConditionalTypeMember(checkType);
726+
node.extendsType = parenthesizeConditionalTypeMember(extendsType);
727+
node.trueType = trueType;
728+
node.falseType = falseType;
729+
return node;
730+
}
731+
732+
export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
733+
return node.checkType !== checkType
734+
|| node.extendsType !== extendsType
735+
|| node.trueType !== trueType
736+
|| node.falseType !== falseType
737+
? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node)
738+
: node;
739+
}
740+
723741
export function createParenthesizedType(type: TypeNode) {
724742
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
725743
node.type = type;
@@ -4081,6 +4099,10 @@ namespace ts {
40814099
return expression;
40824100
}
40834101

4102+
export function parenthesizeConditionalTypeMember(member: TypeNode) {
4103+
return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member;
4104+
}
4105+
40844106
export function parenthesizeElementTypeMember(member: TypeNode) {
40854107
switch (member.kind) {
40864108
case SyntaxKind.UnionType:
@@ -4089,7 +4111,7 @@ namespace ts {
40894111
case SyntaxKind.ConstructorType:
40904112
return createParenthesizedType(member);
40914113
}
4092-
return member;
4114+
return parenthesizeConditionalTypeMember(member);
40934115
}
40944116

40954117
export function parenthesizeArrayTypeMember(member: TypeNode) {

src/compiler/parser.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ namespace ts {
147147
case SyntaxKind.UnionType:
148148
case SyntaxKind.IntersectionType:
149149
return visitNodes(cbNode, cbNodes, (<UnionOrIntersectionTypeNode>node).types);
150+
case SyntaxKind.ConditionalType:
151+
return visitNode(cbNode, (<ConditionalTypeNode>node).checkType) ||
152+
visitNode(cbNode, (<ConditionalTypeNode>node).extendsType) ||
153+
visitNode(cbNode, (<ConditionalTypeNode>node).trueType) ||
154+
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
150155
case SyntaxKind.ParenthesizedType:
151156
case SyntaxKind.TypeOperator:
152157
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
@@ -2760,6 +2765,10 @@ namespace ts {
27602765
type = createJSDocPostfixType(SyntaxKind.JSDocNonNullableType, type);
27612766
break;
27622767
case SyntaxKind.QuestionToken:
2768+
// only parse postfix ? inside jsdoc, otherwise it is a conditional type
2769+
if (!(contextFlags & NodeFlags.JSDoc)) {
2770+
return type;
2771+
}
27632772
type = createJSDocPostfixType(SyntaxKind.JSDocNullableType, type);
27642773
break;
27652774
case SyntaxKind.OpenBracketToken:
@@ -2839,6 +2848,21 @@ namespace ts {
28392848
return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken);
28402849
}
28412850

2851+
function parseConditionalTypeOrHigher(): TypeNode {
2852+
const type = parseUnionTypeOrHigher();
2853+
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
2854+
const node = <ConditionalTypeNode>createNode(SyntaxKind.ConditionalType, type.pos);
2855+
node.checkType = type;
2856+
node.extendsType = parseUnionTypeOrHigher();
2857+
parseExpected(SyntaxKind.QuestionToken);
2858+
node.trueType = parseConditionalTypeOrHigher();
2859+
parseExpected(SyntaxKind.ColonToken);
2860+
node.falseType = parseConditionalTypeOrHigher();
2861+
return finishNode(node);
2862+
}
2863+
return type;
2864+
}
2865+
28422866
function isStartOfFunctionType(): boolean {
28432867
if (token() === SyntaxKind.LessThanToken) {
28442868
return true;
@@ -2928,7 +2952,7 @@ namespace ts {
29282952
if (token() === SyntaxKind.NewKeyword) {
29292953
return parseFunctionOrConstructorType(SyntaxKind.ConstructorType);
29302954
}
2931-
return parseUnionTypeOrHigher();
2955+
return parseConditionalTypeOrHigher();
29322956
}
29332957

29342958
function parseTypeAnnotation(): TypeNode {

src/compiler/transformers/ts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ namespace ts {
385385
case SyntaxKind.TypeReference:
386386
case SyntaxKind.UnionType:
387387
case SyntaxKind.IntersectionType:
388+
case SyntaxKind.ConditionalType:
388389
case SyntaxKind.ParenthesizedType:
389390
case SyntaxKind.ThisType:
390391
case SyntaxKind.TypeOperator:

src/compiler/types.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ namespace ts {
248248
TupleType,
249249
UnionType,
250250
IntersectionType,
251+
ConditionalType,
251252
ParenthesizedType,
252253
ThisType,
253254
TypeOperator,
@@ -1065,6 +1066,14 @@ namespace ts {
10651066
types: NodeArray<TypeNode>;
10661067
}
10671068

1069+
export interface ConditionalTypeNode extends TypeNode {
1070+
kind: SyntaxKind.ConditionalType;
1071+
checkType: TypeNode;
1072+
extendsType: TypeNode;
1073+
trueType: TypeNode;
1074+
falseType: TypeNode;
1075+
}
1076+
10681077
export interface ParenthesizedTypeNode extends TypeNode {
10691078
kind: SyntaxKind.ParenthesizedType;
10701079
type: TypeNode;
@@ -3339,18 +3348,19 @@ namespace ts {
33393348
Intersection = 1 << 18, // Intersection (T & U)
33403349
Index = 1 << 19, // keyof T
33413350
IndexedAccess = 1 << 20, // T[K]
3351+
Conditional = 1 << 21, // A extends B ? T : U
33423352
/* @internal */
3343-
FreshLiteral = 1 << 21, // Fresh literal or unique type
3353+
FreshLiteral = 1 << 22, // Fresh literal or unique type
33443354
/* @internal */
3345-
ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type
3355+
ContainsWideningType = 1 << 23, // Type is or contains undefined or null widening type
33463356
/* @internal */
3347-
ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type
3357+
ContainsObjectLiteral = 1 << 24, // Type is or contains object literal type
33483358
/* @internal */
3349-
ContainsAnyFunctionType = 1 << 24, // Type is or contains the anyFunctionType
3350-
NonPrimitive = 1 << 25, // intrinsic object type
3359+
ContainsAnyFunctionType = 1 << 25, // Type is or contains the anyFunctionType
3360+
NonPrimitive = 1 << 26, // intrinsic object type
33513361
/* @internal */
3352-
JsxAttributes = 1 << 26, // Jsx attributes type
3353-
MarkerType = 1 << 27, // Marker type used for variance probing
3362+
JsxAttributes = 1 << 27, // Jsx attributes type
3363+
MarkerType = 1 << 28, // Marker type used for variance probing
33543364

33553365
/* @internal */
33563366
Nullable = Undefined | Null,
@@ -3373,12 +3383,12 @@ namespace ts {
33733383
ESSymbolLike = ESSymbol | UniqueESSymbol,
33743384
UnionOrIntersection = Union | Intersection,
33753385
StructuredType = Object | Union | Intersection,
3376-
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess,
3377-
TypeVariable = TypeParameter | IndexedAccess,
3386+
TypeVariable = TypeParameter | IndexedAccess | Conditional,
3387+
StructuredOrTypeVariable = StructuredType | TypeVariable | Index,
33783388

33793389
// 'Narrowable' types are types where narrowing actually narrows.
33803390
// This *should* be every type other than null, undefined, void, and never
3381-
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
3391+
Narrowable = Any | StructuredOrTypeVariable | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
33823392
NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive,
33833393
/* @internal */
33843394
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
@@ -3626,6 +3636,13 @@ namespace ts {
36263636
type: TypeVariable | UnionOrIntersectionType;
36273637
}
36283638

3639+
export interface ConditionalType extends TypeVariable {
3640+
checkType: Type;
3641+
extendsType: Type;
3642+
trueType: Type;
3643+
falseType: Type;
3644+
}
3645+
36293646
export const enum SignatureKind {
36303647
Call,
36313648
Construct,

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4527,6 +4527,10 @@ namespace ts {
45274527
return node.kind === SyntaxKind.IntersectionType;
45284528
}
45294529

4530+
export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode {
4531+
return node.kind === SyntaxKind.ConditionalType;
4532+
}
4533+
45304534
export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
45314535
return node.kind === SyntaxKind.ParenthesizedType;
45324536
}

src/compiler/visitor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,13 @@ namespace ts {
385385
return updateIntersectionTypeNode(<IntersectionTypeNode>node,
386386
nodesVisitor((<IntersectionTypeNode>node).types, visitor, isTypeNode));
387387

388+
case SyntaxKind.ConditionalType:
389+
return updateConditionalTypeNode(<ConditionalTypeNode>node,
390+
visitNode((<ConditionalTypeNode>node).checkType, visitor, isTypeNode),
391+
visitNode((<ConditionalTypeNode>node).extendsType, visitor, isTypeNode),
392+
visitNode((<ConditionalTypeNode>node).trueType, visitor, isTypeNode),
393+
visitNode((<ConditionalTypeNode>node).falseType, visitor, isTypeNode));
394+
388395
case SyntaxKind.ParenthesizedType:
389396
return updateParenthesizedType(<ParenthesizedTypeNode>node,
390397
visitNode((<ParenthesizedTypeNode>node).type, visitor, isTypeNode));

0 commit comments

Comments
 (0)