Skip to content

Commit 2a4e1b8

Browse files
dan-zhenglattner
authored andcommitted
Implement @dynamicCallable. (#20305)
* Implement dynamically callable types (`@dynamicCallable`). - Implement dynamically callable types as proposed in SE-0216. - Dynamic calls are resolved based on call-site syntax. - Use the `withArguments:` method if it's defined and there are no keyword arguments. - Otherwise, use the `withKeywordArguments:` method. - Support multiple `dynamicallyCall` methods. - This enables two scenarios: - Overloaded `dynamicallyCall` methods on a single `@dynamicCallable` type. - Multiple `dynamicallyCall` methods from a `@dynamicCallable` superclass or from `@dynamicCallable` protocols. - Add `DynamicCallableApplicableFunction` constraint. This, used with an overload set, is necessary to support multiple `dynamicallyCall` methods.
1 parent 17e5fa3 commit 2a4e1b8

17 files changed

+1069
-68
lines changed

include/swift/AST/Attr.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(optional, Optional,
133133
OnConstructor | OnFunc | OnAccessor | OnVar | OnSubscript |
134134
DeclModifier,
135135
5)
136-
// NOTE: 6 is unused
136+
SIMPLE_DECL_ATTR(dynamicCallable, DynamicCallable,
137+
OnNominalType,
138+
6)
137139
SIMPLE_DECL_ATTR(noreturn, NoReturn,
138140
OnFunc | OnAccessor,
139141
7)

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,14 @@ NOTE(archetype_declared_in_type,none,
10461046
NOTE(unbound_generic_parameter_explicit_fix,none,
10471047
"explicitly specify the generic arguments to fix this issue", ())
10481048

1049+
ERROR(invalid_dynamic_callable_type,none,
1050+
"@dynamicCallable attribute requires %0 to have either a valid "
1051+
"'dynamicallyCall(withArguments:)' method or "
1052+
"'dynamicallyCall(withKeywordArguments:)' method", (Type))
1053+
ERROR(missing_dynamic_callable_kwargs_method,none,
1054+
"@dynamicCallable type %0 cannot be applied with keyword arguments; "
1055+
"missing 'dynamicCall(withKeywordArguments:)' method", (Type))
1056+
10491057
ERROR(type_invalid_dml,none,
10501058
"@dynamicMemberLookup attribute requires %0 to have a "
10511059
"'subscript(dynamicMember:)' member with a string index", (Type))

include/swift/AST/KnownIdentifiers.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ IDENTIFIER(decode)
4747
IDENTIFIER(decodeIfPresent)
4848
IDENTIFIER(Decoder)
4949
IDENTIFIER(decoder)
50+
IDENTIFIER(dynamicallyCall)
5051
IDENTIFIER(dynamicMember)
5152
IDENTIFIER(Element)
5253
IDENTIFIER(Encodable)
@@ -114,6 +115,8 @@ IDENTIFIER(Value)
114115
IDENTIFIER(value)
115116
IDENTIFIER_WITH_NAME(value_, "_value")
116117
IDENTIFIER(with)
118+
IDENTIFIER(withArguments)
119+
IDENTIFIER(withKeywordArguments)
117120

118121
// Kinds of layout constraints
119122
IDENTIFIER_WITH_NAME(UnknownLayout, "_UnknownLayout")

lib/Sema/CSApply.cpp

Lines changed: 132 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7046,6 +7046,74 @@ Expr *ExprRewriter::convertLiteralInPlace(Expr *literal,
70467046
return literal;
70477047
}
70487048

7049+
// Resolve @dynamicCallable applications.
7050+
static Expr *finishApplyDynamicCallable(ConstraintSystem &cs,
7051+
const Solution &solution,
7052+
ApplyExpr *apply,
7053+
ConstraintLocatorBuilder locator) {
7054+
auto &ctx = cs.getASTContext();
7055+
auto *fn = apply->getFn();
7056+
7057+
TupleExpr *arg = dyn_cast<TupleExpr>(apply->getArg());
7058+
if (auto parenExpr = dyn_cast<ParenExpr>(apply->getArg()))
7059+
arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {});
7060+
7061+
// Get resolved `dynamicallyCall` method and verify it.
7062+
auto loc = locator.withPathElement(ConstraintLocator::ApplyFunction);
7063+
auto selected = solution.getOverloadChoice(cs.getConstraintLocator(loc));
7064+
auto *method = dyn_cast<FuncDecl>(selected.choice.getDecl());
7065+
auto methodType = selected.openedType->castTo<AnyFunctionType>();
7066+
assert(method->getName() == ctx.Id_dynamicallyCall &&
7067+
"Expected 'dynamicallyCall' method");
7068+
assert(methodType->getParams().size() == 1 &&
7069+
"Expected 'dynamicallyCall' method with one parameter");
7070+
auto argumentLabel = methodType->getParams()[0].getLabel();
7071+
assert((argumentLabel == ctx.Id_withArguments ||
7072+
argumentLabel == ctx.Id_withKeywordArguments) &&
7073+
"Expected 'dynamicallyCall' method argument label 'withArguments' or "
7074+
"'withKeywordArguments'");
7075+
7076+
// Determine which method was resolved: a `withArguments` method or a
7077+
// `withKeywordArguments` method.
7078+
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;
7079+
7080+
// Construct expression referencing the `dynamicallyCall` method.
7081+
Expr *member =
7082+
new (ctx) MemberRefExpr(fn, fn->getEndLoc(), ConcreteDeclRef(method),
7083+
DeclNameLoc(method->getNameLoc()),
7084+
/*Implicit*/ true);
7085+
7086+
// Construct argument to the method (either an array or dictionary
7087+
// expression).
7088+
Expr *argument = nullptr;
7089+
if (!useKwargsMethod) {
7090+
argument = ArrayExpr::create(ctx, SourceLoc(), arg->getElements(),
7091+
{}, SourceLoc());
7092+
} else {
7093+
SmallVector<Identifier, 4> names;
7094+
SmallVector<Expr *, 4> dictElements;
7095+
for (unsigned i = 0, n = arg->getNumElements(); i < n; i++) {
7096+
Expr *labelExpr =
7097+
new (ctx) StringLiteralExpr(arg->getElementName(i).get(),
7098+
arg->getElementNameLoc(i),
7099+
/*Implicit*/ true);
7100+
Expr *pair =
7101+
TupleExpr::createImplicit(ctx, { labelExpr, arg->getElement(i) }, {});
7102+
dictElements.push_back(pair);
7103+
}
7104+
argument = DictionaryExpr::create(ctx, SourceLoc(), dictElements, {},
7105+
SourceLoc());
7106+
}
7107+
argument->setImplicit();
7108+
7109+
// Construct call to the `dynamicallyCall` method.
7110+
Expr *result = CallExpr::createImplicit(ctx, member, argument,
7111+
{ argumentLabel });
7112+
cs.TC.typeCheckExpression(result, cs.DC);
7113+
cs.cacheExprTypes(result);
7114+
return result;
7115+
}
7116+
70497117
Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
70507118
ConstraintLocatorBuilder locator) {
70517119
TypeChecker &tc = cs.getTypeChecker();
@@ -7340,67 +7408,73 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
73407408
}
73417409

