diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c78369512c5a4..a674de7ae7c95 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4452,6 +4452,9 @@ ERROR(property_wrapper_type_requirement_not_accessible,none, "more restrictive access than its enclosing property wrapper type %3 " "(which is %select{private|fileprivate|internal|public|open}4)", (AccessLevel, DescriptiveDeclKind, DeclName, Type, AccessLevel)) +ERROR(property_wrapper_ambiguous_enclosing_self_subscript, none, + "property wrapper type %0 has multiple enclosing-self subscripts %1", + (Type, DeclName)) ERROR(property_wrapper_attribute_not_on_property, none, "property wrapper attribute %0 can only be applied to a property", diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 890668826d44e..a7c844b4e8914 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -56,6 +56,7 @@ IDENTIFIER(delegateValue) IDENTIFIER(dynamicallyCall) IDENTIFIER(dynamicMember) IDENTIFIER(Element) +IDENTIFIER_(enclosingInstance) IDENTIFIER(Encodable) IDENTIFIER(encode) IDENTIFIER(encodeIfPresent) @@ -95,6 +96,7 @@ IDENTIFIER_(ObjectiveCType) IDENTIFIER(Optional) IDENTIFIER_(OptionalNilComparisonType) IDENTIFIER(parameter) +IDENTIFIER(projected) IDENTIFIER(projectedValue) IDENTIFIER(Protocol) IDENTIFIER(rawValue) @@ -122,6 +124,7 @@ IDENTIFIER(WinSDK) IDENTIFIER(with) IDENTIFIER(withArguments) IDENTIFIER(withKeywordArguments) +IDENTIFIER(wrapped) IDENTIFIER(wrappedValue) IDENTIFIER(wrapperValue) diff --git a/include/swift/AST/PropertyWrappers.h b/include/swift/AST/PropertyWrappers.h index a49c8deb088e3..03d1ba5ea6ede 100644 --- a/include/swift/AST/PropertyWrappers.h +++ b/include/swift/AST/PropertyWrappers.h @@ -52,6 +52,17 @@ struct PropertyWrapperTypeInfo { /// will be created that redirects to this property. VarDecl *projectedValueVar = nullptr; + /// The static subscript through which the access of instance properties + /// of classes can be directed (instead of wrappedValue), providing the + /// ability to reason about the enclosing "self". + SubscriptDecl *enclosingInstanceWrappedSubscript = nullptr; + + /// The static subscript through which the access of instance properties + /// of classes can be directed (instead of projectedValue), providing the + /// ability to reason about the enclosing "self". + SubscriptDecl *enclosingInstanceProjectedSubscript = nullptr; + + /// /// Whether this is a valid property wrapper. bool isValid() const { return valueVar != nullptr; diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index c94d55888075d..a378cb0136f0f 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -369,6 +369,9 @@ static void maybeMarkTransparent(AccessorDecl *accessor, ASTContext &ctx) { return; break; + } else if (var->getOriginalWrappedProperty( + PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) { + break; } } @@ -613,6 +616,57 @@ namespace { }; } // end anonymous namespace +namespace { + /// Describes the information needed to perform property wrapper access via + /// the enclosing self. + struct EnclosingSelfPropertyWrapperAccess { + /// The (genreric) subscript that will be used to perform the access. + SubscriptDecl *subscript; + + /// The property being accessed. + VarDecl *accessedProperty; + }; +} + +/// Determine whether the given property should be accessed via the enclosing-self access pattern. +static Optional +getEnclosingSelfPropertyWrapperAccess(VarDecl *property, bool forProjected) { + // The enclosing-self pattern only applies to instance properties of + // classes. + if (!property->isInstanceMember()) + return None; + auto classDecl = property->getDeclContext()->getSelfClassDecl(); + if (!classDecl) + return None; + + // The pattern currently only works with the outermost property wrapper. + Type outermostWrapperType = property->getAttachedPropertyWrapperType(0); + if (!outermostWrapperType) + return None; + NominalTypeDecl *wrapperTypeDecl = outermostWrapperType->getAnyNominal(); + if (!wrapperTypeDecl) + return None; + + // Look for a generic subscript that fits the general form we need. + auto wrapperInfo = wrapperTypeDecl->getPropertyWrapperTypeInfo(); + auto subscript = + forProjected ? wrapperInfo.enclosingInstanceProjectedSubscript + : wrapperInfo.enclosingInstanceWrappedSubscript; + if (!subscript) + return None; + + EnclosingSelfPropertyWrapperAccess result; + result.subscript = subscript; + + if (forProjected) { + result.accessedProperty = + property->getPropertyWrapperBackingPropertyInfo().storageWrapperVar; + } else { + result.accessedProperty = property; + } + return result; +} + /// Build an l-value for the storage of a declaration. static Expr *buildStorageReference(AccessorDecl *accessor, AbstractStorageDecl *storage, @@ -621,6 +675,7 @@ static Expr *buildStorageReference(AccessorDecl *accessor, ASTContext &ctx) { // Local function to "finish" the expression, creating a member reference // to the given sequence of underlying variables. + Optional enclosingSelfAccess; llvm::TinyPtrVector underlyingVars; auto finish = [&](Expr *result) -> Expr * { for (auto underlyingVar : underlyingVars) { @@ -699,9 +754,20 @@ static Expr *buildStorageReference(AccessorDecl *accessor, case TargetImpl::Wrapper: { auto var = cast(accessor->getStorage()); storage = var->getPropertyWrapperBackingProperty(); - for (unsigned i : indices(var->getAttachedPropertyWrappers())) { - underlyingVars.push_back( - var->getAttachedPropertyWrapperTypeInfo(i).valueVar); + + // If the outermost property wrapper uses the enclosing self pattern, + // record that. + unsigned lastWrapperIdx = var->getAttachedPropertyWrappers().size(); + unsigned firstWrapperIdx = 0; + enclosingSelfAccess = + getEnclosingSelfPropertyWrapperAccess(var, /*forProjected=*/false); + if (enclosingSelfAccess) + firstWrapperIdx = 1; + + // Perform accesses to the wrappedValues along the composition chain. + for (unsigned i : range(firstWrapperIdx, lastWrapperIdx)) { + auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(i); + underlyingVars.push_back(wrapperInfo.valueVar); } semantics = AccessSemantics::DirectToStorage; selfAccessKind = SelfAccessorKind::Peer; @@ -710,9 +776,14 @@ static Expr *buildStorageReference(AccessorDecl *accessor, case TargetImpl::WrapperStorage: { auto var = - cast(accessor->getStorage())->getOriginalWrappedProperty(); + cast(accessor->getStorage())->getOriginalWrappedProperty(); storage = var->getPropertyWrapperBackingProperty(); - underlyingVars.push_back( var->getAttachedPropertyWrapperTypeInfo(0).projectedValueVar); + enclosingSelfAccess = + getEnclosingSelfPropertyWrapperAccess(var, /*forProjected=*/true); + if (!enclosingSelfAccess) { + underlyingVars.push_back( + var->getAttachedPropertyWrapperTypeInfo(0).projectedValueVar); + } semantics = AccessSemantics::DirectToStorage; selfAccessKind = SelfAccessorKind::Peer; break; @@ -757,26 +828,82 @@ static Expr *buildStorageReference(AccessorDecl *accessor, selfDRE = new (ctx) DerivedToBaseExpr(selfDRE, selfTypeForAccess); } - LookupExpr *lookupExpr; + Expr *lookupExpr; ConcreteDeclRef memberRef(storage, subs); + auto type = storage->getValueInterfaceType() + .subst(subs, SubstFlags::UseErrorType); + if (isMemberLValue) + type = LValueType::get(type); + + // When we are performing access via a property wrapper's static subscript + // that accepts the enclosing self along with key paths, form that subscript + // operation now. + if (enclosingSelfAccess) { + Type storageType = storage->getValueInterfaceType() + .subst(subs, SubstFlags::UseErrorType); + // Metatype instance for the wrapper type itself. + TypeExpr *wrapperMetatype = TypeExpr::createImplicit(storageType, ctx); + + // Key path referring to the property being accessed. + Expr *propertyKeyPath = new (ctx) KeyPathDotExpr(SourceLoc()); + propertyKeyPath = new (ctx) UnresolvedDotExpr( + propertyKeyPath, SourceLoc(), + enclosingSelfAccess->accessedProperty->getFullName(), DeclNameLoc(), + /*Implicit=*/true); + propertyKeyPath = new (ctx) KeyPathExpr( + SourceLoc(), nullptr, propertyKeyPath); + + // Key path referring to the backing storage property. + Expr *storageKeyPath = new (ctx) KeyPathDotExpr(SourceLoc()); + storageKeyPath = new (ctx) UnresolvedDotExpr( + storageKeyPath, SourceLoc(), storage->getFullName(), DeclNameLoc(), + /*Implicit=*/true); + storageKeyPath = new (ctx) KeyPathExpr( + SourceLoc(), nullptr, storageKeyPath); + Expr *args[3] = { + selfDRE, + propertyKeyPath, + storageKeyPath + }; - if (auto subscript = dyn_cast(storage)) { + SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript; + auto &tc = static_cast(*ctx.getLazyResolver()); + lookupExpr = SubscriptExpr::create( + ctx, wrapperMetatype, SourceLoc(), args, + subscriptDecl->getFullName().getArgumentNames(), { }, SourceLoc(), + nullptr, subscriptDecl, /*Implicit=*/true); + tc.typeCheckExpression(lookupExpr, accessor); + + // Make sure we produce an lvalue only when desired. + if (isMemberLValue != lookupExpr->getType()->is()) { + if (isMemberLValue) { + // Strip off an extraneous load. + if (auto load = dyn_cast(lookupExpr)) + lookupExpr = load->getSubExpr(); + } else { + lookupExpr = new (ctx) LoadExpr( + lookupExpr, lookupExpr->getType()->getRValueType()); + } + } + } else if (auto subscript = dyn_cast(storage)) { Expr *indices = buildSubscriptIndexReference(ctx, accessor); lookupExpr = SubscriptExpr::create(ctx, selfDRE, indices, memberRef, IsImplicit, semantics); + + if (selfAccessKind == SelfAccessorKind::Super) + cast(lookupExpr)->setIsSuper(true); + + lookupExpr->setType(type); + } else { lookupExpr = new (ctx) MemberRefExpr(selfDRE, SourceLoc(), memberRef, DeclNameLoc(), IsImplicit, semantics); - } - if (selfAccessKind == SelfAccessorKind::Super) - lookupExpr->setIsSuper(true); + if (selfAccessKind == SelfAccessorKind::Super) + cast(lookupExpr)->setIsSuper(true); - auto type = storage->getValueInterfaceType() - .subst(subs, SubstFlags::UseErrorType); - if (isMemberLValue) - type = LValueType::get(type); - lookupExpr->setType(type); + lookupExpr->setType(type); + } return finish(lookupExpr); } diff --git a/lib/Sema/TypeCheckPropertyWrapper.cpp b/lib/Sema/TypeCheckPropertyWrapper.cpp index 1b87c12879fa1..1a59c2899d29b 100644 --- a/lib/Sema/TypeCheckPropertyWrapper.cpp +++ b/lib/Sema/TypeCheckPropertyWrapper.cpp @@ -229,6 +229,67 @@ static ConstructorDecl *findDefaultInit(ASTContext &ctx, return init; } +/// Determine whether we have a suitable static subscript to which we +/// can pass along the enclosing self + key-paths. +static SubscriptDecl *findEnclosingSelfSubscript(ASTContext &ctx, + NominalTypeDecl *nominal, + Identifier propertyName) { + Identifier argNames[] = { + ctx.Id_enclosingInstance, + propertyName, + ctx.Id_storage + }; + DeclName subscriptName(ctx, DeclBaseName::createSubscript(), argNames); + + SmallVector subscripts; + for (auto member : nominal->lookupDirect(subscriptName)) { + auto subscript = dyn_cast(member); + if (!subscript) + continue; + + if (subscript->isInstanceMember()) + continue; + + if (subscript->getDeclContext() != nominal) + continue; + + subscripts.push_back(subscript); + } + + switch (subscripts.size()) { + case 0: + return nullptr; + + case 1: + break; + + default: + // Diagnose ambiguous init() initializers. + nominal->diagnose(diag::property_wrapper_ambiguous_enclosing_self_subscript, + nominal->getDeclaredType(), subscriptName); + for (auto subscript : subscripts) { + subscript->diagnose(diag::kind_declname_declared_here, + subscript->getDescriptiveKind(), + subscript->getFullName()); + } + return nullptr; + + } + + auto subscript = subscripts.front(); + // the subscript must be as accessible as the nominal type. + if (subscript->getFormalAccess() < nominal->getFormalAccess()) { + subscript->diagnose(diag::property_wrapper_type_requirement_not_accessible, + subscript->getFormalAccess(), + subscript->getDescriptiveKind(), + subscript->getFullName(), nominal->getDeclaredType(), + nominal->getFormalAccess()); + return nullptr; + } + + return subscript; +} + llvm::Expected PropertyWrapperTypeInfoRequest::evaluate( Evaluator &eval, NominalTypeDecl *nominal) const { @@ -275,6 +336,10 @@ PropertyWrapperTypeInfoRequest::evaluate( result.projectedValueVar = findValueProperty(ctx, nominal, ctx.Id_projectedValue, /*allowMissing=*/true); + result.enclosingInstanceWrappedSubscript = + findEnclosingSelfSubscript(ctx, nominal, ctx.Id_wrapped); + result.enclosingInstanceProjectedSubscript = + findEnclosingSelfSubscript(ctx, nominal, ctx.Id_projected); // If there was no projectedValue property, but there is a delegateValue // or wrapperValue, property, use that and warn. diff --git a/test/Interpreter/property_wrappers.swift b/test/Interpreter/property_wrappers.swift new file mode 100644 index 0000000000000..d299d7a5dd859 --- /dev/null +++ b/test/Interpreter/property_wrappers.swift @@ -0,0 +1,89 @@ +// RUN: %target-run-simple-swift | %FileCheck %s +// REQUIRES: executable_test + +protocol Observed: AnyObject { + func broadcastValueWillChange(newValue: T) +} + +struct Other { + var value: Value + + func hello() -> String { + return "Hello from \(value)" + } +} + +@propertyWrapper +struct Observable { + private var stored: Value + + + init(initialValue: Value) { + self.stored = initialValue + } + + var wrappedValue: Value { + get { fatalError("called wrappedValue getter") } + set { fatalError("called wrappedValue setter") } + } + + var projectedValue: Other { + get { fatalError("called projectedValue getter") } + set { fatalError("called projectedValue setter") } + } + + static subscript( + _enclosingInstance observed: EnclosingSelf, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> Value { + get { + observed[keyPath: storageKeyPath].stored + } + set { + if observed[keyPath: storageKeyPath].stored != newValue { + observed.broadcastValueWillChange(newValue: newValue) + } + + observed[keyPath: storageKeyPath].stored = newValue + } + } + + static subscript( + _enclosingInstance observed: EnclosingSelf, + projected wrappedKeyPath: ReferenceWritableKeyPath>, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> Other { + get { + Other(value: observed[keyPath: storageKeyPath].stored) + } + set { + } + } +} + +class MyType: Observed { + @Observable var x: T + + init(x: T) { + self.x = x + } + + func broadcastValueWillChange(newValue: T) { + print("Value of 'x' is changing from \(x) to \(newValue)") + print($x.hello()) + } +} + +func testMyType(_ myType: MyType) { + // CHECK: Value of 'x' is changing from 17 to 42 + // CHECK-NEXT: Hello from 17 + myType.x = 42 + + // CHECK-NEXT: Value of 'x' is changing from 42 to 25 + // CHECK-NEXT: Hello from 42 + myType.x = 42 + myType.x = 25 +} + +testMyType(MyType(x: 17)) diff --git a/test/decl/var/property_wrappers_synthesis.swift b/test/decl/var/property_wrappers_synthesis.swift index 49c1ae82e926d..0c5fcf0f8808e 100644 --- a/test/decl/var/property_wrappers_synthesis.swift +++ b/test/decl/var/property_wrappers_synthesis.swift @@ -53,3 +53,42 @@ struct UseWillSetDidSet { } } } + +@propertyWrapper +struct Observable { + private var stored: Value + + init(initialValue: Value) { + self.stored = initialValue + } + + var wrappedValue: Value { + get { fatalError("called wrappedValue getter") } + set { fatalError("called wrappedValue setter") } + } + + static subscript( + _enclosingInstance observed: EnclosingSelf, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> Value { + get { + observed[keyPath: storageKeyPath].stored + } + set { + observed[keyPath: storageKeyPath].stored = newValue + } + } +} + +// CHECK-LABEL: class_decl{{.*}}"MyObservedType" +class MyObservedType { + @Observable var observedProperty = 17 + + // CHECK: accessor_decl{{.*}}get_for=observedProperty + // CHECK: subscript_expr implicit type='@lvalue Int' decl={{.*}}.Observable.subscript(_enclosingInstance:wrapped:storage:) + + // CHECK: accessor_decl{{.*}}set_for=observedProperty + // CHECK: subscript_expr implicit type='@lvalue Int' decl={{.*}}.Observable.subscript(_enclosingInstance:wrapped:storage:) +} +