Skip to content

[Sema] Support additional args in @dynamicMemberLookup subscripts #81148

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
91 changes: 89 additions & 2 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ namespace clang {
class PointerAuthQualifier;
} // end namespace clang

namespace {
class AttributeChecker;
} // end anonymous namespace

namespace swift {
enum class AccessSemantics : unsigned char;
class AccessorDecl;
Expand Down Expand Up @@ -509,9 +513,24 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
defaultArgumentKind : NumDefaultArgumentKindBits
);

SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2,
StaticSpelling : 2
SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2+2,
StaticSpelling : 2,

/// Whether this decl has been evaluated as eligible to satisfy an
/// `@dynamicMemberLookup` requirement, and if eligible, the type of dynamic
/// member parameter it would use to satisfy the requirement.
///
/// * 0b00 - not yet evaluated
/// * 0b01 - evaluated; not eligible
/// * 0b10 - evaluated; eligible, taking a `{{Reference}Writable}KeyPath`
/// value
/// * 0b11 - evaluated; eligible, taking an `ExpressibleByStringLiteral`
/// value
///
/// i.e., 0 or `DynamicMemberLookupSubscriptEligibility` values + 1
DynamicMemberLookupEligibility : 2
);

SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+2+2+8+1+1+1+1+1+1,
/// \see AbstractFunctionDecl::BodyKind
BodyKind : 3,
Expand Down Expand Up @@ -7381,6 +7400,36 @@ enum class ObjCSubscriptKind {
Keyed
};

/// Describes how a `SubscriptDecl` could be eligible to fulfill a
/// `@dynamicMemberLookup` requirement.
///
/// `@dynamicMemberLookup` requires that a subscript:
///
/// 1. Take an initial argument with an explicit `dynamicMember` argument
/// label,
/// 2. Whose parameter type is non-variadic and is either
/// * A `{{Reference}Writable}KeyPath`, or
/// * A concrete type conforming to `ExpressibleByStringLiteral`,
/// 3. And whose following arguments (if any) are all either variadic or have
/// a default value
///
/// Subscripts which don't meet these requirements strictly are not eligible.
enum class DynamicMemberLookupSubscriptEligibility : uint8_t {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

DynamicMemberLookupSubscriptEligibility is... a mouthful — but it's the least-inaccurate name I could come up with. Happy to improve this with suggestions!

/// The `SubscriptDecl` cannot fulfill a `@dynamicMemberLookup` requirement.
///
/// This can be due to a name mismatch, type mismatch, missing default
/// arguments, or otherwise.
None,

/// The `SubscriptDecl` can fulfill a `@dynamicMemberLookup` requirement with
/// a `{{Reference}Writable}KeyPath` dynamic member parameter.
KeyPath,

/// The `SubscriptDecl` can fulfill a `@dynamicMemberLookup` requirement with
/// an `ExpressibleByStringLiteral`-conforming dynamic member parameter.
String,
};

