Skip to content

Commit a6e4b1a

Browse files
committed
[Property wrapers] Fix l-value computation in buildStorageReference
Prior to this commit, buildStorageReference used to take an 'isLValue' argument that specified whether the reference should be an l-value or not. This provided sufficient information in case of a stored property (self.member). However, when accessing a wrapped property (self._member.wrappedValue), this doesn't have enough information to decide whether intermediate components of the expression should be l-values or not. Prior to this commit, this was extrapolated from the 'isLValue' argument, leading to compiler crashes or unnecessary intermediate l-values when using non-standard mutability with property wrappers. To fix this, this commit replaces the 'isLValue' argument with two arguments: isUsedForGetAccess: Specifies that the referenced storage is meant to be accessed with a getter isUsedForSetAccess: Specifies that the referenced storage is meant to be accessed with a setter and updates buildStorageReference to correctly figure out l-value-ness of self and intermediate components from these arguments.
1 parent 66fe1ec commit a6e4b1a

File tree

2 files changed

+103
-38
lines changed

2 files changed

+103
-38
lines changed

lib/Sema/TypeCheckStorage.cpp

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,13 @@ IsSetterMutatingRequest::evaluate(Evaluator &evaluator,
355355
// or not based on the composition of the wrappers.
356356
if (auto var = dyn_cast<VarDecl>(storage)) {
357357
if (auto mut = var->getPropertyWrapperMutability()) {
358-
return mut->Setter == PropertyWrapperMutability::Mutating
359-
&& result;
358+
bool isMutating = mut->Setter == PropertyWrapperMutability::Mutating;
359+
if (var->getParsedAccessor(AccessorKind::DidSet)) {
360+
// If there's a didSet, we call the getter for the 'oldValue', and so
361+
// should consider the getter's mutatingness as well
362+
isMutating |= (mut->Getter == PropertyWrapperMutability::Mutating);
363+
}
364+
return isMutating && result;
360365
}
361366
}
362367

@@ -601,14 +606,18 @@ getEnclosingSelfPropertyWrapperAccess(VarDecl *property, bool forProjected) {
601606
return result;
602607
}
603608

604-
/// Build an l-value for the storage of a declaration. Returns nullptr if there
609+
/// Build a reference to the storage of a declaration. Returns nullptr if there
605610
/// was an error. This should only occur if an invalid declaration was type
606611
/// checked; another diagnostic should have been emitted already.
612+
607613
static Expr *buildStorageReference(AccessorDecl *accessor,
608614
AbstractStorageDecl *storage,
609615
TargetImpl target,
610-
bool isLValue,
616+
bool isUsedForGetAccess,
617+
bool isUsedForSetAccess,
611618
ASTContext &ctx) {
619+
// Whether the last component of the expression should be an l-value
620+
bool isLValue = isUsedForSetAccess;
612621
// Local function to "finish" the expression, creating a member reference
613622
// to the given sequence of underlying variables.
614623
Optional<EnclosingSelfPropertyWrapperAccess> enclosingSelfAccess;
@@ -762,38 +771,27 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
762771
return finish(storageDRE);
763772
}
764773

765-
bool isMemberLValue = isLValue;
766-
auto propertyWrapperMutability =
767-
[&](Decl *decl) -> Optional<std::pair<bool, bool>> {
768-
auto var = dyn_cast<VarDecl>(decl);
769-
if (!var)
770-
return None;
771-
auto mut = var->getPropertyWrapperMutability();
772-
if (!mut)
773-
return None;
774-
return std::make_pair(mut->Getter == PropertyWrapperMutability::Mutating,
775-
mut->Setter == PropertyWrapperMutability::Mutating);
776-
};
774+
// Build self
777775

778-
// If we're accessing a property wrapper, determine if the
779-
// intermediate access requires an lvalue.
780-
if (auto mut = propertyWrapperMutability(accessor->getStorage())) {
781-
isMemberLValue = mut->first;
782-
if (isLValue)
783-
isMemberLValue |= mut->second;
776+
bool isGetterMutating = storage->isGetterMutating();
777+
bool isSetterMutating = storage->isSetterMutating();
778+
if (target == TargetImpl::Wrapper || target == TargetImpl::WrapperStorage) {
779+
auto var = cast<VarDecl>(accessor->getStorage());
780+
if (auto mutability = var->getPropertyWrapperMutability()) {
781+
// We consider the storage's mutability too because the wrapped property
782+
// might be part of a class, in case of which nothing is mutating.
783+
isGetterMutating = (mutability->Getter == PropertyWrapperMutability::Mutating)
784+
? (storage->isGetterMutating() || storage->isSetterMutating())
785+
: storage->isGetterMutating();
786+
isSetterMutating = (mutability->Setter == PropertyWrapperMutability::Mutating)
787+
? (storage->isGetterMutating() || storage->isSetterMutating())
788+
: storage->isGetterMutating();
789+
}
784790
}
785791

786-
bool isSelfLValue = storage->isGetterMutating();
787-
if (isMemberLValue)
788-
isSelfLValue |= storage->isSetterMutating();
789-
790-
// If we're accessing a property wrapper, determine if
791-
// the self requires an lvalue.
792-
if (auto mut = propertyWrapperMutability(storage)) {
793-
isSelfLValue = mut->first;
794-
if (isMemberLValue)
795-
isSelfLValue |= mut->second;
796-
}
792+
// If the accessor is mutating, then self should be referred as an l-value
793+
bool isSelfLValue = (isGetterMutating && isUsedForGetAccess) ||
794+
(isSetterMutating && isUsedForSetAccess);
797795

798796
Expr *selfDRE =
799797
buildSelfReference(selfDecl, selfAccessKind, isSelfLValue,
@@ -806,6 +804,27 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
806804
selfDRE = new (ctx) DerivedToBaseExpr(selfDRE, selfTypeForAccess);
807805
}
808806

807+
// Build self.member or equivalent
808+
809+
bool isMemberLValue = isLValue;
810+
if (target == TargetImpl::Wrapper || target == TargetImpl::WrapperStorage) {
811+
// If the outermost property wrapper's getter / setter is mutating,
812+
// then the reference to the backing storage should be an l-value.
813+
auto var = cast<VarDecl>(accessor->getStorage());
814+
auto varMember = (target == TargetImpl::WrapperStorage)
815+
? &PropertyWrapperTypeInfo::projectedValueVar
816+
: &PropertyWrapperTypeInfo::valueVar;
817+
auto wrapper = (target == TargetImpl::WrapperStorage)
818+
? var->getOriginalWrappedProperty(
819+
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)
820+
->getAttachedPropertyWrapperTypeInfo(0)
821+
: var->getAttachedPropertyWrapperTypeInfo(0);
822+
bool isWrapperGetterMutating = (wrapper.*varMember)->isGetterMutating();
823+
bool isWrapperSetterMutating = (wrapper.*varMember)->isSetterMutating();
824+
isMemberLValue = (isWrapperGetterMutating && isUsedForGetAccess) ||
825+
(isWrapperSetterMutating && isUsedForSetAccess);
826+
}
827+
809828
Expr *lookupExpr;
810829
ConcreteDeclRef memberRef(storage, subs);
811830
auto type = storage->getValueInterfaceType().subst(subs);
@@ -883,6 +902,8 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
883902
lookupExpr->setType(type);
884903
}
885904

905+
// Build self.member.wrappedValue if applicable
906+
886907
return finish(lookupExpr);
887908
}
888909

