Skip to content

Commit e651ed8

Browse files
committed
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 b020ee7 commit e651ed8

17 files changed

+1062
-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
@@ -1038,6 +1038,14 @@ NOTE(archetype_declared_in_type,none,
10381038
NOTE(unbound_generic_parameter_explicit_fix,none,
10391039
"explicitly specify the generic arguments to fix this issue", ())
10401040

1041+
ERROR(invalid_dynamic_callable_type,none,
1042+
"@dynamicCallable attribute requires %0 to have either a valid "
1043+
"'dynamicallyCall(withArguments:)' method or "
1044+
"'dynamicallyCall(withKeywordArguments:)' method", (Type))
1045+
ERROR(missing_dynamic_callable_kwargs_method,none,
1046+
"@dynamicCallable type %0 cannot be applied with keyword arguments; "
1047+
"missing 'dynamicCall(withKeywordArguments:)' method", (Type))
1048+
10411049
ERROR(type_invalid_dml,none,
10421050
"@dynamicMemberLookup attribute requires %0 to have a "
10431051
"'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: 131 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7072,6 +7072,74 @@ Expr *ExprRewriter::convertLiteralInPlace(Expr *literal,
70727072
return literal;
70737073
}
70747074

7075+
// Resolve @dynamicCallable applications.
7076+
static Expr *finishApplyDynamicCallable(ConstraintSystem &cs,
7077+
const Solution &solution,
7078+
ApplyExpr *apply,
7079+
ConstraintLocatorBuilder locator) {
7080+
auto &ctx = cs.getASTContext();
7081+
auto *fn = apply->getFn();
7082+
7083+
TupleExpr *arg = dyn_cast<TupleExpr>(apply->getArg());
7084+
if (auto parenExpr = dyn_cast<ParenExpr>(apply->getArg()))
7085+
arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {});
7086+
7087+
// Get resolved `dynamicallyCall` method and verify it.
7088+
auto loc = locator.withPathElement(ConstraintLocator::ApplyFunction);
7089+
auto selected = solution.getOverloadChoice(cs.getConstraintLocator(loc));
7090+
auto *method = dyn_cast<FuncDecl>(selected.choice.getDecl());
7091+
auto methodType = selected.openedType->castTo<AnyFunctionType>();
7092+
assert(method->getName() == ctx.Id_dynamicallyCall &&
7093+
"Expected 'dynamicallyCall' method");
7094+
assert(methodType->getParams().size() == 1 &&
7095+
"Expected 'dynamicallyCall' method with one parameter");
7096+
auto argumentLabel = methodType->getParams()[0].getLabel();
7097+
assert((argumentLabel == ctx.Id_withArguments ||
7098+
argumentLabel == ctx.Id_withKeywordArguments) &&
7099+
"Expected 'dynamicallyCall' method argument label 'withArguments' or "
7100+
"'withKeywordArguments'");
7101+
7102+
// Determine which method was resolved: a `withArguments` method or a
7103+
// `withKeywordArguments` method.
7104+
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;
7105+
7106+
// Construct expression referencing the `dynamicallyCall` method.
7107+
Expr *member =
7108+
new (ctx) MemberRefExpr(fn, fn->getEndLoc(), ConcreteDeclRef(method),
7109+
DeclNameLoc(method->getNameLoc()),
7110+
/*Implicit*/ true);
7111+
7112+
// Construct argument to the method (either an array or dictionary
7113+
// expression).
7114+
Expr *argument = nullptr;
7115+
if (!useKwargsMethod) {
7116+
argument = ArrayExpr::create(ctx, SourceLoc(), arg->getElements(),
7117+
{}, SourceLoc());
7118+
} else {
7119+
SmallVector<Identifier, 4> names;
7120+
SmallVector<Expr *, 4> dictElements;
7121+
for (unsigned i = 0, n = arg->getNumElements(); i < n; i++) {
7122+
Expr *labelExpr =
7123+
new (ctx) StringLiteralExpr(arg->getElementName(i).get(),
7124+
arg->getElementNameLoc(i),
7125+
/*Implicit*/ true);
7126+
Expr *pair =
7127+
TupleExpr::createImplicit(ctx, { labelExpr, arg->getElement(i) }, {});
7128+
dictElements.push_back(pair);
7129+
}
7130+
argument = DictionaryExpr::create(ctx, SourceLoc(), dictElements, {},
7131+
SourceLoc());
7132+
}
7133+
argument->setImplicit();
7134+
7135+
// Construct call to the `dynamicallyCall` method.
7136+
Expr *result = CallExpr::createImplicit(ctx, member, argument,
7137+
{ argumentLabel });
7138+
cs.TC.typeCheckExpression(result, cs.DC);
7139+
cs.cacheExprTypes(result);
7140+
return result;
7141+
}
7142+
70757143
Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
70767144
ConstraintLocatorBuilder locator) {
70777145
TypeChecker &tc = cs.getTypeChecker();
@@ -7366,67 +7434,72 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
73667434
}
73677435

