Skip to content

Commit d23ef48

Browse files
committed
Add KeyPath Bracket Disambiguation
1 parent 8b3f402 commit d23ef48

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,9 @@ extension Parser {
383383

384384
default:
385385
// If the next token is not an operator, just parse this as expr-postfix.
386-
return self.parsePostfixExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
386+
return self.parsePostfixExpression(
387+
flavor, forDirective: forDirective, inVarOrLet: inVarOrLet,
388+
periodHasKeyPathBehavior: false)
387389
}
388390
}
389391

@@ -405,26 +407,21 @@ extension Parser {
405407
public mutating func parsePostfixExpression(
406408
_ flavor: ExprFlavor,
407409
forDirective: Bool,
408-
inVarOrLet: Bool
410+
inVarOrLet: Bool,
411+
periodHasKeyPathBehavior: Bool
409412
) -> RawExprSyntax {
410413
let head = self.parsePrimaryExpression(inVarOrLet: inVarOrLet)
411414
guard !head.is(RawMissingExprSyntax.self) else {
412415
return head
413416
}
414-
return self.parsePostfixExpressionSuffix(head, flavor, forDirective: forDirective)
417+
return self.parsePostfixExpressionSuffix(
418+
head, flavor, forDirective: forDirective,
419+
periodHasKeyPathBehavior: periodHasKeyPathBehavior)
415420
}
416421

417422
@_spi(RawSyntax)
418423
public mutating func parseDottedExpressionSuffix(_ start: RawExprSyntax?) -> RawExprSyntax {
419424
assert(self.at(any: [.period, .prefixPeriod]))
420-
421-
// A key path is special, because it allows .[, unlike anywhere else. The
422-
// period itself should be left in the token stream. (.? and .! end up
423-
// being operators, and so aren't handled here.)
424-
// if (periodHasKeyPathBehavior && peekToken().is(tok::l_square)) {
425-
// break
426-
// }
427-
428425
let period = self.consumeAnyToken(remapping: .period)
429426
// Handle "x.42" - a tuple index.
430427
if let name = self.consume(if: .integerLiteral) {
@@ -478,7 +475,9 @@ extension Parser {
478475
// TODO: diagnose and skip.
479476
return nil
480477
}
481-
let result = parser.parsePostfixExpressionSuffix(head, flavor, forDirective: forDirective)
478+
let result = parser.parsePostfixExpressionSuffix(
479+
head, flavor, forDirective: forDirective,
480+
periodHasKeyPathBehavior: false)
482481

483482
// TODO: diagnose and skip the remaining token in the current clause.
484483
return result
@@ -513,7 +512,8 @@ extension Parser {
513512
public mutating func parsePostfixExpressionSuffix(
514513
_ start: RawExprSyntax,
515514
_ flavor: ExprFlavor,
516-
forDirective: Bool
515+
forDirective: Bool,
516+
periodHasKeyPathBehavior: Bool
517517
) -> RawExprSyntax {
518518
// Handle suffix expressions.
519519
var leadingExpr = start
@@ -525,6 +525,13 @@ extension Parser {
525525

526526
// Check for a .foo suffix.
527527
if self.at(any: [.period, .prefixPeriod]) {
528+
// A key path is special, because it allows .[, unlike anywhere else. The
529+
// period itself should be left in the token stream. (.? and .! end up
530+
// being operators, and so aren't handled here.)
531+
if periodHasKeyPathBehavior && self.peek().tokenKind == .leftSquareBracket {
532+
break
533+
}
534+
528535
leadingExpr = self.parseDottedExpressionSuffix(leadingExpr)
529536
continue
530537
}
@@ -700,7 +707,9 @@ extension Parser {
700707
// the token is a operator starts with '.', or the following token is '['.
701708
let root: RawExprSyntax?
702709
if !self.currentToken.starts(with: ".") {
703-
root = self.parsePostfixExpression(.basic, forDirective: forDirective, inVarOrLet: inVarOrLet)
710+
root = self.parsePostfixExpression(
711+
.basic, forDirective: forDirective, inVarOrLet: inVarOrLet,
712+
periodHasKeyPathBehavior: true)
704713
} else {
705714
root = nil
706715
}
@@ -714,12 +723,16 @@ extension Parser {
714723
dot = self.consumeAnyToken()
715724
}
716725
let base = RawExprSyntax(RawKeyPathBaseExprSyntax(period: dot, arena: self.arena))
717-
expression = self.parsePostfixExpressionSuffix(base, .basic, forDirective: forDirective)
726+
expression = self.parsePostfixExpressionSuffix(
727+
base, .basic, forDirective: forDirective,
728+
periodHasKeyPathBehavior: false)
718729
} else if self.at(any: [.period, .prefixPeriod]) {
719730
// Inside a keypath's path, the period always behaves normally: the key path
720731
// behavior is only the separation between type and path.
721732
let base = self.parseDottedExpressionSuffix(nil)
722-
expression = self.parsePostfixExpressionSuffix(base, .basic, forDirective: forDirective)
733+
expression = self.parsePostfixExpressionSuffix(
734+
base, .basic, forDirective: forDirective,
735+
periodHasKeyPathBehavior: false)
723736
} else {
724737
expression = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
725738
}

Tests/SwiftParserTest/Declarations.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ final class DeclarationTests: XCTestCase {
988988
func const(_const _ map: String) {}
989989
func isolated(isolated _ map: String) {}
990990
func isolatedConst(isolated _const _ map: String) {}
991+
func nonEphemeralIsolatedConst(@_nonEmphemeral isolated _const _ map: String) {}
991992
"""#)
992993
}
993994
}

Tests/SwiftParserTest/Expressions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ final class ExpressionTests: XCTestCase {
9090
DiagnosticSpec(message: "Expected ']' to end subscript")
9191
]
9292
)
93+
94+
AssertParse(
95+
#"""
96+
_ = \Lens<[Int]>.[0]
97+
"""#)
9398
}
9499

95100
func testBasicLiterals() {

0 commit comments

Comments
 (0)