@@ -893,7 +914,9 @@ createPropertyLoadOrCallSuperclassGetter(AccessorDecl *accessor,
893914
AbstractStorageDecl *storage,
894915
TargetImpl target,
895916
ASTContext &ctx) {
896-
return buildStorageReference(accessor, storage, target, /*isLValue=*/false,
917+
return buildStorageReference(accessor, storage, target,
918+
/*isUsedForGetAccess=*/true,
919+
/*isUsedForSetAccess=*/false,
897920
ctx);
898921
}
899922

@@ -1045,7 +1068,9 @@ void createPropertyStoreOrCallSuperclassSetter(AccessorDecl *accessor,
10451068
return;
10461069

10471070
Expr *dest = buildStorageReference(accessor, storage, target,
1048-
/*isLValue=*/true, ctx);
1071+
/*isUsedForGetAccess=*/false,
1072+
/*isUsedForSetAccess=*/true,
1073+
ctx);
10491074

10501075
// Error recovery.
10511076
if (dest == nullptr)
@@ -1495,7 +1520,10 @@ synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target,
14951520
VarDecl *OldValue = nullptr;
14961521
if (VD->getParsedAccessor(AccessorKind::DidSet)) {
14971522
Expr *OldValueExpr
1498-
= buildStorageReference(Set, VD, target, /*isLValue=*/true, Ctx);
1523+
= buildStorageReference(Set, VD, target,
1524+
/*isUsedForGetAccess=*/true,
1525+
/*isUsedForSetAccess=*/true,
1526+
Ctx);
14991527

15001528
// Error recovery.
15011529
if (OldValueExpr == nullptr) {
@@ -1626,10 +1654,13 @@ synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) {
16261654
SourceLoc loc = storage->getLoc();
16271655
SmallVector<ASTNode, 1> body;
16281656

1629-
bool isLValue = accessor->getAccessorKind() == AccessorKind::Modify;
1657+
bool isModify = accessor->getAccessorKind() == AccessorKind::Modify;
16301658

16311659
// Build a reference to the storage.
1632-
Expr *ref = buildStorageReference(accessor, storage, target, isLValue, ctx);
1660+
Expr *ref = buildStorageReference(accessor, storage, target,
1661+
/*isUsedForGetAccess=*/true,
1662+
/*isUsedForSetAccess=*/isModify,
1663+
ctx);
16331664
if (ref != nullptr) {
16341665
// Wrap it with an `&` marker if this is a modify.
16351666
ref = maybeWrapInOutExpr(ref, ctx);

test/SILGen/property_wrappers.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,20 @@ struct MutatingGet<T> {
363363
}
364364
}
365365

366+
@propertyWrapper
367+
struct MutatingGetNonMutatingSet<T> {
368+
private var fixed: T
369+
370+
var wrappedValue: T {
371+
mutating get { fixed }
372+
nonmutating set { }
373+
}
374+
375+
init(wrappedValue initialValue: T) {
376+
fixed = initialValue
377+
}
378+
}
379+
366380
struct ObservingTest {
367381
// ObservingTest.text.setter
368382
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers13ObservingTestV4textSSvs : $@convention(method) (@owned String, @guaranteed ObservingTest) -> ()
@@ -382,6 +396,26 @@ struct ObservingTest {
382396
@MutatingGet var integer2: Int = 17 {
383397
willSet { }
384398
}
399+
400+
@MutatingGetNonMutatingSet var text3: String = "" {
401+
didSet { }
402+
}
403+
404+
@MutatingGetNonMutatingSet var integer3: Int = 17 {
405+
willSet { }
406+
}
407+
}
408+
409+
struct NonObservingTest {
410+
@NonMutatingSet var text: String = ""
411+
@MutatingGet var text2: String = ""
412+
@MutatingGetNonMutatingSet var text3: String = ""
413+
}
414+
415+
class NonObservingClassTest {
416+
@NonMutatingSet var text: String = ""
417+
@MutatingGet var text2: String = ""
418+
@MutatingGetNonMutatingSet var text3: String = ""
385419
}
386420

387421
// Tuple initial values.

0 commit comments

Comments
 (0)