73687436
// We have a type constructor.
7369-
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
7370-
auto ty = metaTy->getInstanceType();
7371-
7372-
if (!cs.isTypeReference(fn)) {
7373-
bool isExistentialType = false;
7374-
// If this is an attempt to initialize existential type.
7375-
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7376-
auto instanceType = metaType->getInstanceType();
7377-
isExistentialType = instanceType->isExistentialType();
7378-
}
7379-
7380-
if (!isExistentialType) {
7381-
// If the metatype value isn't a type expression,
7382-
// the user should reference '.init' explicitly, for clarity.
7383-
cs.TC
7384-
.diagnose(apply->getArg()->getStartLoc(),
7385-
diag::missing_init_on_metatype_initialization)
7386-
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7387-
}
7388-
}
7389-
7390-
// If we're "constructing" a tuple type, it's simply a conversion.
7391-
if (auto tupleTy = ty->getAs<TupleType>()) {
7392-
// FIXME: Need an AST to represent this properly.
7393-
return coerceToType(apply->getArg(), tupleTy, locator);
7394-
}
7437+
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
7438+
auto ty = metaTy->getInstanceType();
7439+
7440+
if (!cs.isTypeReference(fn)) {
7441+
bool isExistentialType = false;
7442+
// If this is an attempt to initialize existential type.
7443+
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7444+
auto instanceType = metaType->getInstanceType();
7445+
isExistentialType = instanceType->isExistentialType();
7446+
}
7447+
7448+
if (!isExistentialType) {
7449+
// If the metatype value isn't a type expression,
7450+
// the user should reference '.init' explicitly, for clarity.
7451+
cs.TC
7452+
.diagnose(apply->getArg()->getStartLoc(),
7453+
diag::missing_init_on_metatype_initialization)
7454+
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7455+
}
7456+
}
7457+
7458+
// If we're "constructing" a tuple type, it's simply a conversion.
7459+
if (auto tupleTy = ty->getAs<TupleType>()) {
7460+
// FIXME: Need an AST to represent this properly.
7461+
return coerceToType(apply->getArg(), tupleTy, locator);
7462+
}
7463+
7464+
// We're constructing a value of nominal type. Look for the constructor or
7465+
// enum element to use.
7466+
auto ctorLocator = cs.getConstraintLocator(
7467+
locator.withPathElement(ConstraintLocator::ApplyFunction)
7468+
.withPathElement(ConstraintLocator::ConstructorMember));
7469+
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7470+
if (!selected) {
7471+
assert(ty->hasError() || ty->hasUnresolvedType());
7472+
cs.setType(apply, ty);
7473+
return apply;
7474+
}
7475+
7476+
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7477+
ty->isExistentialType() || ty->is<ArchetypeType>());
7478+
7479+
// We have the constructor.
7480+
auto choice = selected->choice;
7481+
7482+
// Consider the constructor decl reference expr 'implicit', but the
7483+
// constructor call expr itself has the apply's 'implicitness'.
7484+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7485+
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7486+
/*dotLoc=*/SourceLoc(), choice,
7487+
DeclNameLoc(fn->getEndLoc()),
7488+
selected->openedType, locator, ctorLocator,
7489+
/*Implicit=*/true, choice.getFunctionRefKind(),
7490+
AccessSemantics::Ordinary, isDynamic);
7491+
if (!declRef)
7492+
return nullptr;
7493+
declRef->setImplicit(apply->isImplicit());
7494+
apply->setFn(declRef);
73957495

