Skip to content

Commit 38ff630

Browse files
authored
Merge pull request #2487 from jckarter/borrowing-switch-contextual-keyword
Parse `_borrowing x` contextually as a pattern binding when `.borrowingSwitch` feature is enabled.
2 parents f38bac3 + 43c13ca commit 38ff630

File tree

8 files changed

+225
-32
lines changed

8 files changed

+225
-32
lines changed

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public enum ExperimentalFeature: String, CaseIterable {
1818
case doExpressions
1919
case nonescapableTypes
2020
case transferringArgsAndResults
21+
case borrowingSwitch
2122

2223
/// The name of the feature, which is used in the doc comment.
2324
public var featureName: String {
@@ -32,6 +33,8 @@ public enum ExperimentalFeature: String, CaseIterable {
3233
return "NonEscableTypes"
3334
case .transferringArgsAndResults:
3435
return "TransferringArgsAndResults"
36+
case .borrowingSwitch:
37+
return "borrowing pattern matching"
3538
}
3639
}
3740

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public struct KeywordSpec {
1919
/// The experimental feature the keyword is part of, or `nil` if this isn't
2020
/// for an experimental feature.
2121
public let experimentalFeature: ExperimentalFeature?
22+
public let experimentalFeature2: ExperimentalFeature?
2223

2324
/// Indicates if the token kind is switched from being an identifier to a keyword in the lexer.
2425
public let isLexerClassified: Bool
@@ -59,6 +60,26 @@ public struct KeywordSpec {
5960
) {
6061
self.name = name
6162
self.experimentalFeature = experimentalFeature
63+
self.experimentalFeature2 = nil
64+
self.isLexerClassified = isLexerClassified
65+
}
66+
67+
/// Initializes a new `KeywordSpec` instance.
68+
///
69+
/// - Parameters:
70+
/// - name: A name of the keyword.
71+
/// - experimentalFeature: The experimental feature the keyword is part of, or `nil` if this isn't for an experimental feature.
72+
/// - or: A second experimental feature the keyword is also part of, or `nil` if this isn't for an experimental feature.
73+
/// - isLexerClassified: Indicates if the token kind is switched from being an identifier to a keyword in the lexer.
74+
init(
75+
_ name: String,
76+
experimentalFeature: ExperimentalFeature,
77+
or experimentalFeature2: ExperimentalFeature,
78+
isLexerClassified: Bool = false
79+
) {
80+
self.name = name
81+
self.experimentalFeature = experimentalFeature
82+
self.experimentalFeature2 = experimentalFeature2
6283
self.isLexerClassified = isLexerClassified
6384
}
6485
}
@@ -723,7 +744,7 @@ public enum Keyword: CaseIterable {
723744
case .yield:
724745
return KeywordSpec("yield")
725746
case ._borrowing:
726-
return KeywordSpec("_borrowing", experimentalFeature: .referenceBindings)
747+
return KeywordSpec("_borrowing", experimentalFeature: .referenceBindings, or: .borrowingSwitch)
727748
case ._consuming:
728749
return KeywordSpec("_consuming", experimentalFeature: .referenceBindings)
729750
case ._mutating:

CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import SwiftSyntaxBuilder
1515
import SyntaxSupport
1616
import Utils
1717

18-
func tokenCaseMatch(_ caseName: TokenSyntax, experimentalFeature: ExperimentalFeature?) -> SwitchCaseSyntax {
19-
let whereClause =
20-
experimentalFeature.map {
21-
"where experimentalFeatures.contains(.\($0.token))"
22-
} ?? ""
18+
func tokenCaseMatch(_ caseName: TokenSyntax, experimentalFeature: ExperimentalFeature?, experimentalFeature2: ExperimentalFeature?) -> SwitchCaseSyntax {
19+
var whereClause = ""
20+
if let feature = experimentalFeature {
21+
whereClause += "where experimentalFeatures.contains(.\(feature.token))"
22+
if let feature2 = experimentalFeature2 {
23+
whereClause += " || experimentalFeatures.contains(.\(feature2.token))"
24+
}
25+
}
2326
return "case TokenSpec(.\(caseName))\(raw: whereClause): self = .\(caseName)"
2427
}
2528

@@ -57,12 +60,14 @@ let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
5760
case .keyword(let keyword):
5861
tokenCaseMatch(
5962
keyword.spec.varOrCaseName,
60-
experimentalFeature: keyword.spec.experimentalFeature
63+
experimentalFeature: keyword.spec.experimentalFeature,
64+
experimentalFeature2: keyword.spec.experimentalFeature2
6165
)
6266
case .token(let token):
6367
tokenCaseMatch(
6468
token.spec.varOrCaseName,
65-
experimentalFeature: token.spec.experimentalFeature
69+
experimentalFeature: token.spec.experimentalFeature,
70+
experimentalFeature2: nil
6671
)
6772
}
6873
}

Sources/SwiftParser/Expressions.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,21 @@ extension Parser {
122122
//
123123
// Only do this if we're parsing a pattern, to improve QoI on malformed
124124
// expressions followed by (e.g.) let/var decls.
125-
if pattern != .none, self.at(anyIn: MatchingPatternStart.self) != nil {
126-
let pattern = self.parseMatchingPattern(context: .matching)
127-
return RawExprSyntax(RawPatternExprSyntax(pattern: pattern, arena: self.arena))
125+
if pattern != .none {
126+
switch self.at(anyIn: MatchingPatternStart.self) {
127+
case (spec: .rhs(let bindingIntroducer), handle: _)? where self.withLookahead { $0.shouldParsePatternBinding(introducer: bindingIntroducer) }:
128+
fallthrough
129+
case (spec: .lhs(_), handle: _)?:
130+
let pattern = self.parseMatchingPattern(context: .matching)
131+
return RawExprSyntax(RawPatternExprSyntax(pattern: pattern, arena: self.arena))
132+
133+
// If the token can't start a pattern, or contextually isn't parsed as a
134+
// binding introducer, handle as the start of a normal expression.
135+
case (spec: .rhs(_), handle: _)?,
136+
nil:
137+
// Not a pattern. Break out to parse as a normal expression below.
138+
break
139+
}
128140
}
129141
return RawExprSyntax(self.parseSequenceExpression(flavor: flavor, pattern: pattern))
130142
}

Sources/SwiftParser/Patterns.swift

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,19 @@ extension Parser {
6767
arena: self.arena
6868
)
6969
)
70-
case (.lhs(.identifier), let handle)?:
70+
case (.rhs(let introducer), let handle)? where self.withLookahead { $0.shouldParsePatternBinding(introducer: introducer) }:
71+
let bindingSpecifier = self.eat(handle)
72+
let value = self.parsePattern()
73+
return RawPatternSyntax(
74+
RawValueBindingPatternSyntax(
75+
bindingSpecifier: bindingSpecifier,
76+
pattern: value,
77+
arena: self.arena
78+
)
79+
)
80+
case (.lhs(.identifier), let handle)?,
81+
// If we shouldn't contextually parse a pattern binding introducer (because the previous pattern match guard failed), then parse it as an identifier.
82+
(.rhs(_), let handle)?:
7183
let identifier = self.eat(handle)
7284
return RawPatternSyntax(
7385
RawIdentifierPatternSyntax(
@@ -85,16 +97,6 @@ extension Parser {
8597
arena: self.arena
8698
)
8799
)
88-
case (.rhs, let handle)?:
89-
let bindingSpecifier = self.eat(handle)
90-
let value = self.parsePattern()
91-
return RawPatternSyntax(
92-
RawValueBindingPatternSyntax(
93-
bindingSpecifier: bindingSpecifier,
94-
pattern: value,
95-
arena: self.arena
96-
)
97-
)
98100
case nil:
99101
break
100102
}
@@ -218,7 +220,7 @@ extension Parser {
218220
arena: self.arena
219221
)
220222
)
221-
case (.rhs, let handle)?:
223+
case (.rhs(let introducer), let handle)? where self.withLookahead { $0.shouldParsePatternBinding(introducer: introducer) }:
222224
let bindingSpecifier = self.eat(handle)
223225
let value = self.parseMatchingPattern(context: .bindingIntroducer)
224226
return RawPatternSyntax(
@@ -228,7 +230,8 @@ extension Parser {
228230
arena: self.arena
229231
)
230232
)
231-
case nil:
233+
case (.rhs(_), _)?,
234+
nil:
232235
break
233236
}
234237

