diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a7e94da09d9..b5b3bdd79ae 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3367,6 +3367,10 @@ namespace ts { case SyntaxKind.TypeOperator: case SyntaxKind.IndexedAccessType: case SyntaxKind.MappedType: + case SyntaxKind.MatchType: + case SyntaxKind.MatchTypeBlock: + case SyntaxKind.MatchTypeMatchClause: + case SyntaxKind.MatchTypeElseClause: case SyntaxKind.LiteralType: case SyntaxKind.NamespaceExportDeclaration: // Types and signatures are TypeScript syntax, and exclude all other facts. diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 269666d7b94..d8c0b99b708 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2534,6 +2534,17 @@ namespace ts { const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } + if (type.flags & TypeFlags.Match) { + const typeArgument = typeToTypeNodeHelper((type).typeArgument, context); + const clauses: MatchTypeMatchOrElseClause[] = map((type).clauses, clause => { + const matchType = typeToTypeNodeHelper(clause.matchType, context); + const resultType = typeToTypeNodeHelper(clause.resultType, context); + return createMatchTypeMatchClause(matchType, resultType); + }); + const elseType = (type).elseType; + if (elseType) clauses.push(createMatchTypeElseClause(typeToTypeNodeHelper(elseType, context))); + return createMatchTypeNode(typeArgument, createMatchTypeBlock(clauses)); + } Debug.fail("Should be unreachable."); @@ -3303,6 +3314,9 @@ namespace ts { writeType((type).indexType, TypeFormatFlags.None); writePunctuation(writer, SyntaxKind.CloseBracketToken); } + else if (type.flags & TypeFlags.Match) { + writeMatchType(type); + } else { // Should never get here // { ... } @@ -3314,6 +3328,35 @@ namespace ts { } } + function writeMatchType(type: MatchType) { + writeType((type).typeArgument, TypeFormatFlags.None); + writeSpace(writer); + writer.writeKeyword("match"); + writeSpace(writer); + writePunctuation(writer, SyntaxKind.OpenBracketToken); + writer.writeLine(); + writer.increaseIndent(); + for (const clause of (type).clauses) { + writeType(clause.matchType, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(clause.resultType, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.CommaToken); + writer.writeLine(); + } + const elseType = (type).elseType; + if (elseType) { + writer.writeKeyword("else"); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(elseType, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.CommaToken); + writer.writeLine(); + } + writer.decreaseIndent(); + writer.writeLine(); + writePunctuation(writer, SyntaxKind.CloseBracketToken); + } function writeTypeList(types: Type[], delimiter: SyntaxKind) { for (let i = 0; i < types.length; i++) { @@ -7730,6 +7773,65 @@ namespace ts { return links.resolvedType; } + function isGenericMatchTypeClause(clause: MatchTypeClause): boolean { + return isGenericObjectType(clause.matchType); + } + + function getMatchTypeConstituents(type: MatchType) { + if (type.clauses.length === 0) return type.elseType || neverType; + const constituents: Type[] = []; + for (const clause of type.clauses) { + constituents.push(clause.resultType); + } + if (type.elseType) { + constituents.push(type.elseType); + } + return getUnionType(constituents); + } + + function getMatchType(typeArgument: Type, clauses: ReadonlyArray, elseType: Type | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]) { + if (clauses.length > 0 && (isGenericObjectType(typeArgument) || forEach(clauses, isGenericMatchTypeClause))) { + if (clauses.length > 0) { + const type = createType(TypeFlags.Match); + type.typeArgument = typeArgument; + type.clauses = clauses; + type.elseType = elseType; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + return type; + } + } + + for (const clause of clauses) { + if (isTypeAssignableTo(typeArgument, clause.matchType)) { + return clause.resultType; + } + } + + return elseType || neverType; + } + + function getTypeFromMatchTypeNode(node: MatchTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const typeArgument = getTypeFromTypeNode(node.typeArgument); + const clauses: MatchTypeClause[] = []; + let elseType: Type | undefined; + for (const clause of node.matchBlock.clauses) { + if (clause.kind === SyntaxKind.MatchTypeElseClause) { + if (!elseType) elseType = getTypeFromTypeNode(clause.resultType); + } + else { + const matchType = getTypeFromTypeNode(clause.matchType); + const resultType = getTypeFromTypeNode(clause.resultType); + clauses.push({ matchType, resultType }); + } + } + links.resolvedType = getMatchType(typeArgument, clauses, elseType, getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node)); + } + return links.resolvedType; + } + function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -7992,6 +8094,8 @@ namespace ts { return getTypeFromIndexedAccessTypeNode(node); case SyntaxKind.MappedType: return getTypeFromMappedTypeNode(node); + case SyntaxKind.MatchType: + return getTypeFromMatchTypeNode(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: @@ -8005,6 +8109,8 @@ namespace ts { } } + function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[]; + function instantiateList(items: ReadonlyArray, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): ReadonlyArray; function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; @@ -8356,9 +8462,23 @@ namespace ts { if (type.flags & TypeFlags.IndexedAccess) { return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); } + if (type.flags & TypeFlags.Match) { + return getMatchType( + instantiateType((type).typeArgument, mapper), + instantiateList((type).clauses, mapper, instantiateMatchTypeClause), + instantiateType((type).elseType, mapper), + type.aliasSymbol, + instantiateTypes(type.aliasTypeArguments, mapper)); + } return type; } + function instantiateMatchTypeClause(clause: MatchTypeClause, mapper: TypeMapper) { + const matchType = instantiateType(clause.matchType, mapper); + const resultType = instantiateType(clause.resultType, mapper); + return { matchType, resultType }; + } + function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper): IndexInfo { return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration); } @@ -9362,6 +9482,14 @@ namespace ts { } } else { + if (source.flags & TypeFlags.Match) { + if (target.flags & TypeFlags.Union) { + if (result = isRelatedTo(getMatchTypeConstituents(source), target, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // We have type references to same target type, see if relationship holds for all type arguments if (result = typeArgumentsRelatedTo(source, target, reportErrors)) { @@ -18879,6 +19007,34 @@ namespace ts { checkTypeAssignableTo(constraintType, stringType, node.typeParameter.constraint); } + function checkMatchType(node: MatchTypeNode) { + checkSourceElement(node.typeArgument); + + let firstElseClause: MatchTypeElseClause; + let hasDuplicateElseClause = false; + for (const clause of node.matchBlock.clauses) { + if (clause.kind === SyntaxKind.MatchTypeElseClause) { + if (hasDuplicateElseClause) continue; + if (!firstElseClause) { + firstElseClause = clause; + } + else { + const sourceFile = getSourceFileOfNode(node); + const start = skipTrivia(sourceFile.text, clause.pos); + const end = clause.resultType ? clause.resultType.pos : clause.end; + grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.An_else_clause_cannot_appear_more_than_once_in_a_match_type); + hasDuplicateElseClause = true; + } + } + else { + checkSourceElement(clause.matchType); + } + + checkSourceElement(clause.resultType); + } + } + + function isPrivateWithinAmbient(node: Node): boolean { return hasModifier(node, ModifierFlags.Private) && isInAmbientContext(node); } @@ -22412,6 +22568,8 @@ namespace ts { return checkIndexedAccessType(node); case SyntaxKind.MappedType: return checkMappedType(node); + case SyntaxKind.MatchType: + return checkMatchType(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 3de20915029..2743af85652 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -447,6 +447,8 @@ namespace ts { return emitIndexedAccessType(type); case SyntaxKind.MappedType: return emitMappedType(type); + case SyntaxKind.MatchType: + return emitMatchType(type); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: return emitSignatureDeclarationWithJsDocComments(type); @@ -579,6 +581,18 @@ namespace ts { enclosingDeclaration = prevEnclosingDeclaration; } + function emitMatchType(node: MatchTypeNode) { + emitType(node.typeArgument); + write(" match {"); + if (node.matchBlock.clauses.length) { + increaseIndent(); + emitCommaList(node.matchBlock.clauses, emitNode); + writeLine(); + decreaseIndent(); + } + write("}"); + } + function emitTypeLiteral(type: TypeLiteralNode) { write("{"); if (type.members.length) { @@ -1808,6 +1822,19 @@ namespace ts { } } + function emitMatchTypeMatchClause(node: MatchTypeMatchClause) { + writeLine(); + emitType(node.matchType); + write(": "); + emitType(node.resultType); + } + + function emitMatchTypeElseClause(node: MatchTypeElseClause) { + writeLine(); + write("else: "); + emitType(node.resultType); + } + function emitNode(node: Node) { switch (node.kind) { case SyntaxKind.FunctionDeclaration: @@ -1841,6 +1868,10 @@ namespace ts { return emitPropertyDeclaration(node); case SyntaxKind.EnumMember: return emitEnumMemberDeclaration(node); + case SyntaxKind.MatchTypeMatchClause: + return emitMatchTypeMatchClause(node); + case SyntaxKind.MatchTypeElseClause: + return emitMatchTypeElseClause(node); case SyntaxKind.ExportAssignment: return emitExportAssignment(node); case SyntaxKind.SourceFile: diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9a3492e5c37..e84d9ce1387 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -907,6 +907,14 @@ "category": "Error", "code": 1328 }, + "Type or 'else' expected.": { + "category": "Error", + "code": 1329 + }, + "An 'else' clause cannot appear more than once in a 'match' type.": { + "category": "Error", + "code": 1330 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5444c618353..1c01f1aab93 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -557,6 +557,15 @@ namespace ts { return emitIndexedAccessType(node); case SyntaxKind.MappedType: return emitMappedType(node); + case SyntaxKind.MatchType: + return emitMatchType(node); + case SyntaxKind.MatchTypeBlock: + return emitMatchTypeBlock(node); + case SyntaxKind.MatchTypeMatchClause: + return emitMatchTypeMatchClause(node); + case SyntaxKind.MatchTypeElseClause: + return emitMatchTypeElseClause(node); + case SyntaxKind.LiteralType: return emitLiteralType(node); @@ -1114,6 +1123,30 @@ namespace ts { write("}"); } + function emitMatchType(node: MatchTypeNode) { + emit(node.typeArgument); + writeToken(SyntaxKind.MatchKeyword, node.typeArgument.end); + write(" "); + emit(node.matchBlock); + } + + function emitMatchTypeBlock(node: MatchTypeBlock) { + writeToken(SyntaxKind.OpenBraceToken, node.pos); + emitList(node, node.clauses, ListFormat.MatchTypeBlockClauses); + writeToken(SyntaxKind.CloseBraceToken, node.clauses.end); + } + + function emitMatchTypeMatchClause(node: MatchTypeMatchClause) { + emit(node.matchType); + write(": "); + emit(node.resultType); + } + + function emitMatchTypeElseClause(node: MatchTypeElseClause) { + write("else: "); + emit(node.resultType); + } + function emitLiteralType(node: LiteralTypeNode) { emitExpression(node.literal); } @@ -3134,6 +3167,7 @@ namespace ts { InterfaceMembers = Indented | MultiLine, EnumMembers = CommaDelimited | Indented | MultiLine, CaseBlockClauses = Indented | MultiLine, + MatchTypeBlockClauses = Indented | MultiLine | CommaDelimited | AllowTrailingComma, NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces, JsxElementChildren = SingleLine | NoInterveningComments, JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 4492c3ed474..a74ca8d5f92 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -773,6 +773,58 @@ namespace ts { : node; } + export function createMatchTypeNode(typeArgument: TypeNode, matchBlock: MatchTypeBlock) { + const node = createSynthesizedNode(SyntaxKind.MatchType) as MatchTypeNode; + node.typeArgument = typeArgument; + node.matchBlock = matchBlock; + return node; + } + + export function updateMatchTypeNode(node: MatchTypeNode, typeArgument: TypeNode, matchBlock: MatchTypeBlock) { + return node.typeArgument !== typeArgument + || node.matchBlock !== matchBlock + ? updateNode(createMatchTypeNode(typeArgument, matchBlock), node) + : node; + } + + export function createMatchTypeBlock(clauses: ReadonlyArray) { + const node = createSynthesizedNode(SyntaxKind.MatchTypeBlock) as MatchTypeBlock; + node.clauses = createNodeArray(clauses); + return node; + } + + export function updateMatchTypeBlock(node: MatchTypeBlock, clauses: ReadonlyArray) { + return node.clauses !== clauses + ? updateNode(createMatchTypeBlock(clauses), node) + : node; + } + + export function createMatchTypeMatchClause(matchType: TypeNode, resultType: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.MatchTypeMatchClause) as MatchTypeMatchClause; + node.matchType = matchType; + node.resultType = resultType; + return node; + } + + export function updateMatchTypeMatchClause(node: MatchTypeMatchClause, matchType: TypeNode, resultType: TypeNode) { + return node.matchType !== matchType + || node.resultType !== resultType + ? updateNode(createMatchTypeMatchClause(matchType, resultType), node) + : node; + } + + export function createMatchTypeElseClause(resultType: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.MatchTypeElseClause) as MatchTypeElseClause; + node.resultType = resultType; + return node; + } + + export function updateMatchTypeElseClause(node: MatchTypeElseClause, resultType: TypeNode) { + return node.resultType !== resultType + ? updateNode(createMatchTypeElseClause(resultType), node) + : node; + } + export function createLiteralTypeNode(literal: Expression) { const node = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; node.literal = literal; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 71c7d3aac49..67ec31db07f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -155,6 +155,16 @@ namespace ts { visitNode(cbNode, (node).typeParameter) || visitNode(cbNode, (node).questionToken) || visitNode(cbNode, (node).type); + case SyntaxKind.MatchType: + return visitNode(cbNode, (node).typeArgument) || + visitNode(cbNode, (node).matchBlock); + case SyntaxKind.MatchTypeBlock: + return visitNodes(cbNode, cbNodes, (node).clauses); + case SyntaxKind.MatchTypeMatchClause: + return visitNode(cbNode, (node).matchType) || + visitNode(cbNode, (node).resultType); + case SyntaxKind.MatchTypeElseClause: + return visitNode(cbNode, (node).resultType); case SyntaxKind.LiteralType: return visitNode(cbNode, (node).literal); case SyntaxKind.ObjectBindingPattern: @@ -1332,6 +1342,8 @@ namespace ts { return !(token() === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement(); case ParsingContext.SwitchClauses: return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.MatchTypeClauses: + return token() === SyntaxKind.ElseKeyword || token() === SyntaxKind.CommaToken || isStartOfType(); case ParsingContext.TypeMembers: return lookAhead(isTypeMemberStart); case ParsingContext.ClassMembers: @@ -1446,6 +1458,7 @@ namespace ts { switch (kind) { case ParsingContext.BlockStatements: case ParsingContext.SwitchClauses: + case ParsingContext.MatchTypeClauses: case ParsingContext.TypeMembers: case ParsingContext.ClassMembers: case ParsingContext.EnumMembers: @@ -1631,6 +1644,9 @@ namespace ts { case ParsingContext.SwitchClauses: return isReusableSwitchClause(node); + case ParsingContext.MatchTypeClauses: + return isReusableMatchTypeClause(node); + case ParsingContext.SourceElements: case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: @@ -1740,6 +1756,17 @@ namespace ts { return false; } + function isReusableMatchTypeClause(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.MatchTypeMatchClause: + case SyntaxKind.MatchTypeElseClause: + return true; + } + } + return false; + } + function isReusableStatement(node: Node) { if (node) { switch (node.kind) { @@ -1848,6 +1875,7 @@ namespace ts { case ParsingContext.BlockStatements: return Diagnostics.Declaration_or_statement_expected; case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected; case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected; + case ParsingContext.MatchTypeClauses: return Diagnostics.Type_or_else_expected; case ParsingContext.RestProperties: // fallthrough case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected; case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected; @@ -2585,6 +2613,45 @@ namespace ts { return finishNode(node); } + function parseMatchTypeMatchClause() { + const node = createNode(SyntaxKind.MatchTypeMatchClause); + node.matchType = parseType(); + parseExpected(SyntaxKind.ColonToken); + node.resultType = parseType(); + return finishNode(node); + } + + function parseMatchTypeElseClause() { + const node = createNode(SyntaxKind.MatchTypeElseClause); + parseExpected(SyntaxKind.ElseKeyword); + parseExpected(SyntaxKind.ColonToken); + node.resultType = parseType(); + return finishNode(node); + } + + function parseMatchTypeCaseOrElseClause(): MatchTypeMatchOrElseClause { + return token() === SyntaxKind.ElseKeyword ? parseMatchTypeElseClause() : parseMatchTypeMatchClause(); + } + + function parseMatchTypeRest(type: TypeNode) { + while (true) { + type = parseArrayTypeRest(type); + if (!scanner.hasPrecedingLineBreak() && token() === SyntaxKind.MatchKeyword) { + const node = createNode(SyntaxKind.MatchType, type.pos); + node.typeArgument = type; + parseExpected(SyntaxKind.MatchKeyword); + const matchBlock = createNode(SyntaxKind.MatchTypeBlock); + parseExpected(SyntaxKind.OpenBraceToken); + matchBlock.clauses = parseDelimitedList(ParsingContext.MatchTypeClauses, parseMatchTypeCaseOrElseClause, /*considerSemicolonAsDelimiter*/ true); + parseExpected(SyntaxKind.CloseBraceToken); + node.matchBlock = finishNode(matchBlock); + type = finishNode(node); + continue; + } + return type; + } + } + function parseTupleType(): TupleTypeNode { const node = createNode(SyntaxKind.TupleType); node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); @@ -2768,6 +2835,10 @@ namespace ts { function parseArrayTypeOrHigher(): TypeNode { let type = parseJSDocPostfixTypeOrHigher(); + return parseMatchTypeRest(type); + } + + function parseArrayTypeRest(type: TypeNode): TypeNode { while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { if (isStartOfType()) { const node = createNode(SyntaxKind.IndexedAccessType, type.pos); @@ -6118,6 +6189,7 @@ namespace ts { BlockStatements, // Statements in block SwitchClauses, // Clauses in switch statement SwitchClauseStatements, // Statements in switch clause + MatchTypeClauses, // Clauses in a match type block TypeMembers, // Members in interface or type literal ClassMembers, // Members in class declaration EnumMembers, // Members in enum declaration diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index a130d8427da..fb2d3c5ed85 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -94,6 +94,7 @@ namespace ts { "is": SyntaxKind.IsKeyword, "keyof": SyntaxKind.KeyOfKeyword, "let": SyntaxKind.LetKeyword, + "match": SyntaxKind.MatchKeyword, "module": SyntaxKind.ModuleKeyword, "namespace": SyntaxKind.NamespaceKeyword, "never": SyntaxKind.NeverKeyword, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0d9bb8d50e8..38ae72155a1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -187,6 +187,7 @@ namespace ts { GetKeyword, IsKeyword, KeyOfKeyword, + MatchKeyword, ModuleKeyword, NamespaceKeyword, NeverKeyword, @@ -239,6 +240,10 @@ namespace ts { TypeOperator, IndexedAccessType, MappedType, + MatchType, + MatchTypeBlock, + MatchTypeMatchClause, + MatchTypeElseClause, LiteralType, // Binding patterns ObjectBindingPattern, @@ -1005,6 +1010,32 @@ namespace ts { type?: TypeNode; } + export interface MatchTypeNode extends TypeNode { + kind: SyntaxKind.MatchType; + typeArgument: TypeNode; + matchBlock: MatchTypeBlock; + } + + export interface MatchTypeBlock extends Node { + kind: SyntaxKind.MatchTypeBlock; + clauses: NodeArray; + } + + export type MatchTypeMatchOrElseClause = + | MatchTypeMatchClause + | MatchTypeElseClause; + + export interface MatchTypeMatchClause extends Node { + kind: SyntaxKind.MatchTypeMatchClause; + matchType: TypeNode; + resultType: TypeNode; + } + + export interface MatchTypeElseClause extends Node { + kind: SyntaxKind.MatchTypeElseClause; + resultType: TypeNode; + } + export interface LiteralTypeNode extends TypeNode { kind: SyntaxKind.LiteralType; literal: Expression; @@ -3156,17 +3187,18 @@ namespace ts { Intersection = 1 << 17, // Intersection (T & U) Index = 1 << 18, // keyof T IndexedAccess = 1 << 19, // T[K] + Match = 1 << 20, // T match { U: V } /* @internal */ - FreshLiteral = 1 << 20, // Fresh literal type + FreshLiteral = 1 << 21, // Fresh literal type /* @internal */ - ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 23, // Type is or contains the anyFunctionType - NonPrimitive = 1 << 24, // intrinsic object type + ContainsAnyFunctionType = 1 << 24, // Type is or contains the anyFunctionType + NonPrimitive = 1 << 25, // intrinsic object type /* @internal */ - JsxAttributes = 1 << 25, // Jsx attributes type + JsxAttributes = 1 << 26, // Jsx attributes type /* @internal */ Nullable = Undefined | Null, @@ -3185,12 +3217,12 @@ namespace ts { EnumLike = Enum | EnumLiteral, UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection, - StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess, - TypeVariable = TypeParameter | IndexedAccess, + StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess | Match, + TypeVariable = TypeParameter | IndexedAccess | Match, // '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 | NonPrimitive, + Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | Match | StringLike | NumberLike | BooleanLike | ESSymbol | NonPrimitive, NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive, /* @internal */ RequiresWidening = ContainsWideningType | ContainsObjectLiteral, @@ -3420,6 +3452,18 @@ namespace ts { type: TypeVariable | UnionOrIntersectionType; } + // T match {} types (TypeFlags.Match) + export interface MatchType extends Type { + typeArgument: Type; + clauses: ReadonlyArray; + elseType?: Type; + } + + export interface MatchTypeClause { + matchType: Type; + resultType: Type; + } + export const enum SignatureKind { Call, Construct, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2a07d2b6560..2a53dc4e506 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4844,7 +4844,6 @@ namespace ts { || kind === SyntaxKind.BooleanKeyword || kind === SyntaxKind.StringKeyword || kind === SyntaxKind.SymbolKeyword - || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.VoidKeyword || kind === SyntaxKind.UndefinedKeyword || kind === SyntaxKind.NullKeyword @@ -4871,6 +4870,16 @@ namespace ts { return false; } + export function isMatchTypeBlock(node: Node): node is MatchTypeBlock { + return node.kind === SyntaxKind.MatchTypeBlock; + } + + export function isMatchTypeMatchOrElseClause(node: Node): node is MatchTypeMatchOrElseClause { + const kind = node.kind; + return kind === SyntaxKind.MatchTypeMatchClause + || kind === SyntaxKind.MatchTypeElseClause; + } + // Binding patterns /* @internal */ diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 1ce42199372..479cc9f7246 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -405,6 +405,24 @@ namespace ts { visitNode((node).questionToken, tokenVisitor, isToken), visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.MatchType: + return updateMatchTypeNode(node, + visitNode((node).typeArgument, visitor, isTypeNode), + visitNode((node).matchBlock, visitor, isMatchTypeBlock)); + + case SyntaxKind.MatchTypeBlock: + return updateMatchTypeBlock(node, + nodesVisitor((node).clauses, visitor, isMatchTypeMatchOrElseClause)); + + case SyntaxKind.MatchTypeMatchClause: + return updateMatchTypeMatchClause(node, + visitNode((node).matchType, visitor, isTypeNode), + visitNode((node).resultType, visitor, isTypeNode)); + + case SyntaxKind.MatchTypeElseClause: + return updateMatchTypeElseClause(node, + visitNode((node).resultType, visitor, isTypeNode)); + case SyntaxKind.LiteralType: return updateLiteralTypeNode(node, visitNode((node).literal, visitor, isExpression));