Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Property wrappers] Prototype support for enclosing self-based access #25884

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work if you have something like this, to handle a value-semantics wrapper type?

// Read only access from a read only base
subscript(_enclosingInstance: EnclosingSelf, storage: KeyPath<EnclosingSelf, Self>) -> Wrapped { get }

// Writable projection from a writable base
subscript(_enclosingInstance: inout EnclosingSelf, storage: WritableKeyPath<EnclosingSelf, Self>) -> Wrapped { get set }


ERROR(property_wrapper_attribute_not_on_property, none,
"property wrapper attribute %0 can only be applied to a property",
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ IDENTIFIER(delegateValue)
IDENTIFIER(dynamicallyCall)
IDENTIFIER(dynamicMember)
IDENTIFIER(Element)
IDENTIFIER_(enclosingInstance)
IDENTIFIER(Encodable)
IDENTIFIER(encode)
IDENTIFIER(encodeIfPresent)
Expand Down Expand Up @@ -95,6 +96,7 @@ IDENTIFIER_(ObjectiveCType)
IDENTIFIER(Optional)
IDENTIFIER_(OptionalNilComparisonType)
IDENTIFIER(parameter)
IDENTIFIER(projected)
IDENTIFIER(projectedValue)
IDENTIFIER(Protocol)
IDENTIFIER(rawValue)
Expand Down Expand Up @@ -122,6 +124,7 @@ IDENTIFIER(WinSDK)
IDENTIFIER(with)
IDENTIFIER(withArguments)
IDENTIFIER(withKeywordArguments)
IDENTIFIER(wrapped)
IDENTIFIER(wrappedValue)
IDENTIFIER(wrapperValue)

Expand Down
11 changes: 11 additions & 0 deletions include/swift/AST/PropertyWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
157 changes: 142 additions & 15 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ static void maybeMarkTransparent(AccessorDecl *accessor, ASTContext &ctx) {
return;

break;
} else if (var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
break;
}
}

Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

SubscriptDecl *subscript;

/// The property being accessed.
VarDecl *accessedProperty;
};
}

/// Determine whether the given property should be accessed via the enclosing-self access pattern.
static Optional<EnclosingSelfPropertyWrapperAccess>
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,
Expand All @@ -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<EnclosingSelfPropertyWrapperAccess> enclosingSelfAccess;
llvm::TinyPtrVector<VarDecl *> underlyingVars;
auto finish = [&](Expr *result) -> Expr * {
for (auto underlyingVar : underlyingVars) {
Expand Down Expand Up @@ -699,9 +754,20 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
case TargetImpl::Wrapper: {
auto var = cast<VarDecl>(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;
Expand All @@ -710,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;
Expand Down Expand Up @@ -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<SubscriptDecl>(storage)) {
SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript;
auto &tc = static_cast<TypeChecker&>(*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<LValueType>()) {
if (isMemberLValue) {
// Strip off an extraneous load.
if (auto load = dyn_cast<LoadExpr>(lookupExpr))
lookupExpr = load->getSubExpr();
} else {
lookupExpr = new (ctx) LoadExpr(
lookupExpr, lookupExpr->getType()->getRValueType());
}
}
} else if (auto subscript = dyn_cast<SubscriptDecl>(storage)) {
Expr *indices = buildSubscriptIndexReference(ctx, accessor);
lookupExpr = SubscriptExpr::create(ctx, selfDRE, indices, memberRef,
IsImplicit, semantics);

if (selfAccessKind == SelfAccessorKind::Super)
cast<LookupExpr>(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>(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);
}
Expand Down
65 changes: 65 additions & 0 deletions lib/Sema/TypeCheckPropertyWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
Loading