-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conditional types #21316
Conditional types #21316
Changes from 38 commits
57ca768
063eed1
ec2bdfd
43e195d
61225cc
9f74a7a
20434fa
ddc631c
000f121
27b945b
f59e2e6
14590f1
100e4f6
c5fd2f1
341c397
3f4911f
abc8110
bb23bb2
c10a552
53b1572
5094f76
925da86
e8d1740
15baf0e
9598acd
e96ec8c
d52fa71
4ec6fdd
fd0dd6e
c360c24
0e73240
5204fd5
eb314d0
cdd50d4
fc7d1c3
f19959a
b869290
4c7ec3c
b42c6b1
8e337b5
4f2b5f3
f990e4e
01516c8
d4dc67a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -175,6 +175,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); | ||
|
@@ -1494,6 +1499,11 @@ namespace ts { | |
return isStartOfExpression(); | ||
} | ||
|
||
function nextTokenIsStartOfType() { | ||
nextToken(); | ||
return isStartOfType(); | ||
} | ||
|
||
// True if positioned at a list terminator | ||
function isListTerminator(kind: ParsingContext): boolean { | ||
if (token() === SyntaxKind.EndOfFileToken) { | ||
|
@@ -2789,6 +2799,10 @@ namespace ts { | |
type = createJSDocPostfixType(SyntaxKind.JSDocNonNullableType, type); | ||
break; | ||
case SyntaxKind.QuestionToken: | ||
// If not in JSDoc and next token is start of a type we have a conditional type | ||
if (!(contextFlags & NodeFlags.JSDoc) && lookAhead(nextTokenIsStartOfType)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to support conditional types in JSDoc for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might consider that, but I'm not sure to what extent we want to permit JSDoc that is only understood type TypeScript. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already support mapped and index types in jsdoc, right? Unless there's parse ambiguity, we should probably just continue exposing all type syntaxes in jsdoc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, we want to support all Typescript types in JSDoc. The ideal is for eager Typescript users to be able to put Typescript types into jsdoc if they are stuck with vanilla javascript for some reason. However, I think there is a conflict between the ?-suffix of jsdoc and the ? of the conditional. For example, I could be wrong, though! It’s really difficult to guess how the parser will behave without testing it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can always make the jsdoc postfix ? have a no-whitespace requirement and make the conditional ? have required whitespace in jsdoc to disambiguate, should it be ambiguous, yeah? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or we can do speculative parsing to see whether we can parse it as a ConditionalType (e.g. try to parse a true branch) and then fall back to postfix- |
||
return type; | ||
} | ||
type = createJSDocPostfixType(SyntaxKind.JSDocNullableType, type); | ||
break; | ||
case SyntaxKind.OpenBracketToken: | ||
|
@@ -2950,14 +2964,26 @@ namespace ts { | |
return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker); | ||
} | ||
|
||
function parseTypeWorker(): TypeNode { | ||
function parseTypeWorker(noConditionalTypes?: boolean): TypeNode { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A better name is allowConditionalTypes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would force us to create yet another function that calls There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I didn't know there was a parameterless usage of |
||
if (isStartOfFunctionType()) { | ||
return parseFunctionOrConstructorType(SyntaxKind.FunctionType); | ||
} | ||
if (token() === SyntaxKind.NewKeyword) { | ||
return parseFunctionOrConstructorType(SyntaxKind.ConstructorType); | ||
} | ||
return parseUnionTypeOrHigher(); | ||
const type = parseUnionTypeOrHigher(); | ||
if (!noConditionalTypes && parseOptional(SyntaxKind.ExtendsKeyword)) { | ||
const node = <ConditionalTypeNode>createNode(SyntaxKind.ConditionalType, type.pos); | ||
node.checkType = type; | ||
// The type following 'extends' is not permitted to be another conditional type | ||
node.extendsType = parseTypeWorker(/*noConditionalTypes*/ true); | ||
parseExpected(SyntaxKind.QuestionToken); | ||
node.trueType = parseTypeWorker(); | ||
parseExpected(SyntaxKind.ColonToken); | ||
node.falseType = parseTypeWorker(); | ||
return finishNode(node); | ||
} | ||
return type; | ||
} | ||
|
||
function parseTypeAnnotation(): TypeNode { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -265,6 +265,7 @@ namespace ts { | |
TupleType, | ||
UnionType, | ||
IntersectionType, | ||
ConditionalType, | ||
ParenthesizedType, | ||
ThisType, | ||
TypeOperator, | ||
|
@@ -1116,6 +1117,14 @@ namespace ts { | |
types: NodeArray<TypeNode>; | ||
} | ||
|
||
export interface ConditionalTypeNode extends TypeNode { | ||
kind: SyntaxKind.ConditionalType; | ||
checkType: TypeNode; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still concerned that baking the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's hard to speculate about what we might do here (i.e. nothing vs. additional conditions vs. discrete conditional operators), so I'm not keen on introducing more abstraction. |
||
extendsType: TypeNode; | ||
trueType: TypeNode; | ||
falseType: TypeNode; | ||
} | ||
|
||
export interface ParenthesizedTypeNode extends TypeNode { | ||
kind: SyntaxKind.ParenthesizedType; | ||
type: TypeNode; | ||
|
@@ -3478,18 +3487,19 @@ namespace ts { | |
Intersection = 1 << 18, // Intersection (T & U) | ||
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 << 21, // Fresh literal or unique type | ||
FreshLiteral = 1 << 23, // Fresh literal or unique type | ||
/* @internal */ | ||
ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type | ||
ContainsWideningType = 1 << 24, // Type is or contains undefined or null widening type | ||
/* @internal */ | ||
ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type | ||
ContainsObjectLiteral = 1 << 25, // 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 << 26, // Type is or contains the anyFunctionType | ||
NonPrimitive = 1 << 27, // intrinsic object type | ||
/* @internal */ | ||
JsxAttributes = 1 << 26, // Jsx attributes type | ||
MarkerType = 1 << 27, // Marker type used for variance probing | ||
GenericMappedType = 1 << 29, // Flag used by maybeTypeOfKind | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not 28 here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hate 28. |
||
|
||
/* @internal */ | ||
Nullable = Undefined | Null, | ||
|
@@ -3512,17 +3522,21 @@ namespace ts { | |
ESSymbolLike = ESSymbol | UniqueESSymbol, | ||
UnionOrIntersection = Union | Intersection, | ||
StructuredType = Object | Union | Intersection, | ||
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess, | ||
TypeVariable = TypeParameter | IndexedAccess, | ||
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution, | ||
InstantiablePrimitive = Index, | ||
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive, | ||
StructuredOrInstantiable = StructuredType | Instantiable, | ||
|
||
// '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 | StructuredOrInstantiable | StringLike | NumberLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, | ||
NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive, | ||
/* @internal */ | ||
RequiresWidening = ContainsWideningType | ContainsObjectLiteral, | ||
/* @internal */ | ||
PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType, | ||
/* @internal */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is not needed |
||
} | ||
|
||
export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; | ||
|
@@ -3581,7 +3595,9 @@ namespace ts { | |
EvolvingArray = 1 << 8, // Evolving array type | ||
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties | ||
ContainsSpread = 1 << 10, // Object literal contains spread operation | ||
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type | ||
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type | ||
JsxAttributes = 1 << 12, // Jsx attributes type | ||
MarkerType = 1 << 13, // Marker type used for variance probing | ||
ClassOrInterface = Class | Interface | ||
} | ||
|
||
|
@@ -3735,15 +3751,15 @@ namespace ts { | |
syntheticType?: Type; | ||
} | ||
|
||
export interface TypeVariable extends Type { | ||
export interface InstantiableType extends Type { | ||
/* @internal */ | ||
resolvedBaseConstraint?: Type; | ||
/* @internal */ | ||
resolvedIndexType?: IndexType; | ||
} | ||
|
||
// Type parameters (TypeFlags.TypeParameter) | ||
export interface TypeParameter extends TypeVariable { | ||
export interface TypeParameter extends InstantiableType { | ||
/** Retrieve using getConstraintFromTypeParameter */ | ||
/* @internal */ | ||
constraint?: Type; // Constraint | ||
|
@@ -3761,15 +3777,38 @@ namespace ts { | |
|
||
// Indexed access types (TypeFlags.IndexedAccess) | ||
// Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable | ||
export interface IndexedAccessType extends TypeVariable { | ||
export interface IndexedAccessType extends InstantiableType { | ||
objectType: Type; | ||
indexType: Type; | ||
constraint?: Type; | ||
} | ||
|
||
// keyof T types (TypeFlags.Index) | ||
export interface IndexType extends Type { | ||
type: TypeVariable | UnionOrIntersectionType; | ||
export interface IndexType extends InstantiableType { | ||
type: InstantiableType | UnionOrIntersectionType; | ||
} | ||
|
||
// T extends U ? X : Y (TypeFlags.Conditional) | ||
export interface ConditionalType extends InstantiableType { | ||
checkType: Type; | ||
extendsType: Type; | ||
trueType: Type; | ||
falseType: Type; | ||
/* @internal */ | ||
target?: ConditionalType; | ||
/* @internal */ | ||
mapper?: TypeMapper; | ||
} | ||
|
||
// Type parameter substitution (TypeFlags.Substitution) | ||
// Substitution types are created for type parameter references that occur in the true branch | ||
// of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the reference to | ||
// T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T. Thus, if | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the problems that we discussed a few weeks back was that for something like type ElementType<T> = T extends any[] ? T[number]: never
I don't think that's obvious from just looking at this (you have to jump to // Substitution types are created for type parameter references that occur in the true branch
// of a conditional type, but are *only* used for validation against type parameters and type operators. etc. |
||
// Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution types | ||
// disappear upon instantiation (just like type parameters). | ||
export interface SubstitutionType extends InstantiableType { | ||
typeParameter: TypeParameter; // Target type parameter | ||
substitute: Type; // Type to substitute for type parameter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be clear on whether the |
||
} | ||
|
||
export const enum SignatureKind { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces should be written using
writeSpace()
, keywords usingwriteKeyword("extends")
, and punctuation usingwritePunctuation("?")
to support the symbol display builder integration with the emitter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything else is written this way in emitter.ts. Are you saying it all needs to change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, I was looking at the old declaration emitter. I will fix this.