diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 1d3b6cfe8d922..afe75358f5699 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -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; @@ -509,9 +513,24 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, 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, @@ -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 { + /// 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 @@ -7410,6 +7459,7 @@ enum class ObjCSubscriptKind { /// signatures (indices and element type) are distinct. /// class SubscriptDecl : public GenericContext, public AbstractStorageDecl { + friend AttributeChecker; friend class ResultTypeRequest; SourceLoc StaticLoc; @@ -7417,6 +7467,15 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl { 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, @@ -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, + 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, @@ -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(); + + /// 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( AbstractStorageDecl::getOverriddenDecl()); diff --git a/include/swift/Sema/OverloadChoice.h b/include/swift/Sema/OverloadChoice.h index 111703be8f50e..c18a918cd2cb2 100644 --- a/include/swift/Sema/OverloadChoice.h +++ b/include/swift/Sema/OverloadChoice.h @@ -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, diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index c60f0fd68cb42..b1537b133d585 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -9640,11 +9640,54 @@ ObjCSubscriptKind SubscriptDecl::getObjCSubscriptKind() const { return ObjCSubscriptKind::Keyed; } +DynamicMemberLookupSubscriptEligibility +SubscriptDecl::getStoredDynamicMemberLookupEligibility() const { + return Bits.SubscriptDecl.DynamicMemberLookupEligibility + ? static_cast( + Bits.SubscriptDecl.DynamicMemberLookupEligibility - 1) + : DynamicMemberLookupSubscriptEligibility::None; +} + +void SubscriptDecl::setDynamicMemberLookupEligibility( + DynamicMemberLookupSubscriptEligibility eligibility) { + Bits.SubscriptDecl.DynamicMemberLookupEligibility = static_cast(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()) { + 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(); +} + SubscriptDecl * SubscriptDecl::createDeserialized(ASTContext &Context, DeclName Name, StaticSpellingKind StaticSpelling, @@ -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(); diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 510274f357efa..ceac0a79b3925 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -2184,6 +2184,41 @@ namespace { return solution.simplifyType(type); } + /// Returns the concrete callee which 'owns' the default argument at a given + /// index. This looks through inheritance for inherited default args. + ConcreteDeclRef getDefaultArgOwner(ConcreteDeclRef owner, unsigned index) { + auto *param = getParameterAt(owner, index); + assert(param); + if (param->getDefaultArgumentKind() == DefaultArgumentKind::Inherited) { + return getDefaultArgOwner(owner.getOverriddenDecl(), index); + } + return owner; + } + + // For variadic generic declarations we need to compute a substituted + // version of bindings because all of the packs are exploaded in the + // substituted function type. + // + // \code + // func fn(_: repeat each T) {} + // + // fn("", 42) + // \endcode + // + // The type of `fn` in the call is `(String, Int) -> Void` but bindings + // have only one parameter at index `0` with two argument positions: 0, 1. + bool shouldSubstituteParameterBindings(ConcreteDeclRef callee) { + auto subst = callee.getSubstitutions(); + if (subst.empty()) + return false; + + auto sig = subst.getGenericSignature(); + return llvm::any_of(sig.getGenericParams(), + [&](const GenericTypeParamType *GP) { + return GP->isParameterPack(); + }); + } + public: /// Coerce the given expression to the given type. /// @@ -2367,10 +2402,12 @@ namespace { ->castTo(); auto appliedWrappers = solution.getAppliedPropertyWrappers(memberLoc->getAnchor()); + args = coerceCallArguments( args, fullSubscriptTy, subscriptRef, nullptr, locator.withPathElement(ConstraintLocator::ApplyArgument), appliedWrappers); + if (!args) return nullptr; @@ -2524,7 +2561,7 @@ namespace { LocatorPathElt::KeyPathDynamicMember(keyPathTy->getAnyNominal())); auto overload = solution.getOverloadChoice(componentLoc); - // Looks like there is a chain of implicit `subscript(dynamicMember:)` + // Looks like there is a chain of implicit `subscript(dynamicMember:...)` // calls necessary to resolve a member reference. switch (overload.choice.getKind()) { case OverloadChoiceKind::DynamicMemberLookup: @@ -3594,6 +3631,60 @@ namespace { llvm_unreachable("Unhandled OverloadChoiceKind in switch."); } + /// Builds an argument list for making a call to the given + /// `@dynamicMemberLookup` subscript by inserting any necessary default + /// arguments. + ArgumentList *buildArgumentListForDynamicMemberLookupSubscript( + const SelectedOverload &overload, SourceLoc componentLoc, + ConstraintLocator *locator) { + assert(overload.choice.isAnyDynamicMemberLookup() && + "Overload must be for dynamic member lookup"); + auto *SD = cast(overload.choice.getDecl()); + auto memberLoc = cs.getCalleeLocator(locator); + auto subscript = resolveConcreteDeclRef(SD, memberLoc); + + auto openedFullFnTy = + simplifyType(overload.adjustedOpenedFullType)->castTo(); + auto fullSubscriptTy = + openedFullFnTy->getResult()->castTo(); + auto params = fullSubscriptTy->getParams(); + + SmallVector args; + for (auto paramIdx : indices(params)) { + Expr *argExpr; + if (paramIdx == 0) { + auto paramTy = params[paramIdx].getPlainType(); + if (overload.choice.isKeyPathDynamicMemberLookup()) { + argExpr = buildKeyPathDynamicMemberArgExpr(paramTy, componentLoc, + memberLoc); + } else { + auto fieldName = + overload.choice.getName().getBaseIdentifier().str(); + argExpr = buildDynamicMemberLookupArgExpr(fieldName, componentLoc, + paramTy); + } + } else { + // If bindings were substituted, we need to find the "original" (or + // contextless) parameter index for the default argument. + if (shouldSubstituteParameterBindings(subscript)) { + auto *paramList = SD->getParameterList(); + assert(paramList); + paramIdx = paramList->getOrigParamIndex( + subscript.getSubstitutions(), paramIdx); + } + + auto owner = getDefaultArgOwner(subscript, paramIdx); + auto paramTy = params[paramIdx].getParameterType(); + argExpr = new (ctx) + DefaultArgumentExpr(owner, paramIdx, componentLoc, paramTy, dc); + } + + args.push_back(cs.cacheType(argExpr)); + } + + return ArgumentList::forImplicitCallTo(SD->getIndices(), args, ctx); + } + /// Form a type checked expression for the argument of a /// @dynamicMemberLookup subscript index parameter. Expr *buildDynamicMemberLookupArgExpr(StringRef name, SourceLoc loc, @@ -3601,6 +3692,7 @@ namespace { // Build and type check the string literal index value to the specific // string type expected by the subscript. auto *nameExpr = new (ctx) StringLiteralExpr(name, loc, /*implicit*/true); + nameExpr->setType(literalTy); cs.setType(nameExpr, literalTy); return handleStringLiteralExpr(nameExpr); } @@ -3610,49 +3702,21 @@ namespace { const SelectedOverload &overload, ConstraintLocator *memberLocator) { // Application of a DynamicMemberLookup result turns - // a member access of `x.foo` into x[dynamicMember: "foo"], or - // x[dynamicMember: KeyPath] - - // Figure out the expected type of the lookup parameter. We know the - // openedFullType will be "xType -> indexType -> resultType". Dig out - // its index type. - auto paramTy = getTypeOfDynamicMemberIndex(overload); - - Expr *argExpr = nullptr; - if (overload.choice.getKind() == - OverloadChoiceKind::DynamicMemberLookup) { - // Build and type check the string literal index value to the specific - // string type expected by the subscript. - auto fieldName = overload.choice.getName().getBaseIdentifier().str(); - argExpr = buildDynamicMemberLookupArgExpr(fieldName, nameLoc, paramTy); - } else { - argExpr = - buildKeyPathDynamicMemberArgExpr(paramTy, dotLoc, memberLocator); - } - - if (!argExpr) - return nullptr; + // a member access of `x.foo` into `x[dynamicMember: "foo", ...]`, or + // `x[dynamicMember: KeyPath, ...]` + auto componentLoc = + overload.choice.getKind() == OverloadChoiceKind::DynamicMemberLookup + ? nameLoc + : dotLoc; + auto *args = buildArgumentListForDynamicMemberLookupSubscript( + overload, componentLoc, memberLocator); - // Build an argument list. - auto *argList = - ArgumentList::forImplicitSingle(ctx, ctx.Id_dynamicMember, argExpr); // Build and return a subscript that uses this string as the index. - return buildSubscript(base, argList, cs.getConstraintLocator(expr), + return buildSubscript(base, args, cs.getConstraintLocator(expr), memberLocator, /*isImplicit*/ true, AccessSemantics::Ordinary, overload); } - Type getTypeOfDynamicMemberIndex(const SelectedOverload &overload) { - assert(overload.choice.isAnyDynamicMemberLookup()); - - auto declTy = solution.simplifyType(overload.adjustedOpenedFullType); - auto subscriptTy = declTy->castTo()->getResult(); - auto refFnType = subscriptTy->castTo(); - assert(refFnType->getParams().size() == 1 && - "subscript always has one arg"); - return refFnType->getParams()[0].getPlainType(); - } - public: Expr *visitUnresolvedDotExpr(UnresolvedDotExpr *expr) { return applyMemberRefExpr(expr, expr->getBase(), expr->getDotLoc(), @@ -5454,23 +5518,14 @@ namespace { // Compute substitutions to refer to the member. auto ref = resolveConcreteDeclRef(subscript, memberLoc); - // If this is a @dynamicMemberLookup reference to resolve a property - // through the subscript(dynamicMember:) member, restore the + // If this is a `@dynamicMemberLookup` reference to resolve a property + // through the `subscript(dynamicMember:...)` member, restore the // openedType and origComponent to its full reference as if the user // wrote out the subscript manually. if (overload.choice.isAnyDynamicMemberLookup()) { - auto indexType = getTypeOfDynamicMemberIndex(overload); - Expr *argExpr = nullptr; - if (overload.choice.isKeyPathDynamicMemberLookup()) { - argExpr = buildKeyPathDynamicMemberArgExpr(indexType, componentLoc, - memberLoc); - } else { - auto fieldName = overload.choice.getName().getBaseIdentifier().str(); - argExpr = buildDynamicMemberLookupArgExpr(fieldName, componentLoc, - indexType); - } - args = ArgumentList::forImplicitSingle(ctx, ctx.Id_dynamicMember, - argExpr); + args = buildArgumentListForDynamicMemberLookupSubscript( + overload, componentLoc, locator); + // Record the implicit subscript expr's parameter bindings and matching // direction as `coerceCallArguments` requires them. solution.recordSingleArgMatchingChoice(locator); @@ -5759,18 +5814,6 @@ Solution::resolveLocatorToDecl(ConstraintLocator *locator) const { return resolveConcreteDeclRef(overload->choice.getDeclOrNull(), locator); } -/// Returns the concrete callee which 'owns' the default argument at a given -/// index. This looks through inheritance for inherited default args. -static ConcreteDeclRef getDefaultArgOwner(ConcreteDeclRef owner, - unsigned index) { - auto *param = getParameterAt(owner, index); - assert(param); - if (param->getDefaultArgumentKind() == DefaultArgumentKind::Inherited) { - return getDefaultArgOwner(owner.getOverriddenDecl(), index); - } - return owner; -} - static bool canPeepholeTupleConversion(Expr *expr, ArrayRef sources) { if (!isa(expr)) @@ -6114,29 +6157,6 @@ static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, } } -// For variadic generic declarations we need to compute a substituted -// version of bindings because all of the packs are exploaded in the -// substituted function type. -// -// \code -// func fn(_: repeat each T) {} -// -// fn("", 42) -// \endcode -// -// The type of `fn` in the call is `(String, Int) -> Void` but bindings -// have only one parameter at index `0` with two argument positions: 0, 1. -static bool shouldSubstituteParameterBindings(ConcreteDeclRef callee) { - auto subst = callee.getSubstitutions(); - if (subst.empty()) - return false; - - auto sig = subst.getGenericSignature(); - return llvm::any_of( - sig.getGenericParams(), - [&](const GenericTypeParamType *GP) { return GP->isParameterPack(); }); -} - /// Compute parameter binding substitutions by exploding pack expansions /// into multiple bindings (if they matched more than one argument) and /// ignoring empty ones. diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index e021ce40f7a70..8be9a93911a52 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -2532,36 +2532,39 @@ AssignmentFailure::getMemberRef(ConstraintLocator *locator) const { if (!member) return std::nullopt; - if (!member->choice.isDecl()) + // If the member is a subscript, it might be a dynamic member lookup access, + // in which case we need to peer through the keypath parameter to get the + // underlying member. + auto *SD = member->choice.isDecl() + ? dyn_cast(member->choice.getDecl()) + : nullptr; + auto eligibility = SD ? SD->getDynamicMemberLookupSubscriptEligibility() + : DynamicMemberLookupSubscriptEligibility::None; + switch (eligibility) { + case DynamicMemberLookupSubscriptEligibility::None: + // Not a decl, subscript, or dynamic member lookup subscript; stick with the + // existing overload choice. return member->choice; - auto *decl = member->choice.getDecl(); - if (isa(decl) && - isValidDynamicMemberLookupSubscript(cast(decl))) { - auto *subscript = cast(decl); - // If this is a keypath dynamic member lookup, we have to - // adjust the locator to find member referred by it. - if (isValidKeyPathDynamicMemberLookup(subscript)) { - // Type has a following format: - // `(Self) -> (dynamicMember: {Writable}KeyPath) -> U` - auto *fullType = member->adjustedOpenedFullType->castTo(); - auto *fnType = fullType->getResult()->castTo(); - - auto paramTy = fnType->getParams()[0].getPlainType(); - auto keyPath = paramTy->getAnyNominal(); - auto memberLoc = getConstraintLocator( - locator, LocatorPathElt::KeyPathDynamicMember(keyPath)); - - auto memberRef = getOverloadChoiceIfAvailable(memberLoc); - return memberRef ? std::optional(memberRef->choice) - : std::nullopt; - } - - // If this is a string based dynamic lookup, there is no member declaration. + case DynamicMemberLookupSubscriptEligibility::String: + // There is no member declaration for string-based dynamic member lookup + // subscripts. return std::nullopt; - } - return member->choice; + case DynamicMemberLookupSubscriptEligibility::KeyPath: { + auto keyPathType = SD->getDynamicMemberLookupKeyPathType(); + assert(keyPathType && "KeyPath-based dynamic member lookup subscripts must " + "have a valid dynamic member type"); + + auto memberLoc = getConstraintLocator( + locator, + LocatorPathElt::KeyPathDynamicMember(keyPathType->getAnyNominal())); + + auto memberRef = getOverloadChoiceIfAvailable(memberLoc); + return memberRef ? std::optional(memberRef->choice) + : std::nullopt; + } + } } Diag AssignmentFailure::findDeclDiagnostic(ASTContext &ctx, diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index fd5e6b9842a97..1bbbb257c86f8 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -10539,20 +10539,23 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, } // While looking for subscript choices it's possible to find - // `subscript(dynamicMember: {Writable}KeyPath)` on types - // marked as `@dynamicMemberLookup`, let's mark this candidate - // as representing "dynamic lookup" unless it's a direct call - // to such subscript (in that case label is expected to match). - if (auto *subscript = dyn_cast(cand)) { - if (memberLocator && instanceTy->hasDynamicMemberLookupAttribute() && - isValidKeyPathDynamicMemberLookup(subscript)) { - auto *args = getArgumentList(memberLocator); - - if (!(args && args->isUnary() && - args->getLabel(0) == getASTContext().Id_dynamicMember)) { - return OverloadChoice::getDynamicMemberLookup( - baseTy, subscript, ctx.getIdentifier("subscript"), - /*isKeyPathBased=*/true); + // `subscript(dynamicMember: {Reference{Writable}}KeyPath, ...)` on types + // marked as `@dynamicMemberLookup`; let's mark this candidate as + // representing "dynamic lookup" unless it's a direct call to such subscript + // (in that case label is expected to match). + if (auto *SD = dyn_cast(cand)) { + if (memberLocator && instanceTy->hasDynamicMemberLookupAttribute()) { + if (SD->getDynamicMemberLookupSubscriptEligibility() == + DynamicMemberLookupSubscriptEligibility::KeyPath) { + auto *args = getArgumentList(memberLocator); + auto isDirectCallToSubscript = + args && args->isUnary() && + args->getLabel(0) == getASTContext().Id_dynamicMember; + if (!isDirectCallToSubscript) { + return OverloadChoice::getDynamicMemberLookup( + baseTy, SD, ctx.getIdentifier("subscript"), + /*isKeyPathBased=*/true); + } } } } @@ -10714,7 +10717,7 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, // or if all of the candidates come from conditional conformances (which // might not be applicable), and we are looking for members in a type with // the @dynamicMemberLookup attribute, then we resolve a reference to a - // `subscript(dynamicMember:)` method and pass the member name as a string + // `subscript(dynamicMember:...)` method and pass the member name as a string // parameter. if (constraintKind == ConstraintKind::ValueMember && memberName.isSimpleName() && !memberName.isSpecial() && @@ -10725,33 +10728,50 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, allFromConditionalConformances(*this, instanceTy, candidates)) && !isSelfRecursiveKeyPathDynamicMemberLookup(*this, baseTy, memberLocator)) { - auto &ctx = getASTContext(); - // Recursively look up `subscript(dynamicMember:)` methods in this type. - DeclNameRef subscriptName( - { ctx, DeclBaseName::createSubscript(), { ctx.Id_dynamicMember } }); auto subscripts = performMemberLookup( - constraintKind, subscriptName, baseTy, functionRefInfo, memberLocator, - includeInaccessibleMembers); + constraintKind, DeclNameRef::createSubscript(), baseTy, + functionRefInfo, memberLocator, includeInaccessibleMembers); // Reflect the candidates found as `DynamicMemberLookup` results. auto name = memberName.getBaseIdentifier(); for (const auto &candidate : subscripts.ViableCandidates) { auto *SD = cast(candidate.getDecl()); - bool isKeyPathBased = isValidKeyPathDynamicMemberLookup(SD); + bool isKeyPathBased; + switch (SD->getDynamicMemberLookupSubscriptEligibility()) { + case DynamicMemberLookupSubscriptEligibility::None: + continue; + case DynamicMemberLookupSubscriptEligibility::KeyPath: + isKeyPathBased = true; + break; + case DynamicMemberLookupSubscriptEligibility::String: + isKeyPathBased = false; + break; + } - if (isValidStringDynamicMemberLookup(SD, DC->getParentModule()) || - isKeyPathBased) - result.addViable(OverloadChoice::getDynamicMemberLookup( - baseTy, SD, name, isKeyPathBased)); + result.addViable(OverloadChoice::getDynamicMemberLookup( + baseTy, SD, name, isKeyPathBased)); } for (auto index : indices(subscripts.UnviableCandidates)) { auto *SD = cast(subscripts.UnviableCandidates[index].getDecl()); - auto choice = OverloadChoice::getDynamicMemberLookup( - baseTy, SD, name, isValidKeyPathDynamicMemberLookup(SD)); - result.addUnviable(choice, subscripts.UnviableReasons[index]); + + bool isKeyPathBased; + switch (SD->getDynamicMemberLookupSubscriptEligibility()) { + case DynamicMemberLookupSubscriptEligibility::None: + continue; + case DynamicMemberLookupSubscriptEligibility::KeyPath: + isKeyPathBased = true; + break; + case DynamicMemberLookupSubscriptEligibility::String: + isKeyPathBased = false; + break; + } + + result.addUnviable(OverloadChoice::getDynamicMemberLookup( + baseTy, SD, name, isKeyPathBased), + subscripts.UnviableReasons[index]); } } } diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 7aea6720aaf22..33c098d09c8fc 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -3952,8 +3952,8 @@ ConstraintSystem::getArgumentInfoLocator(ConstraintLocator *locator) { if (auto *UME = getAsExpr(anchor)) return getConstraintLocator(UME); - // All implicit x[dynamicMember:] subscript calls can share the same argument - // list. + // All implicit `x[dynamicMember:...]` subscript calls can share the same + // argument list. if (locator->findLast()) { return getConstraintLocator( ASTNode(), LocatorPathElt::ImplicitDynamicMemberSubscript()); diff --git a/lib/Sema/IDETypeCheckingRequests.cpp b/lib/Sema/IDETypeCheckingRequests.cpp index 901879a3141e1..1ee06a8a933fe 100644 --- a/lib/Sema/IDETypeCheckingRequests.cpp +++ b/lib/Sema/IDETypeCheckingRequests.cpp @@ -258,7 +258,7 @@ TypeRelationCheckRequest::evaluate(Evaluator &evaluator, TypePair RootAndResultTypeOfKeypathDynamicMemberRequest::evaluate(Evaluator &evaluator, SubscriptDecl *subscript) const { - auto keyPathType = getKeyPathTypeForDynamicMemberLookup(subscript); + auto keyPathType = subscript->getDynamicMemberLookupKeyPathType(); if (!keyPathType) return TypePair(); diff --git a/lib/Sema/LookupVisibleDecls.cpp b/lib/Sema/LookupVisibleDecls.cpp index 80b6155ac732a..094ee3395e512 100644 --- a/lib/Sema/LookupVisibleDecls.cpp +++ b/lib/Sema/LookupVisibleDecls.cpp @@ -20,6 +20,7 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/ClangModuleLoader.h" #include "swift/AST/ConformanceLookup.h" +#include "swift/AST/Decl.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/GenericSignature.h" #include "swift/AST/ImportCache.h" @@ -39,6 +40,7 @@ #include "clang/Basic/Module.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SetVector.h" +#include #include using namespace swift; @@ -1119,21 +1121,18 @@ static void lookupVisibleDynamicMemberLookupDecls( if (!baseType->hasDynamicMemberLookupAttribute()) return; - auto &ctx = dc->getASTContext(); - - // Lookup the `subscript(dynamicMember:)` methods in this type. - DeclNameRef subscriptName( - { ctx, DeclBaseName::createSubscript(), { ctx.Id_dynamicMember} }); - - SmallVector subscripts; - dc->lookupQualified(baseType, subscriptName, loc, + // Fetch all subscripts which satisfy the `@dynamicMemberLookup` attribute. + SmallVector subscripts; + dc->lookupQualified(baseType, DeclNameRef::createSubscript(), loc, NL_QualifiedDefault | NL_ProtocolMembers, subscripts); + (void)std::remove_if(subscripts.begin(), subscripts.end(), [](ValueDecl *VD) { + auto *SD = dyn_cast(VD); + return !SD || SD->getDynamicMemberLookupSubscriptEligibility() == + DynamicMemberLookupSubscriptEligibility::None; + }); for (ValueDecl *VD : subscripts) { - auto *subscript = dyn_cast(VD); - if (!subscript) - continue; - + auto *subscript = cast(VD); auto rootType = evaluateOrDefault(subscript->getASTContext().evaluator, RootTypeOfKeypathDynamicMemberRequest{subscript}, Type()); if (rootType.isNull()) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index a19ccdcebf681..9f12afe10318d 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -27,6 +27,7 @@ #include "swift/AST/ClangModuleLoader.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" +#include "swift/AST/DiagnosticEngine.h" #include "swift/AST/DiagnosticsParse.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/Effects.h" @@ -52,6 +53,7 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Debug.h" +#include #include using namespace swift; @@ -2017,181 +2019,125 @@ visitDynamicCallableAttr(DynamicCallableAttr *attr) { } } -static bool hasSingleNonVariadicParam(SubscriptDecl *decl, - Identifier expectedLabel, - bool ignoreLabel = false) { - auto *indices = decl->getIndices(); - if (decl->isInvalid() || indices->size() != 1) - return false; - - auto *index = indices->get(0); - if (index->isVariadic() || !index->hasInterfaceType()) - return false; - - if (ignoreLabel) { - return true; +DynamicMemberLookupSubscriptEligibility +SubscriptDecl::evaluateDynamicMemberLookupEligibility(const SubscriptDecl *SD, + bool ignoreLabel) { + if (SD->isInvalid()) { + return DynamicMemberLookupSubscriptEligibility::None; } - return index->getArgumentName() == expectedLabel; -} - -/// Returns true if the given subscript method is an valid implementation of -/// the `subscript(dynamicMember:)` requirement for @dynamicMemberLookup. -/// The method is given to be defined as `subscript(dynamicMember:)`. -bool swift::isValidDynamicMemberLookupSubscript(SubscriptDecl *decl, - bool ignoreLabel) { - // It could be - // - `subscript(dynamicMember: {Writable}KeyPath<...>)`; or - // - `subscript(dynamicMember: String*)` - return isValidKeyPathDynamicMemberLookup(decl, ignoreLabel) || - isValidStringDynamicMemberLookup(decl, ignoreLabel); -} - -bool swift::isValidStringDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel) { - auto &ctx = decl->getASTContext(); - // There are two requirements: - // - The subscript method has exactly one, non-variadic parameter. - // - The parameter type conforms to `ExpressibleByStringLiteral`. - if (!hasSingleNonVariadicParam(decl, ctx.Id_dynamicMember, - ignoreLabel)) - return false; - - const auto *param = decl->getIndices()->get(0); - auto paramType = param->getTypeInContext(); - - // If this is `subscript(dynamicMember: String*)` - return TypeChecker::conformsToKnownProtocol( - paramType, KnownProtocolKind::ExpressibleByStringLiteral); -} - -BoundGenericType * -swift::getKeyPathTypeForDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel) { - auto &ctx = decl->getASTContext(); - if (!hasSingleNonVariadicParam(decl, ctx.Id_dynamicMember, - ignoreLabel)) - return nullptr; - - auto paramTy = decl->getIndices()->get(0)->getInterfaceType(); - - // Allow to compose key path type with a `Sendable` protocol as - // a way to express sendability requirement. - if (auto *existential = paramTy->getAs()) { - auto layout = existential->getExistentialLayout(); - - auto protocols = layout.getProtocols(); - if (!llvm::all_of(protocols, - [&](ProtocolDecl *proto) { - if (proto->isSpecificProtocol(KnownProtocolKind::Sendable)) - return true; + auto *indices = SD->getIndices(); + if (indices->size() == 0) { + return DynamicMemberLookupSubscriptEligibility::None; + } - if (proto->getInvertibleProtocolKind()) - return true; + auto dynamicMemberParam = indices->get(0); + if (!ignoreLabel && dynamicMemberParam->getArgumentName() != + SD->getASTContext().Id_dynamicMember) { + return DynamicMemberLookupSubscriptEligibility::None; + } - return false; - })) { - return nullptr; + bool first = true; + for (auto index : *indices) { + if (!index->hasInterfaceType() || (first && index->isVariadic()) || + (!first && !index->isDefaultArgument() && !index->isVariadic())) { + return DynamicMemberLookupSubscriptEligibility::None; } - paramTy = layout.getSuperclass(); - if (!paramTy) - return nullptr; + first = false; } - if (!paramTy->isKeyPath() && - !paramTy->isWritableKeyPath() && - !paramTy->isReferenceWritableKeyPath()) { - return nullptr; + if (TypeChecker::conformsToKnownProtocol( + dynamicMemberParam->getTypeInContext(), + KnownProtocolKind::ExpressibleByStringLiteral)) { + return DynamicMemberLookupSubscriptEligibility::String; + } + + if (getDynamicMemberParamTypeAsKeyPathType( + dynamicMemberParam->getInterfaceType())) { + return DynamicMemberLookupSubscriptEligibility::KeyPath; } - return paramTy->getAs(); -} -bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel) { - return bool(getKeyPathTypeForDynamicMemberLookup(decl, ignoreLabel)); + return DynamicMemberLookupSubscriptEligibility::None; } /// The @dynamicMemberLookup attribute is only allowed on types that have at /// least one subscript member declared like this: /// -/// subscript -/// (dynamicMember name: KeywordType) -> LookupValue { get } +/// subscript(dynamicMember name: T, ...) -> U +/// (where T is a concrete type conforming to `ExpressibleByStringLiteral`) +/// +/// or +/// +/// subscript(dynamicMember name: {{Reference}Writable}KeyPath, ...) -> V /// /// ... but doesn't care about the mutating'ness of the getter/setter. /// We just manually check the requirements here. -void AttributeChecker:: -visitDynamicMemberLookupAttr(DynamicMemberLookupAttr *attr) { +void AttributeChecker::visitDynamicMemberLookupAttr( + DynamicMemberLookupAttr *attr) { // This attribute is only allowed on nominal types. auto decl = cast(D); auto type = decl->getDeclaredType(); - auto &ctx = decl->getASTContext(); - - auto emitInvalidTypeDiagnostic = [&](const SourceLoc loc) { - diagnose(loc, diag::invalid_dynamic_member_lookup_type, type); - attr->setInvalid(); - }; - - // Look up `subscript(dynamicMember:)` candidates. - DeclNameRef subscriptName( - { ctx, DeclBaseName::createSubscript(), { ctx.Id_dynamicMember } }); - auto candidates = TypeChecker::lookupMember(decl, type, subscriptName); - - if (!candidates.empty()) { - // If no candidates are valid, then reject one. - auto oneCandidate = candidates.front().getValueDecl(); - candidates.filter([&](LookupResultEntry entry, bool isOuter) -> bool { - auto cand = cast(entry.getValueDecl()); - return isValidDynamicMemberLookupSubscript(cand); - }); - - if (candidates.empty()) { - emitInvalidTypeDiagnostic(oneCandidate->getLoc()); - } - - return; - } - // If we couldn't find any candidates, it's likely because: - // - // 1. We don't have a subscript with `dynamicMember` label. - // 2. We have a subscript with `dynamicMember` label, but no argument label. - // - // Let's do another lookup using just the base name. - auto newCandidates = + auto candidates = TypeChecker::lookupMember(decl, type, DeclNameRef::createSubscript()); - // Validate the candidates while ignoring the label. - newCandidates.filter([&](const LookupResultEntry entry, bool isOuter) { - auto cand = cast(entry.getValueDecl()); - return isValidDynamicMemberLookupSubscript(cand, /*ignoreLabel*/ true); - }); - - // If there were no potentially valid candidates, then throw an error. - if (newCandidates.empty()) { - emitInvalidTypeDiagnostic(attr->getLocation()); - return; - } + // We first want to consider only subscript members known to match the + // `subscript(dynamicMember:...)` name requirement, so we can partition those + // out. + auto firstMismatch = std::partition( + candidates.begin(), candidates.end(), [](LookupResultEntry entry) { + auto *SD = cast(entry.getValueDecl()); + auto *indices = SD->getIndices(); + return indices->size() > 0 && indices->get(0)->getArgumentName() == + SD->getASTContext().Id_dynamicMember; + }); - // For each candidate, emit a diagnostic. If we don't have an explicit - // argument label, then emit a fix-it to suggest the user to add one. - for (auto cand : newCandidates) { - auto SD = cast(cand.getValueDecl()); - auto index = SD->getIndices()->get(0); - diagnose(SD, diag::invalid_dynamic_member_lookup_type, type); + if (candidates.begin() < firstMismatch) { + // We have at least one candidate whose name matches + // `subscript(dynamicMember:...)`; if any of these candidates satisfy the + // requirement, we don't need to diagnose any mismatches. + DiagnosticTransaction transaction(Ctx.Diags); + for (auto *it = candidates.begin(); it < firstMismatch; ++it) { + auto *SD = cast(it->getValueDecl()); + if (SD->getDynamicMemberLookupSubscriptEligibility() != + DynamicMemberLookupSubscriptEligibility::None) { + transaction.abort(); + return; + } - // If we have something like `subscript(foo:)` then we want to insert - // `dynamicMember` before `foo`. - if (index->getParameterNameLoc().isValid() && - index->getArgumentNameLoc().isInvalid()) { - diagnose(SD, diag::invalid_dynamic_member_subscript) - .highlight(index->getSourceRange()) - .fixItInsert(index->getParameterNameLoc(), "dynamicMember "); + diagnose(SD, diag::invalid_dynamic_member_lookup_type, type); + } + } else if (firstMismatch < candidates.end()) { + // We didn't find any subscripts whose name matches + // `subscript(dynamicMember:...)`, but we might be able to offer fix-its for + // subscripts which would be valid if not for a missing `dynamicMember` + // argument label. + for (auto *it = firstMismatch; it < candidates.end(); ++it) { + auto *SD = cast(it->getValueDecl()); + diagnose(SD, diag::invalid_dynamic_member_lookup_type, type); + + auto *indices = SD->getIndices(); + if (indices->size() > 0) { + auto param = indices->get(0); + if (param->getParameterNameLoc().isValid() && + param->getArgumentNameLoc().isInvalid() && + SubscriptDecl::evaluateDynamicMemberLookupEligibility( + SD, /*ignoreArgLabel=*/true) != + DynamicMemberLookupSubscriptEligibility::None) { + diagnose(SD, diag::invalid_dynamic_member_subscript) + .highlight(param->getSourceRange()) + .fixItInsert(param->getParameterNameLoc(), "dynamicMember "); + } + } } + } else { + // We didn't have any matches at all. + diagnose(attr->getLocation(), diag::invalid_dynamic_member_lookup_type, + type); } attr->setInvalid(); - return; } /// Get the innermost enclosing declaration for a declaration. diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index e87f692218ad8..989eec502a86e 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -1276,40 +1276,6 @@ diag::RequirementKind getProtocolRequirementKind(ValueDecl *Requirement); bool isValidDynamicCallableMethod(FuncDecl *decl, bool hasKeywordArguments); -/// Returns true if the given subscript method is an valid implementation of -/// the `subscript(dynamicMember:)` requirement for @dynamicMemberLookup. -/// The method is given to be defined as `subscript(dynamicMember:)`. -bool isValidDynamicMemberLookupSubscript(SubscriptDecl *decl, - bool ignoreLabel = false); - -/// Returns true if the given subscript method is an valid implementation of -/// the `subscript(dynamicMember:)` requirement for @dynamicMemberLookup. -/// The method is given to be defined as `subscript(dynamicMember:)` which -/// takes a single non-variadic parameter that conforms to -/// `ExpressibleByStringLiteral` protocol. -bool isValidStringDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel = false); - -/// Returns the KeyPath parameter type for a valid implementation of -/// the `subscript(dynamicMember: {Writable}KeyPath<...>)` requirement for -/// @dynamicMemberLookup. -/// The method is given to be defined as `subscript(dynamicMember:)` which -/// takes a single non-variadic parameter of `{Writable}KeyPath` type. -/// -/// Returns null if the given subscript is not a valid dynamic member lookup -/// implementation. -BoundGenericType * -getKeyPathTypeForDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel = false); - -/// Returns true if the given subscript method is an valid implementation of -/// the `subscript(dynamicMember: {Writable}KeyPath<...>)` requirement for -/// @dynamicMemberLookup. -/// The method is given to be defined as `subscript(dynamicMember:)` which -/// takes a single non-variadic parameter of `{Writable}KeyPath` type. -bool isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel = false); - /// Compute the wrapped value type for the given property that has attached /// property wrappers, when the backing storage is known to have the given type. /// diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index ef2606e8b93fb..c69efcaf64fad 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -1969,17 +1969,17 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload, } }; auto addDynamicMemberSubscriptConstraints = [&](Type argTy, Type resultTy) { - // DynamicMemberLookup results are always a (dynamicMember: T1) -> T2 + // DynamicMemberLookup results are always a `(dynamicMember: T1, ...) -> T2` // subscript. auto *fnTy = openedType->castTo(); - assert(fnTy->getParams().size() == 1 && - "subscript always has one argument"); + assert(fnTy->getParams().size() > 0 && + "subscript always has at least one argument"); auto *callLoc = getConstraintLocator( locator, LocatorPathElt::ImplicitDynamicMemberSubscript()); - // Associate an argument list for the implicit x[dynamicMember:] subscript - // if we haven't already. + // Associate an argument list for the implicit `x[dynamicMember:...]` + // subscript if we haven't already. auto *argLoc = getArgumentInfoLocator(callLoc); if (ArgumentLists.find(argLoc) == ArgumentLists.end()) { auto *argList = ArgumentList::createImplicit( @@ -2044,9 +2044,9 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload, if (!stringLiteral) return; - // Form constraints for a x[dynamicMember:] subscript with a string literal - // argument, where the overload type is bound to the result to model the - // fact that this a property access in the source. + // Form constraints for a `x[dynamicMember:...]` subscript with a string + // literal argument, where the overload type is bound to the result to model + // the fact that this a property access in the source. auto argTy = createTypeVariable(locator, /*options*/ 0); addConstraint(ConstraintKind::LiteralConformsTo, argTy, stringLiteral->getDeclaredInterfaceType(), locator); @@ -2055,8 +2055,8 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload, } case OverloadChoiceKind::KeyPathDynamicMemberLookup: { auto *fnType = openedType->castTo(); - assert(fnType->getParams().size() == 1 && - "subscript always has one argument"); + assert(fnType->getParams().size() > 0 && + "subscript always has at least one argument"); // Parameter type is KeyPath where `T` is a root type // and U is a leaf type (aka member type). auto paramTy = fnType->getParams()[0].getPlainType(); @@ -2171,7 +2171,7 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload, // type into "leaf" directly. addConstraint(ConstraintKind::Equal, memberTy, leafTy, keyPathLoc); - // Form constraints for a x[dynamicMember:] subscript with a key path + // Form constraints for a `x[dynamicMember:...]` subscript with a key path // argument, where the overload type is bound to the result to model the // fact that this a property access in the source. addDynamicMemberSubscriptConstraints(/*argTy*/ paramTy, boundType); diff --git a/test/attr/attr_dynamic_member_lookup.swift b/test/attr/attr_dynamic_member_lookup.swift index c921c4aee0414..df29acecdc0cb 100644 --- a/test/attr/attr_dynamic_member_lookup.swift +++ b/test/attr/attr_dynamic_member_lookup.swift @@ -134,6 +134,119 @@ func testIUOResult(x: IUOResult) { // expected-note@-2{{force-unwrap}} } +//===----------------------------------------------------------------------===// +// Taking multiple arguments +//===----------------------------------------------------------------------===// + +// Parameters after the initial `dynamicMember:` are valid as long as they +// have default arguments or are variadic. These can be concrete or generic. + +protocol MultiArgDefaultValue { + static var defaultValue: Self { get } +} + +@dynamicMemberLookup +struct ValidMultiArg1 { + subscript( + dynamicMember member: KeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> KeyPath { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg2 { + subscript( + dynamicMember member: WritableKeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> WritableKeyPath { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg3 { + subscript( + dynamicMember member: ReferenceWritableKeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> WritableKeyPath { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg4 { + subscript( + dynamicMember member: String, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> String { + get { member } + set {} + } +} + +// Simultaneous overloads don't introduce ambiguity + +@dynamicMemberLookup +struct ValidMultiArg5 { + subscript(dynamicMember member: StaticString) -> Int { + get { return 42 } + set {} + } + + subscript( + dynamicMember member: KeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> KeyPath { + get { member } + set {} + } + + subscript( + dynamicMember member: WritableKeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> WritableKeyPath { + get { member } + set {} + } + + subscript( + dynamicMember member: ReferenceWritableKeyPath, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> WritableKeyPath { + get { member } + set {} + } + + subscript( + dynamicMember member: String, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath... + ) -> String { + get { member } + set {} + } +} + //===----------------------------------------------------------------------===// // Error cases //===----------------------------------------------------------------------===// @@ -156,6 +269,25 @@ struct Invalid2 { } } +// Given multiple arguments, all values after `dynamicMember:` must have a +// default value. +@dynamicMemberLookup +struct InvalidMultiArg1 { + // expected-error@+1{{'@dynamicMemberLookup' requires 'InvalidMultiArg1' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}} + subscript(dynamicMember member: String, magic: StaticString) -> String { + member + } +} + +// `dynamicMember:` must still be the first argument among multiple. +@dynamicMemberLookup +struct InvalidMultiArg2 { + // expected-error@+1{{'@dynamicMemberLookup' requires 'InvalidMultiArg2' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}} + subscript(magic: StaticString = #function, dynamicMember member: String) -> String { + member + } +} + // References to overloads are resolved just like normal subscript lookup: // they are either contextually disambiguated or are invalid. @dynamicMemberLookup @@ -174,6 +306,81 @@ func testAmbiguity(a: Ambiguity) { _ = a.dynamism // expected-error {{ambiguous use of 'subscript(dynamicMember:)'}} } +// References to overloads are also resolved just like normal subscript and +// function lookup in the presence of overloads with default arguments: they are +// either contextually disambiguated or are invalid, with a preference for +// overloads with fewer arguments. +@dynamicMemberLookup +struct MultiArgAmbiguity { + subscript(dynamicMember member: String) -> String { + member + } + + subscript(dynamicMember member: String, magic: StaticString = #function, variadic: Any...) -> Int { + 42 + } + + subscript(dynamicMember member: String, generic: V = .defaultValue, variadic: Any...) -> V { + generic + } + + subscript(dynamicMember member: String, magic: StaticString = #function) -> Float { + 42.0 + } + + subscript(dynamicMember member: String, magic: StaticString = #function) -> Double { + 42.0 + } + + subscript(dynamicMember member: KeyPath, magic: StaticString = #function, variadic: Any...) -> KeyPath { + member + } + + subscript(dynamicMember member: WritableKeyPath, magic: StaticString = #function, variadic: Any...) -> WritableKeyPath { + member + } + + subscript(dynamicMember member: ReferenceWritableKeyPath, magic: StaticString = #function, variadic: Any...) -> ReferenceWritableKeyPath { + member + } + + subscript(dynamicMember member: String, magic: StaticString = #function, variadic: Any...) -> String { + member + } +} + +extension Int: MultiArgDefaultValue { + static var defaultValue: Int { 0 } +} + +func testMultiArgAmbiguity(a: MultiArgAmbiguity) { + // `subscript(dynamicMember: String) -> String` is preferred over other + // overloads as it has fewer parameters; even though the other subscripts can + // be ambiguous. + let _ = a.foo + + // This is also true when selecting between specific overloads by return type: + // `subscript(dynamicMember:) -> String` is preferred over + // `subscript(dynamicMember:magic:variadic:) -> String`. + let _: String = a.foo + + // Normal disambiguation rules also apply to multi-arg subscripts: + // `subscript(dynamicMember:magic:variadic:) -> Int` is preferred over + // `subscript(dynamicMember:generic:varadic:) -> V` because it's more + // specific. + let _: Int = a.foo + + // Other normal ambiguity rules still apply too. + let _: Float = a.foo + let _: Double = a.foo + let _: any BinaryFloatingPoint = a.foo // expected-error{{ambiguous use of 'subscript(dynamicMember:_:)'}} + + class Cls { var foo: Int = 42 } + let _: KeyPath = a.foo + let _: WritableKeyPath = a.foo + let _: ReferenceWritableKeyPath = a.foo +} + // expected-error @+1 {{'@dynamicMemberLookup' attribute cannot be applied to this declaration}} @dynamicMemberLookup extension Int { @@ -301,6 +508,12 @@ class BaseClass { subscript(dynamicMember member: String) -> Int { return 42 } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: String, magic: StaticString = #function) -> Int { + return 42 + } } class DerivedClass : BaseClass {} @@ -332,6 +545,13 @@ struct SettableGeneric1 { get {} nonmutating set {} } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: StaticString, variadic: T...) -> T? { + get {} + nonmutating set {} + } } func testGenericType(a: SettableGeneric1, b: T) -> T? { @@ -350,6 +570,13 @@ struct SettableGeneric2 { get {} nonmutating set {} } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: U, variadic: U...) -> T { + get {} + nonmutating set {} + } } func testGenericType2(a: SettableGeneric2, b: T) -> T? { @@ -396,8 +623,17 @@ func testGenerics( @dynamicMemberLookup class KP { subscript(dynamicMember member: String) -> Int { return 7 } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + // + // We're using `String` here instead of `StaticString` since a `KeyPath` + // reference below requires arguments to be `Hashable` and `StaticString` + // isn't. + subscript(dynamicMember member: String, magic: String = #function) -> Int { return 42 } } _ = \KP.[dynamicMember: "hi"] +_ = \KP.[dynamicMember: "hi", #fileID] _ = \KP.testLookup /* KeyPath based dynamic lookup */ @@ -429,6 +665,17 @@ struct Lens { get { return Lens(obj[keyPath: member]) } set { obj[keyPath: member] = newValue.obj } } + + // These overloads are disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: KeyPath, magic: StaticString = #function) -> Lens { + get { return Lens(obj[keyPath: member]) } + } + + subscript(dynamicMember member: WritableKeyPath, magic: StaticString = #function) -> Lens { + get { return Lens(obj[keyPath: member]) } + set { obj[keyPath: member] = newValue.obj } + } } var topLeft = Point(x: 0, y: 0) @@ -538,6 +785,12 @@ class AMetatype { subscript(dynamicMember member: KeyPath) -> U { get { return value[keyPath: member] } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: KeyPath, magic: StaticString = #function) -> U { + get { return value[keyPath: member] } + } } // Let's make sure that keypath dynamic member lookup @@ -567,6 +820,12 @@ extension KeyPathLookup { subscript(dynamicMember member: KeyPath) -> Int! { get { return value[keyPath: member] } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: KeyPath, magic: StaticString = #function) -> Int! { + get { return value[keyPath: member] } + } } class C : KeyPathLookup { @@ -594,6 +853,12 @@ class D { subscript(dynamicMember member: KeyPath) -> (U) -> U { get { return { offset in self.value[keyPath: member] + offset } } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: KeyPath, magic: StaticString = #function) -> (U) -> U { + get { return { offset in self.value[keyPath: member] + offset } } + } } func faz(_ d: D) { @@ -626,6 +891,26 @@ struct SubscriptLens { get { return value[keyPath: member] } set { value[keyPath: member] = newValue } } + + // These overloads are disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(foo: String, magic: StaticString = #function) -> Int { + get { return 42 } + } + + subscript(offset: Int, magic: StaticString = #function) -> Int { + get { return counter } + set { counter = counter + newValue } + } + + subscript(dynamicMember member: KeyPath, magic: StaticString = #function) -> U! { + get { return value[keyPath: member] } + } + + subscript(dynamicMember member: WritableKeyPath, magic: StaticString = #function) -> U { + get { return value[keyPath: member] } + set { value[keyPath: member] = newValue } + } } func keypath_with_subscripts(_ arr: SubscriptLens<[Int]>, @@ -697,6 +982,13 @@ struct SingleChoiceLens { get { return obj[keyPath: member] } set { obj[keyPath: member] = newValue } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript(dynamicMember member: WritableKeyPath, magic: StaticString = #function) -> U { + get { return obj[keyPath: member] } + set { obj[keyPath: member] = newValue } + } } // Make sure that disjunction filtering optimization doesn't