Skip to content

Commit 908a848

Browse files
committed
Parse consuming and borrowing modifiers from SE-0377
And treat them, along with the existing modifiers such as `__shared` and friends, as contextual keywords, since we should never have really reserved them as argument names.
1 parent cc8fc77 commit 908a848

File tree

11 files changed

+116
-16
lines changed

11 files changed

+116
-16
lines changed

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@ public let KEYWORDS: [KeywordSpec] = [
9999
KeywordSpec("await"),
100100
KeywordSpec("before"),
101101
KeywordSpec("block"),
102+
KeywordSpec("borrowing"),
102103
KeywordSpec("break", isLexerClassified: true, requiresTrailingSpace: true),
103104
KeywordSpec("case", isLexerClassified: true, requiresTrailingSpace: true),
104105
KeywordSpec("catch", isLexerClassified: true, requiresLeadingSpace: true),
105106
KeywordSpec("class", isLexerClassified: true, requiresTrailingSpace: true),
107+
KeywordSpec("consuming"),
106108
KeywordSpec("continue", isLexerClassified: true, requiresTrailingSpace: true),
107109
KeywordSpec("convenience"),
108110
KeywordSpec("convention"),

CodeGeneration/Sources/SyntaxSupport/gyb_generated/TypeNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public let TYPE_NODES: [Node] = [
245245
],
246246
children: [
247247
Child(name: "Specifier",
248-
kind: .token(choices: [.keyword(text: "inout"), .keyword(text: "__shared"), .keyword(text: "__owned"), .keyword(text: "isolated"), .keyword(text: "_const")]),
248+
kind: .token(choices: [.keyword(text: "inout"), .keyword(text: "__shared"), .keyword(text: "__owned"), .keyword(text: "isolated"), .keyword(text: "_const"), .keyword(text: "borrowing"), .keyword(text: "consuming")]),
249249
isOptional: true),
250250
Child(name: "Attributes",
251251
kind: .collection(kind: "AttributeList", collectionElementName: "Attribute"),

Sources/SwiftParser/Declarations.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,8 +1102,11 @@ extension Parser {
11021102
let modifiers = parseParameterModifiers(for: subject)
11031103

11041104
var misplacedSpecifiers: [RawTokenSyntax] = []
1105-
while let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
1106-
misplacedSpecifiers.append(specifier)
1105+
if self.withLookahead({ !$0.startsParameterName(isClosure: subject.isClosure, allowMisplacedSpecifierRecovery: false) }) {
1106+
while canHaveParameterSpecifier,
1107+
let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
1108+
misplacedSpecifiers.append(specifier)
1109+
}
11071110
}
11081111

11091112
let unexpectedBeforeFirstName: RawUnexpectedNodesSyntax?

Sources/SwiftParser/Names.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,10 @@ extension Parser.Lookahead {
259259

260260
extension Lexer.Lexeme {
261261
func canBeArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
262-
if TypeSpecifier(lexeme: self) != nil {
263-
return false
264-
}
265262
switch self.rawTokenKind {
263+
// `inout` is reserved as an argument label for historical reasons.
264+
case .keyword(.inout):
265+
return false
266266
case .identifier, .wildcard:
267267
// Identifiers, escaped identifiers, and '_' can be argument labels.
268268
return true

Sources/SwiftParser/Patterns.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,8 @@ extension Parser.Lookahead {
356356
/// specifiers before checking whether this lookahead starts a parameter name.
357357
mutating func startsParameterName(isClosure: Bool, allowMisplacedSpecifierRecovery: Bool) -> Bool {
358358
if allowMisplacedSpecifierRecovery {
359-
while self.consume(ifAnyIn: TypeSpecifier.self) != nil {}
359+
while canHaveParameterSpecifier,
360+
self.consume(ifAnyIn: TypeSpecifier.self) != nil {}
360361
}
361362

362363
// To have a parameter name here, we need a name.
@@ -372,12 +373,20 @@ extension Parser.Lookahead {
372373

373374
// If the next token can be an argument label, we might have a name.
374375
if nextTok.canBeArgumentLabel(allowDollarIdentifier: true) {
375-
// If the first name wasn't "isolated", we're done.
376-
if !self.at(.keyword(.isolated)) && !self.at(.keyword(.some)) && !self.at(.keyword(.any)) && !self.at(.keyword(.each)) && !self.at(.keyword(.repeat)) {
376+
// If the first name wasn't a contextual keyword, we're done.
377+
if !self.at(.keyword(.isolated))
378+
&& !self.at(.keyword(.some))
379+
&& !self.at(.keyword(.any))
380+
&& !self.at(.keyword(.each))
381+
&& !self.at(.keyword(.repeat))
382+
&& !self.at(.keyword(.__shared))
383+
&& !self.at(.keyword(.__owned))
384+
&& !self.at(.keyword(.borrowing))
385+
&& !self.at(.keyword(.consuming)) {
377386
return true
378387
}
379388

380-
// "isolated" can be an argument label, but it's also a contextual keyword,
389+
// Parameter specifiers can be an argument label, but it's also a contextual keyword,
381390
// so look ahead one more token (two total) see if we have a ':' that would
382391
// indicate that this is an argument label.
383392
do {

Sources/SwiftParser/RawTokenKindSubset.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,16 @@ public enum TypeSpecifier: RawTokenKindSubset {
525525
case inoutKeyword
526526
case owned
527527
case shared
528+
case borrowing
529+
case consuming
528530

529531
init?(lexeme: Lexer.Lexeme) {
530532
switch lexeme {
531533
case RawTokenKindMatch(.inout): self = .inoutKeyword
532534
case RawTokenKindMatch(.__owned): self = .owned
533535
case RawTokenKindMatch(.__shared): self = .shared
536+
case RawTokenKindMatch(.consuming): self = .consuming
537+
case RawTokenKindMatch(.borrowing): self = .borrowing
534538
default: return nil
535539
}
536540
}
@@ -540,6 +544,8 @@ public enum TypeSpecifier: RawTokenKindSubset {
540544
case RawTokenKindMatch(.inout): self = .inoutKeyword
541545
case RawTokenKindMatch(.__owned): self = .owned
542546
case RawTokenKindMatch(.__shared): self = .shared
547+
case RawTokenKindMatch(.consuming): self = .consuming
548+
case RawTokenKindMatch(.borrowing): self = .borrowing
543549
default: return nil
544550
}
545551
}
@@ -549,6 +555,8 @@ public enum TypeSpecifier: RawTokenKindSubset {
549555
case .inoutKeyword: return .keyword(.inout)
550556
case .owned: return .keyword(.__owned)
551557
case .shared: return .keyword(.__shared)
558+
case .borrowing: return .keyword(.borrowing)
559+
case .consuming: return .keyword(.consuming)
552560
}
553561
}
554562
}

Sources/SwiftParser/TokenConsumer.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,23 @@ extension TokenConsumer {
274274
}
275275
return missingToken(.identifier, text: nil)
276276
}
277+
278+
var canHaveParameterSpecifier: Bool {
279+
// The parameter specifiers like `isolated`, `consuming`, `borrowing` are
280+
// also valid identifiers and could be the name of a type. Check whether
281+
// the following token is something that can introduce a type (which,
282+
// thankfully, doesn't overlap with the set of tokens that can continue
283+
// a type production), in which case the current token is interpretable
284+
// as a parameter specifier.
285+
return peek().rawTokenKind.is(any: [.atSign,
286+
.keyword(.inout),
287+
.leftParen,
288+
.identifier,
289+
.leftSquareBracket,
290+
.keyword(.Any),
291+
.keyword(.Self),
292+
.wildcard,
293+
.keyword(.var),
294+
.keyword(.let)])
295+
}
277296
}

Sources/SwiftParser/Types.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,8 @@ extension Parser {
495495
let colon: RawTokenSyntax?
496496
var misplacedSpecifiers: [RawTokenSyntax] = []
497497
if self.withLookahead({ $0.startsParameterName(isClosure: false, allowMisplacedSpecifierRecovery: true) }) {
498-
while let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
498+
while canHaveParameterSpecifier,
499+
let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
499500
misplacedSpecifiers.append(specifier)
500501
}
501502
(unexpectedBeforeFirst, first) = self.parseArgumentLabel()
@@ -657,7 +658,8 @@ extension Parser.Lookahead {
657658
mutating func skipTypeAttributeList() {
658659
var specifierProgress = LoopProgressCondition()
659660
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
660-
while self.at(anyIn: TypeSpecifier.self) != nil || self.at(.keyword(.isolated)) || self.at(.keyword(._const)),
661+
while canHaveParameterSpecifier,
662+
self.at(anyIn: TypeSpecifier.self) != nil || self.at(.keyword(.isolated)) || self.at(.keyword(._const)),
661663
specifierProgress.evaluate(currentToken)
662664
{
663665
self.consumeAnyToken()
@@ -923,13 +925,17 @@ extension Parser {
923925
public mutating func parseTypeAttributeList(misplacedSpecifiers: [RawTokenSyntax] = []) -> (
924926
specifier: RawTokenSyntax?, unexpectedBeforeAttributes: RawUnexpectedNodesSyntax?, attributes: RawAttributeListSyntax?
925927
) {
926-
var specifier: RawTokenSyntax? = self.consume(ifAnyIn: TypeSpecifier.self)
928+
var specifier: RawTokenSyntax? = nil
929+
if canHaveParameterSpecifier {
930+
specifier = self.consume(ifAnyIn: TypeSpecifier.self)
931+
}
927932
// We can only stick one specifier on this type. Let's pick the first one
928933
if specifier == nil, let misplacedSpecifier = misplacedSpecifiers.first {
929934
specifier = missingToken(misplacedSpecifier.tokenKind, text: misplacedSpecifier.tokenText)
930935
}
931936
var extraneousSpecifiers: [RawTokenSyntax] = []
932-
while let extraSpecifier = self.consume(ifAny: [.keyword(.inout), .keyword(.__shared), .keyword(.__owned), .keyword(.isolated), .keyword(._const)]) {
937+
while canHaveParameterSpecifier,
938+
let extraSpecifier = self.consume(ifAny: [.keyword(.inout), .keyword(.__shared), .keyword(.__owned), .keyword(.isolated), .keyword(._const)]) {
933939
if specifier == nil {
934940
specifier = extraSpecifier
935941
} else {

Sources/SwiftSyntax/generated/Keyword.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ public enum Keyword: UInt8, Hashable {
148148
case before
149149

150150
case block
151+
152+
case borrowing
151153

152154
case `break`
153155

@@ -157,6 +159,8 @@ public enum Keyword: UInt8, Hashable {
157159

158160
case `class`
159161

162+
case consuming
163+
160164
case `continue`
161165

162166
case convenience
@@ -700,6 +704,10 @@ public enum Keyword: UInt8, Hashable {
700704
self = ._optimize
701705
case "available":
702706
self = .available
707+
case "borrowing":
708+
self = .borrowing
709+
case "consuming":
710+
self = .consuming
703711
case "extension":
704712
self = .`extension`
705713
case "lowerThan":

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,51 @@ final class DeclarationTests: XCTestCase {
14081408
"""
14091409
)
14101410
}
1411+
1412+
func testBorrowingConsumingParameterSpecifiers() {
1413+
AssertParse(
1414+
"""
1415+
struct borrowing {}
1416+
struct consuming {}
1417+
1418+
struct Foo {}
1419+
1420+
func foo(x: borrowing Foo) {}
1421+
func bar(x: consuming Foo) {}
1422+
func baz(x: (borrowing Foo, consuming Foo) -> ()) {}
1423+
1424+
// `borrowing` and `consuming` are contextual keywords, so they should also
1425+
// continue working as type and/or parameter names
1426+
1427+
func zim(x: borrowing) {}
1428+
func zang(x: consuming) {}
1429+
func zung(x: borrowing consuming) {}
1430+
func zip(x: consuming borrowing) {}
1431+
func zap(x: (borrowing, consuming) -> ()) {}
1432+
func zoop(x: (borrowing consuming, consuming borrowing) -> ()) {}
1433+
1434+
// Parameter specifier names are regular identifiers in other positions,
1435+
// including argument labels.
1436+
1437+
func argumentLabelOnly(borrowing: Int) {}
1438+
func argumentLabelOnly(consuming: Int) {}
1439+
func argumentLabelOnly(__shared: Int) {}
1440+
func argumentLabelOnly(__owned: Int) {}
1441+
1442+
func argumentLabel(borrowing consuming: Int) {}
1443+
func argumentLabel(consuming borrowing: Int) {}
1444+
func argumentLabel(__shared __owned: Int) {}
1445+
func argumentLabel(__owned __shared: Int) {}
1446+
1447+
// We should parse them as argument labels in function types, even though that
1448+
// isn't currently supported.
1449+
1450+
func argumentLabel(anonBorrowingInClosure: (_ borrowing: Int) -> ()) {}
1451+
func argumentLabel(anonConsumingInClosure: (_ consuming: Int) -> ()) {}
1452+
func argumentLabel(anonSharedInClosure: (_ __shared: Int) -> ()) {}
1453+
func argumentLabel(anonOwnedInClosure: (_ __owned: Int) -> ()) {}
1454+
""")
1455+
}
14111456
}
14121457

14131458
extension Parser.DeclAttributes {

gyb_syntax_support/TypeNodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@
183183
]),
184184

185185
# attributed-type -> type-specifier? attribute-list? type
186-
# type-specifier -> 'inout' | '__owned' | '__unowned'
186+
# type-specifier -> 'inout' | 'borrowing' | 'consuming' | '__owned' | '__shared'
187187
Node('AttributedType', name_for_diagnostics='type', kind='Type',
188188
traits=['Attributed'],
189189
children=[
190190
Child('Specifier', kind='Token',
191-
token_choices=['KeywordToken|inout', 'KeywordToken|__shared', 'KeywordToken|__owned', 'KeywordToken|isolated', 'KeywordToken|_const'],
191+
token_choices=['KeywordToken|inout', 'KeywordToken|__shared', 'KeywordToken|__owned', 'KeywordToken|isolated', 'KeywordToken|_const', 'KeywordToken|borrowing', 'KeywordToken|consuming'],
192192
is_optional=True),
193193
Child('Attributes', kind='AttributeList',
194194
collection_element_name='Attribute', is_optional=True),

0 commit comments

Comments
 (0)