Skip to content

Commit 0786510

Browse files
committed
Diagnose inout uses of 'lets' in constructors in the type checker.
Stored `let` properties of a struct, class, or actor permit 'inout' modification within the constructor body after they have been initialized. Tentatively remove this rule, only allowing such `let` properties to be initialized (assigned to) and not treated as `inout`. Fixes rdar://127258363.
1 parent c219452 commit 0786510

11 files changed

+170
-40
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,11 @@ class ConstraintSystem {
21572157
/// Note: this is only used to support ObjCSelectorExpr at the moment.
21582158
llvm::SmallPtrSet<Expr *, 2> UnevaluatedRootExprs;
21592159

2160+
/// Expressions that are in the left-hand side of an assignment
2161+
/// expression. This is used to help distinguish assignments from
2162+
/// any other potential use that might need lvalues.
2163+
llvm::SmallPtrSet<Expr *, 2> TargetOfAssignExprs;
2164+
21602165
/// The total number of disjunctions created.
21612166
unsigned CountDisjunctions = 0;
21622167

@@ -4436,7 +4441,10 @@ class ConstraintSystem {
44364441
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency =
44374442
[](const ClosureExpr *closure) {
44384443
return closure->isIsolatedByPreconcurrency();
4439-
});
4444+
},
4445+
llvm::function_ref<bool(Expr *)> isAssignTarget = [](Expr *) {
4446+
return true;
4447+
});
44404448

