Skip to content

Commit c4485bc

Browse files
authored
Merge pull request microsoft#21316 from Microsoft/conditionalTypes
Conditional types
2 parents 89de4c9 + d4dc67a commit c4485bc

File tree

19 files changed

+3290
-380
lines changed

19 files changed

+3290
-380
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3440,6 +3440,7 @@ namespace ts {
34403440
case SyntaxKind.TupleType:
34413441
case SyntaxKind.UnionType:
34423442
case SyntaxKind.IntersectionType:
3443+
case SyntaxKind.ConditionalType:
34433444
case SyntaxKind.ParenthesizedType:
34443445
case SyntaxKind.InterfaceDeclaration:
34453446
case SyntaxKind.TypeAliasDeclaration:

src/compiler/checker.ts

Lines changed: 257 additions & 62 deletions
Large diffs are not rendered by default.

src/compiler/core.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -439,23 +439,21 @@ namespace ts {
439439
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[];
440440
export function sameMap<T>(array: ReadonlyArray<T>, f: (x: T, i: number) => T): ReadonlyArray<T>;
441441
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[] {
442-
let result: T[];
443442
if (array) {
444443
for (let i = 0; i < array.length; i++) {
445-
if (result) {
446-
result.push(f(array[i], i));
447-
}
448-
else {
449-
const item = array[i];
450-
const mapped = f(item, i);
451-
if (item !== mapped) {
452-
result = array.slice(0, i);
453-
result.push(mapped);
444+
const item = array[i];
445+
const mapped = f(item, i);
446+
if (item !== mapped) {
447+
const result = array.slice(0, i);
448+
result.push(mapped);
449+
for (i++; i < array.length; i++) {
450+
result.push(f(array[i], i));
454451
}
452+
return result;
455453
}
456454
}
457455
}
458-
return result || array;
456+
return array;
459457
}
460458

461459
/**

src/compiler/declarationEmitter.ts

Lines changed: 12 additions & 0 deletions
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,6 +547,16 @@ namespace ts {
545547
emitSeparatedList(type.types, " & ", emitType);
546548
}
547549

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+
548560
function emitParenType(type: ParenthesizedTypeNode) {
549561
write("(");
550562
emitType(type.type);

src/compiler/emitter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ namespace ts {
600600
return emitUnionType(<UnionTypeNode>node);
601601
case SyntaxKind.IntersectionType:
602602
return emitIntersectionType(<IntersectionTypeNode>node);
603+
case SyntaxKind.ConditionalType:
604+
return emitConditionalType(<ConditionalTypeNode>node);
603605
case SyntaxKind.ParenthesizedType:
604606
return emitParenthesizedType(<ParenthesizedTypeNode>node);
605607
case SyntaxKind.ExpressionWithTypeArguments:
@@ -1190,6 +1192,16 @@ namespace ts {
11901192
emitList(node, node.types, ListFormat.IntersectionTypeConstituents);
11911193
}
11921194

1195+
function emitConditionalType(node: ConditionalTypeNode) {
1196+
emit(node.checkType);
1197+
write(" extends ");
1198+
emit(node.extendsType);
1199+
write(" ? ");
1200+
emit(node.trueType);
1201+
write(" : ");
1202+
emit(node.falseType);
1203+
}
1204+
11931205
function emitParenthesizedType(node: ParenthesizedTypeNode) {
11941206
writePunctuation("(");
11951207
emit(node.type);

src/compiler/factory.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,24 @@ namespace ts {
731731
: node;
732732
}
733733

734+
export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
735+
const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode;
736+
node.checkType = parenthesizeConditionalTypeMember(checkType);
737+
node.extendsType = parenthesizeConditionalTypeMember(extendsType);
738+
node.trueType = trueType;
739+
node.falseType = falseType;
740+
return node;
741+
}
742+
743+
export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
744+
return node.checkType !== checkType
745+
|| node.extendsType !== extendsType
746+
|| node.trueType !== trueType
747+
|| node.falseType !== falseType
748+
? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node)
749+
: node;
750+
}
751+
734752
export function createParenthesizedType(type: TypeNode) {
735753
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
736754
node.type = type;
@@ -4094,6 +4112,10 @@ namespace ts {
40944112
return expression;
40954113
}
40964114

4115+
export function parenthesizeConditionalTypeMember(member: TypeNode) {
4116+
return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member;
4117+
}
4118+
40974119
export function parenthesizeElementTypeMember(member: TypeNode) {
40984120
switch (member.kind) {
40994121
case SyntaxKind.UnionType:
@@ -4102,7 +4124,7 @@ namespace ts {
41024124
case SyntaxKind.ConstructorType:
41034125
return createParenthesizedType(member);
41044126
}
4105-
return member;
4127+
return parenthesizeConditionalTypeMember(member);
41064128
}
41074129

41084130
export function parenthesizeArrayTypeMember(member: TypeNode) {

src/compiler/parser.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ namespace ts {
175175
case SyntaxKind.UnionType:
176176
case SyntaxKind.IntersectionType:
177177
return visitNodes(cbNode, cbNodes, (<UnionOrIntersectionTypeNode>node).types);
178+
case SyntaxKind.ConditionalType:
179+
return visitNode(cbNode, (<ConditionalTypeNode>node).checkType) ||
180+
visitNode(cbNode, (<ConditionalTypeNode>node).extendsType) ||
181+
visitNode(cbNode, (<ConditionalTypeNode>node).trueType) ||
182+
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
178183
case SyntaxKind.ParenthesizedType:
179184
case SyntaxKind.TypeOperator:
180185
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
@@ -1494,6 +1499,11 @@ namespace ts {
14941499
return isStartOfExpression();
14951500
}
14961501

1502+
function nextTokenIsStartOfType() {
1503+
nextToken();
1504+
return isStartOfType();
1505+
}
1506+
14971507
// True if positioned at a list terminator
14981508
function isListTerminator(kind: ParsingContext): boolean {
14991509
if (token() === SyntaxKind.EndOfFileToken) {
@@ -2789,6 +2799,10 @@ namespace ts {
27892799
type = createJSDocPostfixType(SyntaxKind.JSDocNonNullableType, type);
27902800
break;
27912801
case SyntaxKind.QuestionToken:
2802+
// If not in JSDoc and next token is start of a type we have a conditional type
2803+
if (!(contextFlags & NodeFlags.JSDoc) && lookAhead(nextTokenIsStartOfType)) {
2804+
return type;
2805+
}
27922806
type = createJSDocPostfixType(SyntaxKind.JSDocNullableType, type);
27932807
break;
27942808
case SyntaxKind.OpenBracketToken:
@@ -2950,14 +2964,26 @@ namespace ts {
29502964
return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker);
29512965
}
29522966

2953-
function parseTypeWorker(): TypeNode {
2967+
function parseTypeWorker(noConditionalTypes?: boolean): TypeNode {
29542968
if (isStartOfFunctionType()) {
29552969
return parseFunctionOrConstructorType(SyntaxKind.FunctionType);
29562970
}
29572971
if (token() === SyntaxKind.NewKeyword) {
29582972
return parseFunctionOrConstructorType(SyntaxKind.ConstructorType);
29592973
}
2960-
return parseUnionTypeOrHigher();
2974+
const type = parseUnionTypeOrHigher();
2975+
if (!noConditionalTypes && parseOptional(SyntaxKind.ExtendsKeyword)) {
2976+
const node = <ConditionalTypeNode>createNode(SyntaxKind.ConditionalType, type.pos);
2977+
node.checkType = type;
2978+
// The type following 'extends' is not permitted to be another conditional type
2979+
node.extendsType = parseTypeWorker(/*noConditionalTypes*/ true);
2980+
parseExpected(SyntaxKind.QuestionToken);
2981+
node.trueType = parseTypeWorker();
2982+
parseExpected(SyntaxKind.ColonToken);
2983+
node.falseType = parseTypeWorker();
2984+
return finishNode(node);
2985+
}
2986+
return type;
29612987
}
29622988

29632989
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: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ namespace ts {
265265
TupleType,
266266
UnionType,
267267
IntersectionType,
268+
ConditionalType,
268269
ParenthesizedType,
269270
ThisType,
270271
TypeOperator,
@@ -1116,6 +1117,14 @@ namespace ts {
11161117
types: NodeArray<TypeNode>;
11171118
}
11181119

1120+
export interface ConditionalTypeNode extends TypeNode {
1121+
kind: SyntaxKind.ConditionalType;
1122+
checkType: TypeNode;
1123+
extendsType: TypeNode;
1124+
trueType: TypeNode;
1125+
falseType: TypeNode;
1126+
}
1127+
11191128
export interface ParenthesizedTypeNode extends TypeNode {
11201129
kind: SyntaxKind.ParenthesizedType;
11211130
type: TypeNode;
@@ -3484,18 +3493,19 @@ namespace ts {
34843493
Intersection = 1 << 18, // Intersection (T & U)
34853494
Index = 1 << 19, // keyof T
34863495
IndexedAccess = 1 << 20, // T[K]
3496+
Conditional = 1 << 21, // T extends U ? X : Y
3497+
Substitution = 1 << 22, // Type parameter substitution
34873498
/* @internal */
3488-
FreshLiteral = 1 << 21, // Fresh literal or unique type
3499+
FreshLiteral = 1 << 23, // Fresh literal or unique type
34893500
/* @internal */
3490-
ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type
3501+
ContainsWideningType = 1 << 24, // Type is or contains undefined or null widening type
34913502
/* @internal */
3492-
ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type
3503+
ContainsObjectLiteral = 1 << 25, // Type is or contains object literal type
34933504
/* @internal */
3494-
ContainsAnyFunctionType = 1 << 24, // Type is or contains the anyFunctionType
3495-
NonPrimitive = 1 << 25, // intrinsic object type
3505+
ContainsAnyFunctionType = 1 << 26, // Type is or contains the anyFunctionType
3506+
NonPrimitive = 1 << 27, // intrinsic object type
34963507
/* @internal */
3497-
JsxAttributes = 1 << 26, // Jsx attributes type
3498-
MarkerType = 1 << 27, // Marker type used for variance probing
3508+
GenericMappedType = 1 << 29, // Flag used by maybeTypeOfKind
34993509

35003510
/* @internal */
35013511
Nullable = Undefined | Null,
@@ -3518,17 +3528,21 @@ namespace ts {
35183528
ESSymbolLike = ESSymbol | UniqueESSymbol,
35193529
UnionOrIntersection = Union | Intersection,
35203530
StructuredType = Object | Union | Intersection,
3521-
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess,
35223531
TypeVariable = TypeParameter | IndexedAccess,
3532+
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
3533+
InstantiablePrimitive = Index,
3534+
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
3535+
StructuredOrInstantiable = StructuredType | Instantiable,
35233536

35243537
// 'Narrowable' types are types where narrowing actually narrows.
35253538
// This *should* be every type other than null, undefined, void, and never
3526-
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
3539+
Narrowable = Any | StructuredOrInstantiable | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
35273540
NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive,
35283541
/* @internal */
35293542
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
35303543
/* @internal */
35313544
PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType,
3545+
/* @internal */
35323546
}
35333547

35343548
export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
@@ -3587,7 +3601,9 @@ namespace ts {
35873601
EvolvingArray = 1 << 8, // Evolving array type
35883602
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
35893603
ContainsSpread = 1 << 10, // Object literal contains spread operation
3590-
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type
3604+
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type
3605+
JsxAttributes = 1 << 12, // Jsx attributes type
3606+
MarkerType = 1 << 13, // Marker type used for variance probing
35913607
ClassOrInterface = Class | Interface
35923608
}
35933609

@@ -3741,15 +3757,15 @@ namespace ts {
37413757
syntheticType?: Type;
37423758
}
37433759

3744-
export interface TypeVariable extends Type {
3760+
export interface InstantiableType extends Type {
37453761
/* @internal */
37463762
resolvedBaseConstraint?: Type;
37473763
/* @internal */
37483764
resolvedIndexType?: IndexType;
37493765
}
37503766

37513767
// Type parameters (TypeFlags.TypeParameter)
3752-
export interface TypeParameter extends TypeVariable {
3768+
export interface TypeParameter extends InstantiableType {
37533769
/** Retrieve using getConstraintFromTypeParameter */
37543770
/* @internal */
37553771
constraint?: Type; // Constraint
@@ -3767,15 +3783,38 @@ namespace ts {
37673783

37683784
// Indexed access types (TypeFlags.IndexedAccess)
37693785
// Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable
3770-
export interface IndexedAccessType extends TypeVariable {
3786+
export interface IndexedAccessType extends InstantiableType {
37713787
objectType: Type;
37723788
indexType: Type;
37733789
constraint?: Type;
37743790
}
37753791

37763792
// keyof T types (TypeFlags.Index)
3777-
export interface IndexType extends Type {
3778-
type: TypeVariable | UnionOrIntersectionType;
3793+
export interface IndexType extends InstantiableType {
3794+
type: InstantiableType | UnionOrIntersectionType;
3795+
}
3796+
3797+
// T extends U ? X : Y (TypeFlags.Conditional)
3798+
export interface ConditionalType extends InstantiableType {
3799+
checkType: Type;
3800+
extendsType: Type;
3801+
trueType: Type;
3802+
falseType: Type;
3803+
/* @internal */
3804+
target?: ConditionalType;
3805+
/* @internal */
3806+
mapper?: TypeMapper;
3807+
}
3808+
3809+
// Type parameter substitution (TypeFlags.Substitution)
3810+
// Substitution types are created for type parameter references that occur in the true branch
3811+
// of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the reference to
3812+
// T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T. Thus, if
3813+
// Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution types
3814+
// disappear upon instantiation (just like type parameters).
3815+
export interface SubstitutionType extends InstantiableType {
3816+
typeParameter: TypeParameter; // Target type parameter
3817+
substitute: Type; // Type to substitute for type parameter
37793818
}
37803819

37813820
export const enum SignatureKind {

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4621,6 +4621,10 @@ namespace ts {
46214621
return node.kind === SyntaxKind.IntersectionType;
46224622
}
46234623

4624+
export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode {
4625+
return node.kind === SyntaxKind.ConditionalType;
4626+
}
4627+
46244628
export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
46254629
return node.kind === SyntaxKind.ParenthesizedType;
46264630
}

0 commit comments

Comments
 (0)