Skip to content

Commit 2e68a03

Browse files
committed
parse in expr positition
1 parent 4d2b8fd commit 2e68a03

File tree

4 files changed

+115
-24
lines changed

4 files changed

+115
-24
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,30 @@ extension Parser {
182182
arena: self.arena))
183183
}
184184

185+
/// Parse an unresolved 'as' expression.
186+
///
187+
/// type-casting-operator → 'as' type
188+
/// type-casting-operator → 'as' '?' type
189+
/// type-casting-operator → 'as' '!' type
190+
///
191+
mutating func parseUnresolvedAsExpr(
192+
handle: TokenConsumptionHandle
193+
) -> (operator: RawExprSyntax, rhs: RawExprSyntax) {
194+
let asKeyword = self.eat(handle)
195+
let failable = self.consume(ifAny: [.postfixQuestionMark, .exclamationMark])
196+
let op = RawUnresolvedAsExprSyntax(
197+
asTok: asKeyword,
198+
questionOrExclamationMark: failable,
199+
arena: self.arena
200+
)
201+
202+
// Parse the right type expression operand as part of the 'as' production.
203+
let type = self.parseType()
204+
let rhs = RawTypeExprSyntax(type: type, arena: self.arena)
205+
206+
return (RawExprSyntax(op), RawExprSyntax(rhs))
207+
}
208+
185209
/// Parse an expression sequence operators.
186210
///
187211
/// Returns `nil` if the current token is not at an operator.
@@ -313,19 +337,7 @@ extension Parser {
313337
return (RawExprSyntax(op), RawExprSyntax(rhs))
314338

315339
case (.asKeyword, let handle)?:
316-
let asKeyword = self.eat(handle)
317-
let failable = self.consume(ifAny: [.postfixQuestionMark, .exclamationMark])
318-
let op = RawUnresolvedAsExprSyntax(
319-
asTok: asKeyword,
320-
questionOrExclamationMark: failable,
321-
arena: self.arena
322-
)
323-
324-
// Parse the right type expression operand as part of the 'as' production.
325-
let type = self.parseType()
326-
let rhs = RawTypeExprSyntax(type: type, arena: self.arena)
327-
328-
return (RawExprSyntax(op), RawExprSyntax(rhs))
340+
return parseUnresolvedAsExpr(handle: handle)
329341

330342
case (.async, _)?:
331343
if self.peek().tokenKind == .arrow || self.peek().tokenKind == .throwsKeyword {
@@ -452,8 +464,16 @@ extension Parser {
452464
forDirective: Bool = false,
453465
pattern: PatternContext = .none
454466
) -> RawExprSyntax {
455-
// First check to see if we have the start of a regex literal `/.../`.
456-
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)
467+
468+
if self.at(.switchKeyword) {
469+
return RawExprSyntax(
470+
parseSwitchExpression(switchHandle: .constant(.switchKeyword)))
471+
}
472+
if self.at(.ifKeyword) {
473+
return RawExprSyntax(
474+
parseIfExpression(ifHandle: .constant(.ifKeyword)))
475+
}
476+
457477
switch self.at(anyIn: ExpressionPrefixOperator.self) {
458478
case (.prefixAmpersand, let handle)?:
459479
let amp = self.eat(handle)
@@ -2347,6 +2367,11 @@ extension Parser.Lookahead {
23472367
return false
23482368
}
23492369

2370+
// If this is the start of a switch body, this isn't a trailing closure.
2371+
if self.peek().tokenKind == .caseKeyword {
2372+
return false;
2373+
}
2374+
23502375
// If this is a normal expression (not an expr-basic) then trailing closures
23512376
// are allowed, so this is obviously one.
23522377
// TODO: We could handle try to disambiguate cases like:

Sources/SwiftParser/Statements.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,24 @@ extension Parser {
632632
// MARK: Control Transfer Statements
633633

634634
extension Parser {
635+
private func isStartOfReturnExpr() -> Bool {
636+
if self.at(any: [
637+
.rightBrace, .caseKeyword, .defaultKeyword, .semicolon, .eof,
638+
.poundIfKeyword, .poundErrorKeyword, .poundWarningKeyword,
639+
.poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword
640+
]) {
641+
return false
642+
}
643+
// Allowed for if/switch expressions.
644+
if self.at(any: [.ifKeyword, .switchKeyword]) {
645+
return true
646+
}
647+
if self.atStartOfStatement() || self.atStartOfDeclaration() {
648+
return false
649+
}
650+
return true
651+
}
652+
635653
/// Parse a return statement
636654
///
637655
/// Grammar
@@ -647,13 +665,7 @@ extension Parser {
647665
// enclosing stmt-brace to get it by eagerly eating it unless the return is
648666
// followed by a '}', '', statement or decl start keyword sequence.
649667
let expr: RawExprSyntax?
650-
if
651-
!self.at(any: [
652-
.rightBrace, .caseKeyword, .defaultKeyword, .semicolon, .eof,
653-
.poundIfKeyword, .poundErrorKeyword, .poundWarningKeyword,
654-
.poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword
655-
])
656-
&& !self.atStartOfStatement() && !self.atStartOfDeclaration() {
668+
if isStartOfReturnExpr() {
657669
let parsedExpr = self.parseExpression()
658670
if hasMisplacedTry && !parsedExpr.is(RawTryExprSyntax.self) {
659671
expr = RawExprSyntax(RawTryExprSyntax(

Sources/SwiftParser/TopLevel.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,29 @@ extension Parser {
192192
)
193193
}
194194

195+
private mutating func parseStatementItem() -> RawCodeBlockItemSyntax.Item {
196+
let stmt = self.parseStatement()
197+
198+
// Special case: An 'if' or 'switch' statement followed by an 'as' must
199+
// be an if/switch expression in a coercion.
200+
if at(.asKeyword), let stmtExpr = stmt.as(RawExpressionStmtSyntax.self) {
201+
let expr = stmtExpr.expression
202+
if expr.is(RawIfExprSyntax.self) || expr.is(RawSwitchExprSyntax.self) {
203+
let (op, rhs) = parseUnresolvedAsExpr(
204+
handle: .init(tokenKind: .asKeyword)
205+
)
206+
let sequence = RawExprSyntax(RawSequenceExprSyntax(
207+
elements: RawExprListSyntax(
208+
elements: [expr, op, rhs], arena: self.arena
209+
),
210+
arena: self.arena)
211+
)
212+
return .expr(sequence)
213+
}
214+
}
215+
return .stmt(stmt)
216+
}
217+
195218
/// `isAtTopLevel` determines whether this is trying to parse an item that's at
196219
/// the top level of the source file. If this is the case, we allow skipping
197220
/// closing braces while trying to recover to the next item.
@@ -224,13 +247,13 @@ extension Parser {
224247
} else if self.atStartOfDeclaration(allowInitDecl: allowInitDecl) {
225248
return .decl(self.parseDeclaration())
226249
} else if self.atStartOfStatement() {
227-
return .stmt(self.parseStatement())
250+
return self.parseStatementItem()
228251
} else if self.atStartOfExpression() {
229252
return .expr(self.parseExpression())
230253
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) {
231254
return .decl(self.parseDeclaration())
232255
} else if self.atStartOfStatement(allowRecovery: true) {
233-
return .stmt(self.parseStatement())
256+
return self.parseStatementItem()
234257
} else {
235258
return .expr(RawExprSyntax(RawMissingExprSyntax(arena: self.arena)))
236259
}

Tests/SwiftParserTest/ExpressionTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,3 +794,34 @@ final class ExpressionTests: XCTestCase {
794794
)
795795
}
796796
}
797+
798+
final class StatementExpressionTests: XCTestCase {
799+
func testIfExprInCoercion() {
800+
AssertParse("""
801+
func foo() {
802+
if .random() { 0 } else { 1 } as Int
803+
}
804+
""")
805+
}
806+
func testSwitchExprInCoercion() {
807+
AssertParse("""
808+
func foo() {
809+
switch Bool.random() { case true: 0 case false: 1 } as Int
810+
}
811+
""")
812+
}
813+
func testIfExprInReturn() {
814+
AssertParse("""
815+
func foo() {
816+
return if .random() { 0 } else { 1 }
817+
}
818+
""")
819+
}
820+
func testSwitchExprInReturn() {
821+
AssertParse("""
822+
func foo() {
823+
return switch Bool.random() { case true: 0 case false: 1 }
824+
}
825+
""")
826+
}
827+
}

0 commit comments

Comments
 (0)