Skip to content

Commit f2bfa1c

Browse files
committed
[reference-bindings] Add Sema checks for inout reference bindings to make sure we can only attach to lvalues
rdar://112029192
1 parent 003b335 commit f2bfa1c

File tree

3 files changed

+101
-5
lines changed

3 files changed

+101
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7477,5 +7477,14 @@ NOTE(out_of_order_access_in_init_accessor,none,
74777477
"is called before %1 is initialized",
74787478
(Identifier, Identifier))
74797479

7480+
//------------------------------------------------------------------------------
7481+
// MARK: Reference Bindings Errors
7482+
//------------------------------------------------------------------------------
7483+
7484+
ERROR(referencebindings_binding_must_have_initial_value,none,
7485+
"%0 bindings must have an initial value", (StringRef))
7486+
ERROR(referencebindings_binding_must_be_to_lvalue,none,
7487+
"%0 bindings must be bound to an lvalue", (StringRef))
7488+
74807489
#define UNDEFINE_DIAGNOSTIC_MACROS
74817490
#include "DefineDiagnosticMacros.h"

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,6 +2283,22 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
22832283
}
22842284
}
22852285

2286+
bool checkBoundInOutVarDecl(PatternBindingDecl *pbd, unsigned patternIndex,
2287+
const Pattern *p, VarDecl *vd) {
2288+
// If our var decl doesn't have an initial value, error. We always want an
2289+
// inout var decl to have an lvalue initial value that it is binding to.
2290+
auto *expr = pbd->getInit(patternIndex);
2291+
assert(expr && "Code assumes that we checked for validity");
2292+
2293+
// Next make sure that our initial value was an lvalue.
2294+
if (!isa<LoadExpr>(expr)) {
2295+
vd->diagnose(diag::referencebindings_binding_must_be_to_lvalue, "inout");
2296+
return false;
2297+
}
2298+
2299+
return true;
2300+
}
2301+
22862302
void visitPatternBindingDecl(PatternBindingDecl *PBD) {
22872303
DeclContext *DC = PBD->getDeclContext();
22882304

@@ -2308,6 +2324,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
23082324
Pat->forEachVariable([&](VarDecl *var) {
23092325
this->visitBoundVariable(var);
23102326

2327+
auto markVarAndPBDInvalid = [PBD, var] {
2328+
PBD->setInvalid();
2329+
var->setInvalid();
2330+
};
2331+
23112332
if (PBD->isInitialized(i)) {
23122333
// Add the attribute that preserves the "has an initializer" value
23132334
// across module generation, as required for TBDGen.
@@ -2316,6 +2337,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
23162337
var->getAttrs().add(new (Ctx)
23172338
HasInitialValueAttr(/*IsImplicit=*/true));
23182339
}
2340+
2341+
// If we fail to check the bound inout introducer, mark the variable
2342+
// and pbd invalid().
2343+
if (var->getIntroducer() == VarDecl::Introducer::InOut) {
2344+
if (!checkBoundInOutVarDecl(PBD, i, Pat, var)) {
2345+
markVarAndPBDInvalid();
2346+
}
2347+
}
2348+
23192349
return;
23202350
}
23212351

@@ -2333,11 +2363,6 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
23332363
if (var->isInvalid() || PBD->isInvalid())
23342364
return;
23352365

2336-
auto markVarAndPBDInvalid = [PBD, var] {
2337-
PBD->setInvalid();
2338-
var->setInvalid();
2339-
};
2340-
23412366
// Properties with an opaque return type need an initializer to
23422367
// determine their underlying type.
23432368
if (var->getOpaqueResultTypeDecl()) {
@@ -2393,6 +2418,14 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
23932418
markVarAndPBDInvalid();
23942419
return;
23952420
}
2421+
2422+
// Inout VarDecls need to have an initializer.
2423+
if (var->getIntroducer() == VarDecl::Introducer::InOut) {
2424+
var->diagnose(diag::referencebindings_binding_must_have_initial_value,
2425+
"inout");
2426+
markVarAndPBDInvalid();
2427+
return;
2428+
}
23962429
});
23972430
}
23982431

test/Sema/reference_bindings.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ReferenceBindings
2+
3+
var globalValue = String()
4+
class Klass {
5+
var sStored: String = ""
6+
var sGetter: String { fatalError() }
7+
var sModify: String {
8+
_read {
9+
fatalError()
10+
}
11+
_modify {
12+
fatalError()
13+
}
14+
}
15+
}
16+
17+
struct S : ~Copyable {
18+
var sStored: String = ""
19+
var sGetter: String { fatalError() }
20+
var sModify: String {
21+
_read {
22+
fatalError()
23+
}
24+
_modify {
25+
fatalError()
26+
}
27+
}
28+
}
29+
30+
func foo(_ arg: String, _ consumingArg: consuming String, _ borrowingArg: borrowing String, _ inoutArg: inout String) {
31+
let letValue = 5
32+
var varValue = 5
33+
34+
inout noInitialValue: String // expected-error {{inout bindings must have an initial value}}
35+
inout literalX = 5 // expected-error {{inout bindings must be bound to an lvalue}}
36+
inout consumingArgX = consumingArg
37+
inout borrowingArgX = borrowingArg // expected-error {{inout bindings must be bound to an lvalue}}
38+
inout inoutArgX = inoutArg
39+
inout letValueX = letValue // expected-error {{inout bindings must be bound to an lvalue}}
40+
inout varValueX = varValue
41+
inout globalValueX = globalValue
42+
let k = Klass()
43+
inout storedKlassFieldX = k.sStored
44+
inout getterKlassFieldX = k.sGetter // expected-error {{inout bindings must be bound to an lvalue}}
45+
inout modifyKlassFieldX = k.sModify
46+
let letStruct = S()
47+
inout storedStructLetFieldX = letStruct.sStored // expected-error {{inout bindings must be bound to an lvalue}}
48+
inout getterStructLetFieldX = letStruct.sGetter // expected-error {{inout bindings must be bound to an lvalue}}
49+
inout modifyStructLetFieldX = letStruct.sModify // expected-error {{inout bindings must be bound to an lvalue}}
50+
var varStruct = S()
51+
inout storedStructVarFieldX = varStruct.sStored
52+
inout getterStructVarFieldX = varStruct.sGetter // expected-error {{inout bindings must be bound to an lvalue}}
53+
inout modifyStructVarFieldX = varStruct.sModify
54+
}

0 commit comments

Comments
 (0)