73427410
// We have a type constructor.
7343-
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
7344-
auto ty = metaTy->getInstanceType();
7345-
7346-
if (!cs.isTypeReference(fn)) {
7347-
bool isExistentialType = false;
7348-
// If this is an attempt to initialize existential type.
7349-
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7350-
auto instanceType = metaType->getInstanceType();
7351-
isExistentialType = instanceType->isExistentialType();
7352-
}
7353-
7354-
if (!isExistentialType) {
7355-
// If the metatype value isn't a type expression,
7356-
// the user should reference '.init' explicitly, for clarity.
7357-
cs.TC
7358-
.diagnose(apply->getArg()->getStartLoc(),
7359-
diag::missing_init_on_metatype_initialization)
7360-
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7361-
}
7362-
}
7363-
7364-
// If we're "constructing" a tuple type, it's simply a conversion.
7365-
if (auto tupleTy = ty->getAs<TupleType>()) {
7366-
// FIXME: Need an AST to represent this properly.
7367-
return coerceToType(apply->getArg(), tupleTy, locator);
7368-
}
7411+
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
7412+
auto ty = metaTy->getInstanceType();
7413+
7414+
if (!cs.isTypeReference(fn)) {
7415+
bool isExistentialType = false;
7416+
// If this is an attempt to initialize existential type.
7417+
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7418+
auto instanceType = metaType->getInstanceType();
7419+
isExistentialType = instanceType->isExistentialType();
7420+
}
7421+
7422+
if (!isExistentialType) {
7423+
// If the metatype value isn't a type expression,
7424+
// the user should reference '.init' explicitly, for clarity.
7425+
cs.TC
7426+
.diagnose(apply->getArg()->getStartLoc(),
7427+
diag::missing_init_on_metatype_initialization)
7428+
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7429+
}
7430+
}
7431+
7432+
// If we're "constructing" a tuple type, it's simply a conversion.
7433+
if (auto tupleTy = ty->getAs<TupleType>()) {
7434+
// FIXME: Need an AST to represent this properly.
7435+
return coerceToType(apply->getArg(), tupleTy, locator);
7436+
}
7437+
7438+
// We're constructing a value of nominal type. Look for the constructor or
7439+
// enum element to use.
7440+
auto ctorLocator = cs.getConstraintLocator(
7441+
locator.withPathElement(ConstraintLocator::ApplyFunction)
7442+
.withPathElement(ConstraintLocator::ConstructorMember));
7443+
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7444+
if (!selected) {
7445+
assert(ty->hasError() || ty->hasUnresolvedType());
7446+
cs.setType(apply, ty);
7447+
return apply;
7448+
}
7449+
7450+
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7451+
ty->isExistentialType() || ty->is<ArchetypeType>());
7452+
7453+
// We have the constructor.
7454+
auto choice = selected->choice;
7455+
7456+
// Consider the constructor decl reference expr 'implicit', but the
7457+
// constructor call expr itself has the apply's 'implicitness'.
7458+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7459+
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7460+
/*dotLoc=*/SourceLoc(), choice,
7461+
DeclNameLoc(fn->getEndLoc()),
7462+
selected->openedType, locator, ctorLocator,
7463+
/*Implicit=*/true,
7464+
choice.getFunctionRefKind(),
7465+
AccessSemantics::Ordinary, isDynamic);
7466+
if (!declRef)
7467+
return nullptr;
7468+
declRef->setImplicit(apply->isImplicit());
7469+
apply->setFn(declRef);
73697470

