Skip to content

Commit 355fbb3

Browse files
authored
Merge pull request swiftlang#34109 from hborla/local-property-wrappers
[Property Wrappers] Support local property wrappers
2 parents f2eb9a5 + 763bcf9 commit 355fbb3

19 files changed

+346
-54
lines changed

include/swift/AST/Decl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4842,6 +4842,14 @@ class VarDecl : public AbstractStorageDecl {
48424842
/// property wrapper with a \c projectedValue .
48434843
VarDecl *getPropertyWrapperProjectionVar() const;
48444844

4845+
/// Visit all auxiliary declarations to this VarDecl.
4846+
///
4847+
/// An auxiliary declaration is a declaration synthesized by the compiler to support
4848+
/// this VarDecl, such as synthesized property wrapper variables.
4849+
///
4850+
/// \note this function only visits auxiliary decls that are not part of the AST.
4851+
void visitAuxiliaryDecls(llvm::function_ref<void(VarDecl *)>) const;
4852+
48454853
/// Retrieve the backing storage property for a lazy property.
48464854
VarDecl *getLazyStorageProperty() const;
48474855

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5216,8 +5216,6 @@ ERROR(property_wrapper_mutating_get_composed_to_get_only,none,
52165216
"property wrapper %0 with a mutating getter cannot be composed inside "
52175217
"get-only property wrapper %1", (TypeLoc, TypeLoc))
52185218

5219-
ERROR(property_wrapper_local,none,
5220-
"property wrappers are not yet supported on local properties", ())
52215219
ERROR(property_wrapper_top_level,none,
52225220
"property wrappers are not yet supported in top-level code", ())
52235221
ERROR(property_wrapper_let, none,

lib/AST/Decl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5835,6 +5835,17 @@ VarDecl *VarDecl::getPropertyWrapperProjectionVar() const {
58355835
return getPropertyWrapperBackingPropertyInfo().projectionVar;
58365836
}
58375837

5838+
void VarDecl::visitAuxiliaryDecls(llvm::function_ref<void(VarDecl *)> visit) const {
5839+
if (getDeclContext()->isTypeContext())
5840+
return;
5841+
5842+
if (auto *backingVar = getPropertyWrapperBackingProperty())
5843+
visit(backingVar);
5844+
5845+
if (auto *projectionVar = getPropertyWrapperProjectionVar())
5846+
visit(projectionVar);
5847+
}
5848+
58385849
VarDecl *VarDecl::getLazyStorageProperty() const {
58395850
auto &ctx = getASTContext();
58405851
auto mutableThis = const_cast<VarDecl *>(this);

lib/AST/UnqualifiedLookup.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/ModuleNameLookup.h"
2323
#include "swift/AST/NameLookup.h"
2424
#include "swift/AST/NameLookupRequests.h"
25+
#include "swift/AST/PropertyWrappers.h"
2526
#include "swift/Basic/Debug.h"
2627
#include "swift/Basic/STLExtras.h"
2728
#include "swift/Basic/SourceManager.h"
@@ -564,8 +565,22 @@ bool ASTScopeDeclConsumerForUnqualifiedLookup::consume(
564565
}
565566
}
566567

567-
if (!value->getName().matchesRef(factory.Name.getFullName()))
568-
continue;
568+
auto fullName = factory.Name.getFullName();
569+
if (!value->getName().matchesRef(fullName)) {
570+
bool foundMatch = false;
571+
if (auto *varDecl = dyn_cast<VarDecl>(value)) {
572+
// Check if the name matches any auxiliary decls not in the AST
573+
varDecl->visitAuxiliaryDecls([&](VarDecl *auxiliaryVar) {
574+
if (auxiliaryVar->ValueDecl::getName().matchesRef(fullName)) {
575+
value = auxiliaryVar;
576+
foundMatch = true;
577+
}
578+
});
579+
}
580+
581+
if (!foundMatch)
582+
continue;
583+
}
569584

570585
// In order to preserve the behavior of the existing context-based lookup,
571586
// which finds all results for non-local variables at the top level instead

lib/Parse/ParseExpr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2228,7 +2228,7 @@ Expr *Parser::parseExprIdentifier() {
22282228
}
22292229

22302230
Expr *E;
2231-
if (D == nullptr) {
2231+
if (D == nullptr || D->getAttrs().hasAttribute<CustomAttr>()) {
22322232
if (name.getBaseName().isEditorPlaceholder()) {
22332233
IDSyntaxContext.setCreateSyntax(SyntaxKind::EditorPlaceholderExpr);
22342234
return parseExprEditorPlaceholder(IdentTok, name.getBaseIdentifier());

lib/SIL/IR/SILDeclRef.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,8 @@ IsSerialized_t SILDeclRef::isSerialized() const {
487487

488488
// Stored property initializers are inlinable if the type is explicitly
489489
// marked as @frozen.
490-
if (isStoredPropertyInitializer() || isPropertyWrapperBackingInitializer()) {
490+
if (isStoredPropertyInitializer() || (isPropertyWrapperBackingInitializer() &&
491+
d->getDeclContext()->isTypeContext())) {
491492
auto *nominal = cast<NominalTypeDecl>(d->getDeclContext());
492493
auto scope =
493494
nominal->getFormalAccessScope(/*useDC=*/nullptr,

lib/SILGen/SILGenDecl.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/Module.h"
2323
#include "swift/AST/NameLookup.h"
2424
#include "swift/AST/ProtocolConformance.h"
25+
#include "swift/AST/PropertyWrappers.h"
2526
#include "swift/Basic/ProfileCounter.h"
2627
#include "swift/SIL/FormalLinkage.h"
2728
#include "swift/SIL/PrettyStackTrace.h"
@@ -1171,6 +1172,22 @@ void SILGenFunction::emitPatternBinding(PatternBindingDecl *PBD,
11711172
// the initialization. Otherwise, mark it uninitialized for DI to resolve.
11721173
if (auto *Init = PBD->getExecutableInit(idx)) {
11731174
FullExpr Scope(Cleanups, CleanupLocation(Init));
1175+
1176+
auto *var = PBD->getSingleVar();
1177+
if (var && var->getDeclContext()->isLocalContext()) {
1178+
if (auto *orig = var->getOriginalWrappedProperty()) {
1179+
auto wrapperInfo = orig->getPropertyWrapperBackingPropertyInfo();
1180+
Init = wrapperInfo.wrappedValuePlaceholder->getOriginalWrappedValue();
1181+
1182+
auto value = emitRValue(Init);
1183+
emitApplyOfPropertyWrapperBackingInitializer(SILLocation(PBD), orig,
1184+
getForwardingSubstitutionMap(),
1185+
std::move(value))
1186+
.forwardInto(*this, SILLocation(PBD), initialization.get());
1187+
return;
1188+
}
1189+
}
1190+
11741191
emitExprInto(Init, initialization.get(), SILLocation(PBD));
11751192
} else {
11761193
initialization->finishUninitialized(*this);
@@ -1189,6 +1206,18 @@ void SILGenFunction::visitPatternBindingDecl(PatternBindingDecl *PBD) {
11891206
void SILGenFunction::visitVarDecl(VarDecl *D) {
11901207
// We handle emitting the variable storage when we see the pattern binding.
11911208

1209+
// Emit the property wrapper backing initializer if necessary.
1210+
auto wrapperInfo = D->getPropertyWrapperBackingPropertyInfo();
1211+
if (wrapperInfo && wrapperInfo.initializeFromOriginal)
1212+
SGM.emitPropertyWrapperBackingInitializer(D);
1213+
1214+
D->visitAuxiliaryDecls([&](VarDecl *var) {
1215+
if (auto *patternBinding = var->getParentPatternBinding())
1216+
visitPatternBindingDecl(patternBinding);
1217+
1218+
visit(var);
1219+
});
1220+
11921221
// Emit the variable's accessors.
11931222
D->visitEmittedAccessors([&](AccessorDecl *accessor) {
11941223
SGM.emitFunction(accessor);

lib/SILGen/SILGenFunction.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,18 @@ void SILGenFunction::emitCaptures(SILLocation loc,
187187
// Partial applications take ownership of the context parameters, so we'll
188188
// need to pass ownership rather than merely guaranteeing parameters.
189189
bool canGuarantee;
190+
bool captureCanEscape = true;
190191
switch (purpose) {
191192
case CaptureEmission::PartialApplication:
192193
canGuarantee = false;
193194
break;
194195
case CaptureEmission::ImmediateApplication:
195196
canGuarantee = true;
196197
break;
198+
case CaptureEmission::AssignByWrapper:
199+
canGuarantee = false;
200+
captureCanEscape = false;
201+
break;
197202
}
198203

199204
auto expansion = getTypeExpansionContext();
@@ -381,7 +386,8 @@ void SILGenFunction::emitCaptures(SILLocation loc,
381386
} else {
382387
capturedArgs.push_back(emitManagedRetain(loc, Entry.box));
383388
}
384-
escapesToMark.push_back(entryValue);
389+
if (captureCanEscape)
390+
escapesToMark.push_back(entryValue);
385391
} else {
386392
// Address only 'let' values are passed by box. This isn't great, in
387393
// that a variable captured by multiple closures will be boxed for each

lib/SILGen/SILGenFunction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ enum class CaptureEmission {
115115
/// Captures are being emitted for partial application to form a closure
116116
/// value.
117117
PartialApplication,
118+
/// Captures are being emitted for partial application of a local property
119+
/// wrapper setter for assign_by_wrapper. Captures are guaranteed to not
120+
/// escape, because assign_by_wrapper will not use the setter if the captured
121+
/// variable is not initialized.
122+
AssignByWrapper,
118123
};
119124

120125
/// Different ways in which an l-value can be emitted.

lib/SILGen/SILGenLValue.cpp

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,17 @@ namespace {
13141314
IsOnSelfParameter &&
13151315
isa<ConstructorDecl>(SGF.FunctionDC->getAsDecl());
13161316

1317+
// Assignment to a wrapped property can only be re-written to initialization for
1318+
// members of `self` in an initializer, and for local variables.
1319+
if (!(isAssignmentToSelfParamInInit || VD->getDeclContext()->isLocalContext()))
1320+
return false;
1321+
1322+
// If this var isn't in a type context, assignment will always use the setter
1323+
// if there is an initial value.
1324+
if (!VD->getDeclContext()->isTypeContext() &&
1325+
wrapperInfo.wrappedValuePlaceholder->getOriginalWrappedValue())
1326+
return false;
1327+
13171328
// If we have a nonmutating setter on a value type, the call
13181329
// captures all of 'self' and we cannot rewrite an assignment
13191330
// into an initialization.
@@ -1403,7 +1414,7 @@ namespace {
14031414
assert(getAccessorDecl()->isSetter());
14041415
SILDeclRef setter = Accessor;
14051416

1406-
if (IsOnSelfParameter && canRewriteSetAsPropertyWrapperInit(SGF) &&
1417+
if (canRewriteSetAsPropertyWrapperInit(SGF) &&
14071418
!Storage->isStatic() &&
14081419
isBackingVarVisible(cast<VarDecl>(Storage),
14091420
SGF.FunctionDC)) {
@@ -1434,7 +1445,9 @@ namespace {
14341445

14351446
// Get the address of the storage property.
14361447
ManagedValue proj;
1437-
if (BaseFormalType->mayHaveSuperclass()) {
1448+
if (!BaseFormalType) {
1449+
proj = SGF.maybeEmitValueOfLocalVarDecl(backingVar);
1450+
} else if (BaseFormalType->mayHaveSuperclass()) {
14381451
RefElementComponent REC(backingVar, LValueOptions(), varStorageType,
14391452
typeData);
14401453
proj = std::move(REC).project(SGF, loc, base);
@@ -1471,33 +1484,51 @@ namespace {
14711484
.SILFnType)
14721485
.getValue();
14731486

1474-
} else
1487+
} else {
14751488
setterFRef = SGF.emitGlobalFunctionRef(loc, setter, setterInfo);
1489+
}
1490+
14761491
CanSILFunctionType setterTy = setterFRef->getType().castTo<SILFunctionType>();
14771492
SILFunctionConventions setterConv(setterTy, SGF.SGM.M);
14781493

1479-
SILValue capturedBase;
1480-
unsigned argIdx = setterConv.getNumSILArguments() - 1;
1481-
if (setterConv.getSILArgumentConvention(argIdx).isInoutConvention()) {
1482-
capturedBase = base.getValue();
1483-
} else {
1484-
capturedBase = base.copy(SGF, loc).forward(SGF);
1485-
}
1494+
// Emit captures for the setter
1495+
SmallVector<SILValue, 4> capturedArgs;
1496+
auto captureInfo = SGF.SGM.Types.getLoweredLocalCaptures(setter);
1497+
if (!captureInfo.getCaptures().empty()) {
1498+
SmallVector<ManagedValue, 4> captures;
1499+
SGF.emitCaptures(loc, setter, CaptureEmission::AssignByWrapper, captures);
14861500

1487-
// If the base is a reference and the setter expects a value, emit a
1488-
// load. This pattern is emitted for property wrappers with a
1489-
// nonmutating setter, for example.
1490-
if (base.getType().isAddress() &&
1491-
base.getType().getObjectType() ==
1492-
setterConv.getSILArgumentType(argIdx,
1493-
SGF.getTypeExpansionContext())) {
1494-
capturedBase = SGF.B.createTrivialLoadOr(
1495-
loc, capturedBase, LoadOwnershipQualifier::Take);
1501+
for (auto capture : captures)
1502+
capturedArgs.push_back(capture.forward(SGF));
1503+
} else {
1504+
assert(base);
1505+
1506+
SILValue capturedBase;
1507+
unsigned argIdx = setterConv.getNumSILArguments() - 1;
1508+
1509+
if (setterConv.getSILArgumentConvention(argIdx).isInoutConvention()) {
1510+
capturedBase = base.getValue();
1511+
} else {
1512+
capturedBase = base.copy(SGF, loc).forward(SGF);
1513+
}
1514+
1515+
// If the base is a reference and the setter expects a value, emit a
1516+
// load. This pattern is emitted for property wrappers with a
1517+
// nonmutating setter, for example.
1518+
if (base.getType().isAddress() &&
1519+
base.getType().getObjectType() ==
1520+
setterConv.getSILArgumentType(argIdx,
1521+
SGF.getTypeExpansionContext())) {
1522+
capturedBase = SGF.B.createTrivialLoadOr(
1523+
loc, capturedBase, LoadOwnershipQualifier::Take);
1524+
}
1525+
1526+
capturedArgs.push_back(capturedBase);
14961527
}
14971528

14981529
PartialApplyInst *setterPAI =
14991530
SGF.B.createPartialApply(loc, setterFRef,
1500-
Substitutions, { capturedBase },
1531+
Substitutions, capturedArgs,
15011532
ParameterConvention::Direct_Guaranteed);
15021533
ManagedValue setterFn = SGF.emitManagedRValueWithCleanup(setterPAI);
15031534

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
14541454
(void) VD->getPropertyWrapperBackingProperty();
14551455
(void) VD->getImplInfo();
14561456

1457+
// Visit auxiliary decls first
1458+
VD->visitAuxiliaryDecls([&](VarDecl *var) {
1459+
this->visitBoundVariable(var);
1460+
});
1461+
14571462
// Add the '@_hasStorage' attribute if this property is stored.
14581463
if (VD->hasStorage() && !VD->getAttrs().hasAttribute<HasStorageAttr>())
14591464
VD->getAttrs().add(new (getASTContext())

lib/Sema/TypeCheckPropertyWrapper.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,12 +414,6 @@ AttachedPropertyWrappersRequest::evaluate(Evaluator &evaluator,
414414
// Check various restrictions on which properties can have wrappers
415415
// attached to them.
416416

417-
// Local properties do not yet support wrappers.
418-
if (var->getDeclContext()->isLocalContext()) {
419-
ctx.Diags.diagnose(attr->getLocation(), diag::property_wrapper_local);
420-
continue;
421-
}
422-
423417
// Nor does top-level code.
424418
if (var->getDeclContext()->isModuleScopeContext()) {
425419
ctx.Diags.diagnose(attr->getLocation(), diag::property_wrapper_top_level);

lib/Sema/TypeCheckStorage.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,8 +1169,9 @@ synthesizeTrivialGetterBody(AccessorDecl *getter, TargetImpl target,
11691169
body.push_back(returnStmt);
11701170
}
11711171

1172+
// Don't mark local accessors as type-checked - captures still need to be computed.
11721173
return { BraceStmt::create(ctx, loc, body, loc, true),
1173-
/*isTypeChecked=*/true };
1174+
/*isTypeChecked=*/!getter->getDeclContext()->isLocalContext() };
11741175
}
11751176

11761177
/// Synthesize the body of a getter which just directly accesses the
@@ -1445,8 +1446,9 @@ synthesizeTrivialSetterBodyWithStorage(AccessorDecl *setter,
14451446

14461447
createPropertyStoreOrCallSuperclassSetter(setter, valueDRE, storageToUse,
14471448
target, setterBody, ctx);
1449+
// Don't mark local accessors as type-checked - captures still need to be computed.
14481450
return { BraceStmt::create(ctx, loc, setterBody, loc, true),
1449-
/*isTypeChecked=*/true };
1451+
/*isTypeChecked=*/!setter->getDeclContext()->isLocalContext() };
14501452
}
14511453

14521454
static std::pair<BraceStmt *, bool>
@@ -2757,6 +2759,8 @@ PropertyWrapperBackingPropertyInfoRequest::evaluate(Evaluator &evaluator,
27572759
initializer);
27582760
pbd->setInit(0, initializer);
27592761
pbd->setInitializerChecked(0);
2762+
} else if (var->hasObservers() && !dc->isTypeContext()) {
2763+
var->diagnose(diag::observingprop_requires_initializer);
27602764
}
27612765

27622766
if (var->getOpaqueResultTypeDecl()) {

test/SILGen/objc_properties.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,19 +285,28 @@ public struct SomeWrapper {
285285

286286
class SomeWrapperTests {
287287
@objc @SomeWrapper dynamic var someWrapper: Int = 0
288-
// CHECK-LABEL: sil hidden [ossa] @$s15objc_properties16SomeWrapperTestsC14testAssignmentyyF
288+
@W @objc dynamic var s: String? = nil
289+
290+
// CHECK-LABEL: sil hidden [ossa] @$s15objc_properties16SomeWrapperTestsCyACSScfc : $@convention(method) (@owned String, @owned SomeWrapperTests) -> @owned SomeWrapperTests {
289291
// CHECK: [[M:%.*]] = function_ref @$s15objc_properties16SomeWrapperTestsC04someD0SivsTD
290292
// CHECK: [[C:%.*]] = partial_apply [callee_guaranteed] [[M]]({{.*}})
291293
// CHECK: assign_by_wrapper {{%.*}}: $Int to {{%.*}} : $*SomeWrapper, init {{.*}} : $@callee_guaranteed (Int) -> SomeWrapper, set [[C]] : $@callee_guaranteed (Int) -> ()
294+
// CHECK: [[M:%.*]] = function_ref @$s15objc_properties16SomeWrapperTestsC1sSSSgvsTD
295+
// CHECK: [[C:%.*]] = partial_apply [callee_guaranteed] [[M]](
296+
// CHECK: assign_by_wrapper {{.*}} : $Optional<String> to {{.*}} : $*W<Optional<String>>, init {{.*}} : $@callee_guaranteed (@owned Optional<String>) -> @owned W<Optional<String>>, set [[C]] : $@callee_guaranteed (@owned Optional<String>) -> ()
297+
init(_ s: String) {
298+
someWrapper = 1000
299+
self.s = s
300+
}
301+
302+
// CHECK-LABEL: sil hidden [ossa] @$s15objc_properties16SomeWrapperTestsC14testAssignmentyyF
303+
// CHECK: objc_method %0 : $SomeWrapperTests, #SomeWrapperTests.someWrapper!setter.foreign : (SomeWrapperTests) -> (Int) -> (), $@convention(objc_method) (Int, SomeWrapperTests) -> ()
292304
func testAssignment() {
293305
someWrapper = 1000
294306
}
295307

296-
@W @objc dynamic var s: String? = nil
297308
// CHECK-LABEL: sil hidden [ossa] @$s15objc_properties16SomeWrapperTestsC16testBridgedValueyySSF
298-
// CHECK: [[M:%.*]] = function_ref @$s15objc_properties16SomeWrapperTestsC1sSSSgvsTD
299-
// CHECK: [[C:%.*]] = partial_apply [callee_guaranteed] [[M]](
300-
// CHECK: assign_by_wrapper {{.*}} : $Optional<String> to {{.*}} : $*W<Optional<String>>, init {{.*}} : $@callee_guaranteed (@owned Optional<String>) -> @owned W<Optional<String>>, set [[C]] : $@callee_guaranteed (@owned Optional<String>) -> ()
309+
// CHECK: objc_method %1 : $SomeWrapperTests, #SomeWrapperTests.s!setter.foreign : (SomeWrapperTests) -> (String?) -> (), $@convention(objc_method) (Optional<NSString>, SomeWrapperTests) -> ()
301310
// Let's not crash.
302311
func testBridgedValue(_ s: String) {
303312
self.s = s

0 commit comments

Comments
 (0)