Skip to content

Commit 8c86fdf

Browse files
roophborla
authored andcommitted
[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 66e8572 commit 8c86fdf

File tree

2 files changed

+116
-44
lines changed

2 files changed

+116
-44
lines changed

lib/Sema/TypeCheckStorage.cpp

Lines changed: 82 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,13 @@ IsSetterMutatingRequest::evaluate(Evaluator &evaluator,
341341
// or not based on the composition of the wrappers.
342342
if (auto var = dyn_cast<VarDecl>(storage)) {
343343
if (auto mut = var->getPropertyWrapperMutability()) {
344-
return mut->Setter == PropertyWrapperMutability::Mutating
345-
&& result;
344+
bool isMutating = mut->Setter == PropertyWrapperMutability::Mutating;
345+
if (var->getParsedAccessor(AccessorKind::DidSet)) {
346+
// If there's a didSet, we call the getter for the 'oldValue', and so
347+
// should consider the getter's mutatingness as well
348+
isMutating |= (mut->Getter == PropertyWrapperMutability::Mutating);
349+
}
350+
return isMutating && result;
346351
}
347352
}
348353

@@ -587,14 +592,17 @@ getEnclosingSelfPropertyWrapperAccess(VarDecl *property, bool forProjected) {
587592
return result;
588593
}
589594

590-
/// Build an l-value for the storage of a declaration. Returns nullptr if there
595+
/// Build a reference to the storage of a declaration. Returns nullptr if there
591596
/// was an error. This should only occur if an invalid declaration was type
592597
/// checked; another diagnostic should have been emitted already.
593598
static Expr *buildStorageReference(AccessorDecl *accessor,
594599
AbstractStorageDecl *storage,
595600
TargetImpl target,
596-
bool isLValue,
601+
bool isUsedForGetAccess,
602+
bool isUsedForSetAccess,
597603
ASTContext &ctx) {
604+
// Whether the last component of the expression should be an l-value
605+
bool isLValue = isUsedForSetAccess;
598606
// Local function to "finish" the expression, creating a member reference
599607
// to the given sequence of underlying variables.
600608
Optional<EnclosingSelfPropertyWrapperAccess> enclosingSelfAccess;
@@ -758,46 +766,61 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
758766
return finish(storageDRE);
759767
}
760768

761-
bool isMemberLValue = isLValue;
762-
auto propertyWrapperMutability =
763-
[&](Decl *decl) -> Optional<std::pair<bool, bool>> {
764-
// If we're not accessing via a property wrapper, we don't need to adjust
765-
// the mutability.
766-
if (target != TargetImpl::Wrapper && target != TargetImpl::WrapperStorage)
767-
return None;
769+
// Build self
768770

769-
auto var = dyn_cast<VarDecl>(decl);
770-
if (!var)
771-
return None;
772-
auto mut = var->getPropertyWrapperMutability();
773-
if (!mut)
774-
return None;
775-
return std::make_pair(mut->Getter == PropertyWrapperMutability::Mutating,
776-
mut->Setter == PropertyWrapperMutability::Mutating);
777-
};
771+
bool isGetterMutating = storage->isGetterMutating();
772+
bool isSetterMutating = storage->isSetterMutating();
773+
// If we're not accessing via a property wrapper, we don't need to adjust
774+
// the mutability.
775+
if (target == TargetImpl::Wrapper || target == TargetImpl::WrapperStorage) {
776+
auto var = cast<VarDecl>(accessor->getStorage());
777+
if (auto mutability = var->getPropertyWrapperMutability()) {
778+
// We consider the storage's mutability too because the wrapped property
779+
// might be part of a class, in case of which nothing is mutating.
780+
isGetterMutating = (mutability->Getter == PropertyWrapperMutability::Mutating)
781+
? (storage->isGetterMutating() || storage->isSetterMutating())
782+
: storage->isGetterMutating();
783+
isSetterMutating = (mutability->Setter == PropertyWrapperMutability::Mutating)
784+
? (storage->isGetterMutating() || storage->isSetterMutating())
785+
: storage->isGetterMutating();
786+
}
787+
}
778788

779-
// If we're accessing a property wrapper, determine if the
780-
// intermediate access requires an lvalue.
781-
if (auto mut = propertyWrapperMutability(accessor->getStorage())) {
782-
isMemberLValue = mut->first;
783-
if (isLValue)
784-
isMemberLValue |= mut->second;
789+
// If the accessor is mutating, then self should be referred as an l-value
790+
bool isSelfLValue = (isGetterMutating && isUsedForGetAccess) ||
791+
(isSetterMutating && isUsedForSetAccess);
792+
793+
Expr *selfDRE = buildSelfReference(selfDecl, selfAccessKind, isSelfLValue,
794+
/*convertTy*/ selfTypeForAccess);
795+
if (isSelfLValue)
796+
selfTypeForAccess = LValueType::get(selfTypeForAccess);
797+
798+
if (!selfDRE->getType()->isEqual(selfTypeForAccess)) {
799+
assert(selfAccessKind == SelfAccessorKind::Super);
800+
selfDRE = new (ctx) DerivedToBaseExpr(selfDRE, selfTypeForAccess);
785801
}
786802

787-
bool isSelfLValue = storage->isGetterMutating();
788-
if (isMemberLValue)
789-
isSelfLValue |= storage->isSetterMutating();
803+
// Build self.member or equivalent
790804

791-
// If we're accessing a property wrapper, determine if
792-
// the self requires an lvalue.
793-
if (auto mut = propertyWrapperMutability(storage)) {
794-
isSelfLValue = mut->first;
795-
if (isMemberLValue)
796-
isSelfLValue |= mut->second;
805+
bool isMemberLValue = isLValue;
806+
if (target == TargetImpl::Wrapper || target == TargetImpl::WrapperStorage) {
807+
// If the outermost property wrapper's getter / setter is mutating,
808+
// then the reference to the backing storage should be an l-value.
809+
auto var = cast<VarDecl>(accessor->getStorage());
810+
auto varMember = (target == TargetImpl::WrapperStorage)
811+
? &PropertyWrapperTypeInfo::projectedValueVar
812+
: &PropertyWrapperTypeInfo::valueVar;
813+
auto wrapper = (target == TargetImpl::WrapperStorage)
814+
? var->getOriginalWrappedProperty(
815+
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)
816+
->getAttachedPropertyWrapperTypeInfo(0)
817+
: var->getAttachedPropertyWrapperTypeInfo(0);
818+
bool isWrapperGetterMutating = (wrapper.*varMember)->isGetterMutating();
819+
bool isWrapperSetterMutating = (wrapper.*varMember)->isSetterMutating();
820+
isMemberLValue = (isWrapperGetterMutating && isUsedForGetAccess) ||
821+
(isWrapperSetterMutating && isUsedForSetAccess);
797822
}
798823

799-
Expr *selfDRE = buildSelfReference(selfDecl, selfAccessKind, isSelfLValue,
800-
/*convertTy*/ selfTypeForAccess);
801824
Expr *lookupExpr;
802825
ConcreteDeclRef memberRef(storage, subs);
803826
auto type = storage->getValueInterfaceType().subst(subs);
@@ -871,6 +894,8 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
871894
lookupExpr->setType(type);
872895
}
873896

897+
// Build self.member.wrappedValue if applicable
898+
874899
return finish(lookupExpr);
875900
}
876901

@@ -881,7 +906,9 @@ createPropertyLoadOrCallSuperclassGetter(AccessorDecl *accessor,
881906
AbstractStorageDecl *storage,
882907
TargetImpl target,
883908
ASTContext &ctx) {
884-
return buildStorageReference(accessor, storage, target, /*isLValue=*/false,
909+
return buildStorageReference(accessor, storage, target,
910+
/*isUsedForGetAccess=*/true,
911+
/*isUsedForSetAccess=*/false,
885912
ctx);
886913
}
887914

@@ -1033,7 +1060,9 @@ void createPropertyStoreOrCallSuperclassSetter(AccessorDecl *accessor,
10331060
return;
10341061

10351062
Expr *dest = buildStorageReference(accessor, storage, target,
1036-
/*isLValue=*/true, ctx);
1063+
/*isUsedForGetAccess=*/false,
1064+
/*isUsedForSetAccess=*/true,
1065+
ctx);
10371066

10381067
// Error recovery.
10391068
if (dest == nullptr)
@@ -1493,7 +1522,10 @@ synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target,
14931522
// parameter or it's provided explicitly in the parameter list.
14941523
if (!didSet->isSimpleDidSet()) {
14951524
Expr *OldValueExpr =
1496-
buildStorageReference(Set, VD, target, /*isLValue=*/true, Ctx);
1525+
buildStorageReference(Set, VD, target,
1526+
/*isUsedForGetAccess=*/true,
1527+
/*isUsedForSetAccess=*/true,
1528+
Ctx);
14971529

14981530
// Error recovery.
14991531
if (OldValueExpr == nullptr) {
@@ -1609,7 +1641,10 @@ synthesizeModifyCoroutineBodyWithSimpleDidSet(AccessorDecl *accessor,
16091641

16101642
SmallVector<ASTNode, 1> body;
16111643

1612-
Expr *ref = buildStorageReference(accessor, storage, target, true, ctx);
1644+
Expr *ref = buildStorageReference(accessor, storage, target,
1645+
/*isUsedForGetAccess=*/true,
1646+
/*isUsedForSetAccess=*/true,
1647+
ctx);
16131648
ref = maybeWrapInOutExpr(ref, ctx);
16141649

16151650
YieldStmt *yield = YieldStmt::create(ctx, loc, loc, ref, loc, true);
@@ -1686,16 +1721,19 @@ synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) {
16861721
SourceLoc loc = storage->getLoc();
16871722
SmallVector<ASTNode, 1> body;
16881723

1689-
bool isLValue = accessor->getAccessorKind() == AccessorKind::Modify;
1724+
bool isModify = accessor->getAccessorKind() == AccessorKind::Modify;
16901725

1691-
if (isLValue &&
1726+
if (isModify &&
16921727
(storageReadWriteImpl == ReadWriteImplKind::StoredWithSimpleDidSet ||
16931728
storageReadWriteImpl == ReadWriteImplKind::InheritedWithSimpleDidSet)) {
16941729
return synthesizeModifyCoroutineBodyWithSimpleDidSet(accessor, ctx);
16951730
}
16961731

16971732
// Build a reference to the storage.
1698-
Expr *ref = buildStorageReference(accessor, storage, target, isLValue, ctx);
1733+
Expr *ref = buildStorageReference(accessor, storage, target,
1734+
/*isUsedForGetAccess=*/true,
1735+
/*isUsedForSetAccess=*/isModify,
1736+
ctx);
16991737
if (ref != nullptr) {
17001738
// Wrap it with an `&` marker if this is a modify.
17011739
ref = maybeWrapInOutExpr(ref, ctx);

test/SILGen/property_wrappers.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,20 @@ struct MutatingGet<T> {
454454
}
455455
}
456456

457+
@propertyWrapper
458+
struct MutatingGetNonMutatingSet<T> {
459+
private var fixed: T
460+
461+
var wrappedValue: T {
462+
mutating get { fixed }
463+
nonmutating set { }
464+
}
465+
466+
init(wrappedValue initialValue: T) {
467+
fixed = initialValue
468+
}
469+
}
470+
457471
struct ObservingTest {
458472
// ObservingTest.text.setter
459473
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers13ObservingTestV4textSSvs : $@convention(method) (@owned String, @guaranteed ObservingTest) -> ()
@@ -473,6 +487,26 @@ struct ObservingTest {
473487
@MutatingGet var integer2: Int = 17 {
474488
willSet { }
475489
}
490+
491+
@MutatingGetNonMutatingSet var text3: String = "" {
492+
didSet { }
493+
}
494+
495+
@MutatingGetNonMutatingSet var integer3: Int = 17 {
496+
willSet { }
497+
}
498+
}
499+
500+
struct NonObservingTest {
501+
@NonMutatingSet var text: String = ""
502+
@MutatingGet var text2: String = ""
503+
@MutatingGetNonMutatingSet var text3: String = ""
504+
}
505+
506+
class NonObservingClassTest {
507+
@NonMutatingSet var text: String = ""
508+
@MutatingGet var text2: String = ""
509+
@MutatingGetNonMutatingSet var text3: String = ""
476510
}
477511

478512
// Tuple initial values.

0 commit comments

Comments
 (0)