Skip to content

Commit f49e95c

Browse files
committed
Parse method and initializer keypaths.
1 parent d8eaf70 commit f49e95c

File tree

2 files changed

+359
-2
lines changed

2 files changed

+359
-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 {
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

Tests/SwiftParserTest/ExpressionTests.swift

+287
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,293 @@ final class ExpressionTests: ParserTestCase {
256256
)
257257
}
258258

259+
func testKeyPathMethodAndInitializers() {
260+
assertParse(
261+
#"\Foo.method()"#,
262+
substructure: KeyPathExprSyntax(
263+
root: TypeSyntax("Foo"),
264+
components: KeyPathComponentListSyntax([
265+
KeyPathComponentSyntax(
266+
period: .periodToken(),
267+
component: KeyPathComponentSyntax.Component(
268+
KeyPathMethodComponentSyntax(
269+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
270+
leftParen: .leftParenToken(),
271+
arguments: LabeledExprListSyntax([]),
272+
rightParen: .rightParenToken()
273+
)
274+
)
275+
)
276+
])
277+
),
278+
experimentalFeatures: .keypathWithMethodMembers
279+
)
280+
281+
assertParse(
282+
#"\Foo.method(10)"#,
283+
substructure: KeyPathExprSyntax(
284+
root: TypeSyntax("Foo"),
285+
components: KeyPathComponentListSyntax([
286+
KeyPathComponentSyntax(
287+
period: .periodToken(),
288+
component: .init(
289+
KeyPathMethodComponentSyntax(
290+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
291+
leftParen: .leftParenToken(),
292+
arguments: LabeledExprListSyntax([
293+
LabeledExprSyntax(
294+
label: nil,
295+
colon: nil,
296+
expression: ExprSyntax("10")
297+
)
298+
]),
299+
rightParen: .rightParenToken()
300+
)
301+
)
302+
)
303+
])
304+
),
305+
experimentalFeatures: .keypathWithMethodMembers
306+
)
307+
308+
assertParse(
309+
#"\Foo.method(arg: 10)"#,
310+
substructure: KeyPathExprSyntax(
311+
root: TypeSyntax("Foo"),
312+
components: KeyPathComponentListSyntax([
313+
KeyPathComponentSyntax(
314+
period: .periodToken(),
315+
component: .init(
316+
KeyPathMethodComponentSyntax(
317+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
318+
leftParen: .leftParenToken(),
319+
arguments: LabeledExprListSyntax([
320+
LabeledExprSyntax(
321+
label: .identifier("arg"),
322+
colon: .colonToken(),
323+
expression: ExprSyntax("10")
324+
)
325+
]),
326+
rightParen: .rightParenToken()
327+
)
328+
)
329+
)
330+
])
331+
),
332+
experimentalFeatures: .keypathWithMethodMembers
333+
)
334+
335+
assertParse(
336+
#"\Foo.method(_:)"#,
337+
substructure: KeyPathExprSyntax(
338+
root: TypeSyntax("Foo"),
339+
components: KeyPathComponentListSyntax([
340+
KeyPathComponentSyntax(
341+
period: .periodToken(),
342+
component: .init(
343+
KeyPathPropertyComponentSyntax(
344+
declName: DeclReferenceExprSyntax(
345+
baseName: .identifier("method"),
346+
argumentNames: DeclNameArgumentsSyntax(
347+
leftParen: .leftParenToken(),
348+
arguments: [
349+
DeclNameArgumentSyntax(name: .wildcardToken(), colon: .colonToken())
350+
],
351+
rightParen: .rightParenToken()
352+
)
353+
)
354+
)
355+
)
356+
)
357+
])
358+
),
359+
experimentalFeatures: .keypathWithMethodMembers
360+
)
361+
362+
assertParse(
363+
#"\Foo.method(arg:)"#,
364+
substructure: KeyPathExprSyntax(
365+
root: TypeSyntax("Foo"),
366+
components: KeyPathComponentListSyntax([
367+
KeyPathComponentSyntax(
368+
period: .periodToken(),
369+
component: .init(
370+
KeyPathPropertyComponentSyntax(
371+
declName: DeclReferenceExprSyntax(
372+
baseName: .identifier("method"),
373+
argumentNames: DeclNameArgumentsSyntax(
374+
leftParen: .leftParenToken(),
375+
arguments: [
376+
DeclNameArgumentSyntax(name: .identifier("arg"), colon: .colonToken())
377+
],
378+
rightParen: .rightParenToken()
379+
)
380+
)
381+
)
382+
)
383+
)
384+
])
385+
),
386+
experimentalFeatures: .keypathWithMethodMembers
387+
)
388+
389+
assertParse(
390+
#"\Foo.method().anotherMethod(arg: 10)"#,
391+
substructure: KeyPathExprSyntax(
392+
root: TypeSyntax("Foo"),
393+
components: KeyPathComponentListSyntax([
394+
KeyPathComponentSyntax(
395+
period: .periodToken(),
396+
component: .init(
397+
KeyPathMethodComponentSyntax(
398+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
399+
leftParen: .leftParenToken(),
400+
arguments: LabeledExprListSyntax([]),
401+
rightParen: .rightParenToken()
402+
)
403+
)
404+
),
405+
KeyPathComponentSyntax(
406+
period: .periodToken(),
407+
component: .init(
408+
KeyPathMethodComponentSyntax(
409+
declName: DeclReferenceExprSyntax(baseName: .identifier("anotherMethod")),
410+
leftParen: .leftParenToken(),
411+
arguments: LabeledExprListSyntax([
412+
LabeledExprSyntax(
413+
label: .identifier("arg"),
414+
colon: .colonToken(),
415+
expression: ExprSyntax("10")
416+
)
417+
]),
418+
rightParen: .rightParenToken()
419+
)
420+
)
421+
),
422+
])
423+
),
424+
experimentalFeatures: .keypathWithMethodMembers
425+
)
426+
427+
assertParse(
428+
#"\Foo.Type.init()"#,
429+
substructure: KeyPathExprSyntax(
430+
root: TypeSyntax(
431+
MetatypeTypeSyntax(baseType: TypeSyntax("Foo"), metatypeSpecifier: .keyword(.Type))
432+
),
433+
components: KeyPathComponentListSyntax([
434+
KeyPathComponentSyntax(
435+
period: .periodToken(),
436+
component: KeyPathComponentSyntax.Component(
437+
KeyPathMethodComponentSyntax(
438+
declName: DeclReferenceExprSyntax(baseName: .keyword(.init("init")!)),
439+
leftParen: .leftParenToken(),
440+
arguments: LabeledExprListSyntax([]),
441+
rightParen: .rightParenToken()
442+
)
443+
)
444+
)
445+
])
446+
),
447+
experimentalFeatures: .keypathWithMethodMembers
448+
)
449+
450+
assertParse(
451+
#"""
452+
\Foo.method1️⃣(2️⃣
453+
"""#,
454+
diagnostics: [
455+
DiagnosticSpec(
456+
locationMarker: "1️⃣",
457+
message: "consecutive statements on a line must be separated by newline or ';'",
458+
fixIts: ["insert newline", "insert ';'"]
459+
),
460+
DiagnosticSpec(
461+
locationMarker: "2️⃣",
462+
message: "expected value and ')' to end tuple",
463+
fixIts: ["insert value and ')'"]
464+
),
465+
],
466+
fixedSource: #"""
467+
\Foo.method
468+
(<#expression#>)
469+
"""#,
470+
experimentalFeatures: .keypathWithMethodMembers
471+
)
472+
473+
assertParse(
474+
#"\Foo.1️⃣()"#,
475+
diagnostics: [
476+
DiagnosticSpec(message: "expected identifier in key path method component", fixIts: ["insert identifier"])
477+
],
478+
fixedSource: #"\Foo.<#identifier#>()"#,
479+
experimentalFeatures: .keypathWithMethodMembers
480+
)
481+
482+
assertParse(
483+
#"""
484+
S()[keyPath: \.i] = 1
485+
"""#,
486+
experimentalFeatures: .keypathWithMethodMembers
487+
)
488+
489+
assertParse(
490+
#"""
491+
public let keyPath2FromLibB = \AStruct.Type.property
492+
"""#,
493+
experimentalFeatures: .keypathWithMethodMembers
494+
)
495+
496+
assertParse(
497+
#"""
498+
public let keyPath9FromLibB = \AStruct.Type.init(val: 2025)
499+
"""#,
500+
experimentalFeatures: .keypathWithMethodMembers
501+
)
502+
503+
assertParse(
504+
#"""
505+
_ = ([S]()).map(\.i)
506+
"""#,
507+
experimentalFeatures: .keypathWithMethodMembers
508+
)
509+
510+
assertParse(
511+
#"""
512+
let some = Some(keyPath: \Demo.here)
513+
"""#,
514+
experimentalFeatures: .keypathWithMethodMembers
515+
)
516+
517+
assertParse(
518+
#"""
519+
_ = ([S.Type]()).map(\.init)
520+
"""#,
521+
experimentalFeatures: .keypathWithMethodMembers
522+
)
523+
524+
assertParse(
525+
#"""
526+
\Lens<Lens<Point>>.obj.x
527+
"""#,
528+
experimentalFeatures: .keypathWithMethodMembers
529+
)
530+
531+
assertParse(
532+
#"""
533+
_ = \Lens<Point>.y
534+
"""#,
535+
experimentalFeatures: .keypathWithMethodMembers
536+
)
537+
538+
assertParse(
539+
#"""
540+
_ = f(\String?.!.count)
541+
"""#,
542+
experimentalFeatures: .keypathWithMethodMembers
543+
)
544+
}
545+
259546
func testKeyPathSubscript() {
260547
assertParse(
261548
#"\Foo.Type.[2]"#,

0 commit comments

Comments
 (0)