[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:
Doug Gregor
2019-06-29 23:22:01 -07:00
parent 8c54db727b
commit bc2e605b31
7 changed files with 163 additions and 46 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
@@ -791,9 +776,14 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
case TargetImpl::WrapperStorage: {
auto var =
cast<VarDecl>(accessor->getStorage())->getOriginalWrappedProperty();
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,

View File

@@ -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.

View File

@@ -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
@@ -18,9 +26,15 @@ struct Observable<Value: Equatable> {
get { fatalError("called wrappedValue getter") }
set { fatalError("called wrappedValue setter") }
}
var projectedValue: Other<Value> {
get { fatalError("called projectedValue getter") }
set { fatalError("called projectedValue setter") }
}
static subscript<EnclosingSelf: Observed>(
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
}

View File

@@ -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:)
}