7370-
// We're constructing a value of nominal type. Look for the constructor or
7371-
// enum element to use.
7372-
auto ctorLocator = cs.getConstraintLocator(
7373-
locator.withPathElement(ConstraintLocator::ApplyFunction)
7374-
.withPathElement(ConstraintLocator::ConstructorMember));
7375-
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7376-
if (!selected) {
7377-
assert(ty->hasError() || ty->hasUnresolvedType());
7378-
cs.setType(apply, ty);
7379-
return apply;
7471+
// Tail-recur to actually call the constructor.
7472+
return finishApply(apply, openedType, locator);
73807473
}
73817474

7382-
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7383-
ty->isExistentialType() || ty->is<ArchetypeType>());
7384-
7385-
// We have the constructor.
7386-
auto choice = selected->choice;
7387-
7388-
// Consider the constructor decl reference expr 'implicit', but the
7389-
// constructor call expr itself has the apply's 'implicitness'.
7390-
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7391-
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7392-
/*dotLoc=*/SourceLoc(), choice,
7393-
DeclNameLoc(fn->getEndLoc()),
7394-
selected->openedType, locator, ctorLocator,
7395-
/*Implicit=*/true, choice.getFunctionRefKind(),
7396-
AccessSemantics::Ordinary, isDynamic);
7397-
if (!declRef)
7398-
return nullptr;
7399-
declRef->setImplicit(apply->isImplicit());
7400-
apply->setFn(declRef);
7401-
7402-
// Tail-recur to actually call the constructor.
7403-
return finishApply(apply, openedType, locator);
7475+
// Handle @dynamicCallable applications.
7476+
// At this point, all other ApplyExpr cases have been handled.
7477+
return finishApplyDynamicCallable(cs, solution, apply, locator);
74047478
}
74057479

74067480

lib/Sema/CSBindings.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ static bool shouldBindToValueType(Constraint *constraint) {
153153
case ConstraintKind::CheckedCast:
154154
case ConstraintKind::SelfObjectOfProtocol:
155155
case ConstraintKind::ApplicableFunction:
156+
case ConstraintKind::DynamicCallableApplicableFunction:
156157
case ConstraintKind::BindOverload:
157158
case ConstraintKind::OptionalObject:
158159
return false;
@@ -575,6 +576,7 @@ ConstraintSystem::getPotentialBindings(TypeVariableType *typeVar) {
575576
}
576577

577578
case ConstraintKind::ApplicableFunction:
579+
case ConstraintKind::DynamicCallableApplicableFunction:
578580
case ConstraintKind::BindOverload: {
579581
if (result.FullyBound && result.InvolvesTypeVariables)
580582
continue;

lib/Sema/CSDiag.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5314,9 +5314,28 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
53145314
// non-function/non-metatype type, then we cannot call it!
53155315
if (!isUnresolvedOrTypeVarType(fnType) &&
53165316
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
5317-
5317+
53185318
auto arg = callExpr->getArg();
53195319

5320+
// Diagnose @dynamicCallable errors.
5321+
if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
5322+
auto dynamicCallableMethods =
5323+
CS.DynamicCallableCache[fnType->getCanonicalType()];
5324+
5325+
// Diagnose dynamic calls with keywords on @dynamicCallable types that
5326+
// don't define the `withKeywordArguments` method.
5327+
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
5328+
bool hasArgLabel = llvm::any_of(
5329+
tuple->getElementNames(), [](Identifier i) { return !i.empty(); });
5330+
if (hasArgLabel &&
5331+
dynamicCallableMethods.keywordArgumentsMethods.empty()) {
5332+
diagnose(callExpr->getFn()->getStartLoc(),
5333+
diag::missing_dynamic_callable_kwargs_method, fnType);
5334+
return true;
5335+
}
5336+
}
5337+
}
5338+
53205339
if (fnType->is<ExistentialMetatypeType>()) {
53215340
auto diag = diagnose(arg->getStartLoc(),
53225341
diag::missing_init_on_metatype_initialization);

0 commit comments

Comments
 (0)