@@ -253,6 +256,21 @@ extension Parser {
253256
// MARK: Lookahead
254257

255258
extension Parser.Lookahead {
259+
/// Returns true if we should parse a pattern binding specifier contextually
260+
/// as one.
261+
mutating func shouldParsePatternBinding(introducer: ValueBindingPatternSyntax.BindingSpecifierOptions) -> Bool {
262+
switch introducer {
263+
// TODO: the other ownership modifiers (borrowing/consuming/mutating) more
264+
// than likely need to be made contextual as well before finalizing their
265+
// grammar.
266+
case ._borrowing where experimentalFeatures.contains(.borrowingSwitch):
267+
return peek(isAt: TokenSpec(.identifier, allowAtStartOfLine: false))
268+
default:
269+
// Other keywords can be parsed unconditionally.
270+
return true
271+
}
272+
}
273+
256274
/// pattern ::= identifier
257275
/// pattern ::= '_'
258276
/// pattern ::= pattern-tuple
@@ -288,15 +306,18 @@ extension Parser.Lookahead {
288306
>
289307

290308
switch self.at(anyIn: PatternStartTokens.self) {
291-
case (.lhs(.identifier), let handle)?,
292-
(.lhs(.wildcard), let handle)?:
293-
self.eat(handle)
294-
return true
295309
case (.lhs(.leftParen), _)?:
296310
return self.canParsePatternTuple()
297-
case (.rhs, let handle)?:
311+
case (.rhs(let introducer), let handle)? where shouldParsePatternBinding(introducer: introducer):
312+
// Parse as a binding introducer, like `let x`.
298313
self.eat(handle)
299314
return self.canParsePattern()
315+
case (.lhs(.identifier), let handle)?,
316+
(.lhs(.wildcard), let handle)?,
317+
// If a binding introducer is not contextually introducing a binding, then parse like an identifier.
318+
(.rhs(_), let handle)?:
319+
self.eat(handle)
320+
return true
300321
case nil:
301322
return false
302323
}

Sources/SwiftParser/generated/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@ extension Parser.ExperimentalFeatures {
3838

3939
/// Whether to enable the parsing of TransferringArgsAndResults.
4040
public static let transferringArgsAndResults = Self (rawValue: 1 << 4)
41+
42+
/// Whether to enable the parsing of borrowing pattern matching.
43+
public static let borrowingSwitch = Self (rawValue: 1 << 5)
4144
}

Sources/SwiftParser/generated/Parser+TokenSpecSet.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,7 +2346,7 @@ extension OptionalBindingConditionSyntax {
23462346
self = .inout
23472347
case TokenSpec(._mutating) where experimentalFeatures.contains(.referenceBindings):
23482348
self = ._mutating
2349-
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings):
2349+
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings) || experimentalFeatures.contains(.borrowingSwitch):
23502350
self = ._borrowing
23512351
case TokenSpec(._consuming) where experimentalFeatures.contains(.referenceBindings):
23522352
self = ._consuming
@@ -3020,7 +3020,7 @@ extension ValueBindingPatternSyntax {
30203020
self = .inout
30213021
case TokenSpec(._mutating) where experimentalFeatures.contains(.referenceBindings):
30223022
self = ._mutating
3023-
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings):
3023+
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings) || experimentalFeatures.contains(.borrowingSwitch):
30243024
self = ._borrowing
30253025
case TokenSpec(._consuming) where experimentalFeatures.contains(.referenceBindings):
30263026
self = ._consuming
@@ -3092,7 +3092,7 @@ extension VariableDeclSyntax {
30923092
self = .inout
30933093
case TokenSpec(._mutating) where experimentalFeatures.contains(.referenceBindings):
30943094
self = ._mutating
3095-
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings):
3095+
case TokenSpec(._borrowing) where experimentalFeatures.contains(.referenceBindings) || experimentalFeatures.contains(.borrowingSwitch):
30963096
self = ._borrowing
30973097
case TokenSpec(._consuming) where experimentalFeatures.contains(.referenceBindings):
30983098
self = ._consuming

0 commit comments

Comments
 (0)