mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Property wrappers] Improve enclosing-self subscript handling.
Extend handling of enclosing-self subscripts by differentiating between the original wrapped property (which now goes through `subscript(_enclosingInstance:wrapped:storage:)`) and the projected property (which goes through `subscript(_enclosingInstance:projected:storage:)`). The new middle argument provides a key path to the property that was accessed, allowing one to distinguish the property being updated.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -369,6 +369,9 @@ static void maybeMarkTransparent(AccessorDecl *accessor, ASTContext &ctx) {
|
||||
return;
|
||||
|
||||
break;
|
||||
} else if (var->getOriginalWrappedProperty(
|
||||
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,49 +617,20 @@ namespace {
|
||||
} // end anonymous namespace
|
||||
|
||||
namespace {
|
||||
/// Describes the information needed to perform property wrapper access via the enclosing self.
|
||||
/// 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;
|
||||
|
||||
/// The backing storage property.
|
||||
VarDecl *backingProperty;
|
||||
};
|
||||
}
|
||||
|
||||
/// Find the static subscript(_enclosingInstance:wrapped:storage:) that can be
|
||||
/// used to provide access to the wrapped value of a property wrapper.
|
||||
static SubscriptDecl *findEnclosingSelfSubscript(NominalTypeDecl *nominal) {
|
||||
ASTContext &ctx = nominal->getASTContext();
|
||||
|
||||
// FIXME: Make these known identifiers.
|
||||
Identifier argNames[] = {
|
||||
ctx.getIdentifier("_enclosingInstance"),
|
||||
ctx.getIdentifier("storage")
|
||||
};
|
||||
DeclName subscriptName(ctx, DeclBaseName::createSubscript(), argNames);
|
||||
|
||||
for (auto member : nominal->lookupDirect(subscriptName)) {
|
||||
auto subscript = dyn_cast<SubscriptDecl>(member);
|
||||
if (!subscript)
|
||||
continue;
|
||||
|
||||
if (subscript->isInstanceMember())
|
||||
continue;
|
||||
|
||||
// FIXME: Check access, types, etc, etc.
|
||||
return subscript;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Determine whether the given property should be accessed via the enclosing-self access pattern.
|
||||
static Optional<EnclosingSelfPropertyWrapperAccess>
|
||||
getEnclosingSelfPropertyWrapperAccess(VarDecl *property) {
|
||||
getEnclosingSelfPropertyWrapperAccess(VarDecl *property, bool forProjected) {
|
||||
// The enclosing-self pattern only applies to instance properties of
|
||||
// classes.
|
||||
if (!property->isInstanceMember())
|
||||
@@ -674,12 +648,22 @@ getEnclosingSelfPropertyWrapperAccess(VarDecl *property) {
|
||||
return None;
|
||||
|
||||
// Look for a generic subscript that fits the general form we need.
|
||||
auto subscript = findEnclosingSelfSubscript(wrapperTypeDecl);
|
||||
auto wrapperInfo = wrapperTypeDecl->getPropertyWrapperTypeInfo();
|
||||
auto subscript =
|
||||
forProjected ? wrapperInfo.enclosingInstanceProjectedSubscript
|
||||
: wrapperInfo.enclosingInstanceWrappedSubscript;
|
||||
if (!subscript)
|
||||
return None;
|
||||
|
||||
EnclosingSelfPropertyWrapperAccess result{
|
||||
subscript, property, property->getPropertyWrapperBackingProperty()};
|
||||
EnclosingSelfPropertyWrapperAccess result;
|
||||
result.subscript = subscript;
|
||||
|
||||
if (forProjected) {
|
||||
result.accessedProperty =
|
||||
property->getPropertyWrapperBackingPropertyInfo().storageWrapperVar;
|
||||
} else {
|
||||
result.accessedProperty = property;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -775,14 +759,15 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
|
||||
// record that.
|
||||
unsigned lastWrapperIdx = var->getAttachedPropertyWrappers().size();
|
||||
unsigned firstWrapperIdx = 0;
|
||||
enclosingSelfAccess = getEnclosingSelfPropertyWrapperAccess(var);
|
||||
enclosingSelfAccess =
|
||||
getEnclosingSelfPropertyWrapperAccess(var, /*forProjected=*/false);
|
||||
if (enclosingSelfAccess)
|
||||
firstWrapperIdx = 1;
|
||||
|
||||
// Perform accesses to the wrappedValues along the composition chain.
|
||||
for (unsigned i : range(firstWrapperIdx, lastWrapperIdx)) {
|
||||
underlyingVars.push_back(
|
||||
var->getAttachedPropertyWrapperTypeInfo(i).valueVar);
|
||||
auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(i);
|
||||
underlyingVars.push_back(wrapperInfo.valueVar);
|
||||
}
|
||||
semantics = AccessSemantics::DirectToStorage;
|
||||
selfAccessKind = SelfAccessorKind::Peer;
|
||||
@@ -793,7 +778,12 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
|
||||
auto var =
|
||||
cast<VarDecl>(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;
|
||||
@@ -845,23 +835,38 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
|
||||
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) {
|
||||
SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript;
|
||||
|
||||
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[2] = {
|
||||
Expr *args[3] = {
|
||||
selfDRE,
|
||||
propertyKeyPath,
|
||||
storageKeyPath
|
||||
};
|
||||
|
||||
SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript;
|
||||
auto &tc = static_cast<TypeChecker&>(*ctx.getLazyResolver());
|
||||
lookupExpr = SubscriptExpr::create(
|
||||
ctx, wrapperMetatype, SourceLoc(), args,
|
||||
|
||||
@@ -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<SubscriptDecl *, 2> subscripts;
|
||||
for (auto member : nominal->lookupDirect(subscriptName)) {
|
||||
auto subscript = dyn_cast<SubscriptDecl>(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<PropertyWrapperTypeInfo>
|
||||
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.
|
||||
|
||||
@@ -5,6 +5,14 @@ protocol Observed: AnyObject {
|
||||
func broadcastValueWillChange<T>(newValue: T)
|
||||
}
|
||||
|
||||
struct Other<Value: Equatable> {
|
||||
var value: Value
|
||||
|
||||
func hello() -> String {
|
||||
return "Hello from \(value)"
|
||||
}
|
||||
}
|
||||
|
||||
@propertyWrapper
|
||||
struct Observable<Value: Equatable> {
|
||||
private var stored: Value
|
||||
@@ -19,8 +27,14 @@ struct Observable<Value: Equatable> {
|
||||
set { fatalError("called wrappedValue setter") }
|
||||
}
|
||||
|
||||
static subscript<EnclosingSelf: Observed>(
|
||||
var projectedValue: Other<Value> {
|
||||
get { fatalError("called projectedValue getter") }
|
||||
set { fatalError("called projectedValue setter") }
|
||||
}
|
||||
|
||||
static subscript<EnclosingSelf: Observed, FinalValue>(
|
||||
_enclosingInstance observed: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, FinalValue>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
|
||||
) -> Value {
|
||||
get {
|
||||
@@ -34,6 +48,18 @@ struct Observable<Value: Equatable> {
|
||||
observed[keyPath: storageKeyPath].stored = newValue
|
||||
}
|
||||
}
|
||||
|
||||
static subscript<EnclosingSelf: Observed>(
|
||||
_enclosingInstance observed: EnclosingSelf,
|
||||
projected wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Other<Value>>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
|
||||
) -> Other<Value> {
|
||||
get {
|
||||
Other(value: observed[keyPath: storageKeyPath].stored)
|
||||
}
|
||||
set {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyType<T: Equatable>: Observed {
|
||||
@@ -45,14 +71,17 @@ class MyType<T: Equatable>: Observed {
|
||||
|
||||
func broadcastValueWillChange<T>(newValue: T) {
|
||||
print("Value of 'x' is changing from \(x) to \(newValue)")
|
||||
print($x.hello())
|
||||
}
|
||||
}
|
||||
|
||||
func testMyType(_ myType: MyType<Int>) {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ struct Observable<Value> {
|
||||
|
||||
static subscript<EnclosingSelf>(
|
||||
_enclosingInstance observed: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
|
||||
) -> Value {
|
||||
get {
|
||||
@@ -85,9 +86,9 @@ class MyObservedType {
|
||||
@Observable var observedProperty = 17
|
||||
|
||||
// CHECK: accessor_decl{{.*}}get_for=observedProperty
|
||||
// CHECK: subscript_expr implicit type='@lvalue Int' decl={{.*}}.Observable.subscript(_enclosingInstance:storage:)
|
||||
// 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:storage:)
|
||||
// CHECK: subscript_expr implicit type='@lvalue Int' decl={{.*}}.Observable.subscript(_enclosingInstance:wrapped:storage:)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user