From 94de1332fcc96ec0dd57638061dcf0205195ab20 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 1 Sep 2023 14:32:51 +0100 Subject: [PATCH 1/3] Introduce `then` statements These allow multi-statement `if`/`switch` expression branches that can produce a value at the end by saying `then `. This is gated behind an experimental feature option pending evolution discussion. --- .../Sources/SyntaxSupport/KeywordSpec.swift | 3 + .../Sources/SyntaxSupport/StmtNodes.swift | 26 + .../SyntaxSupport/SyntaxNodeKind.swift | 1 + .../SwiftParser/ExperimentalFeatures.swift | 5 + Sources/SwiftParser/Expressions.swift | 13 +- Sources/SwiftParser/Statements.swift | 104 ++- Sources/SwiftParser/TokenPrecedence.swift | 2 +- Sources/SwiftParser/TokenSpecSet.swift | 89 +- Sources/SwiftParser/TopLevel.swift | 4 +- .../ParseDiagnosticsGenerator.swift | 15 + .../ParserDiagnosticMessages.swift | 3 + .../SwiftParserTest/ThenStatementTests.swift | 760 ++++++++++++++++++ 12 files changed, 989 insertions(+), 36 deletions(-) create mode 100644 Tests/SwiftParserTest/ThenStatementTests.swift diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index 70542af6fbc..b13b3f1185c 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -255,6 +255,7 @@ public enum Keyword: CaseIterable { case swift case `switch` case target + case then case `throw` case `throws` case transpose @@ -644,6 +645,8 @@ public enum Keyword: CaseIterable { return KeywordSpec("switch", isLexerClassified: true) case .target: return KeywordSpec("target") + case .then: + return KeywordSpec("then", isExperimental: true) case .throw: return KeywordSpec("throw", isLexerClassified: true) case .throws: diff --git a/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift b/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift index 072809fa39f..393162253cd 100644 --- a/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift @@ -613,4 +613,30 @@ public let STMT_NODES: [Node] = [ ] ), + // then-stmt -> 'then' expr ';'? + Node( + kind: .thenStmt, + base: .stmt, + isExperimental: true, + nameForDiagnostics: "'then' statement", + documentation: """ + A statement used to indicate the produced value from an if/switch + expression. + + Written as: + ```swift + then + ``` + """, + children: [ + Child( + name: "thenKeyword", + kind: .token(choices: [.keyword(.then)]) + ), + Child( + name: "expression", + kind: .node(kind: .expr) + ), + ] + ), ] diff --git a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift index 36c4bcd10e4..8e55f00a310 100644 --- a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift +++ b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift @@ -267,6 +267,7 @@ public enum SyntaxNodeKind: String, CaseIterable { case switchDefaultLabel case switchExpr case ternaryExpr + case thenStmt case throwStmt case tryExpr case tupleExpr diff --git a/Sources/SwiftParser/ExperimentalFeatures.swift b/Sources/SwiftParser/ExperimentalFeatures.swift index 308f4995624..6fd7e4c7dc9 100644 --- a/Sources/SwiftParser/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/ExperimentalFeatures.swift @@ -22,3 +22,8 @@ extension Parser { public static let referenceBindings = Self(rawValue: 1 << 0) } } + +extension Parser.ExperimentalFeatures { + /// Whether to enable the parsing of 'then' statements. + public static let thenStatements = Self(rawValue: 1 << 0) +} diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 944d5468c43..318f8932330 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -24,7 +24,9 @@ extension TokenConsumer { if backtrack.at(anyIn: IfOrSwitch.self) != nil { return true } - if backtrack.atStartOfDeclaration() || backtrack.atStartOfStatement() { + // Note we currently pass `preferExpr: false` to prefer diagnosing `try then` + // as needing to be `then try`, rather than parsing `then` as an expression. + if backtrack.atStartOfDeclaration() || backtrack.atStartOfStatement(preferExpr: false) { // If after the 'try' we are at a declaration or statement, it can't be a valid expression. // Decide how we want to consume the 'try': // If the declaration or statement starts at a new line, the user probably just forgot to write the expression after 'try' -> parse it as a TryExpr @@ -1545,7 +1547,9 @@ extension Parser { // If the next token is at the beginning of a new line and can never start // an element, break. if self.atStartOfLine - && (self.at(.rightBrace, .poundEndif) || self.atStartOfDeclaration() || self.atStartOfStatement()) + && (self.at(.rightBrace, .poundEndif) + || self.atStartOfDeclaration() + || self.atStartOfStatement(preferExpr: false)) { break } @@ -2166,7 +2170,10 @@ extension Parser { ) ) ) - } else if allowStandaloneStmtRecovery && (self.atStartOfExpression() || self.atStartOfStatement() || self.atStartOfDeclaration()) { + } else if allowStandaloneStmtRecovery + && (self.atStartOfExpression() || self.atStartOfStatement(preferExpr: false) + || self.atStartOfDeclaration()) + { // Synthesize a label for the statement or declaration that isn't covered by a case right now. let statements = parseSwitchCaseBody() if statements.isEmpty { diff --git a/Sources/SwiftParser/Statements.swift b/Sources/SwiftParser/Statements.swift index 91c4cd934f0..53559c6d8f7 100644 --- a/Sources/SwiftParser/Statements.swift +++ b/Sources/SwiftParser/Statements.swift @@ -16,16 +16,22 @@ extension TokenConsumer { /// Returns `true` if the current token represents the start of a statement /// item. /// + /// - Parameters: + /// - allowRecovery: Whether to attempt to perform recovery. + /// - preferExpr: If either an expression or statement could be + /// parsed and this parameter is `true`, the function returns `false` + /// such that an expression can be parsed. + /// /// - Note: This function must be kept in sync with `parseStatement()`. /// - Seealso: ``Parser/parseStatement()`` - func atStartOfStatement(allowRecovery: Bool = false) -> Bool { + func atStartOfStatement(allowRecovery: Bool = false, preferExpr: Bool) -> Bool { var lookahead = self.lookahead() if allowRecovery { // Attributes are not allowed on statements. But for recovery, skip over // misplaced attributes. _ = lookahead.consumeAttributeList() } - return lookahead.atStartOfStatement(allowRecovery: allowRecovery) + return lookahead.atStartOfStatement(allowRecovery: allowRecovery, preferExpr: preferExpr) } } @@ -105,7 +111,9 @@ extension Parser { return label(self.parseDoStatement(doHandle: handle), with: optLabel) case (.yield, let handle)?: return label(self.parseYieldStatement(yieldHandle: handle), with: optLabel) - case nil: + case (.then, let handle)? where experimentalFeatures.contains(.thenStatements): + return label(self.parseThenStatement(handle: handle), with: optLabel) + case nil, (.then, _)?: let missingStmt = RawStmtSyntax(RawMissingStmtSyntax(arena: self.arena)) return label(missingStmt, with: optLabel) } @@ -630,7 +638,7 @@ extension Parser { if self.at(anyIn: IfOrSwitch.self) != nil { return true } - if self.atStartOfStatement() || self.atStartOfDeclaration() { + if self.atStartOfStatement(preferExpr: true) || self.atStartOfDeclaration() { return false } return true @@ -723,6 +731,36 @@ extension Parser { } } +extension Parser { + /// Parse a `then` statement. + mutating func parseThenStatement(handle: RecoveryConsumptionHandle) -> RawStmtSyntax { + assert(experimentalFeatures.contains(.thenStatements)) + + let (unexpectedBeforeThen, then) = self.eat(handle) + let hasMisplacedTry = unexpectedBeforeThen?.containsToken(where: { TokenSpec(.try) ~= $0 }) ?? false + + var expr = self.parseExpression(flavor: .basic, pattern: .none) + if hasMisplacedTry && !expr.is(RawTryExprSyntax.self) { + expr = RawExprSyntax( + RawTryExprSyntax( + tryKeyword: missingToken(.try), + questionOrExclamationMark: nil, + expression: expr, + arena: self.arena + ) + ) + } + return RawStmtSyntax( + RawThenStmtSyntax( + unexpectedBeforeThen, + thenKeyword: then, + expression: expr, + arena: self.arena + ) + ) + } +} + extension Parser { struct StatementLabel { var label: RawTokenSyntax @@ -791,7 +829,7 @@ extension Parser { } guard - self.at(.identifier) && !self.atStartOfStatement() && !self.atStartOfDeclaration() + self.at(.identifier) && !self.atStartOfStatement(preferExpr: true) && !self.atStartOfDeclaration() else { return nil } @@ -806,9 +844,15 @@ extension Parser.Lookahead { /// Returns `true` if the current token represents the start of a statement /// item. /// + /// - Parameters: + /// - allowRecovery: Whether to attempt to perform recovery. + /// - preferExpr: If either an expression or statement could be + /// parsed and this parameter is `true`, the function returns `false` + /// such that an expression can be parsed. + /// /// - Note: This function must be kept in sync with `parseStatement()`. /// - Seealso: ``Parser/parseStatement()`` - mutating func atStartOfStatement(allowRecovery: Bool = false) -> Bool { + mutating func atStartOfStatement(allowRecovery: Bool = false, preferExpr: Bool) -> Bool { if (self.at(anyIn: SwitchCaseStart.self) != nil || self.at(.atSign)) && withLookahead({ $0.atStartOfSwitchCaseItem() }) { // We consider SwitchCaseItems statements so we don't parse the start of a new case item as trailing parts of an expression. return true @@ -877,11 +921,57 @@ extension Parser.Lookahead { // For example, could be the function call "discard()". return false } - case nil: + + case .then where experimentalFeatures.contains(.thenStatements): + return atStartOfThenStatement(preferExpr: preferExpr) + + case nil, .then: return false } } + /// Whether we're currently at a `then` token that should be parsed as a + /// `then` statement. + mutating func atStartOfThenStatement(preferExpr: Bool) -> Bool { + guard self.at(.keyword(.then)) else { + return false + } + + // If we prefer an expr and aren't at the start of a newline, then don't + // parse a ThenStmt. + if preferExpr && !self.atStartOfLine { + return false + } + + let next = peek() + + // If 'then' is followed by a binary or postfix operator, prefer to parse as + // an expr. + if BinaryOperatorLike(lexeme: next) != nil || PostfixOperatorLike(lexeme: next) != nil { + return false + } + + switch PrepareForKeywordMatch(next) { + case TokenSpec(.is), TokenSpec(.as): + // Treat 'is' and 'as' like the binary operator case, and parse as an + // expr. + return false + + case .leftBrace: + // This is a trailing closure. + return false + + case .leftParen, .leftSquare, .period: + // These are handled based on whether there is trivia between the 'then' + // and the token. If so, it's a 'then' statement. Otherwise it should + // be treated as an expression, e.g `then(...)`, `then[...]`, `then.foo`. + return !self.currentToken.trailingTriviaText.isEmpty || !next.leadingTriviaText.isEmpty + default: + break + } + return true + } + /// Returns whether the parser's current position is the start of a switch case, /// given that we're in the middle of a switch already. mutating func atStartOfSwitchCase(allowRecovery: Bool = false) -> Bool { diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 55fea528a53..538387195d3 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -210,7 +210,7 @@ enum TokenPrecedence: Comparable { // Secondary parts of control-flow constructs .case, .catch, .default, .else, // Return-like statements - .break, .continue, .fallthrough, .return, .throw, .yield: + .break, .continue, .fallthrough, .return, .throw, .then, .yield: self = .stmtKeyword // MARK: Decl keywords case // Types diff --git a/Sources/SwiftParser/TokenSpecSet.swift b/Sources/SwiftParser/TokenSpecSet.swift index ee0823b3a38..87aa295d627 100644 --- a/Sources/SwiftParser/TokenSpecSet.swift +++ b/Sources/SwiftParser/TokenSpecSet.swift @@ -97,6 +97,7 @@ enum CanBeStatementStart: TokenSpecSet { case `repeat` case `return` case `switch` + case then case `throw` case `while` case yield @@ -115,6 +116,7 @@ enum CanBeStatementStart: TokenSpecSet { case TokenSpec(.repeat): self = .repeat case TokenSpec(.return): self = .return case TokenSpec(.switch): self = .switch + case TokenSpec(.then): self = .then case TokenSpec(.throw): self = .throw case TokenSpec(.while): self = .while case TokenSpec(.yield): self = .yield @@ -136,6 +138,7 @@ enum CanBeStatementStart: TokenSpecSet { case .repeat: return .keyword(.repeat) case .return: return .keyword(.return) case .switch: return .keyword(.switch) + case .then: return .keyword(.then) case .throw: return .keyword(.throw) case .while: return .keyword(.while) case .yield: return .keyword(.yield) @@ -507,48 +510,88 @@ enum Operator: TokenSpecSet { } } -/// Tokens that can be used in operator declarations -enum OperatorLike: TokenSpecSet { - case `operator`(Operator) - case exclamationMark +/// Tokens that are either binary operators, or can act like binary operators. +enum BinaryOperatorLike: TokenSpecSet { + case binaryOperator case infixQuestionMark - case postfixQuestionMark case equal case arrow init?(lexeme: Lexer.Lexeme) { - if let op = Operator(lexeme: lexeme) { - self = .operator(op) - return - } switch lexeme.rawTokenKind { - case .exclamationMark: self = .exclamationMark + case .binaryOperator: self = .binaryOperator case .infixQuestionMark: self = .infixQuestionMark - case .postfixQuestionMark: self = .postfixQuestionMark case .equal: self = .equal case .arrow: self = .arrow default: return nil } } - static var allCases: [OperatorLike] { - return Operator.allCases.map(Self.operator) + [ - .exclamationMark, - .infixQuestionMark, - .postfixQuestionMark, - .equal, - .arrow, - ] + var spec: TokenSpec { + switch self { + case .binaryOperator: return .binaryOperator + case .infixQuestionMark: return TokenSpec(.infixQuestionMark, remapping: .binaryOperator) + case .equal: return TokenSpec(.equal, remapping: .binaryOperator) + case .arrow: return TokenSpec(.arrow, remapping: .binaryOperator) + } + } +} + +/// Tokens that are either postfix operators, or can act like postfix operators. +enum PostfixOperatorLike: TokenSpecSet { + case postfixOperator + case exclamationMark + case postfixQuestionMark + + init?(lexeme: Lexer.Lexeme) { + switch lexeme.rawTokenKind { + case .postfixOperator: self = .postfixOperator + case .exclamationMark: self = .exclamationMark + case .postfixQuestionMark: self = .postfixQuestionMark + default: return nil + } } var spec: TokenSpec { switch self { - case .operator(let op): return op.spec + case .postfixOperator: return .postfixOperator case .exclamationMark: return TokenSpec(.exclamationMark, remapping: .postfixOperator) - case .infixQuestionMark: return TokenSpec(.infixQuestionMark, remapping: .binaryOperator) case .postfixQuestionMark: return TokenSpec(.postfixQuestionMark, remapping: .postfixOperator) - case .equal: return TokenSpec(.equal, remapping: .binaryOperator) - case .arrow: return TokenSpec(.arrow, remapping: .binaryOperator) + } + } +} + +/// Tokens that can be used in operator declarations. +enum OperatorLike: TokenSpecSet { + case prefixOperator + case binaryOperatorLike(BinaryOperatorLike) + case postfixOperatorLike(PostfixOperatorLike) + + init?(lexeme: Lexer.Lexeme) { + if case .prefixOperator = lexeme.rawTokenKind { + self = .prefixOperator + return + } + if let binOp = BinaryOperatorLike(lexeme: lexeme) { + self = .binaryOperatorLike(binOp) + return + } + if let postfixOp = PostfixOperatorLike(lexeme: lexeme) { + self = .postfixOperatorLike(postfixOp) + return + } + return nil + } + + static var allCases: [OperatorLike] { + [.prefixOperator] + BinaryOperatorLike.allCases.map(Self.binaryOperatorLike) + PostfixOperatorLike.allCases.map(Self.postfixOperatorLike) + } + + var spec: TokenSpec { + switch self { + case .prefixOperator: return .prefixOperator + case .binaryOperatorLike(let op): return op.spec + case .postfixOperatorLike(let op): return op.spec } } } diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index dbfcf43d5b0..08b77823726 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -237,13 +237,13 @@ extension Parser { return .decl(RawDeclSyntax(self.parsePoundSourceLocationDirective())) } else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) { return .decl(self.parseDeclaration()) - } else if self.atStartOfStatement() { + } else if self.atStartOfStatement(preferExpr: false) { return self.parseStatementItem() } else if self.atStartOfExpression() { return .expr(self.parseExpression(flavor: .basic, pattern: .none)) } else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) { return .decl(self.parseDeclaration()) - } else if self.atStartOfStatement(allowRecovery: true) { + } else if self.atStartOfStatement(allowRecovery: true, preferExpr: false) { return self.parseStatementItem() } else { return .expr(RawExprSyntax(RawMissingExprSyntax(arena: self.arena))) diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index a3bb0538744..b48af23df21 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -1492,6 +1492,21 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { return .visitChildren } + @_spi(ExperimentalLanguageFeatures) + public override func visit(_ node: ThenStmtSyntax) -> SyntaxVisitorContinueKind { + if shouldSkip(node) { + return .skipChildren + } + exchangeTokens( + unexpected: node.unexpectedBeforeThenKeyword, + unexpectedTokenCondition: { $0.tokenKind == .keyword(.try) }, + correctTokens: [node.expression.as(TryExprSyntax.self)?.tryKeyword], + message: { _ in .tryMustBePlacedOnThenExpr }, + moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .keyword(.then)) } + ) + return .visitChildren + } + public override func visit(_ node: SameTypeRequirementSyntax) -> SyntaxVisitorContinueKind { if shouldSkip(node) { return .skipChildren diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 6b43127801b..70205eba144 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -249,6 +249,9 @@ extension DiagnosticMessage where Self == StaticParserError { public static var tryMustBePlacedOnThrownExpr: Self { .init("'try' must be placed on the thrown expression") } + public static var tryMustBePlacedOnThenExpr: Self { + .init("'try' must be placed on the produced expression") + } public static var tryOnInitialValueExpression: Self { .init("'try' must be placed on the initial value expression") } diff --git a/Tests/SwiftParserTest/ThenStatementTests.swift b/Tests/SwiftParserTest/ThenStatementTests.swift new file mode 100644 index 00000000000..b774248350f --- /dev/null +++ b/Tests/SwiftParserTest/ThenStatementTests.swift @@ -0,0 +1,760 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftSyntax +@_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftParser +import XCTest + +final class ThenStatementTests: ParserTestCase { + // Enable then statements by default. + override var experimentalFeatures: Parser.ExperimentalFeatures { + return .thenStatements + } + + func testThenStmt1() { + assertParse( + """ + then 0 + """, + substructure: ThenStmtSyntax(expression: IntegerLiteralExprSyntax(0)) + ) + } + + func testThenStmt2() { + assertParse( + """ + then x + """, + substructure: ThenStmtSyntax(expression: DeclReferenceExprSyntax(baseName: "x")) + ) + } + + func testThenStmt3() { + assertParse( + """ + then () + """, + substructure: ThenStmtSyntax(expression: TupleExprSyntax(elements: .init([]))) + ) + } + + func testThenStmt4() { + assertParse( + """ + then (1) + """, + substructure: + ThenStmtSyntax( + expression: TupleExprSyntax( + elements: .init([ + .init(expression: IntegerLiteralExprSyntax(1)) + ]) + ) + ) + ) + } + + func testThenStmt5() { + assertParse( + """ + then (1, 2) + """, + substructure: + ThenStmtSyntax( + expression: TupleExprSyntax( + elements: .init([ + .init(expression: IntegerLiteralExprSyntax(1), trailingComma: .commaToken()), + .init(expression: IntegerLiteralExprSyntax(2)), + ]) + ) + ) + ) + } + + func testThenStmt6() { + assertParse( + """ + then "" + """, + substructure: ThenStmtSyntax(expression: StringLiteralExprSyntax(content: "")) + ) + } + + func testThenStmt7() { + assertParse( + """ + then [] + """, + substructure: ThenStmtSyntax(expression: ArrayExprSyntax(elements: .init(expressions: []))) + ) + } + + func testThenStmt8() { + assertParse( + """ + then [0] + """, + substructure: + ThenStmtSyntax( + expression: ArrayExprSyntax( + elements: .init(expressions: [ + .init(IntegerLiteralExprSyntax(0)) + ]) + ) + ) + ) + } + + func testThenStmt9() { + assertParse( + """ + then if .random() { 0 } else { 1 } + """ + ) + } + + func testThenStmt10() { + assertParse( + """ + then -1 + """, + substructure: + ThenStmtSyntax( + expression: PrefixOperatorExprSyntax( + operator: .prefixOperator("-"), + expression: IntegerLiteralExprSyntax(1) + ) + ) + ) + } + + func testThenStmt11() { + assertParse( + """ + then ~1 + """, + substructure: + ThenStmtSyntax( + expression: PrefixOperatorExprSyntax( + operator: .prefixOperator("~"), + expression: IntegerLiteralExprSyntax(1) + ) + ) + ) + } + + func testThenStmt12() { + assertParse( + """ + then /.../ + """, + substructure: ThenStmtSyntax(expression: RegexLiteralExprSyntax(regex: .regexLiteralPattern("..."))) + ) + } + + func testThenStmt13() { + // This is a a statement. + assertParse( + """ + then .foo + """, + substructure: ThenStmtSyntax(expression: MemberAccessExprSyntax(name: .identifier("foo"))) + ) + } + + func testThenStmt14() { + // This is a member access. + assertParse( + """ + then.foo + """, + substructure: + MemberAccessExprSyntax( + base: DeclReferenceExprSyntax(baseName: .identifier("then")), + name: .identifier("foo") + ) + ) + } + + func testThenStmt15() { + // This could be a member access too, but it seems rare enough to continue + // parsing as a statement. + assertParse( + """ + then . foo + """, + substructure: ThenStmtSyntax(expression: MemberAccessExprSyntax(name: .identifier("foo"))) + ) + } + + func testThenStmt16() { + // This will be diagnosed in ASTGen. + assertParse( + """ + a: then 0 + """, + substructure: + LabeledStmtSyntax( + label: .identifier("a"), + statement: ThenStmtSyntax(expression: IntegerLiteralExprSyntax(0)) + ) + ) + } + + func testThenStmt17() { + // This is a function call. + assertParse( + """ + then() + """, + substructure: + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + leftParen: .leftParenToken(), + arguments: .init([]), + rightParen: .rightParenToken() + ) + ) + } + + func testThenStmt18() { + // This is a function call. + assertParse( + """ + then(0) + """, + substructure: + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + leftParen: .leftParenToken(), + arguments: .init([.init(expression: IntegerLiteralExprSyntax(0))]), + rightParen: .rightParenToken() + ) + ) + } + + func testThenStmt19() { + // This is a function call. + assertParse( + """ + then(x: 0) + """, + substructure: + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + leftParen: .leftParenToken(), + arguments: .init([.init(label: "x", expression: IntegerLiteralExprSyntax(0))]), + rightParen: .rightParenToken() + ) + ) + } + + func testThenStmt20() { + // This is a function call. + assertParse( + """ + then{} + """, + substructure: + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + arguments: .init([]), + trailingClosure: ClosureExprSyntax(statements: .init([])) + ) + ) + } + + func testThenStmt21() { + // This is a function call. + assertParse( + """ + then {} + """, + substructure: + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + arguments: .init([]), + trailingClosure: ClosureExprSyntax(statements: .init([])) + ) + ) + } + + func testThenStmt22() { + assertParse( + """ + then1️⃣ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected expression in 'then' statement", + fixIts: ["insert expression"] + ) + ], + fixedSource: "then <#expression#>" + ) + } + + func testThenStmt23() { + assertParse( + """ + then1️⃣; + """, + diagnostics: [ + DiagnosticSpec( + message: "expected expression in 'then' statement", + fixIts: ["insert expression"] + ) + ], + fixedSource: "then <#expression#>;" + ) + } + + func testThenStmt24() { + assertParse( + """ + (then) + """, + substructure: + TupleExprSyntax( + elements: .init([ + .init(expression: DeclReferenceExprSyntax(baseName: .identifier("then"))) + ]) + ) + ) + } + + func testThenStmt25() { + assertParse( + """ + then + 0 + """, + substructure: ThenStmtSyntax(expression: IntegerLiteralExprSyntax(0)) + ) + } + + func testThenStmt26() { + assertParse( + """ + let x = then + """, + substructure: DeclReferenceExprSyntax(baseName: .identifier("then")) + ) + } + + func testThenStmt27() { + assertParse( + """ + self.then + """, + substructure: + MemberAccessExprSyntax( + base: DeclReferenceExprSyntax(baseName: .keyword(.self)), + name: .identifier("then") + ) + ) + } + + func testThenStmt28() { + assertParse( + """ + then + 2 + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + BinaryOperatorExprSyntax(operator: .binaryOperator("+")) + IntegerLiteralExprSyntax(2) + } + ) + } + + func testThenStmt29() { + assertParse( + """ + then+2 + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + BinaryOperatorExprSyntax(operator: .binaryOperator("+")) + IntegerLiteralExprSyntax(2) + } + ) + } + + func testThenStmt30() { + assertParse( + """ + then = 2 + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + AssignmentExprSyntax() + IntegerLiteralExprSyntax(2) + } + ) + } + + func testThenStmt31() { + assertParse( + """ + then=2 + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + AssignmentExprSyntax() + IntegerLiteralExprSyntax(2) + } + ) + } + + func testThenStmt32() { + assertParse( + """ + then is Int + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + UnresolvedIsExprSyntax() + TypeExprSyntax(type: IdentifierTypeSyntax(name: .identifier("Int"))) + } + ) + } + + func testThenStmt33() { + assertParse( + """ + then as Int + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + UnresolvedAsExprSyntax() + TypeExprSyntax(type: IdentifierTypeSyntax(name: .identifier("Int"))) + } + ) + } + + func testThenStmt34() { + assertParse( + """ + then as? Int + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + UnresolvedAsExprSyntax(questionOrExclamationMark: .postfixQuestionMarkToken()) + TypeExprSyntax(type: IdentifierTypeSyntax(name: .identifier("Int"))) + } + ) + } + + func testThenStmt35() { + assertParse( + """ + then as! Int + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + UnresolvedAsExprSyntax(questionOrExclamationMark: .exclamationMarkToken()) + TypeExprSyntax(type: IdentifierTypeSyntax(name: .identifier("Int"))) + } + ) + } + + func testThenStmt36() { + assertParse( + """ + then ? 0 : 1 + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + UnresolvedTernaryExprSyntax(thenExpression: IntegerLiteralExprSyntax(0)) + IntegerLiteralExprSyntax(1) + } + ) + } + + func testThenStmt37() { + assertParse( + """ + 1️⃣try then 0 + """, + diagnostics: [ + DiagnosticSpec( + message: "'try' must be placed on the produced expression", + fixIts: ["move 'try' after 'then'"] + ) + ], + fixedSource: "then try 0" + ) + } + + func testThenStmt38() { + assertParse( + """ + then try 0 + """ + ) + } + + func testThenStmt39() { + assertParse( + """ + then! + """, + substructure: + ForceUnwrapExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")) + ) + ) + } + + func testThenStmt40() { + assertParse( + """ + then? + """, + substructure: + OptionalChainingExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")) + ) + ) + } + + func testThenStmt41() { + assertParse( + """ + then?.foo + """, + substructure: + MemberAccessExprSyntax( + base: OptionalChainingExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")) + ), + name: .identifier("foo") + ) + ) + } + + func testThenStmt42() { + assertParse( + """ + then!.foo + """, + substructure: + MemberAccessExprSyntax( + base: ForceUnwrapExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")) + ), + name: .identifier("foo") + ) + ) + } + + func testThenStmt43() { + assertParse( + """ + self.then(0) + """, + substructure: + MemberAccessExprSyntax( + base: DeclReferenceExprSyntax(baseName: .keyword(.self)), + name: .identifier("then") + ) + ) + } + + func testThenStmt44() { + assertParse( + """ + then /^ then/ + """, + substructure: + SequenceExprSyntax { + DeclReferenceExprSyntax(baseName: .identifier("then")) + BinaryOperatorExprSyntax(operator: .binaryOperator("/^")) + PostfixOperatorExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")), + operator: .postfixOperator("/") + ) + } + ) + } + + func testThenStmt45() { + assertParse( + """ + return then + """, + substructure: + ReturnStmtSyntax(expression: DeclReferenceExprSyntax(baseName: .identifier("then"))) + ) + } + + func testThenStmt46() { + assertParse( + """ + then[0] + """, + substructure: + SubscriptCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("then")), + arguments: .init([ + .init(expression: IntegerLiteralExprSyntax(0)) + ]) + ) + ) + } + + func testThenStmt47() { + assertParse( + """ + then: for then in [] { + break then + continue then + } + """ + ) + } + + func testThenStmt48() { + assertParse( + """ + throw then + """, + substructure: + ThrowStmtSyntax(expression: DeclReferenceExprSyntax(baseName: .identifier("then"))) + ) + } + + func testThenStmt49() { + assertParse( + """ + try then() + """ + ) + } + + func testThenStmt50() { + assertParse( + """ + try then{} + """ + ) + } + + func testThenStmt51() { + assertParse( + """ + try then {} + """ + ) + } + + func testThenStmt52() { + assertParse( + """ + try then + 1 + """ + ) + } + + func testThenStmt53() { + assertParse( + """ + then + .foo + """, + substructure: ThenStmtSyntax(expression: MemberAccessExprSyntax(name: .identifier("foo"))) + ) + } + + func testThenStmt54() { + assertParse( + """ + return try then + """, + substructure: + ReturnStmtSyntax( + expression: TryExprSyntax( + expression: DeclReferenceExprSyntax(baseName: .identifier("then")) + ) + ) + ) + } + + func testThenStmt55() { + assertParse( + """ + let x = [ + 0, + then + ] + """ + ) + } + + func testThenStmtDisabled1() { + // Make sure it's disabled by default. + assertParse( + """ + then1️⃣ 0 + """, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: [ + "insert newline", + "insert ';'", + ] + ) + ], + fixedSource: """ + then + 0 + """, + experimentalFeatures: [] + ) + } + + func testThenStmtDisabled2() { + // Make sure it's disabled by default. This is specifically testing + // StmtSyntax.parse, since it will try to parse without checking + // `atStartOfThenStatement`. + assertParse( + """ + 1️⃣then 02️⃣ + """, + { StmtSyntax.parse(from: &$0) }, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "unexpected code 'then 0' before statement" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected statement", + fixIts: ["insert statement"] + ), + ], + fixedSource: "<#statement#>then 0", + experimentalFeatures: [] + ) + } +} From 61fb200c5aa81e7a45448a84bc28c9fb786086c6 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 1 Sep 2023 14:32:52 +0100 Subject: [PATCH 2/3] Avoid marking ThenStmtSyntax experimental for now --- CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift | 2 +- CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index b13b3f1185c..4e14169dc7f 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -646,7 +646,7 @@ public enum Keyword: CaseIterable { case .target: return KeywordSpec("target") case .then: - return KeywordSpec("then", isExperimental: true) + return KeywordSpec("then") case .throw: return KeywordSpec("throw", isLexerClassified: true) case .throws: diff --git a/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift b/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift index 393162253cd..5381f7336ef 100644 --- a/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift @@ -617,7 +617,8 @@ public let STMT_NODES: [Node] = [ Node( kind: .thenStmt, base: .stmt, - isExperimental: true, + // FIXME: This should be marked experimental. + isExperimental: false, nameForDiagnostics: "'then' statement", documentation: """ A statement used to indicate the produced value from an if/switch From 33ed2d6fa3bfbe00f79dd54031afaf113c0b9c92 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 1 Sep 2023 14:32:52 +0100 Subject: [PATCH 3/3] [generated] Re-generate code --- .../SyntaxKindNameForDiagnostics.swift | 2 + .../generated/SwiftSyntax.md | 1 + .../generated/ChildNameForKeyPath.swift | 10 ++ Sources/SwiftSyntax/generated/Keyword.swift | 4 + .../generated/SyntaxAnyVisitor.swift | 8 ++ .../generated/SyntaxBaseNodes.swift | 6 +- .../SwiftSyntax/generated/SyntaxEnum.swift | 3 + .../SwiftSyntax/generated/SyntaxKind.swift | 3 + .../generated/SyntaxRewriter.swift | 13 ++ .../generated/SyntaxTransform.swift | 14 ++ .../SwiftSyntax/generated/SyntaxVisitor.swift | 18 +++ .../generated/raw/RawSyntaxNodesQRS.swift | 2 +- .../generated/raw/RawSyntaxNodesTUVWXYZ.swift | 70 ++++++++++ .../generated/raw/RawSyntaxValidation.swift | 7 + .../syntaxNodes/SyntaxNodesTUVWXYZ.swift | 131 ++++++++++++++++++ 15 files changed, 289 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift index a53c595a569..64ff8b4cadd 100644 --- a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift @@ -357,6 +357,8 @@ extension SyntaxKind { return "'switch' statement" case .ternaryExpr: return "ternay expression" + case .thenStmt: + return "'then' statement" case .throwStmt: return "'throw' statement" case .tryExpr: diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index 221e4db6b67..98689c309eb 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -175,6 +175,7 @@ These articles are intended for developers wishing to contribute to SwiftSyntax - - - +- - - - diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index e30fc7f2924..4cc8070057c 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -3063,6 +3063,16 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "elseExpression" case \TernaryExprSyntax.unexpectedAfterElseExpression: return "unexpectedAfterElseExpression" + case \ThenStmtSyntax.unexpectedBeforeThenKeyword: + return "unexpectedBeforeThenKeyword" + case \ThenStmtSyntax.thenKeyword: + return "thenKeyword" + case \ThenStmtSyntax.unexpectedBetweenThenKeywordAndExpression: + return "unexpectedBetweenThenKeywordAndExpression" + case \ThenStmtSyntax.expression: + return "expression" + case \ThenStmtSyntax.unexpectedAfterExpression: + return "unexpectedAfterExpression" case \ThrowStmtSyntax.unexpectedBeforeThrowKeyword: return "unexpectedBeforeThrowKeyword" case \ThrowStmtSyntax.throwKeyword: diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 7d1b0927114..4d2e4ede5e3 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -201,6 +201,7 @@ public enum Keyword: UInt8, Hashable { case swift case `switch` case target + case then case `throw` case `throws` case transpose @@ -312,6 +313,8 @@ public enum Keyword: UInt8, Hashable { self = .Self case "some": self = .some + case "then": + self = .then case "true": self = .true case "Type": @@ -956,6 +959,7 @@ public enum Keyword: UInt8, Hashable { "swift", "switch", "target", + "then", "throw", "throws", "transpose", diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index 28044024525..518cb834c89 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -2000,6 +2000,14 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + override open func visit(_ node: ThenStmtSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + override open func visitPost(_ node: ThenStmtSyntax) { + visitAnyPost(node._syntaxNode) + } + override open func visit(_ node: ThrowStmtSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift index 500f478c827..f70ec0fad7f 100644 --- a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift +++ b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift @@ -512,7 +512,7 @@ public struct StmtSyntax: StmtSyntaxProtocol, SyntaxHashable { public init?(_ node: some SyntaxProtocol) { switch node.raw.kind { - case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .throwStmt, .whileStmt, .yieldStmt: + case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .thenStmt, .throwStmt, .whileStmt, .yieldStmt: self._syntaxNode = node._syntaxNode default: return nil @@ -525,7 +525,7 @@ public struct StmtSyntax: StmtSyntaxProtocol, SyntaxHashable { /// If it is not, the behaviour is undefined. internal init(_ data: SyntaxData) { switch data.raw.kind { - case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .throwStmt, .whileStmt, .yieldStmt: + case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .thenStmt, .throwStmt, .whileStmt, .yieldStmt: break default: preconditionFailure("Unable to create StmtSyntax from \(data.raw.kind)") @@ -576,6 +576,7 @@ public struct StmtSyntax: StmtSyntaxProtocol, SyntaxHashable { .node(MissingStmtSyntax.self), .node(RepeatStmtSyntax.self), .node(ReturnStmtSyntax.self), + .node(ThenStmtSyntax.self), .node(ThrowStmtSyntax.self), .node(WhileStmtSyntax.self), .node(YieldStmtSyntax.self) @@ -966,6 +967,7 @@ extension Syntax { .node(SwitchDefaultLabelSyntax.self), .node(SwitchExprSyntax.self), .node(TernaryExprSyntax.self), + .node(ThenStmtSyntax.self), .node(ThrowStmtSyntax.self), .node(TryExprSyntax.self), .node(TupleExprSyntax.self), diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index f85889c3216..6ea43ee2b8b 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -258,6 +258,7 @@ public enum SyntaxEnum { case switchDefaultLabel(SwitchDefaultLabelSyntax) case switchExpr(SwitchExprSyntax) case ternaryExpr(TernaryExprSyntax) + case thenStmt(ThenStmtSyntax) case throwStmt(ThrowStmtSyntax) case tryExpr(TryExprSyntax) case tupleExpr(TupleExprSyntax) @@ -784,6 +785,8 @@ public extension Syntax { return .switchExpr(SwitchExprSyntax(self)!) case .ternaryExpr: return .ternaryExpr(TernaryExprSyntax(self)!) + case .thenStmt: + return .thenStmt(ThenStmtSyntax(self)!) case .throwStmt: return .throwStmt(ThrowStmtSyntax(self)!) case .tryExpr: diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 54bd33b28d0..d0e659351ec 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -258,6 +258,7 @@ public enum SyntaxKind: CaseIterable { case switchDefaultLabel case switchExpr case ternaryExpr + case thenStmt case throwStmt case tryExpr case tupleExpr @@ -905,6 +906,8 @@ public enum SyntaxKind: CaseIterable { return SwitchExprSyntax.self case .ternaryExpr: return TernaryExprSyntax.self + case .thenStmt: + return ThenStmtSyntax.self case .throwStmt: return ThrowStmtSyntax.self case .tryExpr: diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 51e306ae354..482447fdd9d 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -1781,6 +1781,13 @@ open class SyntaxRewriter { return ExprSyntax(visitChildren(node)) } + /// Visit a ``ThenStmtSyntax``. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + open func visit(_ node: ThenStmtSyntax) -> StmtSyntax { + return StmtSyntax(visitChildren(node)) + } + /// Visit a ``ThrowStmtSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node @@ -3061,6 +3068,10 @@ open class SyntaxRewriter { return { self.visitImpl($0, TernaryExprSyntax.self, self.visit) } + case .thenStmt: + return { + self.visitImpl($0, ThenStmtSyntax.self, self.visit) + } case .throwStmt: return { self.visitImpl($0, ThrowStmtSyntax.self, self.visit) @@ -3685,6 +3696,8 @@ open class SyntaxRewriter { return visitImpl(data, SwitchExprSyntax.self, visit) case .ternaryExpr: return visitImpl(data, TernaryExprSyntax.self, visit) + case .thenStmt: + return visitImpl(data, ThenStmtSyntax.self, visit) case .throwStmt: return visitImpl(data, ThrowStmtSyntax.self, visit) case .tryExpr: diff --git a/Sources/SwiftSyntax/generated/SyntaxTransform.swift b/Sources/SwiftSyntax/generated/SyntaxTransform.swift index f249d3e3587..73d358301fe 100644 --- a/Sources/SwiftSyntax/generated/SyntaxTransform.swift +++ b/Sources/SwiftSyntax/generated/SyntaxTransform.swift @@ -1235,6 +1235,11 @@ public protocol SyntaxTransformVisitor { /// - Returns: the sum of whatever the child visitors return. func visit(_ node: TernaryExprSyntax) -> ResultType + /// Visiting ``ThenStmtSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: the sum of whatever the child visitors return. + func visit(_ node: ThenStmtSyntax) -> ResultType + /// Visiting ``ThrowStmtSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: the sum of whatever the child visitors return. @@ -3102,6 +3107,13 @@ extension SyntaxTransformVisitor { visitAny(Syntax(node)) } + /// Visiting ``ThenStmtSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: nil by default. + public func visit(_ node: ThenStmtSyntax) -> ResultType { + visitAny(Syntax(node)) + } + /// Visiting ``ThrowStmtSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: nil by default. @@ -3816,6 +3828,8 @@ extension SyntaxTransformVisitor { return visit(derived) case .ternaryExpr(let derived): return visit(derived) + case .thenStmt(let derived): + return visit(derived) case .throwStmt(let derived): return visit(derived) case .tryExpr(let derived): diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index 5bb0200744a..3bb57bdbde0 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -2950,6 +2950,18 @@ open class SyntaxVisitor { open func visitPost(_ node: TernaryExprSyntax) { } + /// Visiting ``ThenStmtSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + open func visit(_ node: ThenStmtSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting ``ThenStmtSyntax`` and its descendants. + /// - node: the node we just finished visiting. + open func visitPost(_ node: ThenStmtSyntax) { + } + /// Visiting ``ThrowStmtSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. @@ -4368,6 +4380,10 @@ open class SyntaxVisitor { return { self.visitImpl($0, TernaryExprSyntax.self, self.visit, self.visitPost) } + case .thenStmt: + return { + self.visitImpl($0, ThenStmtSyntax.self, self.visit, self.visitPost) + } case .throwStmt: return { self.visitImpl($0, ThrowStmtSyntax.self, self.visit, self.visitPost) @@ -4995,6 +5011,8 @@ open class SyntaxVisitor { visitImpl(data, SwitchExprSyntax.self, visit, visitPost) case .ternaryExpr: visitImpl(data, TernaryExprSyntax.self, visit, visitPost) + case .thenStmt: + visitImpl(data, ThenStmtSyntax.self, visit, visitPost) case .throwStmt: visitImpl(data, ThrowStmtSyntax.self, visit, visitPost) case .tryExpr: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift index fdfc62c1e3a..a2f062d7d02 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift @@ -1056,7 +1056,7 @@ public struct RawStmtSyntax: RawStmtSyntaxNodeProtocol { public static func isKindOf(_ raw: RawSyntax) -> Bool { switch raw.kind { - case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .throwStmt, .whileStmt, .yieldStmt: + case .breakStmt, .continueStmt, .deferStmt, .discardStmt, .doStmt, .expressionStmt, .fallThroughStmt, .forStmt, .guardStmt, .labeledStmt, .missingStmt, .repeatStmt, .returnStmt, .thenStmt, .throwStmt, .whileStmt, .yieldStmt: return true default: return false diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift index c3fc4bd4451..21693ed8fdc 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift @@ -107,6 +107,76 @@ public struct RawTernaryExprSyntax: RawExprSyntaxNodeProtocol { } } +@_spi(RawSyntax) +public struct RawThenStmtSyntax: RawStmtSyntaxNodeProtocol { + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .thenStmt + } + + public var raw: RawSyntax + + init(raw: RawSyntax) { + precondition(Self.isKindOf(raw)) + self.raw = raw + } + + private init(unchecked raw: RawSyntax) { + self.raw = raw + } + + public init?(_ other: some RawSyntaxNodeProtocol) { + guard Self.isKindOf(other.raw) else { + return nil + } + self.init(unchecked: other.raw) + } + + public init( + _ unexpectedBeforeThenKeyword: RawUnexpectedNodesSyntax? = nil, + thenKeyword: RawTokenSyntax, + _ unexpectedBetweenThenKeywordAndExpression: RawUnexpectedNodesSyntax? = nil, + expression: RawExprSyntax, + _ unexpectedAfterExpression: RawUnexpectedNodesSyntax? = nil, + arena: __shared SyntaxArena + ) { + let raw = RawSyntax.makeLayout( + kind: .thenStmt, uninitializedCount: 5, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedBeforeThenKeyword?.raw + layout[1] = thenKeyword.raw + layout[2] = unexpectedBetweenThenKeywordAndExpression?.raw + layout[3] = expression.raw + layout[4] = unexpectedAfterExpression?.raw + } + self.init(unchecked: raw) + } + + public var unexpectedBeforeThenKeyword: RawUnexpectedNodesSyntax? { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var thenKeyword: RawTokenSyntax { + layoutView.children[1].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedBetweenThenKeywordAndExpression: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var expression: RawExprSyntax { + layoutView.children[3].map(RawExprSyntax.init(raw:))! + } + + public var unexpectedAfterExpression: RawUnexpectedNodesSyntax? { + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + } +} + @_spi(RawSyntax) public struct RawThrowStmtSyntax: RawStmtSyntaxNodeProtocol { @_spi(RawSyntax) diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 9242a9420c6..b226fdd1edd 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -2451,6 +2451,13 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 9, verify(layout[9], as: RawExprSyntax.self)) assertNoError(kind, 10, verify(layout[10], as: RawUnexpectedNodesSyntax?.self)) + case .thenStmt: + assert(layout.count == 5) + assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.keyword("then")])) + assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 3, verify(layout[3], as: RawExprSyntax.self)) + assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) case .throwStmt: assert(layout.count == 5) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift index 5d49af229c5..902ede51d54 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift @@ -216,6 +216,137 @@ public struct TernaryExprSyntax: ExprSyntaxProtocol, SyntaxHashable { } } +// MARK: - ThenStmtSyntax + +/// A statement used to indicate the produced value from an if/switch +/// expression. +/// +/// Written as: +/// ```swift +/// then +/// ``` +/// +/// ### Children +/// +/// - `thenKeyword`: `'then'` +/// - `expression`: ``ExprSyntax`` +public struct ThenStmtSyntax: StmtSyntaxProtocol, SyntaxHashable { + public let _syntaxNode: Syntax + + public init?(_ node: some SyntaxProtocol) { + guard node.raw.kind == .thenStmt else { + return nil + } + self._syntaxNode = node._syntaxNode + } + + /// Creates a ``ThenStmtSyntax`` node from the given ``SyntaxData``. + /// + /// - Warning: This assumes that the `SyntaxData` is of the correct kind. + /// If it is not, the behaviour is undefined. + internal init(_ data: SyntaxData) { + precondition(data.raw.kind == .thenStmt) + self._syntaxNode = Syntax(data) + } + + /// - Parameters: + /// - leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. + /// - trailingTrivia: Trivia to be appended to the trailing trivia of the node’s last token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. + public init( + leadingTrivia: Trivia? = nil, + _ unexpectedBeforeThenKeyword: UnexpectedNodesSyntax? = nil, + thenKeyword: TokenSyntax = .keyword(.then), + _ unexpectedBetweenThenKeywordAndExpression: UnexpectedNodesSyntax? = nil, + expression: some ExprSyntaxProtocol, + _ unexpectedAfterExpression: UnexpectedNodesSyntax? = nil, + trailingTrivia: Trivia? = nil + + ) { + // Extend the lifetime of all parameters so their arenas don't get destroyed + // before they can be added as children of the new arena. + let data: SyntaxData = withExtendedLifetime((SyntaxArena(), ( + unexpectedBeforeThenKeyword, + thenKeyword, + unexpectedBetweenThenKeywordAndExpression, + expression, + unexpectedAfterExpression + ))) { (arena, _) in + let layout: [RawSyntax?] = [ + unexpectedBeforeThenKeyword?.raw, + thenKeyword.raw, + unexpectedBetweenThenKeywordAndExpression?.raw, + expression.raw, + unexpectedAfterExpression?.raw + ] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.thenStmt, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + + ) + return SyntaxData.forRoot(raw, rawNodeArena: arena) + } + self.init(data) + } + + public var unexpectedBeforeThenKeyword: UnexpectedNodesSyntax? { + get { + return data.child(at: 0).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = ThenStmtSyntax(data.replacingChild(at: 0, with: value?.data, arena: SyntaxArena())) + } + } + + public var thenKeyword: TokenSyntax { + get { + return TokenSyntax(data.child(at: 1)!) + } + set(value) { + self = ThenStmtSyntax(data.replacingChild(at: 1, with: value.data, arena: SyntaxArena())) + } + } + + public var unexpectedBetweenThenKeywordAndExpression: UnexpectedNodesSyntax? { + get { + return data.child(at: 2).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = ThenStmtSyntax(data.replacingChild(at: 2, with: value?.data, arena: SyntaxArena())) + } + } + + public var expression: ExprSyntax { + get { + return ExprSyntax(data.child(at: 3)!) + } + set(value) { + self = ThenStmtSyntax(data.replacingChild(at: 3, with: value.data, arena: SyntaxArena())) + } + } + + public var unexpectedAfterExpression: UnexpectedNodesSyntax? { + get { + return data.child(at: 4).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = ThenStmtSyntax(data.replacingChild(at: 4, with: value?.data, arena: SyntaxArena())) + } + } + + public static var structure: SyntaxNodeStructure { + return .layout([ + \Self.unexpectedBeforeThenKeyword, + \Self.thenKeyword, + \Self.unexpectedBetweenThenKeywordAndExpression, + \Self.expression, + \Self.unexpectedAfterExpression + ]) + } +} + // MARK: - ThrowStmtSyntax /// ### Children