Skip to content

Commit cff9459

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

File tree

2 files changed

+294
-2
lines changed

2 files changed

+294
-2
lines changed

Sources/SwiftParser/Expressions.swift

+58-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.isPartiallyAppliedKeyPathMethod() })
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,23 @@ extension Parser {
20172056
}
20182057

20192058
extension Parser.Lookahead {
2059+
/// Check if partially or fully applied keypath method.
2060+
mutating func isPartiallyAppliedKeyPathMethod() -> Bool {
2061+
var lookahead = self.lookahead()
2062+
while true {
2063+
let token = lookahead.peek().rawTokenKind
2064+
if token == .endOfFile {
2065+
return false
2066+
}
2067+
if token == .colon {
2068+
lookahead.consumeAnyToken()
2069+
return lookahead.peek().rawTokenKind == .rightParen
2070+
}
2071+
lookahead.consumeAnyToken()
2072+
}
2073+
return false
2074+
}
2075+
20202076
mutating func atStartOfLabelledTrailingClosure() -> Bool {
20212077
// Fast path: the next two tokens must be a label and a colon.
20222078
// But 'default:' is ambiguous with switch cases and we disallow it

Tests/SwiftParserTest/ExpressionTests.swift

+236
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,242 @@ 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<Int>()"#,
283+
// substructure: KeyPathExprSyntax(
284+
// root: TypeSyntax("Foo"),
285+
// components: KeyPathComponentListSyntax([
286+
// KeyPathComponentSyntax(
287+
// period: .periodToken(),
288+
// component: KeyPathComponentSyntax.Component(
289+
// KeyPathPropertyComponentSyntax(
290+
// declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
291+
// leftParen: .leftParenToken(),
292+
// arguments: LabeledExprListSyntax([]),
293+
// rightParen: .rightParenToken()
294+
// )
295+
// )
296+
// )
297+
// ])
298+
// ),
299+
// experimentalFeatures: .keypathWithMethodMembers
300+
// )
301+
302+
assertParse(
303+
#"\Foo.method(10)"#,
304+
substructure: KeyPathExprSyntax(
305+
root: TypeSyntax("Foo"),
306+
components: KeyPathComponentListSyntax([
307+
KeyPathComponentSyntax(
308+
period: .periodToken(),
309+
component: .init(
310+
KeyPathMethodComponentSyntax(
311+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
312+
leftParen: .leftParenToken(),
313+
arguments: LabeledExprListSyntax([
314+
LabeledExprSyntax(
315+
label: nil,
316+
colon: nil,
317+
expression: ExprSyntax("10")
318+
)
319+
]),
320+
rightParen: .rightParenToken()
321+
)
322+
)
323+
)
324+
])
325+
),
326+
experimentalFeatures: .keypathWithMethodMembers
327+
)
328+
329+
assertParse(
330+
#"\Foo.method(arg: 10)"#,
331+
substructure: KeyPathExprSyntax(
332+
root: TypeSyntax("Foo"),
333+
components: KeyPathComponentListSyntax([
334+
KeyPathComponentSyntax(
335+
period: .periodToken(),
336+
component: .init(
337+
KeyPathMethodComponentSyntax(
338+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
339+
leftParen: .leftParenToken(),
340+
arguments: LabeledExprListSyntax([
341+
LabeledExprSyntax(
342+
label: .identifier("arg"),
343+
colon: .colonToken(),
344+
expression: ExprSyntax("10")
345+
)
346+
]),
347+
rightParen: .rightParenToken()
348+
)
349+
)
350+
)
351+
])
352+
),
353+
experimentalFeatures: .keypathWithMethodMembers
354+
)
355+
356+
assertParse(
357+
#"\Foo.method(_:)"#,
358+
substructure: KeyPathExprSyntax(
359+
root: TypeSyntax("Foo"),
360+
components: KeyPathComponentListSyntax([
361+
KeyPathComponentSyntax(
362+
period: .periodToken(),
363+
component: .init(
364+
KeyPathPropertyComponentSyntax(
365+
declName: DeclReferenceExprSyntax(
366+
baseName: .identifier("method"),
367+
argumentNames: DeclNameArgumentsSyntax(
368+
leftParen: .leftParenToken(),
369+
arguments: [
370+
DeclNameArgumentSyntax(name: .wildcardToken(), colon: .colonToken())
371+
],
372+
rightParen: .rightParenToken()
373+
)
374+
)
375+
)
376+
)
377+
)
378+
])
379+
),
380+
experimentalFeatures: .keypathWithMethodMembers
381+
)
382+
383+
assertParse(
384+
#"\Foo.method(arg:)"#,
385+
substructure: KeyPathExprSyntax(
386+
root: TypeSyntax("Foo"),
387+
components: KeyPathComponentListSyntax([
388+
KeyPathComponentSyntax(
389+
period: .periodToken(),
390+
component: .init(
391+
KeyPathPropertyComponentSyntax(
392+
declName: DeclReferenceExprSyntax(
393+
baseName: .identifier("method"),
394+
argumentNames: DeclNameArgumentsSyntax(
395+
leftParen: .leftParenToken(),
396+
arguments: [
397+
DeclNameArgumentSyntax(name: .identifier("arg"), colon: .colonToken())
398+
],
399+
rightParen: .rightParenToken()
400+
)
401+
)
402+
)
403+
)
404+
)
405+
])
406+
),
407+
experimentalFeatures: .keypathWithMethodMembers
408+
)
409+
410+
assertParse(
411+
#"\Foo.method().anotherMethod(arg: 10)"#,
412+
substructure: KeyPathExprSyntax(
413+
root: TypeSyntax("Foo"),
414+
components: KeyPathComponentListSyntax([
415+
KeyPathComponentSyntax(
416+
period: .periodToken(),
417+
component: .init(
418+
KeyPathMethodComponentSyntax(
419+
declName: DeclReferenceExprSyntax(baseName: .identifier("method")),
420+
leftParen: .leftParenToken(),
421+
arguments: LabeledExprListSyntax([]),
422+
rightParen: .rightParenToken()
423+
)
424+
)
425+
),
426+
KeyPathComponentSyntax(
427+
period: .periodToken(),
428+
component: .init(
429+
KeyPathMethodComponentSyntax(
430+
declName: DeclReferenceExprSyntax(baseName: .identifier("anotherMethod")),
431+
leftParen: .leftParenToken(),
432+
arguments: LabeledExprListSyntax([
433+
LabeledExprSyntax(
434+
label: .identifier("arg"),
435+
colon: .colonToken(),
436+
expression: ExprSyntax("10")
437+
)
438+
]),
439+
rightParen: .rightParenToken()
440+
)
441+
)
442+
),
443+
])
444+
),
445+
experimentalFeatures: .keypathWithMethodMembers
446+
)
447+
448+
assertParse(
449+
#"\Foo.Type.init()"#,
450+
substructure: KeyPathExprSyntax(
451+
root: TypeSyntax(
452+
MetatypeTypeSyntax(baseType: TypeSyntax("Foo"), metatypeSpecifier: .keyword(.Type))
453+
),
454+
components: KeyPathComponentListSyntax([
455+
KeyPathComponentSyntax(
456+
period: .periodToken(),
457+
component: KeyPathComponentSyntax.Component(
458+
KeyPathMethodComponentSyntax(
459+
declName: DeclReferenceExprSyntax(baseName: .keyword(.init("init")!)),
460+
leftParen: .leftParenToken(),
461+
arguments: LabeledExprListSyntax([]),
462+
rightParen: .rightParenToken()
463+
)
464+
)
465+
)
466+
])
467+
),
468+
experimentalFeatures: .keypathWithMethodMembers
469+
)
470+
471+
assertParse(
472+
#"""
473+
\Foo.method(1️⃣
474+
"""#,
475+
diagnostics: [
476+
DiagnosticSpec(
477+
message: "expected value and ')' to end key path method component",
478+
fixIts: ["insert value and ')'"]
479+
)
480+
],
481+
fixedSource: #"\Foo.method(<#expression#>)"#,
482+
experimentalFeatures: .keypathWithMethodMembers
483+
)
484+
485+
assertParse(
486+
#"\Foo.1️⃣()"#,
487+
diagnostics: [
488+
DiagnosticSpec(message: "expected identifier in key path method component", fixIts: ["insert identifier"])
489+
],
490+
fixedSource: #"\Foo.<#identifier#>()"#,
491+
experimentalFeatures: .keypathWithMethodMembers
492+
)
493+
}
494+
259495
func testKeyPathSubscript() {
260496
assertParse(
261497
#"\Foo.Type.[2]"#,

0 commit comments

Comments
 (0)