diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index cdd2b73ce6a1c..b90fefa53ce6e 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -133,7 +133,9 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(optional, Optional, OnConstructor | OnFunc | OnAccessor | OnVar | OnSubscript | DeclModifier, 5) -// NOTE: 6 is unused +SIMPLE_DECL_ATTR(dynamicCallable, DynamicCallable, + OnNominalType, + 6) SIMPLE_DECL_ATTR(noreturn, NoReturn, OnFunc | OnAccessor, 7) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index f52f2ef1b0b81..621e641909237 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1046,6 +1046,14 @@ NOTE(archetype_declared_in_type,none, NOTE(unbound_generic_parameter_explicit_fix,none, "explicitly specify the generic arguments to fix this issue", ()) +ERROR(invalid_dynamic_callable_type,none, + "@dynamicCallable attribute requires %0 to have either a valid " + "'dynamicallyCall(withArguments:)' method or " + "'dynamicallyCall(withKeywordArguments:)' method", (Type)) +ERROR(missing_dynamic_callable_kwargs_method,none, + "@dynamicCallable type %0 cannot be applied with keyword arguments; " + "missing 'dynamicCall(withKeywordArguments:)' method", (Type)) + ERROR(type_invalid_dml,none, "@dynamicMemberLookup attribute requires %0 to have a " "'subscript(dynamicMember:)' member with a string index", (Type)) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 91192838f24f7..0808f93b9f5ef 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -47,6 +47,7 @@ IDENTIFIER(decode) IDENTIFIER(decodeIfPresent) IDENTIFIER(Decoder) IDENTIFIER(decoder) +IDENTIFIER(dynamicallyCall) IDENTIFIER(dynamicMember) IDENTIFIER(Element) IDENTIFIER(Encodable) @@ -114,6 +115,8 @@ IDENTIFIER(Value) IDENTIFIER(value) IDENTIFIER_WITH_NAME(value_, "_value") IDENTIFIER(with) +IDENTIFIER(withArguments) +IDENTIFIER(withKeywordArguments) // Kinds of layout constraints IDENTIFIER_WITH_NAME(UnknownLayout, "_UnknownLayout") diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index eb1af0a59898e..ad3b91086a44d 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -7046,6 +7046,74 @@ Expr *ExprRewriter::convertLiteralInPlace(Expr *literal, return literal; } +// Resolve @dynamicCallable applications. +static Expr *finishApplyDynamicCallable(ConstraintSystem &cs, + const Solution &solution, + ApplyExpr *apply, + ConstraintLocatorBuilder locator) { + auto &ctx = cs.getASTContext(); + auto *fn = apply->getFn(); + + TupleExpr *arg = dyn_cast(apply->getArg()); + if (auto parenExpr = dyn_cast(apply->getArg())) + arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {}); + + // Get resolved `dynamicallyCall` method and verify it. + auto loc = locator.withPathElement(ConstraintLocator::ApplyFunction); + auto selected = solution.getOverloadChoice(cs.getConstraintLocator(loc)); + auto *method = dyn_cast(selected.choice.getDecl()); + auto methodType = selected.openedType->castTo(); + assert(method->getName() == ctx.Id_dynamicallyCall && + "Expected 'dynamicallyCall' method"); + assert(methodType->getParams().size() == 1 && + "Expected 'dynamicallyCall' method with one parameter"); + auto argumentLabel = methodType->getParams()[0].getLabel(); + assert((argumentLabel == ctx.Id_withArguments || + argumentLabel == ctx.Id_withKeywordArguments) && + "Expected 'dynamicallyCall' method argument label 'withArguments' or " + "'withKeywordArguments'"); + + // Determine which method was resolved: a `withArguments` method or a + // `withKeywordArguments` method. + bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments; + + // Construct expression referencing the `dynamicallyCall` method. + Expr *member = + new (ctx) MemberRefExpr(fn, fn->getEndLoc(), ConcreteDeclRef(method), + DeclNameLoc(method->getNameLoc()), + /*Implicit*/ true); + + // Construct argument to the method (either an array or dictionary + // expression). + Expr *argument = nullptr; + if (!useKwargsMethod) { + argument = ArrayExpr::create(ctx, SourceLoc(), arg->getElements(), + {}, SourceLoc()); + } else { + SmallVector names; + SmallVector dictElements; + for (unsigned i = 0, n = arg->getNumElements(); i < n; i++) { + Expr *labelExpr = + new (ctx) StringLiteralExpr(arg->getElementName(i).get(), + arg->getElementNameLoc(i), + /*Implicit*/ true); + Expr *pair = + TupleExpr::createImplicit(ctx, { labelExpr, arg->getElement(i) }, {}); + dictElements.push_back(pair); + } + argument = DictionaryExpr::create(ctx, SourceLoc(), dictElements, {}, + SourceLoc()); + } + argument->setImplicit(); + + // Construct call to the `dynamicallyCall` method. + Expr *result = CallExpr::createImplicit(ctx, member, argument, + { argumentLabel }); + cs.TC.typeCheckExpression(result, cs.DC); + cs.cacheExprTypes(result); + return result; +} + Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, ConstraintLocatorBuilder locator) { TypeChecker &tc = cs.getTypeChecker(); @@ -7340,67 +7408,73 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, } // We have a type constructor. - auto metaTy = cs.getType(fn)->castTo(); - auto ty = metaTy->getInstanceType(); - - if (!cs.isTypeReference(fn)) { - bool isExistentialType = false; - // If this is an attempt to initialize existential type. - if (auto metaType = cs.getType(fn)->getAs()) { - auto instanceType = metaType->getInstanceType(); - isExistentialType = instanceType->isExistentialType(); - } - - if (!isExistentialType) { - // If the metatype value isn't a type expression, - // the user should reference '.init' explicitly, for clarity. - cs.TC - .diagnose(apply->getArg()->getStartLoc(), - diag::missing_init_on_metatype_initialization) - .fixItInsert(apply->getArg()->getStartLoc(), ".init"); - } - } - - // If we're "constructing" a tuple type, it's simply a conversion. - if (auto tupleTy = ty->getAs()) { - // FIXME: Need an AST to represent this properly. - return coerceToType(apply->getArg(), tupleTy, locator); - } + if (auto metaTy = cs.getType(fn)->getAs()) { + auto ty = metaTy->getInstanceType(); + + if (!cs.isTypeReference(fn)) { + bool isExistentialType = false; + // If this is an attempt to initialize existential type. + if (auto metaType = cs.getType(fn)->getAs()) { + auto instanceType = metaType->getInstanceType(); + isExistentialType = instanceType->isExistentialType(); + } + + if (!isExistentialType) { + // If the metatype value isn't a type expression, + // the user should reference '.init' explicitly, for clarity. + cs.TC + .diagnose(apply->getArg()->getStartLoc(), + diag::missing_init_on_metatype_initialization) + .fixItInsert(apply->getArg()->getStartLoc(), ".init"); + } + } + + // If we're "constructing" a tuple type, it's simply a conversion. + if (auto tupleTy = ty->getAs()) { + // FIXME: Need an AST to represent this properly. + return coerceToType(apply->getArg(), tupleTy, locator); + } + + // We're constructing a value of nominal type. Look for the constructor or + // enum element to use. + auto ctorLocator = cs.getConstraintLocator( + locator.withPathElement(ConstraintLocator::ApplyFunction) + .withPathElement(ConstraintLocator::ConstructorMember)); + auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator); + if (!selected) { + assert(ty->hasError() || ty->hasUnresolvedType()); + cs.setType(apply, ty); + return apply; + } + + assert(ty->getNominalOrBoundGenericNominal() || ty->is() || + ty->isExistentialType() || ty->is()); + + // We have the constructor. + auto choice = selected->choice; + + // Consider the constructor decl reference expr 'implicit', but the + // constructor call expr itself has the apply's 'implicitness'. + bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic; + Expr *declRef = buildMemberRef(fn, selected->openedFullType, + /*dotLoc=*/SourceLoc(), choice, + DeclNameLoc(fn->getEndLoc()), + selected->openedType, locator, ctorLocator, + /*Implicit=*/true, + choice.getFunctionRefKind(), + AccessSemantics::Ordinary, isDynamic); + if (!declRef) + return nullptr; + declRef->setImplicit(apply->isImplicit()); + apply->setFn(declRef); - // We're constructing a value of nominal type. Look for the constructor or - // enum element to use. - auto ctorLocator = cs.getConstraintLocator( - locator.withPathElement(ConstraintLocator::ApplyFunction) - .withPathElement(ConstraintLocator::ConstructorMember)); - auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator); - if (!selected) { - assert(ty->hasError() || ty->hasUnresolvedType()); - cs.setType(apply, ty); - return apply; + // Tail-recur to actually call the constructor. + return finishApply(apply, openedType, locator); } - assert(ty->getNominalOrBoundGenericNominal() || ty->is() || - ty->isExistentialType() || ty->is()); - - // We have the constructor. - auto choice = selected->choice; - - // Consider the constructor decl reference expr 'implicit', but the - // constructor call expr itself has the apply's 'implicitness'. - bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic; - Expr *declRef = buildMemberRef(fn, selected->openedFullType, - /*dotLoc=*/SourceLoc(), choice, - DeclNameLoc(fn->getEndLoc()), - selected->openedType, locator, ctorLocator, - /*Implicit=*/true, choice.getFunctionRefKind(), - AccessSemantics::Ordinary, isDynamic); - if (!declRef) - return nullptr; - declRef->setImplicit(apply->isImplicit()); - apply->setFn(declRef); - - // Tail-recur to actually call the constructor. - return finishApply(apply, openedType, locator); + // Handle @dynamicCallable applications. + // At this point, all other ApplyExpr cases have been handled. + return finishApplyDynamicCallable(cs, solution, apply, locator); } diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index 2f6399948517a..c545874413a12 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -153,6 +153,7 @@ static bool shouldBindToValueType(Constraint *constraint) { case ConstraintKind::CheckedCast: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::OptionalObject: return false; @@ -575,6 +576,7 @@ ConstraintSystem::getPotentialBindings(TypeVariableType *typeVar) { } case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: { if (result.FullyBound && result.InvolvesTypeVariables) continue; diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index 3ea4b0b4c3ae4..01eb3dd4bc2d4 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -5314,9 +5314,28 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) { // non-function/non-metatype type, then we cannot call it! if (!isUnresolvedOrTypeVarType(fnType) && !fnType->is() && !fnType->is()) { - + auto arg = callExpr->getArg(); + // Diagnose @dynamicCallable errors. + if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) { + auto dynamicCallableMethods = + CS.DynamicCallableCache[fnType->getCanonicalType()]; + + // Diagnose dynamic calls with keywords on @dynamicCallable types that + // don't define the `withKeywordArguments` method. + if (auto tuple = dyn_cast(arg)) { + bool hasArgLabel = llvm::any_of( + tuple->getElementNames(), [](Identifier i) { return !i.empty(); }); + if (hasArgLabel && + dynamicCallableMethods.keywordArgumentsMethods.empty()) { + diagnose(callExpr->getFn()->getStartLoc(), + diag::missing_dynamic_callable_kwargs_method, fnType); + return true; + } + } + } + if (fnType->is()) { auto diag = diagnose(arg->getStartLoc(), diag::missing_init_on_metatype_initialization); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index f83c5dc4d84ea..61fdfad8711b5 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -920,6 +920,7 @@ ConstraintSystem::matchTupleTypes(TupleType *tuple1, TupleType *tuple2, case ConstraintKind::Equal: case ConstraintKind::Subtype: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::CheckedCast: case ConstraintKind::ConformsTo: @@ -1013,6 +1014,7 @@ static bool matchFunctionRepresentations(FunctionTypeRepresentation rep1, case ConstraintKind::ArgumentConversion: case ConstraintKind::OperatorArgumentConversion: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::CheckedCast: case ConstraintKind::ConformsTo: @@ -1090,6 +1092,7 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2, break; case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::CheckedCast: case ConstraintKind::ConformsTo: @@ -1787,6 +1790,7 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, break; case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::BridgingConversion: case ConstraintKind::CheckedCast: @@ -3043,7 +3047,6 @@ getArgumentLabels(ConstraintSystem &cs, ConstraintLocatorBuilder locator) { return known->second; } - /// Return true if the specified type or a super-class/super-protocol has the /// @dynamicMemberLookup attribute on it. This implementation is not /// particularly fast in the face of deep class hierarchies or lots of protocol @@ -4442,6 +4445,10 @@ ConstraintSystem::simplifyApplicableFnConstraint( ConstraintLocatorBuilder outerLocator = getConstraintLocator(anchor, parts, locator.getSummaryFlags()); + // Before stripping optional types, save original type for handling + // @dynamicCallable applications. This supports the fringe case where + // `Optional` itself is extended with @dynamicCallable functionality. + auto origType2 = desugar2; unsigned unwrapCount = 0; if (shouldAttemptFixes()) { // If we have an optional type, try forcing it to see if that @@ -4510,7 +4517,283 @@ ConstraintSystem::simplifyApplicableFnConstraint( return simplified; } - return SolutionKind::Error; + // Handle applications of @dynamicCallable types. + return simplifyDynamicCallableApplicableFnConstraint(type1, origType2, + subflags, locator); +} + +/// Looks up and returns the @dynamicCallable required methods (if they exist) +/// implemented by a type. +static llvm::DenseSet +lookupDynamicCallableMethods(Type type, ConstraintSystem &CS, + const ConstraintLocatorBuilder &locator, + Identifier argumentName, bool hasKeywordArgs) { + auto &ctx = CS.getASTContext(); + auto decl = type->getAnyNominal(); + auto methodName = DeclName(ctx, ctx.Id_dynamicallyCall, { argumentName }); + auto matches = CS.performMemberLookup(ConstraintKind::ValueMember, + methodName, type, + FunctionRefKind::SingleApply, + CS.getConstraintLocator(locator), + /*includeInaccessibleMembers*/ false); + // Filter valid candidates. + auto candidates = matches.ViableCandidates; + auto filter = [&](OverloadChoice choice) { + auto cand = cast(choice.getDecl()); + return !isValidDynamicCallableMethod(cand, decl, CS.TC, hasKeywordArgs); + }; + candidates.erase( + std::remove_if(candidates.begin(), candidates.end(), filter), + candidates.end()); + + llvm::DenseSet methods; + for (auto candidate : candidates) + methods.insert(cast(candidate.getDecl())); + return methods; +} + +/// Looks up and returns the @dynamicCallable required methods (if they exist) +/// implemented by a type. This function should not be called directly: +/// instead, call `getDynamicCallableMethods` which performs caching. +static DynamicCallableMethods +lookupDynamicCallableMethods(Type type, ConstraintSystem &CS, + const ConstraintLocatorBuilder &locator) { + auto &ctx = CS.getASTContext(); + DynamicCallableMethods methods; + methods.argumentsMethods = + lookupDynamicCallableMethods(type, CS, locator, ctx.Id_withArguments, + /*hasKeywordArgs*/ false); + methods.keywordArgumentsMethods = + lookupDynamicCallableMethods(type, CS, locator, + ctx.Id_withKeywordArguments, + /*hasKeywordArgs*/ true); + return methods; +} + +/// Returns the @dynamicCallable required methods (if they exist) implemented +/// by a type. +/// This function may be slow for deep class hierarchies and multiple protocol +/// conformances, but it is invoked only after other constraint simplification +/// rules fail. +static DynamicCallableMethods +getDynamicCallableMethods(Type type, ConstraintSystem &CS, + const ConstraintLocatorBuilder &locator) { + auto canType = type->getCanonicalType(); + auto it = CS.DynamicCallableCache.find(canType); + if (it != CS.DynamicCallableCache.end()) return it->second; + + // Calculate @dynamicCallable methods for composite types with multiple + // components (protocol composition types and archetypes). + auto calculateForComponentTypes = + [&](ArrayRef componentTypes) -> DynamicCallableMethods { + DynamicCallableMethods methods; + for (auto componentType : componentTypes) { + auto tmp = getDynamicCallableMethods(componentType, CS, locator); + methods.argumentsMethods.insert(tmp.argumentsMethods.begin(), + tmp.argumentsMethods.end()); + methods.keywordArgumentsMethods.insert( + tmp.keywordArgumentsMethods.begin(), + tmp.keywordArgumentsMethods.end()); + } + return methods; + }; + + // Calculate @dynamicCallable methods. + auto calculate = [&]() -> DynamicCallableMethods { + // If this is an archetype type, check if any types it conforms to + // (superclass or protocols) have the attribute. + if (auto archetype = dyn_cast(canType)) { + SmallVector componentTypes; + for (auto protocolDecl : archetype->getConformsTo()) + componentTypes.push_back(protocolDecl->getDeclaredType()); + if (auto superclass = archetype->getSuperclass()) + componentTypes.push_back(superclass); + return calculateForComponentTypes(componentTypes); + } + + // If this is a protocol composition, check if any of its members have the + // attribute. + if (auto protocolComp = dyn_cast(canType)) + return calculateForComponentTypes(protocolComp->getMembers()); + + // Otherwise, this must be a nominal type. + // Dynamic calling doesn't work for tuples, etc. + auto nominal = canType->getAnyNominal(); + if (!nominal) return DynamicCallableMethods(); + + // If this type conforms to a protocol which has the attribute, then + // look up the methods. + for (auto p : nominal->getAllProtocols()) + if (p->getAttrs().hasAttribute()) + return lookupDynamicCallableMethods(type, CS, locator); + + // Walk superclasses, if present. + llvm::SmallPtrSet visitedDecls; + while (1) { + // If we found a circular parent class chain, reject this. + if (!visitedDecls.insert(nominal).second) + return DynamicCallableMethods(); + + // If this type has the attribute on it, then look up the methods. + if (nominal->getAttrs().hasAttribute()) + return lookupDynamicCallableMethods(type, CS, locator); + + // If this type is a class with a superclass, check superclasses. + if (auto *cd = dyn_cast(nominal)) { + if (auto superClass = cd->getSuperclassDecl()) { + nominal = superClass; + continue; + } + } + + return DynamicCallableMethods(); + } + }; + + return CS.DynamicCallableCache[canType] = calculate(); +} + +ConstraintSystem::SolutionKind +ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint( + Type type1, + Type type2, + TypeMatchOptions flags, + ConstraintLocatorBuilder locator) { + auto &ctx = getASTContext(); + + // By construction, the left hand side is a function type: $T1 -> $T2. + assert(type1->is()); + + // Drill down to the concrete type on the right hand side. + type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true); + auto desugar2 = type2->getDesugaredType(); + + TypeMatchOptions subflags = getDefaultDecompositionOptions(flags); + + // If the types are obviously equivalent, we're done. + if (type1.getPointer() == desugar2) + return SolutionKind::Solved; + + // Local function to form an unsolved result. + auto formUnsolved = [&] { + if (flags.contains(TMF_GenerateConstraints)) { + addUnsolvedConstraint( + Constraint::create(*this, + ConstraintKind::DynamicCallableApplicableFunction, type1, type2, + getConstraintLocator(locator))); + return SolutionKind::Solved; + } + return SolutionKind::Unsolved; + }; + + // If right-hand side is a type variable, the constraint is unsolved. + if (desugar2->isTypeVariableOrMember()) + return formUnsolved(); + + // If right-hand side is a function type, it must be a valid + // `dynamicallyCall` method type. Bind the output and convert the argument + // to the input. + auto func1 = type1->castTo(); + if (auto func2 = dyn_cast(desugar2)) { + // The argument type must be convertible to the input type. + assert(func1->getParams().size() == 1 && func2->getParams().size() == 1 && + "Expected `dynamicallyCall` method with one parameter"); + assert((func2->getParams()[0].getLabel() == ctx.Id_withArguments || + func2->getParams()[0].getLabel() == ctx.Id_withKeywordArguments) && + "Expected 'dynamicallyCall' method argument label 'withArguments' " + "or 'withKeywordArguments'"); + if (matchTypes(func1->getParams()[0].getPlainType(), + func2->getParams()[0].getPlainType(), + ConstraintKind::ArgumentConversion, + subflags, + locator.withPathElement( + ConstraintLocator::ApplyArgument)).isFailure()) + return SolutionKind::Error; + + // The result types are equivalent. + if (matchTypes(func1->getResult(), + func2->getResult(), + ConstraintKind::Bind, + subflags, + locator.withPathElement( + ConstraintLocator::FunctionResult)).isFailure()) + return SolutionKind::Error; + + return SolutionKind::Solved; + } + + // If the right-hand side is not a function type, it must be a valid + // @dynamicCallable type. Attempt to get valid `dynamicallyCall` methods. + auto methods = getDynamicCallableMethods(desugar2, *this, locator); + if (!methods.isValid()) return SolutionKind::Error; + + // Determine whether to call a `withArguments` method or a + // `withKeywordArguments` method. + bool useKwargsMethod = methods.argumentsMethods.empty(); + useKwargsMethod |= llvm::any_of( + func1->getParams(), [](AnyFunctionType::Param p) { return p.hasLabel(); }); + + auto candidates = useKwargsMethod ? + methods.keywordArgumentsMethods : + methods.argumentsMethods; + + // Create a type variable for the `dynamicallyCall` method. + auto loc = getConstraintLocator(locator); + auto tv = createTypeVariable(loc, TVO_CanBindToLValue); + + // Record the 'dynamicallyCall` method overload set. + SmallVector choices; + for (auto candidate : candidates) { + TC.validateDecl(candidate); + if (candidate->isInvalid()) continue; + choices.push_back( + OverloadChoice(type2, candidate, FunctionRefKind::SingleApply)); + } + if (choices.empty()) return SolutionKind::Error; + addOverloadSet(tv, choices, DC, loc); + + // Create a type variable for the argument to the `dynamicallyCall` method. + auto tvParam = createTypeVariable(loc); + AnyFunctionType *funcType = + FunctionType::get({ AnyFunctionType::Param(tvParam) }, func1->getResult()); + addConstraint(ConstraintKind::DynamicCallableApplicableFunction, + funcType, tv, locator); + + // Get argument type for the `dynamicallyCall` method. + Type argumentType; + if (!useKwargsMethod) { + auto arrayLitProto = + ctx.getProtocol(KnownProtocolKind::ExpressibleByArrayLiteral); + addConstraint(ConstraintKind::ConformsTo, tvParam, + arrayLitProto->getDeclaredType(), locator); + auto elementAssocType = cast( + arrayLitProto->lookupDirect(ctx.Id_ArrayLiteralElement).front()); + argumentType = DependentMemberType::get(tvParam, elementAssocType); + } else { + auto dictLitProto = + ctx.getProtocol(KnownProtocolKind::ExpressibleByDictionaryLiteral); + addConstraint(ConstraintKind::ConformsTo, tvParam, + dictLitProto->getDeclaredType(), locator); + auto valueAssocType = cast( + dictLitProto->lookupDirect(ctx.Id_Value).front()); + argumentType = DependentMemberType::get(tvParam, valueAssocType); + } + + // Argument type can default to `Any`. + addConstraint(ConstraintKind::Defaultable, argumentType, + ctx.TheAnyType, locator); + + // All dynamic call parameter types must be convertible to the argument type. + for (auto i : indices(func1->getParams())) { + auto param = func1->getParams()[i]; + auto paramType = param.getPlainType(); + auto locatorBuilder = + locator.withPathElement(LocatorPathElt::getTupleElement(i)); + addConstraint(ConstraintKind::ArgumentConversion, paramType, + argumentType, locatorBuilder); + } + + return SolutionKind::Solved; } static Type getBaseTypeForPointer(ConstraintSystem &cs, TypeBase *type) { @@ -5101,6 +5384,10 @@ ConstraintSystem::addConstraintImpl(ConstraintKind kind, Type first, case ConstraintKind::ApplicableFunction: return simplifyApplicableFnConstraint(first, second, subflags, locator); + case ConstraintKind::DynamicCallableApplicableFunction: + return simplifyDynamicCallableApplicableFnConstraint(first, second, + subflags, locator); + case ConstraintKind::DynamicTypeOf: return simplifyDynamicTypeOfConstraint(first, second, subflags, locator); @@ -5378,6 +5665,11 @@ ConstraintSystem::simplifyConstraint(const Constraint &constraint) { constraint.getSecondType(), None, constraint.getLocator()); + case ConstraintKind::DynamicCallableApplicableFunction: + return simplifyDynamicCallableApplicableFnConstraint( + constraint.getFirstType(), constraint.getSecondType(), None, + constraint.getLocator()); + case ConstraintKind::DynamicTypeOf: return simplifyDynamicTypeOfConstraint(constraint.getFirstType(), constraint.getSecondType(), diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 5a4dabf14ed83..93885ec503eb8 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -1550,6 +1550,7 @@ void ConstraintSystem::ArgumentInfoCollector::walk(Type argType) { case ConstraintKind::CheckedCast: case ConstraintKind::OpenedExistentialOf: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::FunctionInput: case ConstraintKind::FunctionResult: diff --git a/lib/Sema/Constraint.cpp b/lib/Sema/Constraint.cpp index 22bc83db08a04..509897345372b 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -69,6 +69,7 @@ Constraint::Constraint(ConstraintKind Kind, Type First, Type Second, assert(!Second.isNull()); break; case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: assert(First->is() && "The left-hand side type should be a function type"); break; @@ -123,6 +124,7 @@ Constraint::Constraint(ConstraintKind Kind, Type First, Type Second, Type Third, case ConstraintKind::OpenedExistentialOf: case ConstraintKind::OptionalObject: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::ValueMember: case ConstraintKind::UnresolvedValueMember: case ConstraintKind::Defaultable: @@ -227,6 +229,7 @@ Constraint *Constraint::clone(ConstraintSystem &cs) const { case ConstraintKind::OpenedExistentialOf: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::OptionalObject: case ConstraintKind::Defaultable: case ConstraintKind::FunctionInput: @@ -297,6 +300,8 @@ void Constraint::print(llvm::raw_ostream &Out, SourceManager *sm) const { case ConstraintKind::CheckedCast: Out << " checked cast to "; break; case ConstraintKind::SelfObjectOfProtocol: Out << " Self type of "; break; case ConstraintKind::ApplicableFunction: Out << " applicable fn "; break; + case ConstraintKind::DynamicCallableApplicableFunction: + Out << " dynamic callable applicable fn "; break; case ConstraintKind::DynamicTypeOf: Out << " dynamicType type of "; break; case ConstraintKind::EscapableFunctionOf: Out << " @escaping type of "; break; case ConstraintKind::OpenedExistentialOf: Out << " opened archetype of "; break; @@ -487,6 +492,7 @@ gatherReferencedTypeVars(Constraint *constraint, LLVM_FALLTHROUGH; case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::Bind: case ConstraintKind::BindParam: case ConstraintKind::BindToPointerType: diff --git a/lib/Sema/Constraint.h b/lib/Sema/Constraint.h index f2f9cd08845d4..9f5bd4a07a43c 100644 --- a/lib/Sema/Constraint.h +++ b/lib/Sema/Constraint.h @@ -97,6 +97,12 @@ enum class ConstraintKind : char { /// function type is expected to become a function type. Note, we /// do not require the function type attributes to match. ApplicableFunction, + /// \brief The first type is a function type whose input is the value passed + /// to the function and whose output is a type variable describing the output. + /// The second type is either a `@dynamicCallable` nominal type or the + /// function type of a `dynamicallyCall` method defined on a + /// `@dynamicCallable` nominal type. + DynamicCallableApplicableFunction, /// \brief The first type is the type of the dynamicType member of the /// second type. DynamicTypeOf, @@ -472,6 +478,7 @@ class Constraint final : public llvm::ilist_node, case ConstraintKind::CheckedCast: case ConstraintKind::SelfObjectOfProtocol: case ConstraintKind::ApplicableFunction: + case ConstraintKind::DynamicCallableApplicableFunction: case ConstraintKind::BindOverload: case ConstraintKind::OptionalObject: return ConstraintClassification::Relational; diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h index 1e29f7046d74a..4d59e4ec6492e 100644 --- a/lib/Sema/ConstraintSystem.h +++ b/lib/Sema/ConstraintSystem.h @@ -906,8 +906,28 @@ struct MemberLookupResult { } }; - - + +/// \brief Stores the required methods for @dynamicCallable types. +struct DynamicCallableMethods { + llvm::DenseSet argumentsMethods; + llvm::DenseSet keywordArgumentsMethods; + + void addArgumentsMethod(FuncDecl *method) { + argumentsMethods.insert(method); + } + + void addKeywordArgumentsMethod(FuncDecl *method) { + keywordArgumentsMethods.insert(method); + } + + /// \brief Returns true if type defines either of the @dynamicCallable + /// required methods. Returns false iff type does not satisfy @dynamicCallable + /// requirements. + bool isValid() const { + return !argumentsMethods.empty() || !keywordArgumentsMethods.empty(); + } +}; + /// \brief Describes a system of constraints on type variables, the /// solution of which assigns concrete types to each of the type variables. /// Constraint systems are typically generated given an (untyped) expression. @@ -1061,6 +1081,10 @@ class ConstraintSystem { /// The locators of \c Defaultable constraints whose defaults were used. SmallVector DefaultedConstraints; + /// A cache that stores the @dynamicCallable required methods implemented by + /// types. + llvm::DenseMap DynamicCallableCache; + /// This is a cache that keeps track of whether a given type is known (or not) /// to be a @dynamicMemberLookup type. /// @@ -2649,6 +2673,13 @@ class ConstraintSystem { TypeMatchOptions flags, ConstraintLocatorBuilder locator); + /// \brief Attempt to simplify the DynamicCallableApplicableFunction constraint. + SolutionKind simplifyDynamicCallableApplicableFnConstraint( + Type type1, + Type type2, + TypeMatchOptions flags, + ConstraintLocatorBuilder locator); + /// \brief Attempt to simplify the given DynamicTypeOf constraint. SolutionKind simplifyDynamicTypeOfConstraint( Type type1, Type type2, diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 1dfd200b00213..6e7d5ee707c7b 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -78,6 +78,7 @@ class AttributeEarlyChecker : public AttributeVisitor { IGNORED_ATTR(ClangImporterSynthesizedType) IGNORED_ATTR(Convenience) IGNORED_ATTR(DiscardableResult) + IGNORED_ATTR(DynamicCallable) IGNORED_ATTR(DynamicMemberLookup) IGNORED_ATTR(Effects) IGNORED_ATTR(Exported) @@ -860,6 +861,8 @@ class AttributeChecker : public AttributeVisitor { void visitCDeclAttr(CDeclAttr *attr); + void visitDynamicCallableAttr(DynamicCallableAttr *attr); + void visitDynamicMemberLookupAttr(DynamicMemberLookupAttr *attr); void visitFinalAttr(FinalAttr *attr); @@ -946,6 +949,98 @@ static bool isRelaxedIBAction(TypeChecker &TC) { return isiOS(TC) || iswatchOS(TC); } +/// Returns true if a method is an valid implementation of a @dynamicCallable +/// attribute requirement. The method is given to be defined as one of the +/// following: `dynamicallyCall(withArguments:)` or +/// `dynamicallyCall(withKeywordArguments:)`. +bool swift::isValidDynamicCallableMethod(FuncDecl *funcDecl, DeclContext *DC, + TypeChecker &TC, + bool hasKeywordArguments) { + // There are two cases to check. + // 1. `dynamicallyCall(withArguments:)`. + // In this case, the method is valid if the argument has type `A` where + // `A` conforms to `ExpressibleByArrayLiteral`. + // `A.ArrayLiteralElement` and the return type can be arbitrary. + // 2. `dynamicallyCall(withKeywordArguments:)` + // In this case, the method is valid if the argument has type `D` where + // `D` conforms to `ExpressibleByDictionaryLiteral` and `D.Key` conforms to + // `ExpressibleByStringLiteral`. + // `D.Value` and the return type can be arbitrary. + + TC.validateDeclForNameLookup(funcDecl); + auto paramList = funcDecl->getParameters(); + if (paramList->size() != 1 || paramList->get(0)->isVariadic()) return false; + auto argType = paramList->get(0)->getType(); + + // If non-keyword (positional) arguments, check that argument type conforms to + // `ExpressibleByArrayLiteral`. + if (!hasKeywordArguments) { + auto arrayLitProto = + TC.Context.getProtocol(KnownProtocolKind::ExpressibleByArrayLiteral); + return TC.conformsToProtocol(argType, arrayLitProto, DC, + ConformanceCheckOptions()).hasValue(); + } + // If keyword arguments, check that argument type conforms to + // `ExpressibleByDictionaryLiteral` and that the `Key` associated type + // conforms to `ExpressibleByStringLiteral`. + auto stringLitProtocol = + TC.Context.getProtocol(KnownProtocolKind::ExpressibleByStringLiteral); + auto dictLitProto = + TC.Context.getProtocol(KnownProtocolKind::ExpressibleByDictionaryLiteral); + auto dictConf = TC.conformsToProtocol(argType, dictLitProto, DC, + ConformanceCheckOptions()); + if (!dictConf) return false; + auto lookup = dictLitProto->lookupDirect(TC.Context.Id_Key); + auto keyAssocType = + cast(lookup[0])->getDeclaredInterfaceType(); + auto keyType = dictConf.getValue().getAssociatedType(argType, keyAssocType); + return TC.conformsToProtocol(keyType, stringLitProtocol, DC, + ConformanceCheckOptions()).hasValue(); +} + +/// Returns true if a declaration has a valid implementation of a +/// @dynamicCallable attribute requirement. +static bool hasValidDynamicCallableMethod(TypeChecker &TC, + NominalTypeDecl *decl, + Identifier argumentName, + bool hasKeywordArgs) { + auto declType = decl->getDeclaredType(); + auto methodName = DeclName(TC.Context, + DeclBaseName(TC.Context.Id_dynamicallyCall), + { argumentName }); + auto candidates = TC.lookupMember(decl, declType, methodName); + if (candidates.empty()) return false; + + // Filter valid candidates. + candidates.filter([&](LookupResultEntry entry, bool isOuter) { + auto candidate = cast(entry.getValueDecl()); + return isValidDynamicCallableMethod(candidate, decl, TC, hasKeywordArgs); + }); + + // If there are no valid candidates, return false. + if (candidates.size() == 0) return false; + return true; +} + +void AttributeChecker:: +visitDynamicCallableAttr(DynamicCallableAttr *attr) { + // This attribute is only allowed on nominal types. + auto decl = cast(D); + auto type = decl->getDeclaredType(); + + bool hasValidMethod = false; + hasValidMethod |= + hasValidDynamicCallableMethod(TC, decl, TC.Context.Id_withArguments, + /*hasKeywordArgs*/ false); + hasValidMethod |= + hasValidDynamicCallableMethod(TC, decl, TC.Context.Id_withKeywordArguments, + /*hasKeywordArgs*/ true); + if (!hasValidMethod) { + TC.diagnose(attr->getLocation(), diag::invalid_dynamic_callable_type, type); + attr->setInvalid(); + } +} + /// Given a subscript defined as "subscript(dynamicMember:)->T", return true if /// it is an acceptable implementation of the @dynamicMemberLookup attribute's /// requirement. diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 1aaf37fe5f352..7c117578ed6af 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1170,6 +1170,7 @@ namespace { UNINTERESTING_ATTR(CDecl) UNINTERESTING_ATTR(Consuming) UNINTERESTING_ATTR(Dynamic) + UNINTERESTING_ATTR(DynamicCallable) UNINTERESTING_ATTR(DynamicMemberLookup) UNINTERESTING_ATTR(SILGenName) UNINTERESTING_ATTR(Exported) diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 8e142d5d1dd3a..aada5a695efde 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -2158,6 +2158,13 @@ class EncodedDiagnosticMessage { const StringRef Message; }; +/// Returns true if a method is an valid implementation of a @dynamicCallable +/// attribute requirement. The method is given to be defined as one of the +/// following: `dynamicallyCall(withArguments:)` or +/// `dynamicallyCall(withKeywordArguments:)`. +bool isValidDynamicCallableMethod(FuncDecl *funcDecl, DeclContext *DC, + TypeChecker &TC, bool hasKeywordArguments); + /// Given a subscript defined as "subscript(dynamicMember:)->T", return true if /// it is an acceptable implementation of the @dynamicMemberLookup attribute's /// requirement. diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index eb0e55b8a5422..b8e093ff9334e 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -50,9 +50,10 @@ func method(){} @#^KEYWORD3^# class C {} -// KEYWORD3: Begin completions, 9 items +// KEYWORD3: Begin completions, 10 items // KEYWORD3-NEXT: Keyword/None: available[#Class Attribute#]; name=available{{$}} // KEYWORD3-NEXT: Keyword/None: objc[#Class Attribute#]; name=objc{{$}} +// KEYWORD3-NEXT: Keyword/None: dynamicCallable[#Class Attribute#]; name=dynamicCallable{{$}} // KEYWORD3-NEXT: Keyword/None: dynamicMemberLookup[#Class Attribute#]; name=dynamicMemberLookup{{$}} // KEYWORD3-NEXT: Keyword/None: IBDesignable[#Class Attribute#]; name=IBDesignable{{$}} // KEYWORD3-NEXT: Keyword/None: UIApplicationMain[#Class Attribute#]; name=UIApplicationMain{{$}} @@ -64,9 +65,10 @@ class C {} @#^KEYWORD4^# enum E {} -// KEYWORD4: Begin completions, 4 items +// KEYWORD4: Begin completions, 5 items // KEYWORD4-NEXT: Keyword/None: available[#Enum Attribute#]; name=available{{$}} // KEYWORD4-NEXT: Keyword/None: objc[#Enum Attribute#]; name=objc{{$}} +// KEYWORD4-NEXT: Keyword/None: dynamicCallable[#Enum Attribute#]; name=dynamicCallable // KEYWORD4-NEXT: Keyword/None: dynamicMemberLookup[#Enum Attribute#]; name=dynamicMemberLookup // KEYWORD4-NEXT: Keyword/None: usableFromInline[#Enum Attribute#]; name=usableFromInline // KEYWORD4-NEXT: End completions @@ -74,8 +76,9 @@ enum E {} @#^KEYWORD5^# struct S{} -// KEYWORD5: Begin completions, 3 items +// KEYWORD5: Begin completions, 4 items // KEYWORD5-NEXT: Keyword/None: available[#Struct Attribute#]; name=available{{$}} +// KEYWORD5-NEXT: Keyword/None: dynamicCallable[#Struct Attribute#]; name=dynamicCallable // KEYWORD5-NEXT: Keyword/None: dynamicMemberLookup[#Struct Attribute#]; name=dynamicMemberLookup // KEYWORD5-NEXT: Keyword/None: usableFromInline[#Struct Attribute#]; name=usableFromInline // KEYWORD5-NEXT: End completions @@ -83,9 +86,10 @@ struct S{} @#^KEYWORD_LAST^# -// KEYWORD_LAST: Begin completions, 21 items +// KEYWORD_LAST: Begin completions, 22 items // KEYWORD_LAST-NEXT: Keyword/None: available[#Declaration Attribute#]; name=available{{$}} // KEYWORD_LAST-NEXT: Keyword/None: objc[#Declaration Attribute#]; name=objc{{$}} +// KEYWORD_LAST-NEXT: Keyword/None: dynamicCallable[#Declaration Attribute#]; name=dynamicCallable // KEYWORD_LAST-NEXT: Keyword/None: noreturn[#Declaration Attribute#]; name=noreturn{{$}} // KEYWORD_LAST-NEXT: Keyword/None: dynamicMemberLookup[#Declaration Attribute#]; name=dynamicMemberLookup // KEYWORD_LAST-NEXT: Keyword/None: NSCopying[#Declaration Attribute#]; name=NSCopying{{$}} diff --git a/test/SILGen/dynamic_callable_attribute.swift b/test/SILGen/dynamic_callable_attribute.swift new file mode 100644 index 0000000000000..9bf9ae9e2d9ed --- /dev/null +++ b/test/SILGen/dynamic_callable_attribute.swift @@ -0,0 +1,26 @@ +// RUN: %target-swift-emit-silgen -verify %s | %FileCheck %s + +// Check that dynamic calls resolve to the right `dynamicallyCall` method in SIL. + +@dynamicCallable +public struct Callable { + func dynamicallyCall(withArguments: [Int]) {} + func dynamicallyCall(withKeywordArguments: KeyValuePairs) {} +} + +@_silgen_name("foo") +public func foo(a: Callable) { + // The first two calls should resolve to the `withArguments:` method. + a() + a(1, 2, 3) + // The last call should resolve to the `withKeywordArguments:` method. + a(1, 2, 3, label: 4) +} + +// CHECK-LABEL: sil @foo +// CHECK: bb0(%0 : @trivial $Callable): +// CHECK: [[DYN_CALL_1:%.*]] = function_ref @$s26dynamic_callable_attribute8CallableV15dynamicallyCall13withArgumentsySaySiG_tF +// CHECK-NEXT: apply [[DYN_CALL_1]] +// CHECK: [[DYN_CALL_2:%.*]] = function_ref @$s26dynamic_callable_attribute8CallableV15dynamicallyCall13withArgumentsySaySiG_tF +// CHECK-NEXT: apply [[DYN_CALL_2]] +// CHECK: [[DYN_CALL_3:%.*]] = function_ref @$s26dynamic_callable_attribute8CallableV15dynamicallyCall20withKeywordArgumentsys13KeyValuePairsVySSSiG_tF diff --git a/test/attr/attr_dynamic_callable.swift b/test/attr/attr_dynamic_callable.swift new file mode 100644 index 0000000000000..0d0d9d1c49997 --- /dev/null +++ b/test/attr/attr_dynamic_callable.swift @@ -0,0 +1,423 @@ +// RUN: %target-swift-frontend -typecheck -verify %s + +@dynamicCallable +struct Callable { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return arguments.count + } +} + +@dynamicCallable +struct DiscardableResult { + @discardableResult + func dynamicallyCall(withArguments arguments: [Double]) -> Int { + return arguments.count + } +} + +@dynamicCallable +struct Throwing { + func dynamicallyCall(withArguments arguments: [String]) throws -> Int { + return arguments.count + } +} + +@dynamicCallable +struct KeywordArgumentCallable { + @discardableResult + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return arguments.count + } +} + +func testCallable( + a: Callable, b: DiscardableResult, c: Throwing, d: KeywordArgumentCallable +) { + _ = a() + let a1 = a(1, 2, 3, 4) // expected-warning {{initialization of immutable value 'a1' was never used}} + + b() + b(1, 2, 3, 4.0) + + _ = try? c() + let c1 = try! c("hello", "world") // expected-warning {{initialization of immutable value 'c1' was never used}} + + d() + d(1, 2.0, 3) + d(x1: 1, 2.0, x2: 3) +} + +func testIUO( + a: Callable!, b: DiscardableResult!, c: Throwing!, d: KeywordArgumentCallable! +) { + print(a(1, 2, 3)) + print(b(1, 2, 3.0)) + print(try! c("hello", "world")) + print(d(foo: 1, 2.0, bar: 3)) +} + +//===----------------------------------------------------------------------===// +// Returning a function +//===----------------------------------------------------------------------===// + +@dynamicCallable +struct CallableReturningFunction { + func dynamicallyCall(withArguments arguments: [Int]) -> (_ a: Int) -> Void { + return { a in () } + } +} + +func testFunction(a: CallableReturningFunction) { + a(1, 2, 3)(1) +} + +//===----------------------------------------------------------------------===// +// Error cases +//===----------------------------------------------------------------------===// + +// Arguments' type may not be variadic. +// expected-error @+1 {{@dynamicCallable attribute requires 'Invalid1' to have either a valid 'dynamicallyCall(withArguments:)' method or 'dynamicallyCall(withKeywordArguments:)' method}} +@dynamicCallable +struct Invalid1 { + func dynamicallyCall(withArguments arguments: [Int]...) -> Int { + return 1 + } +} + +// Keyword arguments' key type must be ExpressibleByStringLiteral. +// expected-error @+1 {{@dynamicCallable attribute requires 'Invalid2' to have either a valid 'dynamicallyCall(withArguments:)' method or 'dynamicallyCall(withKeywordArguments:)' method}} +@dynamicCallable +struct Invalid2 { + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return 1 + } +} + +// Dynamic calls with keyword arguments require `dynamicallyCall(withKeywordArguments:)` to be defined. +@dynamicCallable +class NoKeyword { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { return 1 } +} +@dynamicCallable +protocol NoKeywordProtocol { + func dynamicallyCall(withArguments arguments: [Int]) -> Int +} + +func testInvalidKeywordCall(x: NoKeyword, y: NoKeywordProtocol & AnyObject) { + x(a: 1, b: 2) // expected-error {{@dynamicCallable type 'NoKeyword' cannot be applied with keyword arguments; missing 'dynamicCall(withKeywordArguments:)' method}} + y(a: 1, b: 2) // expected-error {{@dynamicCallable type 'NoKeywordProtocol & AnyObject' cannot be applied with keyword arguments; missing 'dynamicCall(withKeywordArguments:)' method}} +} + +// expected-error @+1 {{'@dynamicCallable' attribute cannot be applied to this declaration}} +@dynamicCallable +extension Int { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return 1 + } +} + +// expected-error @+1 {{'@dynamicCallable' attribute cannot be applied to this declaration}} +@dynamicCallable +func NotAllowedOnFunc() {} + +// @dynamicCallable cannot be declared on a base class and fulfilled with a +// derived class. + +// expected-error @+1 {{@dynamicCallable attribute requires 'InvalidBase' to have either a valid 'dynamicallyCall(withArguments:)' method or 'dynamicallyCall(withKeywordArguments:)' method}} +@dynamicCallable +class InvalidBase {} + +class InvalidDerived : InvalidBase { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return 1 + } +} + +//===----------------------------------------------------------------------===// +// Multiple `dynamicallyCall` method tests +//===----------------------------------------------------------------------===// + +@dynamicCallable +struct OverloadedCallable { + // expected-note @+1 {{found this candidate}} + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return 1 + } + // expected-note @+1 {{found this candidate}} + func dynamicallyCall(withArguments arguments: [Int]) -> Float { + return 1.0 + } +} + +func testOverloaded(x: OverloadedCallable) { + let _: Int = x(1, 2, 3) + let _: Float = x(1, 2, 3) + let _ = x(1, 2, 3) // expected-error {{ambiguous use of 'dynamicallyCall(withArguments:)'}} +} + +//===----------------------------------------------------------------------===// +// Existential tests +//===----------------------------------------------------------------------===// + +@dynamicCallable +protocol CallableProtocol { + func dynamicallyCall(withArguments arguments: [Int]) -> Int +} + +@dynamicCallable +protocol KeywordCallableProtocol {} +extension KeywordCallableProtocol { + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return arguments.count + } +} + +extension String : CallableProtocol, KeywordCallableProtocol { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return arguments.count + } +} + +func testProtoExtension() -> Int { + let str = "test" + return str(1, 2, 3) + str(label1: 1, 2, label2: 3) +} + +struct CallableStruct : CallableProtocol { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return arguments.count + } +} +class CallableClass : KeywordCallableProtocol {} + +func testExistential( + a: CallableProtocol, b: KeywordCallableProtocol, + c: CallableStruct, d: CallableClass +) -> Int { + // Types that define only the `withKeywordsArguments` method can be called + // with no argument labels. + _ = b() + b(1, 2) + d() + d(1, 2) + return a(1, 2, 3) + b(label1: 1, 2, label2: 3) + c(1, 2, 3) + d(label1: 1, 2, 3) +} + +// Verify protocol compositions and refinements work. +protocol SubProtocol : CallableProtocol {} + +typealias ProtocolComp = AnyObject & CallableProtocol +typealias ProtocolComp2 = KeywordCallableProtocol & CallableClass + +func testExistential2(a: AnyObject & CallableProtocol, + b: SubProtocol, + c: ProtocolComp & AnyObject, + d: CallableClass, + e: CallableProtocol & KeywordCallableProtocol, + f: CallableProtocol & ProtocolComp2) { + print(a(1, 2, 3)) + print(b(1, 2, 3)) + print(c(1, 2, 3)) + print(d(1, 2, 3)) + print(e() + e(label1: 1, 2, label2: 3)) + print(f() + f(label1: 1, 2, label2: 3)) +} +func testConstrainedClassType( + a: C +) -> Int where C : CallableProtocol { + return a(1, 2, 3) +} +func testRefinedProtocolType

