Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 13.7k
Conditional types#21316
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
Uh oh!
There was an error while loading. Please reload this page.
Conditional types #21316
Changes from all commits
57ca768063eed1ec2bdfd43e195d61225cc9f74a7a20434faddc631c000f12127b945bf59e2e614590f1100e4f6c5fd2f1341c3973f4911fabc8110bb23bb2c10a55253b15725094f76925da86e8d174015baf0e9598acde96ec8cd52fa714ec6fddfd0dd6ec360c240e732405204fd5eb314d0cdd50d4fc7d1c3f19959ab8692904c7ec3cb42c6b18e337b54f2b5f3f990e4e01516c8d4dc67aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| 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)) { | ||
Contributor 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 MemberAuthor 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. Member 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. Member 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. Member 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? Contributor 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 { | ||
Member 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 MemberAuthor 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 Member 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; | ||
| ||
| extendsType: TypeNode; | ||
| trueType: TypeNode; | ||
| falseType: TypeNode; | ||
| } | ||
| export interface ParenthesizedTypeNode extends TypeNode { | ||
| kind: SyntaxKind.ParenthesizedType; | ||
| type: TypeNode; | ||
| @@ -3484,18 +3493,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 | ||
Member 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? MemberAuthor 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, | ||
| @@ -3518,17 +3528,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 */ | ||
Member 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; | ||
| @@ -3587,7 +3601,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 | ||
| } | ||
| @@ -3741,15 +3757,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 | ||
| @@ -3767,15 +3783,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 | ||
Member 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 typeElementType<T>=Textendsany[] ? 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 | ||
| } | ||
| export const enum SignatureKind { | ||
Uh oh!
There was an error while loading. Please reload this page.
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.