Skip to content

Commit c044bdf

Browse files
committed
Keep Track of Whether We're Under a Let or a Var
The legacy parser maintains a stack of contexts that determine whether a matching pattern is being parsed, and whether a let or var is the introducer. This turns on and off a particular production in expression parsing that helps to meld patterns like `Optional<String>.none` into shape depending on whether it comes after the keyword "case" or after "let" or "var".
1 parent 87f0d2e commit c044bdf

File tree

5 files changed

+97
-19
lines changed

5 files changed

+97
-19
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,26 @@ extension Parser {
3434
}
3535

3636
public enum PatternContext {
37+
/// There is no ambient pattern context.
3738
case none
39+
/// We're parsing a matching pattern that is not introduced via `let` or `var`.
40+
///
41+
/// In this context, identifiers are references to the enclosing scopes, not a variable binding.
42+
///
43+
/// ```
44+
/// case x.y <- 'x' must refer to some 'x' defined in another scope, it cannot be e.g. an enum type.
45+
/// ```
3846
case matching
39-
case `var`
40-
case `let`
41-
case implicitlyImmutable
47+
/// We're parsing a matching pattern that is introduced via `let` or `var`.
48+
///
49+
/// ```
50+
/// case let x.y <- 'x' must refer to the base of some member access, y must refer to some pattern-compatible identfier
51+
/// ```
52+
case letOrVar
4253

4354
var admitsBinding: Bool {
4455
switch self {
45-
case .implicitlyImmutable, .var, .let:
56+
case .letOrVar:
4657
return true
4758
case .none, .matching:
4859
return false
@@ -65,8 +76,8 @@ extension Parser {
6576
//
6677
// Only do this if we're parsing a pattern, to improve QoI on malformed
6778
// expressions followed by (e.g.) let/var decls.
68-
if self.at(anyIn: MatchingPatternStart.self) != nil {
69-
let pattern = self.parseMatchingPattern()
79+
if pattern != .none, self.at(anyIn: MatchingPatternStart.self) != nil {
80+
let pattern = self.parseMatchingPattern(context: .matching)
7081
return RawExprSyntax(RawUnresolvedPatternExprSyntax(pattern: pattern, arena: self.arena))
7182
}
7283
return RawExprSyntax(self.parseSequenceExpression(flavor, pattern: pattern))
@@ -261,9 +272,9 @@ extension Parser {
261272

262273
case (.equal, let handle)?:
263274
switch pattern {
264-
case .matching, .let, .var:
275+
case .matching, .letOrVar:
265276
return nil
266-
case .none, .implicitlyImmutable:
277+
case .none:
267278
let eq = self.eat(handle)
268279
let op = RawAssignmentExprSyntax(
269280
assignToken: eq,

Sources/SwiftParser/Patterns.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ extension Parser {
7272
case .varKeyword: return .varKeyword
7373
}
7474
}
75-
7675
}
7776

7877
switch self.at(anyIn: ExpectedTokens.self) {
@@ -171,13 +170,13 @@ extension Parser {
171170
extension Parser {
172171
/// Parse a pattern that appears immediately under syntax for conditionals like
173172
/// for-in loops and guard clauses.
174-
mutating func parseMatchingPattern() -> RawPatternSyntax {
173+
mutating func parseMatchingPattern(context: PatternContext) -> RawPatternSyntax {
175174
// Parse productions that can only be patterns.
176175
switch self.at(anyIn: MatchingPatternStart.self) {
177176
case (.varKeyword, let handle)?,
178-
(.letKeyword, let handle)?:
177+
(.letKeyword, let handle)?:
179178
let letOrVar = self.eat(handle)
180-
let value = self.parseMatchingPattern()
179+
let value = self.parseMatchingPattern(context: .letOrVar)
181180
return RawPatternSyntax(RawValueBindingPatternSyntax(
182181
letOrVarKeyword: letOrVar, valuePattern: value, arena: self.arena))
183182
case (.isKeyword, let handle)?:
@@ -192,7 +191,7 @@ extension Parser {
192191
// matching-pattern ::= expr
193192
// Fall back to expression parsing for ambiguous forms. Name lookup will
194193
// disambiguate.
195-
let patternSyntax = self.parseSequenceExpression(.basic, pattern: .matching)
194+
let patternSyntax = self.parseSequenceExpression(.basic, pattern: context)
196195
if let pat = patternSyntax.as(RawUnresolvedPatternExprSyntax.self) {
197196
// The most common case here is to parse something that was a lexically
198197
// obvious pattern, which will come back wrapped in an immediate

Sources/SwiftParser/Statements.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,11 @@ extension Parser {
261261

262262
let kind: BindingKind
263263
if let caseKeyword = self.consume(if: .caseKeyword) {
264-
let pattern = self.parseMatchingPattern()
264+
let pattern = self.parseMatchingPattern(context: .matching)
265265
kind = .pattern(caseKeyword, pattern)
266266
} else {
267267
let letOrVar = self.consumeAnyToken()
268-
let pattern = self.parseMatchingPattern()
268+
let pattern = self.parseMatchingPattern(context: .letOrVar)
269269
kind = .optional(letOrVar, pattern)
270270
}
271271

@@ -563,7 +563,7 @@ extension Parser {
563563
let pattern: RawPatternSyntax
564564
let type: RawTypeAnnotationSyntax?
565565
if caseKeyword != nil {
566-
pattern = self.parseMatchingPattern()
566+
pattern = self.parseMatchingPattern(context: .matching)
567567
// Now parse an optional type annotation.
568568
if let colon = self.consume(if: .colon) {
569569
let resultType = self.parseType()
@@ -827,7 +827,7 @@ extension Parser {
827827
flavor = .basic
828828
}
829829

830-
let pattern = self.parseMatchingPattern()
830+
let pattern = self.parseMatchingPattern(context: .matching)
831831

832832
// Parse the optional 'where' guard, with this particular pattern's bound
833833
// vars in scope.

Tests/SwiftParserTest/Statements.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,72 @@ final class StatementTests: XCTestCase {
288288
))
289289
)
290290
}
291+
292+
func testCaseContext() {
293+
AssertParse(
294+
"""
295+
graphQLMap["clientMutationId"] as? #^SPECIALIZATION^#Swift.Optional<String?> ?? Swift.Optional<String?>.none
296+
""",
297+
// 0: MemberTypeIdentifierSyntax children=4
298+
// 0: SimpleTypeIdentifierSyntax children=1
299+
// 0: identifier("Swift")
300+
// 1: period
301+
// 2: identifier("Optional")
302+
// 3: GenericArgumentClauseSyntax children=3
303+
// 0: leftAngle
304+
// 1: GenericArgumentListSyntax children=1
305+
// 0: GenericArgumentSyntax children=1
306+
// 0: OptionalTypeSyntax children=2
307+
// 0: SimpleTypeIdentifierSyntax children=1
308+
// 0: identifier("String")
309+
// 1: postfixQuestionMark
310+
// 2: rightAngle
311+
substructure: Syntax(MemberTypeIdentifierSyntax(
312+
baseType: TypeSyntax(SimpleTypeIdentifierSyntax(
313+
name: .identifier("Swift"),
314+
genericArgumentClause: nil)),
315+
period: .periodToken(),
316+
name: .identifier("Optional"),
317+
genericArgumentClause: GenericArgumentClauseSyntax(
318+
leftAngleBracket: .leftAngleToken(),
319+
arguments: GenericArgumentListSyntax([
320+
GenericArgumentSyntax(
321+
argumentType: TypeSyntax(OptionalTypeSyntax(
322+
wrappedType: TypeSyntax(SimpleTypeIdentifierSyntax(
323+
name: .identifier("String"),
324+
genericArgumentClause: nil)),
325+
questionMark: .postfixQuestionMarkToken())),
326+
trailingComma: nil)
327+
]),
328+
rightAngleBracket: .rightAngleToken()))),
329+
substructureAfterMarker: "SPECIALIZATION")
330+
331+
AssertParse(
332+
"""
333+
if case #^SPECIALIZATION^#Optional<Any>.none = object["anyCol"] { }
334+
""",
335+
// 0: SpecializeExprSyntax children=2
336+
// 0: IdentifierExprSyntax children=1
337+
// 0: identifier("Optional")
338+
// 1: GenericArgumentClauseSyntax children=3
339+
// 0: leftAngle
340+
// 1: GenericArgumentListSyntax children=1
341+
// 0: GenericArgumentSyntax children=1
342+
// 0: SimpleTypeIdentifierSyntax children=1
343+
// 0: anyKeyword
344+
// 2: rightAngle
345+
// 1: period
346+
// 2: identifier("none")
347+
substructure: Syntax(SpecializeExprSyntax(
348+
expression: ExprSyntax(IdentifierExprSyntax(
349+
identifier: .identifier("Optional"), declNameArguments: nil)),
350+
genericArgumentClause: GenericArgumentClauseSyntax(
351+
leftAngleBracket: .leftAngleToken(), arguments: GenericArgumentListSyntax([
352+
GenericArgumentSyntax(
353+
argumentType: TypeSyntax(SimpleTypeIdentifierSyntax(
354+
name: .anyKeyword(), genericArgumentClause: nil)),
355+
trailingComma: nil)
356+
]), rightAngleBracket: .rightAngleToken()))),
357+
substructureAfterMarker: "SPECIALIZATION")
358+
}
291359
}

Tests/SwiftParserTest/translated/InvalidTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,10 @@ final class InvalidTests: XCTestCase {
273273
DiagnosticSpec(locationMarker: "DIAG_12", message: "expected identifier in function"),
274274
DiagnosticSpec(locationMarker: "DIAG_12", message: "expected argument list in function declaration"),
275275
DiagnosticSpec(locationMarker: "DIAG_13", message: "unexpected text '()' in 'repeat' statement"),
276-
DiagnosticSpec(locationMarker: "DIAG_14", message: "expected 'while' in 'repeat' statement"),
276+
DiagnosticSpec(locationMarker: "DIAG_14", message: "expected 'while' and condition in 'repeat' statement"),
277277
// TODO: Old parser expected error on line 30: keyword 'for' cannot be used as an identifier here
278278
// TODO: Old parser expected note on line 30: if this name is unavoidable, use backticks to escape it, Fix-It replacements: 5 - 8 = '`for`'
279-
DiagnosticSpec(locationMarker: "DIAG_15", message: "expected expression in pattern"),
279+
DiagnosticSpec(locationMarker: "DIAG_15", message: "expected pattern in variable"),
280280
DiagnosticSpec(locationMarker: "DIAG_16", message: "expected pattern, 'in' and expression in 'for' statement"),
281281
DiagnosticSpec(locationMarker: "DIAG_16", message: "expected '{' in 'for' statement"),
282282
DiagnosticSpec(locationMarker: "DIAG_16", message: "unexpected text '= 2' before function"),

0 commit comments

Comments
 (0)