Skip to content

Commit a85c0bf

Browse files
committed
Parse method and initializer keypaths.
1 parent 4754b46 commit a85c0bf

File tree

2 files changed

+374
-2
lines changed

2 files changed

+374
-2
lines changed

Sources/SwiftParser/Expressions.swift

+72-2
Original file line numberDiff line numberDiff line change
@@ -1103,8 +1103,48 @@ extension Parser {
11031103
continue
11041104
}
11051105

1106-
// Check for a .name or .1 suffix.
1106+
// Check for a .name, .1, .name(), .name("Kiwi"), .name(fruit:),
1107+
// .name(_:), .name(fruit: "Kiwi) suffix.
11071108
if self.at(.period) {
1109+
// Parse as a keypath method if fully applied.
1110+
if self.experimentalFeatures.contains(.keypathWithMethodMembers)
1111+
&& self.withLookahead({ $0.isAppliedKeyPathMethod() })
1112+
{
1113+
let (unexpectedPeriod, period, declName, _) = parseDottedExpressionSuffix(
1114+
previousNode: components.last?.raw ?? rootType?.raw ?? backslash.raw
1115+
)
1116+
let leftParen = self.consumeAnyToken()
1117+
var args: [RawLabeledExprSyntax] = []
1118+
if !self.at(.rightParen) {
1119+
args = self.parseArgumentListElements(
1120+
pattern: pattern,
1121+
allowTrailingComma: true
1122+
)
1123+
}
1124+
let (unexpectedBeforeRParen, rightParen) = self.expect(.rightParen)
1125+
components.append(
1126+
RawKeyPathComponentSyntax(
1127+
unexpectedPeriod,
1128+
period: period,
1129+
component: .method(
1130+
RawKeyPathMethodComponentSyntax(
1131+
declName: declName,
1132+
leftParen: leftParen,
1133+
arguments: RawLabeledExprListSyntax(
1134+
elements: args,
1135+
arena: self.arena
1136+
),
1137+
unexpectedBeforeRParen,
1138+
rightParen: rightParen,
1139+
arena: self.arena
1140+
)
1141+
),
1142+
arena: self.arena
1143+
)
1144+
)
1145+
continue
1146+
}
1147+
// Else, parse as a property.
11081148
let (unexpectedPeriod, period, declName, generics) = parseDottedExpressionSuffix(
11091149
previousNode: components.last?.raw ?? rootType?.raw ?? backslash.raw
11101150
)
@@ -1128,7 +1168,6 @@ extension Parser {
11281168
// No more postfix expressions.
11291169
break
11301170
}
1131-
11321171
return RawKeyPathExprSyntax(
11331172
unexpectedBeforeBackslash,
11341173
backslash: backslash,
@@ -2017,6 +2056,37 @@ extension Parser {
20172056
}
20182057

20192058
extension Parser.Lookahead {
2059+
/// Check if the keypath method is applied, and not partially applied which should be parsed as a key path property.
2060+
mutating func isAppliedKeyPathMethod() -> Bool {
2061+
var lookahead = self.lookahead()
2062+
var hasLParen = false, hasRParen = false
2063+
2064+
while true {
2065+
let token = lookahead.peek().rawTokenKind
2066+
if token == .endOfFile || lookahead.atStartOfLine {
2067+
break
2068+
}
2069+
if token == .leftParen {
2070+
hasLParen = true
2071+
}
2072+
if token == .colon {
2073+
lookahead.consumeAnyToken()
2074+
// If there's a colon followed by a right parenthesis, it is
2075+
// a partial application and should be parsed as a property.
2076+
if lookahead.peek().rawTokenKind == .rightParen {
2077+
return false
2078+
}
2079+
}
2080+
if token == .rightParen {
2081+
hasRParen = true
2082+
}
2083+
lookahead.consumeAnyToken()
2084+
}
2085+
// If parentheses exist with no partial application pattern,
2086+
// parse as a key path method.
2087+
return hasLParen && hasRParen ? true : false
2088+
}
2089+
20202090
mutating func atStartOfLabelledTrailingClosure() -> Bool {
20212091
// Fast path: the next two tokens must be a label and a colon.
20222092
// But 'default:' is ambiguous with switch cases and we disallow it

0 commit comments

Comments
 (0)