Skip to content

Commit 1b8d5e9

Browse files
authored
Merge pull request #1306 from jckarter/se-0377-finalize
Parse `consuming` and `borrowing` modifiers from SE-0377
2 parents 857111a + d25a988 commit 1b8d5e9

File tree

11 files changed

+135
-14
lines changed

11 files changed

+135
-14
lines changed

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,12 @@ public let KEYWORDS: [KeywordSpec] = [
100100
KeywordSpec("backDeployed"),
101101
KeywordSpec("before"),
102102
KeywordSpec("block"),
103+
KeywordSpec("borrowing"),
103104
KeywordSpec("break", isLexerClassified: true, requiresTrailingSpace: true),
104105
KeywordSpec("case", isLexerClassified: true, requiresTrailingSpace: true),
105106
KeywordSpec("catch", isLexerClassified: true, requiresLeadingSpace: true),
106107
KeywordSpec("class", isLexerClassified: true, requiresTrailingSpace: true),
108+
KeywordSpec("consuming"),
107109
KeywordSpec("continue", isLexerClassified: true, requiresTrailingSpace: true),
108110
KeywordSpec("convenience"),
109111
KeywordSpec("convention"),

CodeGeneration/Sources/SyntaxSupport/gyb_generated/TypeNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public let TYPE_NODES: [Node] = [
3333
],
3434
children: [
3535
Child(name: "Specifier",
36-
kind: .token(choices: [.keyword(text: "inout"), .keyword(text: "__shared"), .keyword(text: "__owned"), .keyword(text: "isolated"), .keyword(text: "_const")]),
36+
kind: .token(choices: [.keyword(text: "inout"), .keyword(text: "__shared"), .keyword(text: "__owned"), .keyword(text: "isolated"), .keyword(text: "_const"), .keyword(text: "borrowing"), .keyword(text: "consuming")]),
3737
isOptional: true),
3838
Child(name: "Attributes",
3939
kind: .collection(kind: "AttributeList", collectionElementName: "Attribute"),

Sources/SwiftParser/Declarations.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,8 +1142,12 @@ extension Parser {
11421142
let modifiers = parseParameterModifiers(for: subject)
11431143

11441144
var misplacedSpecifiers: [RawTokenSyntax] = []
1145-
while let specifier = self.consume(ifAnyIn: TypeSpecifier.self) {
1146-
misplacedSpecifiers.append(specifier)
1145+
if self.withLookahead({ !$0.startsParameterName(isClosure: subject.isClosure, allowMisplacedSpecifierRecovery: false) }) {
1146+
while canHaveParameterSpecifier,
1147+
let specifier = self.consume(ifAnyIn: TypeSpecifier.self)
1148+
{
1149+
misplacedSpecifiers.append(specifier)
1150+
}
11471151
}
11481152

11491153
let unexpectedBeforeFirstName: RawUnexpectedNodesSyntax?

Sources/SwiftParser/Names.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,11 @@ extension Parser.Lookahead {
259259

260260
extension Lexer.Lexeme {
261261
func canBeArgumentLabel(allowDollarIdentifier: Bool = false) -> Bool {
262-
if TypeSpecifier(lexeme: self) != nil {
262+
// `inout` is reserved as an argument label for historical reasons.
263+
if TypeSpecifier(lexeme: self) == .inoutKeyword {
263264
return false
264265
}
266+
265267
switch self.rawTokenKind {
266268
case .identifier, .wildcard:
267269
// Identifiers, escaped identifiers, and '_' can be argument labels.

Sources/SwiftParser/Patterns.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,9 @@ 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
361+
{}
360362
}
361363

362364
// To have a parameter name here, we need a name.
@@ -372,12 +374,21 @@ extension Parser.Lookahead {
372374

373375
// If the next token can be an argument label, we might have a name.
374376
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)) {
377+
// If the first name wasn't a contextual keyword, we're done.
378+
if !self.at(.keyword(.isolated))
379+
&& !self.at(.keyword(.some))
380+
&& !self.at(.keyword(.any))
381+
&& !self.at(.keyword(.each))
382+
&& !self.at(.keyword(.repeat))
383+
&& !self.at(.keyword(.__shared))
384+
&& !self.at(.keyword(.__owned))
385+
&& !self.at(.keyword(.borrowing))
386+
&& !self.at(.keyword(.consuming))
387+
{
377388
return true
378389
}
379390

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

Sources/SwiftParser/TokenConsumer.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,30 @@ extension TokenConsumer {
234234
}
235235
return missingToken(.identifier, text: nil)
236236
}
237+
238+
var canHaveParameterSpecifier: Bool {
239+
// The parameter specifiers like `isolated`, `consuming`, `borrowing` are
240+
// also valid identifiers and could be the name of a type. Check whether
241+
// the following token is something that can introduce a type (which,
242+
// thankfully, doesn't overlap with the set of tokens that can continue
243+
// a type production), in which case the current token is interpretable
244+
// as a parameter specifier.
245+
let lexeme = peek()
246+
247+
switch lexeme.rawTokenKind {
248+
case .atSign, .leftParen, .identifier, .leftSquareBracket, .wildcard:
249+
return true
250+
251+
case .keyword:
252+
switch PrepareForKeywordMatch(lexeme) {
253+
case TokenSpec(.inout), TokenSpec(.Any), TokenSpec(.Self), TokenSpec(.var), TokenSpec(.let):
254+
return true
255+
default:
256+
return false
257+
}
258+
259+
default:
260+
return false
261+
}
262+
}
237263
}

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,16 @@ public enum TypeSpecifier: TokenSpecSet {
467467
case inoutKeyword
468468
case owned
469469
case shared
470+
case borrowing
471+
case consuming
470472

471473
init?(lexeme: Lexer.Lexeme) {
472474
switch PrepareForKeywordMatch(lexeme) {
473475
case TokenSpec(.inout): self = .inoutKeyword
474476
case TokenSpec(.__owned): self = .owned
475477
case TokenSpec(.__shared): self = .shared
478+
case TokenSpec(.consuming): self = .consuming
479+
case TokenSpec(.borrowing): self = .borrowing
476480
default: return nil
477481
}
478482
}
@@ -482,6 +486,8 @@ public enum TypeSpecifier: TokenSpecSet {
482486
case TokenSpec(.inout): self = .inoutKeyword
483487
case TokenSpec(.__owned): self = .owned
484488
case TokenSpec(.__shared): self = .shared
489+
case TokenSpec(.consuming): self = .shared
490+
case TokenSpec(.borrowing): self = .shared
485491
default: return nil
486492
}
487493
}
@@ -491,6 +497,8 @@ public enum TypeSpecifier: TokenSpecSet {
491497
case .inoutKeyword: return .keyword(.inout)
492498
case .owned: return .keyword(.__owned)
493499
case .shared: return .keyword(.__shared)
500+
case .borrowing: return .keyword(.borrowing)
501+
case .consuming: return .keyword(.consuming)
494502
}
495503
}
496504
}

Sources/SwiftParser/Types.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,9 @@ 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)
500+
{
499501
misplacedSpecifiers.append(specifier)
500502
}
501503
(unexpectedBeforeFirst, first) = self.parseArgumentLabel()
@@ -657,7 +659,8 @@ extension Parser.Lookahead {
657659
mutating func skipTypeAttributeList() {
658660
var specifierProgress = LoopProgressCondition()
659661
// 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)),
662+
while canHaveParameterSpecifier,
663+
self.at(anyIn: TypeSpecifier.self) != nil || self.at(.keyword(.isolated)) || self.at(.keyword(._const)),
661664
specifierProgress.evaluate(currentToken)
662665
{
663666
self.consumeAnyToken()
@@ -924,7 +927,10 @@ extension Parser {
924927
public mutating func parseTypeAttributeList(misplacedSpecifiers: [RawTokenSyntax] = []) -> (
925928
specifier: RawTokenSyntax?, unexpectedBeforeAttributes: RawUnexpectedNodesSyntax?, attributes: RawAttributeListSyntax?
926929
) {
927-
var specifier: RawTokenSyntax? = self.consume(ifAnyIn: TypeSpecifier.self)
930+
var specifier: RawTokenSyntax? = nil
931+
if canHaveParameterSpecifier {
932+
specifier = self.consume(ifAnyIn: TypeSpecifier.self)
933+
}
928934
// We can only stick one specifier on this type. Let's pick the first one
929935
if specifier == nil, let misplacedSpecifier = misplacedSpecifiers.first {
930936
specifier = missingToken(misplacedSpecifier.tokenKind, text: misplacedSpecifier.tokenText)
@@ -937,6 +943,8 @@ extension Parser {
937943
case __owned
938944
case isolated
939945
case _const
946+
case consuming
947+
case borrowing
940948

941949
var spec: TokenSpec {
942950
switch self {
@@ -945,6 +953,8 @@ extension Parser {
945953
case .__owned: return .keyword(.__owned)
946954
case .isolated: return .keyword(.isolated)
947955
case ._const: return .keyword(._const)
956+
case .consuming: return .keyword(.consuming)
957+
case .borrowing: return .keyword(.borrowing)
948958
}
949959
}
950960

@@ -955,12 +965,16 @@ extension Parser {
955965
case TokenSpec(.__owned): self = .__owned
956966
case TokenSpec(.isolated): self = .isolated
957967
case TokenSpec(._const): self = ._const
968+
case TokenSpec(.consuming): self = .consuming
969+
case TokenSpec(.borrowing): self = .borrowing
958970
default: return nil
959971
}
960972
}
961973
}
962974

963-
while let extraSpecifier = self.consume(ifAnyIn: ExtraneousSpecifier.self) {
975+
while canHaveParameterSpecifier,
976+
let extraSpecifier = self.consume(ifAnyIn: ExtraneousSpecifier.self)
977+
{
964978
if specifier == nil {
965979
specifier = extraSpecifier
966980
} else {

Sources/SwiftSyntax/generated/Keyword.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ public enum Keyword: UInt8, Hashable {
8484
case backDeployed
8585
case before
8686
case block
87+
case borrowing
8788
case `break`
8889
case `case`
8990
case `catch`
9091
case `class`
92+
case consuming
9193
case `continue`
9294
case convenience
9395
case convention
@@ -506,6 +508,10 @@ public enum Keyword: UInt8, Hashable {
506508
self = ._optimize
507509
case "available":
508510
self = .available
511+
case "borrowing":
512+
self = .borrowing
513+
case "consuming":
514+
self = .consuming
509515
case "extension":
510516
self = .`extension`
511517
case "lowerThan":
@@ -907,10 +913,12 @@ public enum Keyword: UInt8, Hashable {
907913
"backDeployed",
908914
"before",
909915
"block",
916+
"borrowing",
910917
"break",
911918
"case",
912919
"catch",
913920
"class",
921+
"consuming",
914922
"continue",
915923
"convenience",
916924
"convention",

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,52 @@ final class DeclarationTests: XCTestCase {
14851485
"""
14861486
)
14871487
}
1488+
1489+
func testBorrowingConsumingParameterSpecifiers() {
1490+
AssertParse(
1491+
"""
1492+
struct borrowing {}
1493+
struct consuming {}
1494+
1495+
struct Foo {}
1496+
1497+
func foo(x: borrowing Foo) {}
1498+
func bar(x: consuming Foo) {}
1499+
func baz(x: (borrowing Foo, consuming Foo) -> ()) {}
1500+
1501+
// `borrowing` and `consuming` are contextual keywords, so they should also
1502+
// continue working as type and/or parameter names
1503+
1504+
func zim(x: borrowing) {}
1505+
func zang(x: consuming) {}
1506+
func zung(x: borrowing consuming) {}
1507+
func zip(x: consuming borrowing) {}
1508+
func zap(x: (borrowing, consuming) -> ()) {}
1509+
func zoop(x: (borrowing consuming, consuming borrowing) -> ()) {}
1510+
1511+
// Parameter specifier names are regular identifiers in other positions,
1512+
// including argument labels.
1513+
1514+
func argumentLabelOnly(borrowing: Int) {}
1515+
func argumentLabelOnly(consuming: Int) {}
1516+
func argumentLabelOnly(__shared: Int) {}
1517+
func argumentLabelOnly(__owned: Int) {}
1518+
1519+
func argumentLabel(borrowing consuming: Int) {}
1520+
func argumentLabel(consuming borrowing: Int) {}
1521+
func argumentLabel(__shared __owned: Int) {}
1522+
func argumentLabel(__owned __shared: Int) {}
1523+
1524+
// We should parse them as argument labels in function types, even though that
1525+
// isn't currently supported.
1526+
1527+
func argumentLabel(anonBorrowingInClosure: (_ borrowing: Int) -> ()) {}
1528+
func argumentLabel(anonConsumingInClosure: (_ consuming: Int) -> ()) {}
1529+
func argumentLabel(anonSharedInClosure: (_ __shared: Int) -> ()) {}
1530+
func argumentLabel(anonOwnedInClosure: (_ __owned: Int) -> ()) {}
1531+
"""
1532+
)
1533+
}
14881534
}
14891535

14901536
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)