Skip to content

Commit 725aeab

Browse files
committed
Support additional args in @dynamicMemberLookup subscripts
Adds support for satisfying `@dynamicMemberLookup` requirements using subscripts which have arguments following `dynamicMember:` so long as they are either variadic or have default arguments. This allows transforming member references into `x[dynamicMember:...]` calls which can pass in arguments such as `#function`, `#fileID`, `#line`, etc.
1 parent 5d36643 commit 725aeab

13 files changed

+748
-350
lines changed

include/swift/AST/Decl.h

+89-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ namespace clang {
6262
class PointerAuthQualifier;
6363
} // end namespace clang
6464

65+
namespace {
66+
class AttributeChecker;
67+
} // end anonymous namespace
68+
6569
namespace swift {
6670
enum class AccessSemantics : unsigned char;
6771
class AccessorDecl;
@@ -509,9 +513,24 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
509513
defaultArgumentKind : NumDefaultArgumentKindBits
510514
);
511515

512-
SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2,
513-
StaticSpelling : 2
516+
SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2+2,
517+
StaticSpelling : 2,
518+
519+
/// Whether this decl has been evaluated as eligible to satisfy an
520+
/// `@dynamicMemberLookup` requirement, and if eligible, the type of dynamic
521+
/// member parameter it would use to satisfy the requirement.
522+
///
523+
/// * 0b00 - not yet evaluated
524+
/// * 0b01 - evaluated; not eligible
525+
/// * 0b10 - evaluated; eligible, taking a `{{Reference}Writable}KeyPath`
526+
/// value
527+
/// * 0b11 - evaluated; eligible, taking an `ExpressibleByStringLiteral`
528+
/// value
529+
///
530+
/// i.e., 0 or `DynamicMemberLookupSubscriptEligibility` values + 1
531+
DynamicMemberLookupEligibility : 2
514532
);
533+
515534
SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+2+2+8+1+1+1+1+1+1,
516535
/// \see AbstractFunctionDecl::BodyKind
517536
BodyKind : 3,
@@ -7381,6 +7400,36 @@ enum class ObjCSubscriptKind {
73817400
Keyed
73827401
};
73837402

