Skip to content

Commit 3a7e378

Browse files
authored
Merge pull request #2871 from ahoppen/metatype-keypath
Support parsing of key path subscripts on metatypes
2 parents 054f108 + 7b52d07 commit 3a7e378

File tree

3 files changed

+182
-22
lines changed

3 files changed

+182
-22
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -925,14 +925,16 @@ extension Parser {
925925

926926
extension Parser {
927927
/// Determine if this is a key path postfix operator like ".?!?".
928-
private func getNumOptionalKeyPathPostfixComponents(
929-
_ tokenText: SyntaxText
930-
) -> Int? {
928+
private func getNumOptionalKeyPathPostfixComponents(_ tokenText: SyntaxText, mayBeAfterTypeName: Bool) -> Int? {
929+
var mayBeAfterTypeName = mayBeAfterTypeName
931930
// Make sure every character is ".", "!", or "?", without two "."s in a row.
932931
var numComponents = 0
933932
var lastWasDot = false
934933
for byte in tokenText {
935934
if byte == UInt8(ascii: ".") {
935+
if !mayBeAfterTypeName {
936+
break
937+
}
936938
if lastWasDot {
937939
return nil
938940
}
@@ -942,6 +944,7 @@ extension Parser {
942944
}
943945

944946
if byte == UInt8(ascii: "!") || byte == UInt8(ascii: "?") {
947+
mayBeAfterTypeName = false
945948
lastWasDot = false
946949
numComponents += 1
947950
continue
@@ -955,22 +958,31 @@ extension Parser {
955958

956959
/// Consume the optional key path postfix ino a set of key path components.
957960
private mutating func consumeOptionalKeyPathPostfix(
958-
numComponents: Int
961+
numComponents: Int,
962+
mayBeAfterTypeName: inout Bool
959963
) -> [RawKeyPathComponentSyntax] {
960964
var components: [RawKeyPathComponentSyntax] = []
961965

962966
for _ in 0..<numComponents {
963967
// Consume a period, if there is one.
964-
let period = self.consume(ifPrefix: ".", as: .period)
968+
var unexpectedBeforeComponent: RawUnexpectedNodesSyntax?
969+
var period = self.consume(ifPrefix: ".", as: .period)
970+
if !mayBeAfterTypeName {
971+
// A period is only permitted after the type name. See comment on `mayBeAfterTypeName` in `parseKeyPathExpression`.
972+
unexpectedBeforeComponent = RawUnexpectedNodesSyntax([period], arena: arena)
973+
period = nil
974+
}
965975

966976
// Consume the '!' or '?'.
967977
let questionOrExclaim =
968978
self.consume(ifPrefix: "!", as: .exclamationMark)
969979
?? self.expectWithoutRecovery(prefix: "?", as: .postfixQuestionMark)
970980

981+
mayBeAfterTypeName = false
971982
components.append(
972983
RawKeyPathComponentSyntax(
973984
period: period,
985+
unexpectedBeforeComponent,
974986
component: .optional(
975987
RawKeyPathOptionalComponentSyntax(
976988
questionOrExclamationMark: questionOrExclaim,
@@ -997,18 +1009,22 @@ extension Parser {
9971009
// the token is an operator starts with '.', or the following token is '['.
9981010
let rootType: RawTypeSyntax?
9991011
if !self.at(prefix: ".") {
1000-
rootType = self.parseSimpleType(stopAtFirstPeriod: true)
1012+
rootType = self.parseSimpleType(allowMemberTypes: false)
10011013
} else {
10021014
rootType = nil
10031015
}
10041016

10051017
var components: [RawKeyPathComponentSyntax] = []
10061018
var loopProgress = LoopProgressCondition()
1019+
// Whether all components parsed so far are property components and we could thus be after the base type name of the
1020+
// subscript. Syntax like `.[2]` or `.?` is only permitted after the type name. Since we don't know what constitutes
1021+
// a nested type reference and what constitutes a type's member, we can only disallow it in the parser after seeing
1022+
// the first non-property component.
1023+
var mayBeAfterTypeName = true
10071024
while self.hasProgressed(&loopProgress) {
1008-
// Check for a [] or .[] suffix. The latter is only permitted when there
1009-
// are no components.
1025+
// Check for a [] or .[] suffix.
10101026
if self.at(TokenSpec(.leftSquare, allowAtStartOfLine: false))
1011-
|| (components.isEmpty && self.at(.period) && self.peek(isAt: .leftSquare))
1027+
|| (mayBeAfterTypeName && self.at(.period) && self.peek(isAt: .leftSquare))
10121028
{
10131029
// Consume the '.', if it's allowed here.
10141030
let period: RawTokenSyntax?
@@ -1031,6 +1047,7 @@ extension Parser {
10311047
}
10321048
let (unexpectedBeforeRSquare, rsquare) = self.expect(.rightSquare)
10331049

1050+
mayBeAfterTypeName = false
10341051
components.append(
10351052
RawKeyPathComponentSyntax(
10361053
period: period,
@@ -1056,13 +1073,14 @@ extension Parser {
10561073
// periods, '?'s, and '!'s. Expand that into key path components.
10571074
if self.at(.prefixOperator, .binaryOperator, .postfixOperator) || self.at(.postfixQuestionMark, .exclamationMark),
10581075
let numComponents = getNumOptionalKeyPathPostfixComponents(
1059-
self.currentToken.tokenText
1060-
)
1076+
currentToken.tokenText,
1077+
mayBeAfterTypeName: mayBeAfterTypeName
1078+
),
1079+
numComponents > 0
10611080
{
1062-
components.append(
1063-
contentsOf: self.consumeOptionalKeyPathPostfix(
1064-
numComponents: numComponents
1065-
)
1081+
components += self.consumeOptionalKeyPathPostfix(
1082+
numComponents: numComponents,
1083+
mayBeAfterTypeName: &mayBeAfterTypeName
10661084
)
10671085
continue
10681086
}

Sources/SwiftParser/Types.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,8 @@ extension Parser {
190190
return parseSimpleType(forAttributeName: true)
191191
}
192192

193-
/// Parse a "simple" type
194193
mutating func parseSimpleType(
195-
stopAtFirstPeriod: Bool = false,
194+
allowMemberTypes: Bool = true,
196195
forAttributeName: Bool = false
197196
) -> RawTypeSyntax {
198197
enum TypeBaseStart: TokenSpecSet {
@@ -260,7 +259,7 @@ extension Parser {
260259

261260
var loopProgress = LoopProgressCondition()
262261
while self.hasProgressed(&loopProgress) {
263-
if !stopAtFirstPeriod, self.at(.period) {
262+
if self.at(.period) && (allowMemberTypes || self.peek(isAt: .keyword(.Type), .keyword(.Protocol))) {
264263
let (unexpectedPeriod, period, skipMemberName) = self.consumeMemberPeriod(previousNode: base)
265264
if skipMemberName {
266265
let missingIdentifier = missingToken(.identifier)

Tests/SwiftParserTest/ExpressionTests.swift

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ final class ExpressionTests: ParserTestCase {
230230

231231
assertParse(
232232
#"""
233-
\String?.!.count.?
234-
"""#
233+
\String?.!.count1️⃣.?
234+
"""#,
235+
diagnostics: [DiagnosticSpec(message: "extraneous code '.?' at top level")]
235236
)
236237

237238
assertParse(
@@ -242,8 +243,9 @@ final class ExpressionTests: ParserTestCase {
242243

243244
assertParse(
244245
#"""
245-
\Optional.?!?!?!?.??!
246-
"""#
246+
\Optional.?!?!?!?1️⃣.??!
247+
"""#,
248+
diagnostics: [DiagnosticSpec(message: "extraneous code '.??!' at top level")]
247249
)
248250

249251
assertParse(
@@ -254,6 +256,147 @@ final class ExpressionTests: ParserTestCase {
254256
)
255257
}
256258

259+
func testKeyPathSubscript() {
260+
assertParse(
261+
#"\Foo.Type.[2]"#,
262+
substructure: KeyPathExprSyntax(
263+
root: TypeSyntax(
264+
MetatypeTypeSyntax(baseType: TypeSyntax("Foo"), metatypeSpecifier: .keyword(.Type))
265+
),
266+
components: KeyPathComponentListSyntax([
267+
KeyPathComponentSyntax(
268+
period: .periodToken(),
269+
component: KeyPathComponentSyntax.Component(
270+
KeyPathSubscriptComponentSyntax(
271+
arguments: LabeledExprListSyntax([LabeledExprSyntax(expression: ExprSyntax("2"))])
272+
)
273+
)
274+
)
275+
])
276+
)
277+
)
278+
279+
assertParse(
280+
#"\Foo.Bar.[2]"#,
281+
substructure: KeyPathExprSyntax(
282+
root: TypeSyntax("Foo"),
283+
components: KeyPathComponentListSyntax([
284+
KeyPathComponentSyntax(
285+
period: .periodToken(),
286+
component: KeyPathComponentSyntax.Component(
287+
KeyPathPropertyComponentSyntax(declName: DeclReferenceExprSyntax(baseName: .identifier("Bar")))
288+
)
289+
),
290+
KeyPathComponentSyntax(
291+
period: .periodToken(),
292+
component: KeyPathComponentSyntax.Component(
293+
KeyPathSubscriptComponentSyntax(
294+
arguments: LabeledExprListSyntax([LabeledExprSyntax(expression: ExprSyntax("2"))])
295+
)
296+
)
297+
),
298+
])
299+
)
300+
)
301+
302+
assertParse(
303+
#"\Foo.Bar.[2].1️⃣[1]"#,
304+
diagnostics: [
305+
DiagnosticSpec(message: "expected identifier in key path property component", fixIts: ["insert identifier"])
306+
],
307+
fixedSource: #"\Foo.Bar.[2].<#identifier#>[1]"#
308+
)
309+
310+
assertParse(
311+
#"\Foo.Bar.?.1️⃣[1]"#,
312+
diagnostics: [
313+
DiagnosticSpec(message: "expected identifier in key path property component", fixIts: ["insert identifier"])
314+
],
315+
fixedSource: #"\Foo.Bar.?.<#identifier#>[1]"#
316+
)
317+
}
318+
319+
func testChainedOptionalUnwrapsWithDot() {
320+
assertParse(
321+
#"\T.?1️⃣.!"#,
322+
diagnostics: [DiagnosticSpec(message: "extraneous code '.!' at top level")]
323+
)
324+
}
325+
326+
func testChainedOptionalUnwrapsAfterSubscript() {
327+
assertParse(
328+
#"\T.abc[2]1️⃣.?"#,
329+
diagnostics: [DiagnosticSpec(message: "extraneous code '.?' at top level")]
330+
)
331+
}
332+
333+
func testKeyPathFollowedByOperator() {
334+
// The following is valid Swift. Make sure we parse it as such.
335+
//
336+
// struct Foo {
337+
// var bar: Int?
338+
// }
339+
//
340+
// infix operator .?.
341+
//
342+
// func .?.(_ x: AnyKeyPath, _ a: Int) {}
343+
//
344+
// var blah = 2
345+
// \Foo?.?.bar.?.blah
346+
// \Foo?.?.?.blah
347+
348+
assertParse(
349+
#"\Foo?.?.bar.?.blah"#,
350+
substructure: SequenceExprSyntax(
351+
elements: ExprListSyntax([
352+
ExprSyntax(
353+
KeyPathExprSyntax(
354+
root: TypeSyntax("Foo?"),
355+
components: [
356+
KeyPathComponentSyntax(
357+
period: .periodToken(),
358+
component: .optional(
359+
KeyPathOptionalComponentSyntax(questionOrExclamationMark: .postfixQuestionMarkToken())
360+
)
361+
),
362+
KeyPathComponentSyntax(
363+
period: .periodToken(),
364+
component: .property(
365+
KeyPathPropertyComponentSyntax(declName: DeclReferenceExprSyntax(baseName: "bar"))
366+
)
367+
),
368+
]
369+
)
370+
),
371+
ExprSyntax(BinaryOperatorExprSyntax(operator: .binaryOperator(".?."))),
372+
ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("blah"))),
373+
])
374+
)
375+
)
376+
assertParse(
377+
#"\Foo?.?.?.blah"#,
378+
substructure: SequenceExprSyntax(
379+
elements: ExprListSyntax([
380+
ExprSyntax(
381+
KeyPathExprSyntax(
382+
root: TypeSyntax("Foo?"),
383+
components: [
384+
KeyPathComponentSyntax(
385+
period: .periodToken(),
386+
component: .optional(
387+
KeyPathOptionalComponentSyntax(questionOrExclamationMark: .postfixQuestionMarkToken())
388+
)
389+
)
390+
]
391+
)
392+
),
393+
ExprSyntax(BinaryOperatorExprSyntax(operator: .binaryOperator(".?."))),
394+
ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("blah"))),
395+
])
396+
)
397+
)
398+
}
399+
257400
func testKeypathExpressionWithSugaredRoot() {
258401
let cases: [UInt: String] = [
259402
// Identifiers

0 commit comments

Comments
 (0)