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 5 commits 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,11 @@ namespace clang {
class PointerAuthQualifier;
} // end namespace clang

namespace {
class AttributeChecker;
class DeclChecker;
} // end anonymous namespace

namespace swift {
enum class AccessSemantics : unsigned char;
class AccessorDecl;
Expand Down Expand Up @@ -509,9 +514,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 +7401,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 +7460,35 @@ enum class ObjCSubscriptKind {
/// signatures (indices and element type) are distinct.
///
class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
friend AttributeChecker;
friend DeclChecker;
friend class ResultTypeRequest;

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

// Evaluates and stores the decl's eligibility to fulfill
// `@dynamicMemberLookup` requirements. Given `Diags`, emits diagnostics for
// requirement mismatches; should only be passed in by `AttributeChecker` and
// `DeclChecker` within a type-checking context.
//
// Implemented in the type checker because checking the validity of a
// string-based dynamic member parameter requires checking conformance to
// `ExpressibleByStringLiteral`.
DynamicMemberLookupSubscriptEligibility
evaluateDynamicMemberLookupEligibility(DiagnosticEngine *Diags = nullptr);

// 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 +7507,11 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
setIndices(Indices);
}

/// 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
11 changes: 8 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1700,9 +1700,14 @@ ERROR(invalid_dynamic_member_lookup_type,none,
"'@dynamicMemberLookup' requires %0 to have a "
"'subscript(dynamicMember:)' method that accepts either "
"'ExpressibleByStringLiteral' or a key path", (Type))
NOTE(invalid_dynamic_member_subscript, none,
"add an explicit argument label to this subscript to satisfy "
"the '@dynamicMemberLookup' requirement", ())
NOTE(invalid_dynamic_member_subscript_missing_label, none,
"add an explicit argument label to this subscript to satisfy the "
"'@dynamicMemberLookup' requirement", ())
NOTE(invalid_dynamic_member_subscript_out_of_order, none,
"'dynamicMember' argument must appear first in the argument list", ())
NOTE(invalid_dynamic_member_subscript_missing_default, none,
"add a default value to this argument to satisfy the "
"'@dynamicMemberLookup' requirement", ())

ERROR(string_index_not_integer,none,
"String must not be indexed with %0, it has variable size elements",
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
62 changes: 62 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9641,11 +9641,55 @@ 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 @@ -9744,6 +9788,24 @@ SourceRange SubscriptDecl::getSignatureSourceRange() const {
return getSubscriptLoc();
}

DynamicMemberLookupSubscriptEligibility
SubscriptDecl::getDynamicMemberLookupSubscriptEligibility() {
evaluateDynamicMemberLookupEligibility();
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