Skip to content

Parse _borrowing x in patterns as a borrow binding. #71263

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 1 commit into from
Jan 31, 2024
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
18 changes: 18 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5909,7 +5909,21 @@ class VarDecl : public AbstractStorageDecl {
Let = 0,
Var = 1,
InOut = 2,
Borrowing = 3,
};

static StringRef getIntroducerStringRef(Introducer i) {
switch (i) {
case VarDecl::Introducer::Let:
return "let";
case VarDecl::Introducer::Var:
return "var";
case VarDecl::Introducer::InOut:
return "inout";
case VarDecl::Introducer::Borrowing:
return "_borrowing";
}
}

protected:
PointerUnion<PatternBindingDecl *,
Expand Down Expand Up @@ -6156,6 +6170,10 @@ class VarDecl : public AbstractStorageDecl {
Introducer getIntroducer() const {
return Introducer(Bits.VarDecl.Introducer);
}

StringRef getIntroducerStringRef() const {
return getIntroducerStringRef(getIntroducer());
}

CaptureListExpr *getParentCaptureList() const {
if (!Parent)
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,8 @@ ERROR(extra_var_in_multiple_pattern_list,none,
"%0 must be bound in every pattern", (Identifier))
ERROR(let_pattern_in_immutable_context,none,
"'let' pattern cannot appear nested in an already immutable context", ())
ERROR(borrowing_subpattern_unsupported,none,
"'_borrowing' pattern modifier must be directly applied to pattern variable name", ())
ERROR(specifier_must_have_type,none,
"%0 arguments must have a type specified", (StringRef))
ERROR(expected_rparen_parameter,PointsToFirstBadToken,
Expand Down
11 changes: 1 addition & 10 deletions include/swift/AST/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -817,17 +817,8 @@ class BindingPattern : public Pattern {
return VP;
}

bool isLet() const { return getIntroducer() == VarDecl::Introducer::Let; }

StringRef getIntroducerStringRef() const {
switch (getIntroducer()) {
case VarDecl::Introducer::Let:
return "let";
case VarDecl::Introducer::Var:
return "var";
case VarDecl::Introducer::InOut:
return "inout";
}
return VarDecl::getIntroducerStringRef(getIntroducer());
}

SourceLoc getLoc() const { return VarLoc; }
Expand Down
1 change: 1 addition & 0 deletions include/swift/Parse/PatternBindingState.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct PatternBindingState {
: kind(NotInBinding) {
switch (introducer) {
case VarDecl::Introducer::Let:
case VarDecl::Introducer::Borrowing:
kind = InLet;
break;
case VarDecl::Introducer::Var:
Expand Down
6 changes: 5 additions & 1 deletion lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,9 @@ static StringRef getDumpString(RequirementKind kind) {

llvm_unreachable("Unhandled RequirementKind in switch.");
}
static StringRef getDumpString(StringRef s) {
return s;
}
static unsigned getDumpString(unsigned value) {
return value;
}
Expand Down Expand Up @@ -1015,7 +1018,8 @@ namespace {
printFoot();
}
void visitBindingPattern(BindingPattern *P, StringRef label) {
printCommon(P, P->isLet() ? "pattern_let" : "pattern_var", label);
printCommon(P, "pattern_binding", label);
printField(P->getIntroducerStringRef(), "kind");
printRec(P->getSubPattern());
printFoot();
}
Expand Down
7 changes: 5 additions & 2 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,7 @@ void PrintAST::printPattern(const Pattern *pattern) {
case PatternKind::Binding: {
auto bPattern = cast<BindingPattern>(pattern);
Printer.printIntroducerKeyword(
bPattern->isLet() ? "let" : "var",
bPattern->getIntroducerStringRef(),
Options, " ");
printPattern(bPattern->getSubPattern());
}
Expand Down Expand Up @@ -4634,7 +4634,10 @@ void PrintAST::visitVarDecl(VarDecl *decl) {
if (decl->getKind() == DeclKind::Var || Options.PrintParameterSpecifiers) {
// Map all non-let specifiers to 'var'. This is not correct, but
// SourceKit relies on this for info about parameter decls.
Printer.printIntroducerKeyword(decl->isLet() ? "let" : "var", Options, " ");

Printer.printIntroducerKeyword(
decl->getIntroducer() == VarDecl::Introducer::Let ? "let" : "var",
Options, " ");
}
printContextIfNeeded(decl);
recordDeclLoc(decl,
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7758,7 +7758,8 @@ bool VarDecl::isLet() const {
if (auto *PD = dyn_cast<ParamDecl>(this)) {
return PD->isImmutableInFunctionBody();
}
return getIntroducer() == Introducer::Let;
return getIntroducer() == Introducer::Let
|| getIntroducer() == Introducer::Borrowing;
}

bool VarDecl::isAsyncLet() const {
Expand Down
30 changes: 27 additions & 3 deletions lib/AST/Pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ DescriptivePatternKind Pattern::getDescriptiveKind() const {
TRIVIAL_PATTERN_KIND(Expr);

case PatternKind::Binding:
return cast<BindingPattern>(this)->isLet() ? DescriptivePatternKind::Let
: DescriptivePatternKind::Var;
switch (cast<BindingPattern>(this)->getIntroducer()) {
case VarDecl::Introducer::Let:
case VarDecl::Introducer::Borrowing:
return DescriptivePatternKind::Let;

case VarDecl::Introducer::Var:
case VarDecl::Introducer::InOut:
return DescriptivePatternKind::Var;
}
}
#undef TRIVIAL_PATTERN_KIND
llvm_unreachable("bad DescriptivePatternKind");
Expand Down Expand Up @@ -770,7 +777,24 @@ Pattern::getOwnership(
void visitNamedPattern(NamedPattern *p) {
// `var` and `let` bindings consume the matched value.
// TODO: borrowing/mutating/consuming parameters
increaseOwnership(ValueOwnership::Owned, p);
switch (p->getDecl()->getIntroducer()) {
case VarDecl::Introducer::Let:
case VarDecl::Introducer::Var:
// `let` and `var` consume the bound value to move it into a new
// independent variable.
increaseOwnership(ValueOwnership::Owned, p);
break;

case VarDecl::Introducer::InOut:
// `inout` bindings modify the value in-place.
increaseOwnership(ValueOwnership::InOut, p);
break;

case VarDecl::Introducer::Borrowing:
// `borrow` bindings borrow parts of the value in-place so they don't
// need stronger access to the subject value.
break;
}
}

void visitAnyPattern(AnyPattern *p) {
Expand Down
45 changes: 45 additions & 0 deletions lib/Parse/ParsePattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,42 @@ ParserResult<Pattern> Parser::parseMatchingPattern(bool isExprBasic) {
return parseMatchingPatternAsBinding(newPatternBindingState, varLoc,
isExprBasic);
}

// The `borrowing` modifier is a contextual keyword, so it's only accepted
// directly applied to a binding name, as in `case .foo(borrowing x)`.
if (Context.LangOpts.hasFeature(Feature::BorrowingSwitch)) {
if (Tok.isContextualKeyword("_borrowing")
&& peekToken().isAny(tok::identifier, tok::kw_self, tok::dollarident,
tok::code_complete)
&& !peekToken().isAtStartOfLine()) {
Tok.setKind(tok::contextual_keyword);
SourceLoc borrowingLoc = consumeToken();

// If we have `case borrowing x.`, `x(`, `x[`, or `x<` then this looks
// like an attempt to include a subexpression under a `borrowing`
// binding, which isn't yet supported.
if (peekToken().isAny(tok::period, tok::period_prefix, tok::l_paren,
tok::l_square)
|| (peekToken().isAnyOperator() && peekToken().getText().equals("<"))) {

// Diagnose the unsupported production.
diagnose(Tok.getLoc(),
diag::borrowing_subpattern_unsupported);

// Recover by parsing as if it was supported.
return parseMatchingPattern(isExprBasic);
}
Identifier name;
SourceLoc nameLoc = consumeIdentifier(name,
/*diagnoseDollarPrefix*/ false);
auto namedPattern = createBindingFromPattern(nameLoc, name,
VarDecl::Introducer::Borrowing);
auto bindPattern = new (Context) BindingPattern(
borrowingLoc, VarDecl::Introducer::Borrowing, namedPattern);

return makeParserResult(bindPattern);
}
}

// matching-pattern ::= 'is' type
if (Tok.is(tok::kw_is)) {
Expand Down Expand Up @@ -1396,6 +1432,15 @@ Parser::parseMatchingPatternAsBinding(PatternBindingState newState,
}

bool Parser::isOnlyStartOfMatchingPattern() {
if (Context.LangOpts.hasFeature(Feature::BorrowingSwitch)) {
if (Tok.isContextualKeyword("_borrowing")
&& peekToken().isAny(tok::identifier, tok::kw_self, tok::dollarident,
tok::code_complete)
&& !peekToken().isAtStartOfLine()) {
return true;
}
}

return Tok.isAny(tok::kw_var, tok::kw_let, tok::kw_is) ||
(Context.LangOpts.hasFeature(Feature::ReferenceBindings) &&
Tok.isAny(tok::kw_inout));
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3545,8 +3545,9 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
foundVP = VP;
});

if (foundVP && !foundVP->isLet())
if (foundVP && foundVP->getIntroducer() != VarDecl::Introducer::Let) {
FixItLoc = foundVP->getLoc();
}
}

// If this is a parameter explicitly marked 'var', remove it.
Expand Down
42 changes: 23 additions & 19 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,21 @@ ParameterList *ModuleFile::readParameterList() {
return ParameterList::create(getContext(), params);
}

static llvm::Optional<swift::VarDecl::Introducer>
getActualVarDeclIntroducer(serialization::VarDeclIntroducer raw) {
switch (raw) {
#define CASE(ID) \
case serialization::VarDeclIntroducer::ID: \
return swift::VarDecl::Introducer::ID;
CASE(Let)
CASE(Var)
CASE(InOut)
CASE(Borrowing)
}
#undef CASE
return llvm::None;
}

Expected<Pattern *> ModuleFile::readPattern(DeclContext *owningDC) {
// Currently, the only case in which this function can fail (return an error)
// is when reading a pattern for a single variable declaration.
Expand Down Expand Up @@ -643,15 +658,18 @@ Expected<Pattern *> ModuleFile::readPattern(DeclContext *owningDC) {
return result;
}
case decls_block::VAR_PATTERN: {
bool isLet;
BindingPatternLayout::readRecord(scratch, isLet);
unsigned rawIntroducer;
BindingPatternLayout::readRecord(scratch, rawIntroducer);

Pattern *subPattern = readPatternUnchecked(owningDC);

auto introducer = getActualVarDeclIntroducer(
(serialization::VarDeclIntroducer) rawIntroducer);
if (!introducer)
return diagnoseFatal();

auto result = BindingPattern::createImplicit(
getContext(),
isLet ? VarDecl::Introducer::Let : VarDecl::Introducer::Var,
subPattern);
getContext(), *introducer, subPattern);
if (Type interfaceType = subPattern->getDelayedInterfaceType())
result->setDelayedInterfaceType(interfaceType, owningDC);
else
Expand Down Expand Up @@ -2871,20 +2889,6 @@ getActualParamDeclSpecifier(serialization::ParamDeclSpecifier raw) {
return llvm::None;
}

static llvm::Optional<swift::VarDecl::Introducer>
getActualVarDeclIntroducer(serialization::VarDeclIntroducer raw) {
switch (raw) {
#define CASE(ID) \
case serialization::VarDeclIntroducer::ID: \
return swift::VarDecl::Introducer::ID;
CASE(Let)
CASE(Var)
CASE(InOut)
}
#undef CASE
return llvm::None;
}

static llvm::Optional<swift::OpaqueReadOwnership>
getActualOpaqueReadOwnership(unsigned rawKind) {
switch (serialization::OpaqueReadOwnership(rawKind)) {
Expand Down
5 changes: 3 additions & 2 deletions lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 844; // remove IsolatedAttr
const uint16_t SWIFTMODULE_VERSION_MINOR = 845; // borrowing var introducer

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down Expand Up @@ -369,6 +369,7 @@ enum class VarDeclIntroducer : uint8_t {
Let = 0,
Var = 1,
InOut = 2,
Borrowing = 3,
};
using VarDeclIntroducerField = BCFixed<2>;

Expand Down Expand Up @@ -1897,7 +1898,7 @@ namespace decls_block {

using BindingPatternLayout = BCRecordLayout<
VAR_PATTERN,
BCFixed<1> // isLet?
BCFixed<2> // introducer (var, let, etc.)
// The sub-pattern trails the record.
>;

Expand Down
4 changes: 3 additions & 1 deletion lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,8 @@ static uint8_t getRawStableVarDeclIntroducer(swift::VarDecl::Introducer intr) {
return uint8_t(serialization::VarDeclIntroducer::Var);
case swift::VarDecl::Introducer::InOut:
return uint8_t(serialization::VarDeclIntroducer::InOut);
case swift::VarDecl::Introducer::Borrowing:
return uint8_t(serialization::VarDeclIntroducer::Borrowing);
}
llvm_unreachable("bad variable decl introducer kind");
}
Expand Down Expand Up @@ -3674,7 +3676,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {

unsigned abbrCode = S.DeclTypeAbbrCodes[BindingPatternLayout::Code];
BindingPatternLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
var->isLet());
getRawStableVarDeclIntroducer(var->getIntroducer()));
writePattern(var->getSubPattern());
break;
}
Expand Down
53 changes: 53 additions & 0 deletions test/Parse/pattern_borrow_bindings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// RUN: %target-swift-frontend -enable-experimental-feature BorrowingSwitch -typecheck -verify %s

struct Payload: ~Copyable {
var x: Int
var y: String
}

enum Foo: ~Copyable {
case payload(Payload)
case noPayload
}

enum Bar: ~Copyable {
case payload(Foo)
case noPayload

var member: Bar { fatalError() }
}

struct SourceBreakTest {
func foo() -> Bar {}

func callAsFunction() -> Bar { fatalError() }
}

let _borrowing = SourceBreakTest()

func ~=(_: borrowing Bar, _: borrowing Bar) -> Bool { fatalError() }

func useBorrowBar(_: borrowing Bar) { fatalError() }
func useBorrowFoo(_: borrowing Foo) { fatalError() }
func useBorrowPayload(_: borrowing Payload) { fatalError() }

func testBorrowingPatterns(bar: borrowing Bar) {
switch bar {
case _borrowing .foo(): // parses as `_borrowing.foo()` as before
break
case _borrowing (): // parses as `_borrowing()` as before
break

case _borrowing x:
useBorrowBar(x)

case .payload(_borrowing x):
useBorrowFoo(x)

case _borrowing x.member: // expected-error{{'_borrowing' pattern modifier must be directly applied to pattern variable name}} expected-error{{cannot find 'x' in scope}}
break

default:
break
}
}