7403+
/// Describes how a `SubscriptDecl` could be eligible to fulfill a
7404+
/// `@dynamicMemberLookup` requirement.
7405+
///
7406+
/// `@dynamicMemberLookup` requires that a subscript:
7407+
///
7408+
/// 1. Take an initial argument with an explicit `dynamicMember` argument
7409+
/// label,
7410+
/// 2. Whose parameter type is non-variadic and is either
7411+
/// * A `{{Reference}Writable}KeyPath`, or
7412+
/// * A concrete type conforming to `ExpressibleByStringLiteral`,
7413+
/// 3. And whose following arguments (if any) are all either variadic or have
7414+
/// a default value
7415+
///
7416+
/// Subscripts which don't meet these requirements strictly are not eligible.
7417+
enum class DynamicMemberLookupSubscriptEligibility : uint8_t {
7418+
/// The `SubscriptDecl` cannot fulfill a `@dynamicMemberLookup` requirement.
7419+
///
7420+
/// This can be due to a name mismatch, type mismatch, missing default
7421+
/// arguments, or otherwise.
7422+
None,
7423+
7424+
/// The `SubscriptDecl` can fulfill a `@dynamicMemberLookup` requirement with
7425+
/// a `{{Reference}Writable}KeyPath` dynamic member parameter.
7426+
KeyPath,
7427+
7428+
/// The `SubscriptDecl` can fulfill a `@dynamicMemberLookup` requirement with
7429+
/// an `ExpressibleByStringLiteral`-conforming dynamic member parameter.
7430+
String,
7431+
};
7432+
73847433
/// Declares a subscripting operator for a type.
73857434
///
73867435
/// A subscript declaration is defined as a get/set pair that produces a
@@ -7410,13 +7459,23 @@ enum class ObjCSubscriptKind {
74107459
/// signatures (indices and element type) are distinct.
74117460
///
74127461
class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
7462+
friend AttributeChecker;
74137463
friend class ResultTypeRequest;
74147464

74157465
SourceLoc StaticLoc;
74167466
SourceLoc ArrowLoc;
74177467
ParameterList *Indices;
74187468
TypeLoc ElementTy;
74197469

7470+
// Maps `Bits.SubscriptDecl.DynamicMemberLookupEligibility` as stored to a
7471+
// `DynamicMemberLookupSubscriptEligibility`; `None` if `@dynamicMemberLookup`
7472+
// requirements have not been checked yet.
7473+
DynamicMemberLookupSubscriptEligibility
7474+
getStoredDynamicMemberLookupEligibility() const;
7475+
7476+
void setDynamicMemberLookupEligibility(
7477+
DynamicMemberLookupSubscriptEligibility eligibility);
7478+
74207479
void setElementInterfaceType(Type type);
74217480

74227481
SubscriptDecl(DeclName Name,
@@ -7435,6 +7494,24 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
74357494
setIndices(Indices);
74367495
}
74377496

7497+
// Evaluates whether `SD` could satisfy `@dynamicMemberLookup` requirements
7498+
// (without storing the result in `SD`). `ignoreArgLabel` allows checking of
7499+
// requirements even if the subscript doesn't have a `dynamicMember:` argument
7500+
// label; this is used by `AttributeChecker::visitDynamicMemberLookupAttr` to
7501+
// produce fix-its and should otherwise be `false`.
7502+
//
7503+
// Implemented in the typechecker because it requires access to type checking
7504+
// to validate the conformance of the `dynamicMember:` argument to
7505+
// `ExpressibleByStringLiteral`.
7506+
static DynamicMemberLookupSubscriptEligibility
7507+
evaluateDynamicMemberLookupEligibility(const SubscriptDecl *SD,
7508+
bool ignoreArgLabel = false);
7509+
7510+
/// Returns the given as a `BoundGenericType` if it is a
7511+
/// `{{Reference}Writable}KeyPath` type which could be used to fulfill
7512+
/// `@dynamicMemberLookup` requirements; `nullptr` otherwise.
7513+
static BoundGenericType *getDynamicMemberParamTypeAsKeyPathType(Type paramTy);
7514+
74387515
public:
74397516
/// Factory function only for use by deserialization.
74407517
static SubscriptDecl *createDeserialized(ASTContext &Context, DeclName Name,
@@ -7496,6 +7573,16 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl {
74967573
/// implies.
74977574
ObjCSubscriptKind getObjCSubscriptKind() const;
74987575

7576+
/// Determines, caches, and returns whether the decl can be used to satisfy an
7577+
/// `@dynamicMemberLookup` requirement (and if so, how).
7578+
DynamicMemberLookupSubscriptEligibility
7579+
getDynamicMemberLookupSubscriptEligibility();
7580+
7581+
/// If the decl can be used to satisfy an `@dynamicMemberLookup` requirement
7582+
/// using a `{{Reference}Writable}KeyPath` dynamic member parameter, returns
7583+
/// the type of that parameter; `nullptr` otherwise.
7584+
BoundGenericType *getDynamicMemberLookupKeyPathType();
7585+
74997586
SubscriptDecl *getOverriddenDecl() const {
75007587
return cast_or_null<SubscriptDecl>(
75017588
AbstractStorageDecl::getOverriddenDecl());

include/swift/Sema/OverloadChoice.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class OverloadChoice {
206206
}
207207

208208
/// Retrieve an overload choice for a declaration that was found via
209-
/// dynamic member lookup. The `ValueDecl` is a `subscript(dynamicMember:)`
209+
/// dynamic member lookup. The `ValueDecl` is a `subscript(dynamicMember:...)`
210210
/// method.
211211
static OverloadChoice getDynamicMemberLookup(Type base, ValueDecl *value,
212212
Identifier name,

lib/AST/Decl.cpp

+65
Original file line numberDiff line numberDiff line change
@@ -9640,11 +9640,54 @@ ObjCSubscriptKind SubscriptDecl::getObjCSubscriptKind() const {
96409640
return ObjCSubscriptKind::Keyed;
96419641
}
96429642

9643+
DynamicMemberLookupSubscriptEligibility
9644+
SubscriptDecl::getStoredDynamicMemberLookupEligibility() const {
9645+
return Bits.SubscriptDecl.DynamicMemberLookupEligibility
9646+
? static_cast<DynamicMemberLookupSubscriptEligibility>(
9647+
Bits.SubscriptDecl.DynamicMemberLookupEligibility - 1)
9648+
: DynamicMemberLookupSubscriptEligibility::None;
9649+
}
9650+
9651+
void SubscriptDecl::setDynamicMemberLookupEligibility(
9652+
DynamicMemberLookupSubscriptEligibility eligibility) {
9653+
Bits.SubscriptDecl.DynamicMemberLookupEligibility = static_cast<uint8_t>(eligibility) + 1;
9654+
}
9655+
96439656
void SubscriptDecl::setElementInterfaceType(Type type) {
96449657
getASTContext().evaluator.cacheOutput(ResultTypeRequest{this},
96459658
std::move(type));
96469659
}
96479660

9661+
BoundGenericType *
9662+
SubscriptDecl::getDynamicMemberParamTypeAsKeyPathType(Type paramTy) {
9663+
// Allow composing a key path type with a `Sendable` protocol as a way to
9664+
// express sendability requirements.
9665+
if (auto *existential = paramTy->getAs<ExistentialType>()) {
9666+
auto layout = existential->getExistentialLayout();
9667+
9668+
auto protocols = layout.getProtocols();
9669+
if (!llvm::all_of(protocols, [](ProtocolDecl *proto) {
9670+
return proto->isSpecificProtocol(KnownProtocolKind::Sendable) ||
9671+
proto->getInvertibleProtocolKind();
9672+
})) {
9673+
return nullptr;
9674+
}
9675+
9676+
paramTy = layout.getSuperclass();
9677+
if (!paramTy) {
9678+
return nullptr;
9679+
}
9680+
}
9681+
9682+
if (!paramTy->isKeyPath() &&
9683+
!paramTy->isWritableKeyPath() &&
9684+
!paramTy->isReferenceWritableKeyPath()) {
9685+
return nullptr;
9686+
}
9687+
9688+
return paramTy->getAs<BoundGenericType>();
9689+
}
9690+
96489691
SubscriptDecl *
96499692
SubscriptDecl::createDeserialized(ASTContext &Context, DeclName Name,
96509693
StaticSpellingKind StaticSpelling,
@@ -9743,6 +9786,28 @@ SourceRange SubscriptDecl::getSignatureSourceRange() const {
97439786
return getSubscriptLoc();
97449787
}
97459788

9789+
DynamicMemberLookupSubscriptEligibility
9790+
SubscriptDecl::getDynamicMemberLookupSubscriptEligibility() {
9791+
if (!Bits.SubscriptDecl.DynamicMemberLookupEligibility) {
9792+
auto eligibility = evaluateDynamicMemberLookupEligibility(this);
9793+
setDynamicMemberLookupEligibility(eligibility);
9794+
}
9795+
9796+
return getStoredDynamicMemberLookupEligibility();
9797+
}
9798+
9799+
BoundGenericType *SubscriptDecl::getDynamicMemberLookupKeyPathType() {
9800+
if (getDynamicMemberLookupSubscriptEligibility() !=
9801+
DynamicMemberLookupSubscriptEligibility::KeyPath) {
9802+
return nullptr;
9803+
}
9804+
9805+
auto *indices = getIndices();
9806+
assert(indices->size() > 0 && "subscript must have at least one arg");
9807+
return getDynamicMemberParamTypeAsKeyPathType(
9808+
indices->get(0)->getInterfaceType());
9809+
}
9810+
97469811
DeclName AbstractFunctionDecl::getEffectiveFullName() const {
97479812
if (getName())
97489813
return getName();

0 commit comments

Comments
 (0)