Skip to content

[reference-bindings] Add support for borrow, inout bindings. #1363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ public let DECL_NODES: [Node] = [
Child(name: "ImportTok",
kind: .token(choices: [.keyword(text: "import")])),
Child(name: "ImportKind",
kind: .token(choices: [.keyword(text: "typealias"), .keyword(text: "struct"), .keyword(text: "class"), .keyword(text: "enum"), .keyword(text: "protocol"), .keyword(text: "var"), .keyword(text: "let"), .keyword(text: "func")]),
kind: .token(choices: [.keyword(text: "typealias"), .keyword(text: "struct"), .keyword(text: "class"), .keyword(text: "enum"), .keyword(text: "protocol"), .keyword(text: "var"), .keyword(text: "let"), .keyword(text: "func"), .keyword(text: "inout")]),
isOptional: true),
Child(name: "Path",
kind: .collection(kind: "AccessPath", collectionElementName: "PathComponent"))
Expand Down Expand Up @@ -1214,7 +1214,7 @@ public let DECL_NODES: [Node] = [
nameForDiagnostics: "modifiers",
isOptional: true),
Child(name: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var")])),
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])),
Child(name: "Bindings",
kind: .collection(kind: "PatternBindingList", collectionElementName: "Binding"))
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public let PATTERN_NODES: [Node] = [
kind: "Pattern",
children: [
Child(name: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var")])),
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])),
Child(name: "ValuePattern",
kind: .node(kind: "Pattern"))
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public let STMT_NODES: [Node] = [
kind: "Syntax",
children: [
Child(name: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var")])),
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])),
Child(name: "Pattern",
kind: .node(kind: "Pattern")),
Child(name: "TypeAnnotation",
Expand Down
6 changes: 5 additions & 1 deletion Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ extension Parser {
return RawDeclSyntax(self.parseFuncDeclaration(attrs, handle))
case (.subscriptKeyword, let handle)?:
return RawDeclSyntax(self.parseSubscriptDeclaration(attrs, handle))
case (.letKeyword, let handle)?, (.varKeyword, let handle)?:
case (.letKeyword, let handle)?, (.varKeyword, let handle)?,
(.inoutKeyword, let handle)?:
return RawDeclSyntax(self.parseBindingDeclaration(attrs, handle, inMemberDeclList: inMemberDeclList))
case (.initKeyword, let handle)?:
return RawDeclSyntax(self.parseInitializerDeclaration(attrs, handle))
Expand Down Expand Up @@ -330,6 +331,7 @@ extension Parser {
case `var`
case `let`
case `func`
case `inout`

var spec: TokenSpec {
switch self {
Expand All @@ -341,6 +343,7 @@ extension Parser {
case .var: return .keyword(.var)
case .let: return .keyword(.let)
case .func: return .keyword(.func)
case .inout: return .keyword(.inout)
}
}

Expand All @@ -354,6 +357,7 @@ extension Parser {
case TokenSpec(.var): self = .var
case TokenSpec(.let): self = .let
case TokenSpec(.func): self = .func
case TokenSpec(.inout): self = .inout
default: return nil
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ extension Parser {
/// case x.y <- 'x' must refer to some 'x' defined in another scope, it cannot be e.g. an enum type.
/// ```
case matching
/// We're parsing a matching pattern that is introduced via `let` or `var`.
/// We're parsing a matching pattern that is introduced via `let`, `var`, or `inout`
///
/// ```
/// case let x.y <- 'x' must refer to the base of some member access, y must refer to some pattern-compatible identfier
/// ```
case letOrVar
case bindingIntroducer

var admitsBinding: Bool {
switch self {
case .letOrVar:
case .bindingIntroducer:
return true
case .none, .matching:
return false
Expand Down Expand Up @@ -328,7 +328,7 @@ extension Parser {

case (.equal, let handle)?:
switch pattern {
case .matching, .letOrVar:
case .matching, .bindingIntroducer:
return nil
case .none:
let eq = self.eat(handle)
Expand Down
26 changes: 18 additions & 8 deletions Sources/SwiftParser/Patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extension Parser {
case dollarIdentifier // For recovery
case letKeyword
case varKeyword
case inoutKeyword

init?(lexeme: Lexer.Lexeme) {
switch PrepareForKeywordMatch(lexeme) {
Expand All @@ -62,6 +63,7 @@ extension Parser {
case TokenSpec(.dollarIdentifier): self = .dollarIdentifier
case TokenSpec(.let): self = .letKeyword
case TokenSpec(.var): self = .varKeyword
case TokenSpec(.inout): self = .inoutKeyword
default: return nil
}
}
Expand All @@ -74,6 +76,7 @@ extension Parser {
case .dollarIdentifier: return .dollarIdentifier
case .letKeyword: return .keyword(.let)
case .varKeyword: return .keyword(.var)
case .inoutKeyword: return .keyword(.inout)
}
}
}
Expand Down Expand Up @@ -120,12 +123,13 @@ extension Parser {
)
)
case (.letKeyword, let handle)?,
(.varKeyword, let handle)?:
let letOrVar = self.eat(handle)
(.varKeyword, let handle)?,
(.inoutKeyword, let handle)?:
let bindingKeyword = self.eat(handle)
let value = self.parsePattern()
return RawPatternSyntax(
RawValueBindingPatternSyntax(
bindingKeyword: letOrVar,
bindingKeyword: bindingKeyword,
valuePattern: value,
arena: self.arena
)
Expand Down Expand Up @@ -239,12 +243,13 @@ extension Parser {
// Parse productions that can only be patterns.
switch self.at(anyIn: MatchingPatternStart.self) {
case (.varKeyword, let handle)?,
(.letKeyword, let handle)?:
let letOrVar = self.eat(handle)
let value = self.parseMatchingPattern(context: .letOrVar)
(.letKeyword, let handle)?,
(.inoutKeyword, let handle)?:
let bindingKeyword = self.eat(handle)
let value = self.parseMatchingPattern(context: .bindingIntroducer)
return RawPatternSyntax(
RawValueBindingPatternSyntax(
bindingKeyword: letOrVar,
bindingKeyword: bindingKeyword,
valuePattern: value,
arena: self.arena
)
Expand Down Expand Up @@ -287,13 +292,15 @@ extension Parser.Lookahead {
/// pattern ::= pattern-tuple
/// pattern ::= 'var' pattern
/// pattern ::= 'let' pattern
/// pattern ::= 'inout' pattern
mutating func canParsePattern() -> Bool {
enum PatternStartTokens: TokenSpecSet {
case identifier
case wildcard
case letKeyword
case varKeyword
case leftParen
case inoutKeyword

init?(lexeme: Lexer.Lexeme) {
switch PrepareForKeywordMatch(lexeme) {
Expand All @@ -302,6 +309,7 @@ extension Parser.Lookahead {
case TokenSpec(.let): self = .letKeyword
case TokenSpec(.var): self = .varKeyword
case TokenSpec(.leftParen): self = .leftParen
case TokenSpec(.inout): self = .inoutKeyword
default: return nil
}
}
Expand All @@ -313,6 +321,7 @@ extension Parser.Lookahead {
case .letKeyword: return .keyword(.let)
case .varKeyword: return .keyword(.var)
case .leftParen: return .leftParen
case .inoutKeyword: return .keyword(.inout)
}
}
}
Expand All @@ -323,7 +332,8 @@ extension Parser.Lookahead {
self.eat(handle)
return true
case (.letKeyword, let handle)?,
(.varKeyword, let handle)?:
(.varKeyword, let handle)?,
(.inoutKeyword, let handle)?:
self.eat(handle)
return self.canParsePattern()
case (.leftParen, _)?:
Expand Down
17 changes: 9 additions & 8 deletions Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,18 @@ extension Parser {
/// condition → expression | availability-condition | case-condition | optional-binding-condition
///
/// case-condition → 'case' pattern initializer
/// optional-binding-condition → 'let' pattern initializer? | 'var' pattern initializer?
/// optional-binding-condition → 'let' pattern initializer? | 'var' pattern initializer? |
/// 'inout' pattern initializer?
@_spi(RawSyntax)
public mutating func parseConditionElement() -> RawConditionElementSyntax.Condition {
// Parse a leading #available/#unavailable condition if present.
if self.at(.poundAvailableKeyword, .poundUnavailableKeyword) {
return self.parsePoundAvailableConditionElement()
}

// Parse the basic expression case. If we have a leading let/var/case
// keyword or an assignment, then we know this is a binding.
guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) else {
// Parse the basic expression case. If we have a leading let, var, inout,
// borrow, case keyword or an assignment, then we know this is a binding.
guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) || self.at(.keyword(.inout)) else {
// If we lack it, then this is theoretically a boolean condition.
// However, we also need to handle migrating from Swift 2 syntax, in
// which a comma followed by an expression could actually be a pattern
Expand All @@ -240,7 +241,7 @@ extension Parser {
}

// We're parsing a conditional binding.
assert(self.at(.keyword(.let), .keyword(.var), .keyword(.case)))
assert(self.at(.keyword(.let), .keyword(.var)) || self.at(.keyword(.inout), .keyword(.case)))
enum BindingKind {
case pattern(RawTokenSyntax, RawPatternSyntax)
case optional(RawTokenSyntax, RawPatternSyntax)
Expand All @@ -252,7 +253,7 @@ extension Parser {
kind = .pattern(caseKeyword, pattern)
} else {
let letOrVar = self.consumeAnyToken()
let pattern = self.parseMatchingPattern(context: .letOrVar)
let pattern = self.parseMatchingPattern(context: .bindingIntroducer)
kind = .optional(letOrVar, pattern)
}

Expand Down Expand Up @@ -285,10 +286,10 @@ extension Parser {
}

switch kind {
case let .optional(letOrVar, pattern):
case let .optional(bindingKeyword, pattern):
return .optionalBinding(
RawOptionalBindingConditionSyntax(
bindingKeyword: letOrVar,
bindingKeyword: bindingKeyword,
pattern: pattern,
typeAnnotation: annotation,
initializer: initializer,
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftParser/TokenPrecedence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ public enum TokenPrecedence: Comparable {
// Keywords in function types (we should be allowed to skip them inside parenthesis)
.rethrows, .throws,
// Consider 'any' and 'inout' like a prefix operator to a type and a type is expression-like.
//
// NOTE: We reuse this for inout bindings and choose the higher precedence level of expr keywords
// so we do not break anything.
Comment on lines +195 to +197
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//
// NOTE: We reuse this for inout bindings and choose the higher precedence level of expr keywords
// so we do not break anything.
//
// NOTE: We reuse this for inout bindings and choose the lower precedence level of expr keywords
// so we don’t skip over expression keywords while looking for `inout` in an expression position.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahoppen I am going to do that in a follow on commit. I don't want to restart CI

.inout,
// Consider 'any' and 'inout' like a prefix operator to a type and a type is expression-like.
.Any,
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftParser/TokenSpecSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ enum DeclarationStart: TokenSpecSet {
case subscriptKeyword
case typealiasKeyword
case varKeyword
case inoutKeyword

init?(lexeme: Lexer.Lexeme) {
switch PrepareForKeywordMatch(lexeme) {
Expand All @@ -263,6 +264,7 @@ enum DeclarationStart: TokenSpecSet {
case TokenSpec(.subscript): self = .subscriptKeyword
case TokenSpec(.typealias): self = .typealiasKeyword
case TokenSpec(.var): self = .varKeyword
case TokenSpec(.inout): self = .inoutKeyword
default: return nil
}
}
Expand All @@ -288,6 +290,7 @@ enum DeclarationStart: TokenSpecSet {
case .subscriptKeyword: return .keyword(.subscript)
case .typealiasKeyword: return .keyword(.typealias)
case .varKeyword: return .keyword(.var)
case .inoutKeyword: return TokenSpec(.inout, recoveryPrecedence: .declKeyword)
}
}
}
Expand Down Expand Up @@ -570,12 +573,14 @@ enum MatchingPatternStart: TokenSpecSet {
case isKeyword
case letKeyword
case varKeyword
case inoutKeyword

init?(lexeme: Lexer.Lexeme) {
switch PrepareForKeywordMatch(lexeme) {
case TokenSpec(.is): self = .isKeyword
case TokenSpec(.let): self = .letKeyword
case TokenSpec(.var): self = .varKeyword
case TokenSpec(.inout): self = .inoutKeyword
default: return nil
}
}
Expand All @@ -585,6 +590,7 @@ enum MatchingPatternStart: TokenSpecSet {
case .isKeyword: return .keyword(.is)
case .letKeyword: return .keyword(.let)
case .varKeyword: return .keyword(.var)
case .inoutKeyword: return .keyword(.inout)
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,10 @@ extension Parser.Lookahead {

mutating func canParseTupleBodyType() -> Bool {
guard
!self.at(.rightParen, .rightBrace) && !self.atContextualPunctuator("...") && !self.atStartOfDeclaration()
!self.at(.rightParen, .rightBrace) && !self.atContextualPunctuator("...")
// In types, we do not allow for an inout binding to be declared in a
// tuple type.
&& (self.at(.keyword(.inout)) || !self.atStartOfDeclaration())
else {
return self.consume(if: .rightParen) != nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ final class MatchingPatternsTests: XCTestCase {
a = 1
case let a:
a = 1
case inout a:
a = 1
case var var a:
a += 1
case var let a:
Expand Down Expand Up @@ -193,6 +195,7 @@ final class MatchingPatternsTests: XCTestCase {
var n : Voluntary<Int> = .Naught
if case let .Naught(value) = n {}
if case let .Naught(value1, value2, value3) = n {}
if case inout .Naught(value) = n {}
"""
)
}
Expand Down Expand Up @@ -369,6 +372,8 @@ final class MatchingPatternsTests: XCTestCase {
case .Payload((let x)):
acceptInt(x)
acceptString("\(x)")
case .Payload(inout x):
acceptInt(x)
}
"""#
)
Expand Down Expand Up @@ -432,6 +437,8 @@ final class MatchingPatternsTests: XCTestCase {
switch t {
case (_, var a, 3):
a += 1
case (_, inout a, 3):
a += 1
case var (_, b, 3):
b += 1
case var (_, var c, 3):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ final class PatternWithoutVariablesTests: XCTestCase {
AssertParse(
"""
let _ = 1
inout _ = 1
"""
)
}
Expand All @@ -28,6 +29,7 @@ final class PatternWithoutVariablesTests: XCTestCase {
"""
func foo() {
let _ = 1 // OK
inout _ = 1
}
"""
)
Expand All @@ -42,6 +44,7 @@ final class PatternWithoutVariablesTests: XCTestCase {
func foo() {
let _ = 1 // OK
}
inout (_, _) = (1, 2)
}
"""
)
Expand Down Expand Up @@ -73,6 +76,9 @@ final class PatternWithoutVariablesTests: XCTestCase {
case let (_, x): _ = x; break // ok
}
if case let _ = "str" {}
switch a {
case inout .Bar: break
}
}
"""#
)
Expand Down
Loading