( + a: P +) -> Int where P : CallableProtocol { + return a(1, 2, 3) +} + +//===----------------------------------------------------------------------===// +// Extension tests +//===----------------------------------------------------------------------===// + +extension Optional : KeywordCallableProtocol {} +extension Array : KeywordCallableProtocol {} + +func testExtensions() { + let x: Int? = 3 + // Test `Optional` extension. + print(x()) + print(x(label: 1, 2)) + // Test `Array` extension. + print([1]()) + print([1](label: 1, 2)) +} + +//===----------------------------------------------------------------------===// +// Class inheritance tests +//===----------------------------------------------------------------------===// + +@dynamicCallable +class BaseClass { + func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return arguments.count + } +} +class DerivedClass1 : BaseClass {} + +class DerivedClass2 : BaseClass { + override func dynamicallyCall(withArguments arguments: [Int]) -> Int { + return arguments.count + } +} + +class DerivedClass3 : BaseClass { + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return arguments.count + } +} + +func testDerivedClass( + a: BaseClass, b: DerivedClass1, c: DerivedClass2, d: DerivedClass3 +) -> Int { + return a() - b(1, 2) + c(3, 4) - d(x1: 5, 6, x2: 7) +} + +//===----------------------------------------------------------------------===// +// Enum tests +//===----------------------------------------------------------------------===// + +@dynamicCallable +enum BinaryOperation { + case add + case subtract + case multiply + + func dynamicallyCall(withArguments arguments: [T]) -> T { + precondition(arguments.count == 2, "Must have 2 arguments") + let x = arguments[0] + let y = arguments[1] + switch self { + case .add: + return x + y + case .subtract: + return x - y + case .multiply: + return x * y + } + } +} + +func testEnum() { + let ops: [BinaryOperation] = [.add, .subtract, .multiply] + for op in ops { + print(op(3, 4)) + } +} + +//===----------------------------------------------------------------------===// +// Generics tests +//===----------------------------------------------------------------------===// + +@dynamicCallable +struct CallableGenericArray { + func dynamicallyCall(withArguments arguments: A) -> Int { + return 1 + } +} +func testGenericArray( + a: CallableGenericArray, x: A.ArrayLiteralElement +) -> Int { + return a() + a(x, x) +} + +@dynamicCallable +struct CallableGenericDictionary + where D.Key : ExpressibleByStringLiteral { + func dynamicallyCall(withKeywordArguments arguments: D) -> Int { + return 1 + } +} +func testGenericDictionary( + a: CallableGenericDictionary, x: D.Value +) -> Int where D.Key : ExpressibleByStringLiteral { + return a() + a(label1: x, x, label2: x) +} + +@dynamicCallable +struct CallableGeneric1 { + func dynamicallyCall(withArguments arguments: [T]) -> Int { + return arguments.count + } +} +func testGenericType1(a: CallableGeneric1, x: T) -> Int { + return a() + a(x, x) +} +func testConcreteGenericType2(a: CallableGeneric1) -> Int { + return a() + a(1, 2) +} + +@dynamicCallable +struct CallableGeneric2 { + func dynamicallyCall(withArguments arguments: [Any]) -> Int { + return arguments.count + } +} +func testGenericType2(a: CallableGeneric2) -> Int { + return a(1, 2) + a("asdf", 123) +} +func testConcreteGenericType2(a: CallableGeneric2) -> Int { + return a(1, 2) + a("asdf", 123) +} + +@dynamicCallable +struct CallableGeneric3 { + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return arguments.count + } +} +func testGenericType3(a: CallableGeneric3, x: T) -> Int { + return a() + a(x1: x, x, x, x2: x) +} +func testConcreteGenericType3(a: CallableGeneric3) -> Int { + return a() + a(x1: 123, 1, 2, x2: 123) +} + +@dynamicCallable +struct CallableGeneric4 { + func dynamicallyCall(withArguments arguments: [U]) -> Int { + return arguments.count + } + + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> Int { + return arguments.count + } +} +func testGenericType4(a: CallableGeneric4) -> Int { + return a() + a(1, 2, 3) + a(x1: 1, 2, x3: 3) +} + +@dynamicCallable +class CallableGeneric5 { + func dynamicallyCall(withArguments arguments: [U]) -> U { + return arguments[0] + } + + func dynamicallyCall( + withKeywordArguments arguments: KeyValuePairs + ) -> U { + return arguments[0].1 + } +} +func testGenericType5(a: CallableGeneric5) -> Double { + return a(1, 2, 3) + a(x1: 1, 2, x3: 3) +} +func testArchetypeType5>(a: C) -> Double { + return a(1, 2, 3) + a(x1: 1, 2, x3: 3) +}