/// Declares a subscripting operator for a type.
///
/// A subscript declaration is defined as a get/set pair that produces a
Expand Down Expand Up @@ -7410,13 +7459,23 @@ enum class ObjCSubscriptKind {
/// signatures (indices and element type) are distinct.
///
class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
friend AttributeChecker;
friend class ResultTypeRequest;

SourceLoc StaticLoc;
SourceLoc ArrowLoc;
ParameterList *Indices;
TypeLoc ElementTy;

// Maps `Bits.SubscriptDecl.DynamicMemberLookupEligibility` as stored to a
// `DynamicMemberLookupSubscriptEligibility`; `None` if `@dynamicMemberLookup`
// requirements have not been checked yet.
DynamicMemberLookupSubscriptEligibility
getStoredDynamicMemberLookupEligibility() const;

void setDynamicMemberLookupEligibility(
DynamicMemberLookupSubscriptEligibility eligibility);

void setElementInterfaceType(Type type);

SubscriptDecl(DeclName Name,
Expand All @@ -7435,6 +7494,24 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
setIndices(Indices);
}

// Evaluates whether `SD` could satisfy `@dynamicMemberLookup` requirements
// (without storing the result in `SD`). `ignoreArgLabel` allows checking of
// requirements even if the subscript doesn't have a `dynamicMember:` argument
// label; this is used by `AttributeChecker::visitDynamicMemberLookupAttr` to
// produce fix-its and should otherwise be `false`.
//
// Implemented in the typechecker because it requires access to type checking
// to validate the conformance of the `dynamicMember:` argument to
// `ExpressibleByStringLiteral`.
static DynamicMemberLookupSubscriptEligibility
evaluateDynamicMemberLookupEligibility(const SubscriptDecl *SD,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the core of what was previously isValidKeyPathDynamicMemberLookup and isValidStringDynamicMemberLookup, factored out

bool ignoreArgLabel = false);

/// Returns the given as a `BoundGenericType` if it is a
/// `{{Reference}Writable}KeyPath` type which could be used to fulfill
/// `@dynamicMemberLookup` requirements; `nullptr` otherwise.
static BoundGenericType *getDynamicMemberParamTypeAsKeyPathType(Type paramTy);

public:
/// Factory function only for use by deserialization.
static SubscriptDecl *createDeserialized(ASTContext &Context, DeclName Name,
Expand Down Expand Up @@ -7496,6 +7573,16 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
/// implies.
ObjCSubscriptKind getObjCSubscriptKind() const;

/// Determines, caches, and returns whether the decl can be used to satisfy an
/// `@dynamicMemberLookup` requirement (and if so, how).
DynamicMemberLookupSubscriptEligibility
getDynamicMemberLookupSubscriptEligibility();
Comment on lines +7578 to +7579
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of exposing this enum, I suppose it is also possible to restore isValidDynamicMemberLookupSubscript() et. al., but I do think it's nicer to have a type-safe interface that's harder to typo.


/// If the decl can be used to satisfy an `@dynamicMemberLookup` requirement
/// using a `{{Reference}Writable}KeyPath` dynamic member parameter, returns
/// the type of that parameter; `nullptr` otherwise.
BoundGenericType *getDynamicMemberLookupKeyPathType();

SubscriptDecl *getOverriddenDecl() const {
return cast_or_null<SubscriptDecl>(
AbstractStorageDecl::getOverriddenDecl());
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Sema/OverloadChoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class OverloadChoice {
}

/// Retrieve an overload choice for a declaration that was found via
/// dynamic member lookup. The `ValueDecl` is a `subscript(dynamicMember:)`
/// dynamic member lookup. The `ValueDecl` is a `subscript(dynamicMember:...)`
/// method.
static OverloadChoice getDynamicMemberLookup(Type base, ValueDecl *value,
Identifier name,
Expand Down
65 changes: 65 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9640,11 +9640,54 @@ ObjCSubscriptKind SubscriptDecl::getObjCSubscriptKind() const {
return ObjCSubscriptKind::Keyed;
}

DynamicMemberLookupSubscriptEligibility
SubscriptDecl::getStoredDynamicMemberLookupEligibility() const {
return Bits.SubscriptDecl.DynamicMemberLookupEligibility
? static_cast<DynamicMemberLookupSubscriptEligibility>(
Bits.SubscriptDecl.DynamicMemberLookupEligibility - 1)
: DynamicMemberLookupSubscriptEligibility::None;
}

void SubscriptDecl::setDynamicMemberLookupEligibility(
DynamicMemberLookupSubscriptEligibility eligibility) {
Bits.SubscriptDecl.DynamicMemberLookupEligibility = static_cast<uint8_t>(eligibility) + 1;
}

void SubscriptDecl::setElementInterfaceType(Type type) {
getASTContext().evaluator.cacheOutput(ResultTypeRequest{this},
std::move(type));
}

BoundGenericType *
SubscriptDecl::getDynamicMemberParamTypeAsKeyPathType(Type paramTy) {
// Allow composing a key path type with a `Sendable` protocol as a way to
// express sendability requirements.
if (auto *existential = paramTy->getAs<ExistentialType>()) {
auto layout = existential->getExistentialLayout();

auto protocols = layout.getProtocols();
if (!llvm::all_of(protocols, [](ProtocolDecl *proto) {
return proto->isSpecificProtocol(KnownProtocolKind::Sendable) ||
proto->getInvertibleProtocolKind();
})) {
return nullptr;
}

paramTy = layout.getSuperclass();
if (!paramTy) {
return nullptr;
}
}

if (!paramTy->isKeyPath() &&
!paramTy->isWritableKeyPath() &&
!paramTy->isReferenceWritableKeyPath()) {
return nullptr;
}

return paramTy->getAs<BoundGenericType>();
}

SubscriptDecl *
SubscriptDecl::createDeserialized(ASTContext &Context, DeclName Name,
StaticSpellingKind StaticSpelling,
Expand Down Expand Up @@ -9743,6 +9786,28 @@ SourceRange SubscriptDecl::getSignatureSourceRange() const {
return getSubscriptLoc();
}

DynamicMemberLookupSubscriptEligibility
SubscriptDecl::getDynamicMemberLookupSubscriptEligibility() {
if (!Bits.SubscriptDecl.DynamicMemberLookupEligibility) {
auto eligibility = evaluateDynamicMemberLookupEligibility(this);
setDynamicMemberLookupEligibility(eligibility);
}

return getStoredDynamicMemberLookupEligibility();
}

BoundGenericType *SubscriptDecl::getDynamicMemberLookupKeyPathType() {
if (getDynamicMemberLookupSubscriptEligibility() !=
DynamicMemberLookupSubscriptEligibility::KeyPath) {
return nullptr;
}

auto *indices = getIndices();
assert(indices->size() > 0 && "subscript must have at least one arg");
return getDynamicMemberParamTypeAsKeyPathType(
indices->get(0)->getInterfaceType());
}

DeclName AbstractFunctionDecl::getEffectiveFullName() const {
if (getName())
return getName();
Expand Down
Loading