44414449
/// Given the opened type and a pile of information about a member reference,
44424450
/// determine the reference type of the member reference.

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,8 @@ bool SILGenFunction::unsafelyInheritsExecutor() {
647647

648648
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
649649
if (mustReturnToExecutor) {
650-
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
650+
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor() ||
651+
SGF.isCtorWithHopsInjectedByDefiniteInit());
651652
if (auto executor = SGF.ExpectedExecutor)
652653
SGF.B.createHopToExecutor(
653654
RegularLocation::getDebugOnlyLocation(loc, SGF.getModule()), executor,

lib/SILGen/SILGenConstructor.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,19 @@ static bool ctorHopsInjectedByDefiniteInit(ConstructorDecl *ctor,
618618
}
619619
}
620620

621+
bool SILGenFunction::isCtorWithHopsInjectedByDefiniteInit() {
622+
auto declRef = F.getDeclRef();
623+
if (!declRef || !declRef.isConstructor())
624+
return false;
625+
626+
auto ctor = dyn_cast_or_null<ConstructorDecl>(declRef.getDecl());
627+
if (!ctor)
628+
return false;
629+
630+
auto isolation = getActorIsolation(ctor);
631+
return ctorHopsInjectedByDefiniteInit(ctor, isolation);
632+
}
633+
621634
void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
622635
MagicFunctionName = SILGenModule::getMagicFunctionName(ctor);
623636

lib/SILGen/SILGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
820820
/// the fields.
821821
void emitDeallocatingMoveOnlyDestructor(DestructorDecl *dd);
822822

823+
/// Whether we are inside a constructor whose hops are injected by
824+
/// definite initialization.
825+
bool isCtorWithHopsInjectedByDefiniteInit();
826+
823827
/// Generates code for a struct constructor.
824828
/// This allocates the new 'self' value, emits the
825829
/// body code, then returns the final initialized 'self'.

lib/Sema/CSGen.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4242,6 +4242,12 @@ namespace {
42424242
return Action::SkipNode(expr);
42434243
}
42444244

4245+
// Find all of the entries on the left-hand side of an assignment
4246+
// expression.
4247+
if (auto assign = dyn_cast<AssignExpr>(expr)) {
4248+
recordAssignmentTargets(assign->getDest());
4249+
}
4250+
42454251
// Note that the subexpression of a #selector expression is
42464252
// unevaluated.
42474253
if (auto sel = dyn_cast<ObjCSelectorExpr>(expr)) {
@@ -4307,6 +4313,49 @@ namespace {
43074313
return Action::Continue(expr);
43084314
}
43094315

4316+
/// Walk the destination of an assignment expression, recording
4317+
/// each of the
4318+
void recordAssignmentTargets(Expr *dest) {
4319+
if (!dest)
4320+
return;
4321+
4322+
if (auto tuple = dyn_cast<TupleExpr>(dest)) {
4323+
for (auto element : tuple->getElements())
4324+
recordAssignmentTargets(element);
4325+
}
4326+
4327+
// Insert this expression. If we already walked it, there's nothing
4328+
// more to do.
4329+
auto &CS = CG.getConstraintSystem();
4330+
if (!CS.TargetOfAssignExprs.insert(dest).second)
4331+
return;
4332+
4333+
if (auto identity = dyn_cast<IdentityExpr>(dest)) {
4334+
recordAssignmentTargets(identity->getSubExpr());
4335+
return;
4336+
}
4337+
4338+
if (auto tryExpr = dyn_cast<AnyTryExpr>(dest)) {
4339+
recordAssignmentTargets(tryExpr->getSubExpr());
4340+
return;
4341+
}
4342+
4343+
if (auto lookup = dyn_cast<LookupExpr>(dest)) {
4344+
recordAssignmentTargets(lookup->getBase());
4345+
return;
4346+
}
4347+
4348+
if (auto dot = dyn_cast<UnresolvedDotExpr>(dest)) {
4349+
recordAssignmentTargets(dot->getBase());
4350+
return;
4351+
}
4352+
4353+
if (auto bind = dyn_cast<BindOptionalExpr>(dest)) {
4354+
recordAssignmentTargets(bind->getSubExpr());
4355+
return;
4356+
}
4357+
}
4358+
43104359
/// Once we've visited the children of the given expression,
43114360
/// generate constraints from the expression.
43124361
PostWalkResult<Expr *> walkToExprPost(Expr *expr) override {

lib/Sema/ConstraintSystem.cpp

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,9 +1434,13 @@ ConstraintSystem::getWrappedPropertyInformation(
14341434
/// is being accessed; must be null if and only if this is not
14351435
/// a type member
14361436
static bool
1437-
doesStorageProduceLValue(AbstractStorageDecl *storage, Type baseType,
1438-
DeclContext *useDC,
1439-
ConstraintLocator *memberLocator = nullptr) {
1437+
doesStorageProduceLValue(
1438+
AbstractStorageDecl *storage, Type baseType,
1439+
DeclContext *useDC,
1440+
llvm::function_ref<bool(Expr *)> isAssignTarget = [](Expr *) {
1441+
return true;
1442+
},
1443+
ConstraintLocator *memberLocator = nullptr) {
14401444
const DeclRefExpr *base = nullptr;
14411445
if (memberLocator) {
14421446
if (auto *const E = getAsExpr(memberLocator->getAnchor())) {
@@ -1448,9 +1452,33 @@ doesStorageProduceLValue(AbstractStorageDecl *storage, Type baseType,
14481452
}
14491453
}
14501454

1451-
// Unsettable storage decls always produce rvalues.
1452-
if (!storage->isSettableInSwift(useDC, base))
1453-
return false;
1455+
switch (storage->mutabilityInSwift(useDC, base)) {
1456+
case StorageMutability::Immutable:
1457+
// Immutable storage decls always produce rvalues.
1458+
return false;
1459+
1460+
case StorageMutability::Mutable:
1461+
break;
1462+
1463+
case StorageMutability::Initializable: {
1464+
// If the member is not known to be on the left-hand side of
1465+
// an assignment, treat it as an rvalue so we can't pass it
1466+
// inout.
1467+
if (!memberLocator)
1468+
break;
1469+
1470+
auto *anchor = getAsExpr(memberLocator->getAnchor());
1471+
if (!anchor)
1472+
break;
1473+
1474+
// If the anchor isn't known to be the target of an assignment,
1475+
// treat as immutable.
1476+
if (!isAssignTarget(anchor))
1477+
return false;
1478+
1479+
break;
1480+
}
1481+
}
14541482

14551483
if (!storage->isSetterAccessibleFrom(useDC))
14561484
return false;
@@ -1506,7 +1534,8 @@ Type ConstraintSystem::getUnopenedTypeOfReference(
15061534
},
15071535
memberLocator, wantInterfaceType, adjustForPreconcurrency,
15081536
GetClosureType{*this},
1509-
ClosureIsolatedByPreconcurrency{*this});
1537+
ClosureIsolatedByPreconcurrency{*this},
1538+
[this](Expr *expr) { return TargetOfAssignExprs.contains(expr); });
15101539
}
15111540

15121541
Type ConstraintSystem::getUnopenedTypeOfReference(
@@ -1515,7 +1544,8 @@ Type ConstraintSystem::getUnopenedTypeOfReference(
15151544
ConstraintLocator *memberLocator,
15161545
bool wantInterfaceType, bool adjustForPreconcurrency,
15171546
llvm::function_ref<Type(const AbstractClosureExpr *)> getClosureType,
1518-
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency) {
1547+
llvm::function_ref<bool(const ClosureExpr *)> isolatedByPreconcurrency,
1548+
llvm::function_ref<bool(Expr *)> isAssignTarget) {
15191549
Type requestedType =
15201550
getType(value)->getWithoutSpecifierType()->getReferenceStorageReferent();
15211551

@@ -1540,7 +1570,8 @@ Type ConstraintSystem::getUnopenedTypeOfReference(
15401570

15411571
// Qualify storage declarations with an lvalue when appropriate.
15421572
// Otherwise, they yield rvalues (and the access must be a load).
1543-
if (doesStorageProduceLValue(value, baseType, UseDC, memberLocator) &&
1573+
if (doesStorageProduceLValue(value, baseType, UseDC, isAssignTarget,
1574+
memberLocator) &&
15441575
!requestedType->hasError()) {
15451576
return LValueType::get(requestedType);
15461577
}
@@ -2711,7 +2742,11 @@ DeclReferenceType ConstraintSystem::getTypeOfMemberReference(
27112742
if (auto *subscript = dyn_cast<SubscriptDecl>(value)) {
27122743
auto elementTy = subscript->getElementInterfaceType();
27132744

2714-
if (doesStorageProduceLValue(subscript, baseTy, useDC, locator))
2745+
auto isAssignTarget = [&](Expr *expr) {
2746+
return TargetOfAssignExprs.contains(expr);
2747+
};
2748+
if (doesStorageProduceLValue(subscript, baseTy, useDC, isAssignTarget,
2749+
locator))
27152750
elementTy = LValueType::get(elementTy);
27162751

27172752
auto indices = subscript->getInterfaceType()

test/SILOptimizer/definite_init_diagnostics.swift

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,9 @@ func throwingSwap<T>(_ a: inout T, _ b: inout T) throws {}
772772

773773
// <rdar://problem/19035287> let properties should only be initializable, not reassignable
774774
struct LetProperties {
775-
// expected-note @+1 5 {{change 'let' to 'var' to make it mutable}} {{3-6=var}}
775+
// expected-note @+1 2 {{change 'let' to 'var' to make it mutable}} {{3-6=var}}
776776
let arr : [Int]
777-
// expected-note @+1 7 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
777+
// expected-note @+1 2 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
778778
let (u, v) : (Int, Int)
779779
// expected-note @+1 2 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
780780
let w : (Int, Int)
@@ -828,21 +828,10 @@ struct LetProperties {
828828
init() throws {
829829
u = 1; v = 13; w = (1,2); y = 1 ; z = u
830830

831-
var variable = 42
832-
swap(&u, &variable) // expected-error {{immutable value 'self.u' must not be passed inout}}
833-
try throwingSwap(&u, &variable) // expected-error {{immutable value 'self.u' must not be passed inout}}
834-
835831
u.inspect() // ok, non mutating.
836-
u.mutate() // expected-error {{mutating method 'mutate' may not be used on immutable value 'self.u'}}
837832

838833
arr = []
839-
arr += [] // expected-error {{mutating operator '+=' may not be used on immutable value 'self.arr'}}
840-
arr.append(4) // expected-error {{mutating method 'append' may not be used on immutable value 'self.arr'}}
841834
arr[12] = 17 // expected-error {{cannot mutate subscript of immutable value 'self.arr'}}
842-
let _ = arr[replacing: 12, with: 17] // expected-error {{mutating accessor for subscript may not be used on immutable value 'self.arr'}}
843-
844-
methodTakesInOut(&u) // expected-error {{immutable value 'self.u' must not be passed inout}}
845-
try throwingMethodTakesInOut(&u) // expected-error {{immutable value 'self.u' must not be passed inout}}
846835
}
847836
}
848837

@@ -855,12 +844,11 @@ protocol TestMutabilityProtocol {
855844

856845
class C<T : TestMutabilityProtocol> {
857846
let x : T
858-
let y : T // expected-note {{change 'let' to 'var' to make it mutable}}
847+
let y : T
859848

860849
init(a : T) {
861850
x = a; y = a
862851
x.toIntMax()
863-
y.changeToIntMax() // expected-error {{mutating method 'changeToIntMax' may not be used on immutable value 'self.y'}}
864852
}
865853
}
866854

@@ -949,14 +937,11 @@ struct StructMutatingMethodTest {
949937

950938
// <rdar://problem/19268443> DI should reject this call to transparent function
951939
class TransparentFunction {
952-
let x : Int // expected-note {{change 'let' to 'var' to make it mutable}}
953-
let y : Int // expected-note {{change 'let' to 'var' to make it mutable}}
940+
let x : Int
941+
let y : Int
954942
init() {
955943
x = 42
956-
x += 1 // expected-error {{mutating operator '+=' may not be used on immutable value 'self.x'}}
957-
958944
y = 12
959-
myTransparentFunction(&y) // expected-error {{immutable value 'self.y' must not be passed inout}}
960945
}
961946
}
962947

test/SILOptimizer/definite_init_existential_let.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class ImmutableP {
1515

1616
init(field: P) {
1717
self.field = field
18-
self.field.foo() // expected-error{{}}
1918
self.field.bar = 4 // expected-error{{}}
2019
}
2120
}
@@ -24,6 +23,5 @@ func immutableP(field: P) {
2423
let x: P // expected-note* {{}}
2524

2625
x = field
27-
x.foo() // expected-error{{}}
2826
x.bar = 4 // expected-error{{}}
2927
}

test/SILOptimizer/definite_init_inout_super_init.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class A : B {
1515

1616
init() {
1717
self.x = 12
18-
super.init(x: &x) // expected-error {{immutable value 'self.x' must not be passed inout}}
18+
super.init(x: &x) // expected-error {{cannot pass immutable value as inout argument: 'x' is a 'let' constant}}
1919
}
2020
}
2121

@@ -24,7 +24,7 @@ class C : B {
2424

2525
init() {
2626
self.x = Klass()
27-
super.init(x: &x) // expected-error {{immutable value 'self.x' must not be passed inout}}
27+
super.init(x: &x) // expected-error {{cannot pass immutable value as inout argument: 'x' is a 'let' constant}}
2828
}
2929
}
3030

test/Sema/immutability.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,19 @@ func takesClosure(_: (Int) -> Int) {
404404

405405
func updateInt(_ x : inout Int) {}
406406

407+
extension Int {
408+
mutating func negateMe() { }
409+
}
410+
407411
// rdar://15785677 - allow 'let' declarations in structs/classes be initialized in init()
408412
class LetClassMembers {
409-
let a : Int // expected-note 2 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
413+
let a : Int // expected-note 4 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
410414
let b : Int // expected-note {{change 'let' to 'var' to make it mutable}} {{3-6=var}}
411415

412416
init(arg : Int) {
413417
a = arg // ok, a is mutable in init()
414-
updateInt(&a) // ok, a is mutable in init() and has been initialized
418+
a.negateMe() // expected-error{{cannot use mutating member on immutable value: 'a' is a 'let' constant}}
419+
updateInt(&a) // expected-error{{cannot pass immutable value as inout argument: 'a' is a 'let' constant}}
415420
b = 17 // ok, b is mutable in init()
416421
}
417422

@@ -422,12 +427,13 @@ class LetClassMembers {
422427
}
423428
}
424429
struct LetStructMembers {
425-
let a : Int // expected-note 2 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
430+
let a : Int // expected-note 4 {{change 'let' to 'var' to make it mutable}} {{3-6=var}} {{3-6=var}}
426431
let b : Int // expected-note {{change 'let' to 'var' to make it mutable}} {{3-6=var}}
427432

428433
init(arg : Int) {
429434
a = arg // ok, a is mutable in init()
430-
updateInt(&a) // ok, a is mutable in init() and has been initialized
435+
updateInt(&a) // expected-error {{cannot pass immutable value as inout argument: 'a' is a 'let' constant}}
436+
a += 1 // expected-error {{left side of mutating operator isn't mutable: 'a' is a 'let' constant}}
431437
b = 17 // ok, b is mutable in init()
432438
}
433439

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify
2+
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted
3+
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete
4+
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation
5+
6+
// REQUIRES: concurrency
7+
// REQUIRES: asserts
8+
9+
protocol Iterator {
10+
associatedtype Failure: Error
11+
mutating func next() async throws -> Int?
12+
}
13+
14+
extension Iterator {
15+
mutating func next() async throws(Failure) -> Int? {
16+
nil
17+
}
18+
}
19+
20+
actor C: Iterator {
21+
typealias Failure = any Error
22+
func next() throws -> Int? { nil }
23+
}
24+
25+
actor A {
26+
let c = C()
27+
28+
init() async {
29+
_ = try! await self.c.next()
30+
}
31+
}

0 commit comments

Comments
 (0)