7396-
// We're constructing a value of nominal type. Look for the constructor or
7397-
// enum element to use.
7398-
auto ctorLocator = cs.getConstraintLocator(
7399-
locator.withPathElement(ConstraintLocator::ApplyFunction)
7400-
.withPathElement(ConstraintLocator::ConstructorMember));
7401-
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7402-
if (!selected) {
7403-
assert(ty->hasError() || ty->hasUnresolvedType());
7404-
cs.setType(apply, ty);
7405-
return apply;
7496+
// Tail-recur to actually call the constructor.
7497+
return finishApply(apply, openedType, locator);
74067498
}
74077499

7408-
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7409-
ty->isExistentialType() || ty->is<ArchetypeType>());
7410-
7411-
// We have the constructor.
7412-
auto choice = selected->choice;
7413-
7414-
// Consider the constructor decl reference expr 'implicit', but the
7415-
// constructor call expr itself has the apply's 'implicitness'.
7416-
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7417-
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7418-
/*dotLoc=*/SourceLoc(), choice,
7419-
DeclNameLoc(fn->getEndLoc()),
7420-
selected->openedType, locator, ctorLocator,
7421-
/*Implicit=*/true, choice.getFunctionRefKind(),
7422-
AccessSemantics::Ordinary, isDynamic);
7423-
if (!declRef)
7424-
return nullptr;
7425-
declRef->setImplicit(apply->isImplicit());
7426-
apply->setFn(declRef);
7427-
7428-
// Tail-recur to actually call the constructor.
7429-
return finishApply(apply, openedType, locator);
7500+
// Handle @dynamicCallable applications.
7501+
// At this point, all other ApplyExpr cases have been handled.
7502+
return finishApplyDynamicCallable(cs, solution, apply, locator);
74307503
}
74317504

74327505

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
@@ -5357,9 +5357,28 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
53575357
// non-function/non-metatype type, then we cannot call it!
53585358
if (!isUnresolvedOrTypeVarType(fnType) &&
53595359
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
5360-
5360+
53615361
auto arg = callExpr->getArg();
53625362

5363+
// Diagnose @dynamicCallable errors.
5364+
if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
5365+
auto dynamicCallableMethods =
5366+
CS.DynamicCallableCache[fnType->getCanonicalType()];
5367+
5368+
// Diagnose dynamic calls with keywords on @dynamicCallable types that
5369+
// don't define the `withKeywordArguments` method.
5370+
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
5371+
bool hasArgLabel = llvm::any_of(
5372+
tuple->getElementNames(), [](Identifier i) { return !i.empty(); });
5373+
if (hasArgLabel &&
5374+
dynamicCallableMethods.keywordArgumentsMethods.empty()) {
5375+
diagnose(callExpr->getFn()->getStartLoc(),
5376+
diag::missing_dynamic_callable_kwargs_method, fnType);
5377+
return true;
5378+
}
5379+
}
5380+
}
5381+
53635382
if (fnType->is<ExistentialMetatypeType>()) {
53645383
auto diag = diagnose(arg->getStartLoc(),
53655384
diag::missing_init_on_metatype_initialization);

0 commit comments

Comments
 (0)