diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 1d3b6cfe8d922..afe2a7fce1f77 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -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; @@ -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, @@ -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 { + /// 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 +7460,8 @@ 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; @@ -7417,6 +7469,26 @@ class SubscriptDecl : public GenericContext, public AbstractStorageDecl { 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, @@ -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, @@ -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<SubscriptDecl>( AbstractStorageDecl::getOverriddenDecl()); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 88755b944e6a5..32bc3a4f2eb1c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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", 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 d1e7c5f3e98f2..3c1c03bb52778 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -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, @@ -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(); diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 7745773eb3aee..e7a518c26c99f 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -2368,10 +2368,12 @@ namespace { ->castTo<FunctionType>(); auto appliedWrappers = solution.getAppliedPropertyWrappers(memberLoc->getAnchor()); + args = coerceCallArguments( args, fullSubscriptTy, subscriptRef, nullptr, locator.withPathElement(ConstraintLocator::ApplyArgument), appliedWrappers); + if (!args) return nullptr; @@ -2525,7 +2527,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: @@ -3585,6 +3587,51 @@ 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<SubscriptDecl>(overload.choice.getDecl()); + auto memberLoc = cs.getCalleeLocator(locator); + auto subscript = resolveConcreteDeclRef(SD, memberLoc); + + // We specifically don't want the full type here to avoid losing parameter + // pack references. + auto fnType = overload.adjustedOpenedType->castTo<FunctionType>(); + + auto params = fnType->getParams(); + SmallVector<Expr *, 4> args; + for (auto paramIdx : indices(params)) { + auto param = params[paramIdx]; + auto paramTy = + simplifyType(param.getParameterType(/*forCanonical=*/true, &ctx)); + + Expr *argExpr; + if (paramIdx == 0) { + if (overload.choice.isKeyPathDynamicMemberLookup()) { + argExpr = buildKeyPathDynamicMemberArgExpr(paramTy, componentLoc, + memberLoc); + } else { + auto fieldName = + overload.choice.getName().getBaseIdentifier().str(); + argExpr = buildDynamicMemberLookupArgExpr(fieldName, componentLoc, + paramTy); + } + } else { + argExpr = new (ctx) DefaultArgumentExpr(subscript, paramIdx, + componentLoc, paramTy, dc); + } + + args.emplace_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, @@ -3592,6 +3639,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); } @@ -3601,49 +3649,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<T, U>] - - // 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<T, U>, ...]` + 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<FunctionType>()->getResult(); - auto refFnType = subscriptTy->castTo<FunctionType>(); - 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(), @@ -5445,23 +5465,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); 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<SubscriptDecl>(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<SubscriptDecl>(decl) && - isValidDynamicMemberLookupSubscript(cast<SubscriptDecl>(decl))) { - auto *subscript = cast<SubscriptDecl>(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<T, U>) -> U` - auto *fullType = member->adjustedOpenedFullType->castTo<FunctionType>(); - auto *fnType = fullType->getResult()->castTo<FunctionType>(); - - 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<OverloadChoice>(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<OverloadChoice>(memberRef->choice) + : std::nullopt; + } + } } Diag<StringRef> AssignmentFailure::findDeclDiagnostic(ASTContext &ctx, diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index fd5e6b9842a97..cf5ca977ca43e 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<SubscriptDecl>(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<SubscriptDecl>(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<SubscriptDecl>(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<SubscriptDecl>(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<UnresolvedMemberExpr>(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<LocatorPathElt::ImplicitDynamicMemberSubscript>()) { 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 <algorithm> #include <set> 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<ValueDecl *, 2> subscripts; - dc->lookupQualified(baseType, subscriptName, loc, + // Fetch all subscripts which satisfy the `@dynamicMemberLookup` attribute. + SmallVector<ValueDecl *, 4> 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<SubscriptDecl>(VD); + return !SD || SD->getDynamicMemberLookupSubscriptEligibility() == + DynamicMemberLookupSubscriptEligibility::None; + }); for (ValueDecl *VD : subscripts) { - auto *subscript = dyn_cast<SubscriptDecl>(VD); - if (!subscript) - continue; - + auto *subscript = cast<SubscriptDecl>(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 cee993d42311c..720a4214e0898 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" @@ -45,6 +46,7 @@ #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" #include "swift/Basic/Assertions.h" +#include "swift/Basic/SourceLoc.h" #include "swift/Parse/Lexer.h" #include "swift/Parse/ParseDeclName.h" #include "swift/Sema/IDETypeChecking.h" @@ -52,6 +54,7 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Debug.h" +#include <algorithm> #include <optional> using namespace swift; @@ -2025,181 +2028,153 @@ 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(DiagnosticEngine *Diags) { + if (Bits.SubscriptDecl.DynamicMemberLookupEligibility) { + return getStoredDynamicMemberLookupEligibility(); } - 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); -} + auto eligibility = DynamicMemberLookupSubscriptEligibility::None; + [&] { + // If we have an explicit diagnostic engine, we'll output to it on error; if + // we don't then all queued diagnostics will get discarded. + DiagnosticQueue queue{Diags ? *Diags : getDiags(), + /*emitOnDestruction=*/(bool)Diags}; + queue.getDiags().diagnose(this, diag::invalid_dynamic_member_lookup_type, + getDeclContext()->getDeclaredInterfaceType()); -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); -} + if (isInvalid()) { + return; + } -BoundGenericType * -swift::getKeyPathTypeForDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel) { - auto &ctx = decl->getASTContext(); - if (!hasSingleNonVariadicParam(decl, ctx.Id_dynamicMember, - ignoreLabel)) - return nullptr; + auto *indices = getIndices(); + if (indices->size() == 0) { + return; + } - auto paramTy = decl->getIndices()->get(0)->getInterfaceType(); + auto isInvalid = false; + auto &ctx = getASTContext(); + auto diagnosedOutOfOrder = false; + auto firstIndex = indices->get(0); + if (firstIndex->getArgumentName() != ctx.Id_dynamicMember) { + if (!Diags) { + // If we're not here to produce diagnostics during type-checking, + // there's no need to keep going. + return; + } - // Allow to compose key path type with a `Sendable` protocol as - // a way to express sendability requirement. - if (auto *existential = paramTy->getAs<ExistentialType>()) { - auto layout = existential->getExistentialLayout(); + isInvalid = true; + + // We can separately diagnose whether the `dynamicMember` parameter is out + // of order vs. whether we're missing a `dynamicMember` label altogether. + if (indices->size() > 1) { + for (auto idx : range(1, indices->size())) { + auto index = indices->get(idx); + if (index->getArgumentName() == ctx.Id_dynamicMember) { + queue.getDiags() + .diagnose(this, + diag::invalid_dynamic_member_subscript_out_of_order) + .highlight(index->getSourceRange()); + diagnosedOutOfOrder = true; + break; + } + } + } - auto protocols = layout.getProtocols(); - if (!llvm::all_of(protocols, - [&](ProtocolDecl *proto) { - if (proto->isSpecificProtocol(KnownProtocolKind::Sendable)) - return true; + // If there is no argument label at all, we can offer a fix-it to insert + // it. Note that we intentionally don't want to emit a diagnostic if there + // _is_ a label, since it's clearly just not a dynamic member lookup + // subscript. + if (!diagnosedOutOfOrder && firstIndex->getParameterNameLoc().isValid() && + firstIndex->getArgumentNameLoc().isInvalid()) { + queue.getDiags() + .diagnose(this, + diag::invalid_dynamic_member_subscript_missing_label) + .highlight(firstIndex->getSourceRange()) + .fixItInsert(firstIndex->getParameterNameLoc(), "dynamicMember "); + } + } - if (proto->getInvertibleProtocolKind()) - return true; + for (auto index : *indices) { + if (!index->hasInterfaceType() || + (index == firstIndex && index->isVariadic())) { + isInvalid = true; + } - return false; - })) { - return nullptr; + if (index != firstIndex && + (!diagnosedOutOfOrder || + index->getArgumentName() != ctx.Id_dynamicMember) && + (!index->isDefaultArgument() && !index->isVariadic() && + !isa<PackExpansionType>( + index->getInterfaceType()->getCanonicalType()))) { + queue.getDiags() + .diagnose(this, + diag::invalid_dynamic_member_subscript_missing_default) + .highlight(index->getSourceRange()) + .fixItInsertAfter(index->getEndLoc(), " = <#Default#>"); + isInvalid = true; + } } - paramTy = layout.getSuperclass(); - if (!paramTy) - return nullptr; - } + if (isInvalid) { + // There's no point in checking the `dynamicMember` argument type since + // we've already output diagnostics. + return; + } - if (!paramTy->isKeyPath() && - !paramTy->isWritableKeyPath() && - !paramTy->isReferenceWritableKeyPath()) { - return nullptr; - } - return paramTy->getAs<BoundGenericType>(); -} + if (TypeChecker::conformsToKnownProtocol( + firstIndex->getTypeInContext(), + KnownProtocolKind::ExpressibleByStringLiteral)) { + queue.clear(); + eligibility = DynamicMemberLookupSubscriptEligibility::String; + } else if (getDynamicMemberParamTypeAsKeyPathType( + firstIndex->getInterfaceType())) { + queue.clear(); + eligibility = DynamicMemberLookupSubscriptEligibility::KeyPath; + } + }(); -bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl, - bool ignoreLabel) { - return bool(getKeyPathTypeForDynamicMemberLookup(decl, ignoreLabel)); + setDynamicMemberLookupEligibility(eligibility); + return eligibility; } /// The @dynamicMemberLookup attribute is only allowed on types that have at /// least one subscript member declared like this: /// -/// subscript<KeywordType: ExpressibleByStringLiteral, LookupValue> -/// (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<T, U>, ...) -> 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<NominalTypeDecl>(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<SubscriptDecl>(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<SubscriptDecl>(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; - } - - // 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<SubscriptDecl>(cand.getValueDecl()); - auto index = SD->getIndices()->get(0); - diagnose(SD, diag::invalid_dynamic_member_lookup_type, type); - - // 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 "); + // If we find at least one valid candidate, we won't produce any diagnostics. + DiagnosticQueue queue{Ctx.Diags, /*emitOnDestruction=*/true}; + if (candidates.empty()) { + queue.getDiags().diagnose(attr->getLocation(), + diag::invalid_dynamic_member_lookup_type, type); + } else { + for (auto candidate : candidates) { + auto *SD = cast<SubscriptDecl>(candidate.getValueDecl()); + if (SD->evaluateDynamicMemberLookupEligibility(&queue.getDiags()) != + DynamicMemberLookupSubscriptEligibility::None) { + queue.clear(); + return; + } } } attr->setInvalid(); - return; } /// Get the innermost enclosing declaration for a declaration. diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index f7fef359f9466..650b94fe5da7c 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -3011,10 +3011,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> { checkExplicitAvailability(SD); if (!checkOverrides(SD)) { - // If a subscript has an override attribute but does not override - // anything, complain. if (auto *OA = SD->getAttrs().getAttribute<OverrideAttr>()) { - if (!SD->getOverriddenDecl()) { + auto *overriddenDecl = SD->getOverriddenDecl(); + if (!overriddenDecl) { + // If a subscript has an override attribute but does not override + // anything, complain. auto DC = SD->getDeclContext(); auto isClassContext = DC->getSelfClassDecl() != nullptr; auto isStructOrEnumContext = DC->getSelfEnumDecl() != nullptr || @@ -3028,6 +3029,25 @@ class DeclChecker : public DeclVisitor<DeclChecker> { .highlight(OA->getLocation()); } OA->setInvalid(); + } else if (auto outerTy = DC->getDeclaredInterfaceType()) { + // This is an overridden subscript; if the outer type has + // `@dynamicMemberLookup` but isn't directly annotated with it (e.g., + // it's inherited), its members won't go through + // `@dynamicMemberLookup` checking via `AttributeChecker`. We need to + // ensure that if the overridden decl was a valid dynamic member + // lookup subscript, this override remains valid. + if (outerTy->hasDynamicMemberLookupAttribute() && + !outerTy->getAnyNominal() + ->getAttrs() + .hasAttribute<DynamicMemberLookupAttr>()) { + if (overriddenDecl->getDynamicMemberLookupSubscriptEligibility() != + DynamicMemberLookupSubscriptEligibility::None && + SD->evaluateDynamicMemberLookupEligibility( + &SD->getDiags()) == + DynamicMemberLookupSubscriptEligibility::None) { + SD->setInvalid(); + } + } } } } 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<T, U>` 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<T, U>` 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..c15217755b4f4 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -22,6 +22,7 @@ #include "TypeCheckType.h" #include "TypeChecker.h" #include "swift/AST/ConformanceLookup.h" +#include "swift/AST/ExtInfo.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/Effects.h" #include "swift/AST/MacroDefinition.h" @@ -1628,6 +1629,13 @@ DeclReferenceType ConstraintSystem::getTypeOfMemberReference( thrownErrorType = Type(); } + // Mark the isolation if any parameter is isolated. + if (llvm::any_of(indices, [&](AnyFunctionType::Param p) { + return p.isIsolated(); + })) { + info = info.withIsolation(FunctionTypeIsolation::forParameter()); + } + refType = FunctionType::get(indices, elementTy, info); } else { // Delay the adjustment for preconcurrency until after we've formed @@ -1969,17 +1977,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<FunctionType>(); - 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 +2052,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 +2063,8 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload, } case OverloadChoiceKind::KeyPathDynamicMemberLookup: { auto *fnType = openedType->castTo<FunctionType>(); - 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<T, U> where `T` is a root type // and U is a leaf type (aka member type). auto paramTy = fnType->getParams()[0].getPlainType(); @@ -2171,7 +2179,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..ca0d5f6ad4537 100644 --- a/test/attr/attr_dynamic_member_lookup.swift +++ b/test/attr/attr_dynamic_member_lookup.swift @@ -134,6 +134,127 @@ 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<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: KeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> KeyPath<T, U> { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg2 { + subscript<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: WritableKeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> WritableKeyPath<T, U> { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg3 { + subscript<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: ReferenceWritableKeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> WritableKeyPath<T, U> { + get { member } + set {} + } +} + +@dynamicMemberLookup +struct ValidMultiArg4 { + subscript<V: MultiArgDefaultValue, each W>( + dynamicMember member: String, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> String { + get { member } + set {} + } +} + +// Simultaneous overloads don't introduce ambiguity + +@dynamicMemberLookup +struct ValidMultiArg5 { + subscript(dynamicMember member: StaticString) -> Int { + get { return 42 } + set {} + } + + subscript<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: KeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> KeyPath<T, U> { + get { member } + set {} + } + + subscript<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: WritableKeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> WritableKeyPath<T, U> { + get { member } + set {} + } + + subscript<T, U, V: MultiArgDefaultValue, each W>( + dynamicMember member: ReferenceWritableKeyPath<T, U>, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> WritableKeyPath<T, U> { + get { member } + set {} + } + + subscript<V: MultiArgDefaultValue, each W>( + dynamicMember member: String, + magic: StaticString = #function, + generic: V = .defaultValue, + variadic: KeyPath<V, V>..., + parameterPack pack: repeat each W + ) -> String { + get { member } + set {} + } +} + //===----------------------------------------------------------------------===// // Error cases //===----------------------------------------------------------------------===// @@ -156,6 +277,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 { // expected-note{{add a default value to this argument to satisfy the '@dynamicMemberLookup' requirement}} {{62-62= = <#Default#>}} + 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 { // expected-note{{'dynamicMember' argument must appear first in the argument list}} + member + } +} + // References to overloads are resolved just like normal subscript lookup: // they are either contextually disambiguated or are invalid. @dynamicMemberLookup @@ -174,6 +314,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<V: MultiArgDefaultValue>(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<T, U>(dynamicMember member: KeyPath<T, U>, magic: StaticString = #function, variadic: Any...) -> KeyPath<T, U> { + member + } + + subscript<T, U>(dynamicMember member: WritableKeyPath<T, U>, magic: StaticString = #function, variadic: Any...) -> WritableKeyPath<T, U> { + member + } + + subscript<T, U>(dynamicMember member: ReferenceWritableKeyPath<T, U>, magic: StaticString = #function, variadic: Any...) -> ReferenceWritableKeyPath<T, U> { + 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<V>(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<V>(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<Cls, _> = a.foo + let _: WritableKeyPath<Cls, _> = a.foo + let _: ReferenceWritableKeyPath<Cls, _> = a.foo +} + // expected-error @+1 {{'@dynamicMemberLookup' attribute cannot be applied to this declaration}} @dynamicMemberLookup extension Int { @@ -301,7 +516,22 @@ class BaseClass { subscript(dynamicMember member: String) -> Int { return 42 } + + subscript(dynamicMember member: String, magic: StaticString = #function) -> String { + return member + } + + // These overloads are disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript<each W>(dynamicMember member: String, magic: StaticString = #function, pack: repeat each W) -> Int { + return 42 + } + + subscript<each W>(dynamicMember member: String, magic: StaticString = #function, pack: repeat each W) -> String { + return member + } } + class DerivedClass : BaseClass {} func testDerivedClass(x: BaseClass, y: DerivedClass) -> Int { @@ -322,6 +552,31 @@ func testOverrideSubscript(a: BaseClass, b: DerivedClassWithSetter) { a.balboza = 12 // expected-error {{cannot assign through dynamic lookup property: 'a' is a 'let' constant}} } +// Overriding with a different default value is valid. +class DerivedClassWithOverriddenDefault : BaseClass { + override subscript(dynamicMember member: String, magic: StaticString = #fileID) -> String { + return member + } +} + +func testOverrideSubscript(a: BaseClass, b: DerivedClassWithOverriddenDefault) { + let _: String = a.magic + let _: String = b.magic +} + +// Overriding with a different default value is valid. +class DerivedClassWithMissingDefault : BaseClass { + // expected-error@+1{{'@dynamicMemberLookup' requires 'DerivedClassWithMissingDefault' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}} + override subscript(dynamicMember member: String, magic: StaticString) -> String { // expected-note{{add a default value to this argument to satisfy the '@dynamicMemberLookup' requirement}} {{71-71= = <#Default#>}} + return member + } +} + +func testOverrideSubscript(a: BaseClass, b: DerivedClassWithMissingDefault) { + let _: String = a.magic + let _: String = b.magic +} + //===----------------------------------------------------------------------===// // Generics //===----------------------------------------------------------------------===// @@ -332,6 +587,13 @@ struct SettableGeneric1<T> { get {} nonmutating set {} } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript<each W>(dynamicMember member: StaticString, variadic: T..., parameterPack pack: repeat each W) -> T? { + get {} + nonmutating set {} + } } func testGenericType<T>(a: SettableGeneric1<T>, b: T) -> T? { @@ -350,6 +612,13 @@ struct SettableGeneric2<T> { get {} nonmutating set {} } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript<U: ExpressibleByStringLiteral, each W>(dynamicMember member: U, variadic: U..., parameterPack pack: repeat each W) -> T { + get {} + nonmutating set {} + } } func testGenericType2<T>(a: SettableGeneric2<T>, b: T) -> T? { @@ -396,8 +665,17 @@ func testGenerics<S, T, P: GenericProtocol>( @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 +707,17 @@ struct Lens<T> { get { return Lens<U>(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<U, each W>(dynamicMember member: KeyPath<T, U>, magic: StaticString = #function, pack: repeat each W) -> Lens<U> { + get { return Lens<U>(obj[keyPath: member]) } + } + + subscript<U>(dynamicMember member: WritableKeyPath<T, U>, magic: StaticString = #function) -> Lens<U> { + get { return Lens<U>(obj[keyPath: member]) } + set { obj[keyPath: member] = newValue.obj } + } } var topLeft = Point(x: 0, y: 0) @@ -538,6 +827,12 @@ class AMetatype<T> { subscript<U>(dynamicMember member: KeyPath<T.Type, U>) -> U { get { return value[keyPath: member] } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript<U, each W>(dynamicMember member: KeyPath<T.Type, U>, magic: StaticString = #function, pack: repeat each W) -> U { + get { return value[keyPath: member] } + } } // Let's make sure that keypath dynamic member lookup @@ -567,6 +862,12 @@ extension KeyPathLookup { subscript(dynamicMember member: KeyPath<T, Int>) -> Int! { get { return value[keyPath: member] } } + + // This overload is disfavored at callsites compared to the above and + // shouldn't introduce ambiguity. + subscript<each W>(dynamicMember member: KeyPath<T, Int>, magic: StaticString = #function, pack: repeat each W) -> Int! { + get { return value[keyPath: member] } + } } class C<T> : KeyPathLookup { @@ -594,6 +895,12 @@ class D<T> { subscript<U: Numeric>(dynamicMember member: KeyPath<T, U>) -> (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<U: Numeric, each W>(dynamicMember member: KeyPath<T, U>, magic: StaticString = #function, pack: repeat each W) -> (U) -> U { + get { return { offset in self.value[keyPath: member] + offset } } + } } func faz(_ d: D<Point>) { @@ -626,6 +933,26 @@ struct SubscriptLens<T> { 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<each W>(foo: String, magic: StaticString = #function, pack: repeat each W) -> Int { + get { return 42 } + } + + subscript(offset: Int, magic: StaticString = #function) -> Int { + get { return counter } + set { counter = counter + newValue } + } + + subscript<U>(dynamicMember member: KeyPath<T, U>, magic: StaticString = #function) -> U! { + get { return value[keyPath: member] } + } + + subscript<U>(dynamicMember member: WritableKeyPath<T, U>, magic: StaticString = #function) -> U { + get { return value[keyPath: member] } + set { value[keyPath: member] = newValue } + } } func keypath_with_subscripts(_ arr: SubscriptLens<[Int]>, @@ -697,6 +1024,13 @@ struct SingleChoiceLens<T> { 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<U, each W>(dynamicMember member: WritableKeyPath<T, U>, magic: StaticString = #function, pack: repeat each W) -> U { + get { return obj[keyPath: member] } + set { obj[keyPath: member] = newValue } + } } // Make sure that disjunction filtering optimization doesn't @@ -746,6 +1080,39 @@ func invalid_refs_through_dynamic_lookup() { } } +//===----------------------------------------------------------------------===// +// Isolation +//===----------------------------------------------------------------------===// + +@dynamicMemberLookup +struct Isolated { + subscript(dynamicMember member: String, isolation: isolated (any Actor)? = #isolation) -> String { // expected-note {{subscript declared here}} + return member + } +} + +func nonIsolatedAccess(to value: Isolated) { + let _ = value.member // expected-error {{actor-isolated subscript 'subscript(dynamicMember:_:)' can not be referenced from a nonisolated context}} + Task { @MainActor in + // expected-error@+1 {{expression is 'async' but is not marked with 'await'}} + let _ = value.member // expected-note {{subscript access is 'async'}} + + let _ = await value.member + } +} + +@MainActor +func isolatedAccess(to value: Isolated) async { + // expected-error@+1 {{expression is 'async' but is not marked with 'await'}} + let _ = value.member // expected-note {{subscript access is 'async'}} + + let _ = await value.member +} + +//===----------------------------------------------------------------------===// +// Specific Issues +//===----------------------------------------------------------------------===// + // https://github.com/apple/swift/issues/52997 final class C1_52997 {} diff --git a/test/attr/attr_dynamic_member_lookup_default_args.swift b/test/attr/attr_dynamic_member_lookup_default_args.swift new file mode 100644 index 0000000000000..c5f919099e75a --- /dev/null +++ b/test/attr/attr_dynamic_member_lookup_default_args.swift @@ -0,0 +1,158 @@ +// RUN: %target-run-simple-swift %s +// REQUIRES: executable_test + +//===----------------------------------------------------------------------===// +// Magic Literals +//===----------------------------------------------------------------------===// + +@dynamicMemberLookup +struct Magic: Hashable { + let fileID: String + let file: String + let filePath: String + let function: String + let line: Int + let column: Int + let dsohandle: UnsafeRawPointer + + init( + fileID: String = #fileID, + file: String = #file, + filePath: String = #filePath, + function: String = #function, + line: Int = #line, + column: Int = #column, + dsohandle: UnsafeRawPointer = #dsohandle + ) { + self.fileID = fileID + self.file = file + self.filePath = filePath + self.function = function + self.line = line + self.column = column + self.dsohandle = dsohandle + } + + subscript( + dynamicMember member: String, + fileID: String = #fileID, + file: String = #file, + filePath: String = #filePath, + function: String = #function, + line: Int = #line, + column: Int = #column, + dsohandle: UnsafeRawPointer = #dsohandle + ) -> Magic { + return Magic( + fileID: fileID, + file: file, + filePath: filePath, + function: function, + line: line, + column: column, + dsohandle: dsohandle + ) + } + + func offset(lineBy lineOffset: Int, columnBy columnOffset: Int) -> Magic { + Magic( + fileID: fileID, + file: file, + filePath: filePath, + function: function, + line: line + lineOffset, + column: column + columnOffset, + dsohandle: dsohandle + ) + } +} + +/* ! ENSURE FORMATTERS ARE DISABLED TO PRESERVE WHITESPACE ! */ +let m1 = Magic() // 71:15 +let m2 = m1.member // 72:13 +assert(m1.offset(lineBy: +1, columnBy: -2) == m2) + +let m3 = m1 + .member // 76:4 +assert(m1.offset(lineBy: +5, columnBy: -11) == m3) + +let m4 = m1 + . + member // 81:3 +assert(m1.offset(lineBy: +10, columnBy: -12) == m4) + +//===----------------------------------------------------------------------===// +// Inheritance +//===----------------------------------------------------------------------===// + +@dynamicMemberLookup +class Parent { + subscript( + dynamicMember member: String, + kept: String = #fileID, + overridden: String = #fileID + ) -> String { + [member, kept, overridden].joined(separator: ":") + } +} + +@dynamicMemberLookup +class Child: Parent { + override subscript( + dynamicMember member: String, + kept: String = #fileID, + overridden: String = #filePath + ) -> String { + [member, kept, overridden].joined(separator: ":") + } +} + +assert(Parent().member == ["member", #fileID, #fileID].joined(separator: ":")) +assert(Child().member == ["member", #fileID, #filePath].joined(separator: ":")) + +//===----------------------------------------------------------------------===// +// Parameter Packs +//===----------------------------------------------------------------------===// + +@dynamicMemberLookup +struct Pack { + subscript<each T>(dynamicMember member: String, pack: repeat each T) -> (repeat each T) { + (repeat each pack) + } + + subscript<each T, each U>( + dynamicMember member: String, pack1 pack1: repeat each T, pack2 pack2: repeat each U + ) -> Int { + var count = 0 + for _ in repeat each pack1 { + count += 1 + } + + for _ in repeat each pack2 { + count += 1 + } + + return count + } +} + +let p = Pack() +assert(p.member == ()) +assert(p.member == 0) + +//===----------------------------------------------------------------------===// +// Isolation +//===----------------------------------------------------------------------===// + +@dynamicMemberLookup +struct Isolated { + subscript(dynamicMember member: String, isolation: isolated (any Actor)? = #isolation) -> (any Actor)? { + isolation + } +} + +Task<Void, Never>.startSynchronously { @MainActor in + let `actor` = await Isolated().actor + assert(`actor` === MainActor.shared) +} +