Skip to content

Commit adb7648

Browse files
committed
Implement dynamically callable types (@dynamicCallable).
This proposal introduces the `@dynamicCallable` attribute, which enables nominal types to be "callable" via a simple syntactic sugar. This is the implementation of the Swift evolution proposal here: swiftlang/swift-evolution#858. Read there for more details.
1 parent feb3857 commit adb7648

File tree

12 files changed

+907
-62
lines changed

12 files changed

+907
-62
lines changed

include/swift/AST/Attr.def

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

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,18 @@ NOTE(archetype_declared_in_type,none,
991991
NOTE(unbound_generic_parameter_explicit_fix,none,
992992
"explicitly specify the generic arguments to fix this issue", ())
993993

994+
ERROR(invalid_dynamic_callable_type,none,
995+
"@dynamicCallable attribute requires %0 to have either a valid, "
996+
"non-generic 'dynamicallyCall(withArguments:)' method or "
997+
"'dynamicallyCall(withKeywordArguments:)' method", (Type))
998+
ERROR(missing_dynamic_callable_kwargs_method,none,
999+
"@dynamicCallable type %0 cannot be applied with keyword arguments; "
1000+
"missing `dynamicCall(withKeywordArguments:)` method",
1001+
(Type))
1002+
ERROR(ambiguous_dynamic_callable_method,none,
1003+
"@dynamicCallable attribute does not support ambiguous method %0",
1004+
(DeclName))
1005+
9941006
ERROR(type_invalid_dml,none,
9951007
"@dynamicMemberLookup attribute requires %0 to have a "
9961008
"'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: 134 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7601,67 +7601,147 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
76017601
}
76027602

76037603
// We have a type constructor.
7604-
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
7605-
auto ty = metaTy->getInstanceType();
7606-
7607-
if (!cs.isTypeReference(fn)) {
7608-
bool isExistentialType = false;
7609-
// If this is an attempt to initialize existential type.
7610-
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7611-
auto instanceType = metaType->getInstanceType();
7612-
isExistentialType = instanceType->isExistentialType();
7613-
}
7604+
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
7605+
auto ty = metaTy->getInstanceType();
7606+
7607+
if (!cs.isTypeReference(fn)) {
7608+
bool isExistentialType = false;
7609+
// If this is an attempt to initialize existential type.
7610+
if (auto metaType = cs.getType(fn)->getAs<MetatypeType>()) {
7611+
auto instanceType = metaType->getInstanceType();
7612+
isExistentialType = instanceType->isExistentialType();
7613+
}
7614+
7615+
if (!isExistentialType) {
7616+
// If the metatype value isn't a type expression,
7617+
// the user should reference '.init' explicitly, for clarity.
7618+
cs.TC
7619+
.diagnose(apply->getArg()->getStartLoc(),
7620+
diag::missing_init_on_metatype_initialization)
7621+
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7622+
}
7623+
}
7624+
7625+
// If we're "constructing" a tuple type, it's simply a conversion.
7626+
if (auto tupleTy = ty->getAs<TupleType>()) {
7627+
// FIXME: Need an AST to represent this properly.
7628+
return coerceToType(apply->getArg(), tupleTy, locator);
7629+
}
7630+
7631+
// We're constructing a value of nominal type. Look for the constructor or
7632+
// enum element to use.
7633+
auto ctorLocator = cs.getConstraintLocator(
7634+
locator.withPathElement(ConstraintLocator::ApplyFunction)
7635+
.withPathElement(ConstraintLocator::ConstructorMember));
7636+
auto selected = getOverloadChoiceIfAvailable(ctorLocator);
7637+
if (!selected) {
7638+
assert(ty->hasError() || ty->hasUnresolvedType());
7639+
cs.setType(apply, ty);
7640+
return apply;
7641+
}
7642+
7643+
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7644+
ty->isExistentialType() || ty->is<ArchetypeType>());
7645+
7646+
// We have the constructor.
7647+
auto choice = selected->choice;
7648+
7649+
// Consider the constructor decl reference expr 'implicit', but the
7650+
// constructor call expr itself has the apply's 'implicitness'.
7651+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7652+
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7653+
/*dotLoc=*/SourceLoc(), choice,
7654+
DeclNameLoc(fn->getEndLoc()),
7655+
selected->openedType, locator, ctorLocator,
7656+
/*Implicit=*/true, choice.getFunctionRefKind(),
7657+
AccessSemantics::Ordinary, isDynamic);
7658+
if (!declRef)
7659+
return nullptr;
7660+
declRef->setImplicit(apply->isImplicit());
7661+
apply->setFn(declRef);
76147662

7615-
if (!isExistentialType) {
7616-
// If the metatype value isn't a type expression,
7617-
// the user should reference '.init' explicitly, for clarity.
7618-
cs.TC
7619-
.diagnose(apply->getArg()->getStartLoc(),
7620-
diag::missing_init_on_metatype_initialization)
7621-
.fixItInsert(apply->getArg()->getStartLoc(), ".init");
7622-
}
7663+
// Tail-recur to actually call the constructor.
7664+
return finishApply(apply, openedType, locator);
76237665
}
76247666

7625-
// If we're "constructing" a tuple type, it's simply a conversion.
7626-
if (auto tupleTy = ty->getAs<TupleType>()) {
7627-
// FIXME: Need an AST to represent this properly.
7628-
return coerceToType(apply->getArg(), tupleTy, locator);
7629-
}
7667+
// Handle @dynamicCallable applications.
7668+
// At this point, all other AppyExpr cases have been handled.
7669+
auto isDynamicCallable = [&](Expr *base) {
7670+
auto type = cs.getType(base);
7671+
return type && cs.DynamicCallableCache.count(type->getCanonicalType());
7672+
};
7673+
assert(isDynamicCallable(fn) && "Expected a valid @dynamicCallable type");
76307674

7631-
// We're constructing a value of nominal type. Look for the constructor or
7632-
// enum element to use.
7633-
auto ctorLocator = cs.getConstraintLocator(
7634-
locator.withPathElement(ConstraintLocator::ApplyFunction)
7635-
.withPathElement(ConstraintLocator::ConstructorMember));
7636-
auto selected = getOverloadChoiceIfAvailable(ctorLocator);
7637-
if (!selected) {
7638-
assert(ty->hasError() || ty->hasUnresolvedType());
7639-
cs.setType(apply, ty);
7640-
return apply;
7641-
}
7675+
auto &ctx = tc.Context;
7676+
auto methods = cs.DynamicCallableCache[cs.getType(fn)->getCanonicalType()];
7677+
assert(methods.isValid() && "Expected a valid @dynamicCallable type");
76427678

7643-
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7644-
ty->isExistentialType() || ty->is<ArchetypeType>());
7679+
TupleExpr *arg = dyn_cast<TupleExpr>(apply->getArg());
7680+
if (auto parenExpr = dyn_cast<ParenExpr>(apply->getArg())) {
7681+
arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {});
7682+
}
76457683

7646-
// We have the constructor.
7647-
auto choice = selected->choice;
7648-
7649-
// Consider the constructor decl reference expr 'implicit', but the
7650-
// constructor call expr itself has the apply's 'implicitness'.
7651-
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7652-
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7653-
/*dotLoc=*/SourceLoc(), choice,
7654-
DeclNameLoc(fn->getEndLoc()),
7655-
selected->openedType, locator, ctorLocator,
7656-
/*Implicit=*/true, choice.getFunctionRefKind(),
7657-
AccessSemantics::Ordinary, isDynamic);
7658-
if (!declRef)
7659-
return nullptr;
7660-
declRef->setImplicit(apply->isImplicit());
7661-
apply->setFn(declRef);
7684+
// Determine whether to call the positional arguments method or the
7685+
// keyword arguments method.
7686+
bool useKwargsMethod = methods.argumentsMethod == nullptr;
7687+
if (!useKwargsMethod) {
7688+
for (auto name : arg->getElementNames()) {
7689+
if (!name.empty()) {
7690+
useKwargsMethod = true;
7691+
break;
7692+
}
7693+
}
7694+
}
76627695

7663-
// Tail-recur to actually call the constructor.
7664-
return finishApply(apply, openedType, locator);
7696+
auto method = useKwargsMethod
7697+
? methods.keywordArgumentsMethod
7698+
: methods.argumentsMethod;
7699+
assert(method && "Dynamic call method should exist");
7700+
7701+
auto memberType =
7702+
cs.getTypeOfMemberReference(cs.getType(fn), method, cs.DC,
7703+
/*isDynamicResult*/ false,
7704+
FunctionRefKind::DoubleApply,
7705+
locator).second;
7706+
auto methodType = memberType->castTo<AnyFunctionType>();
7707+
7708+
// Construct expression referencing the `dynamicallyCall` method.
7709+
Expr *member =
7710+
new (ctx) MemberRefExpr(fn, fn->getEndLoc(), ConcreteDeclRef(method),
7711+
DeclNameLoc(method->getNameLoc()),
7712+
/*Implicit*/ true);
7713+
7714+
// Construct argument to the method (either an array or dictionary
7715+
// expression).
7716+
Expr *argument = nullptr;
7717+
if (!useKwargsMethod) {
7718+
argument = ArrayExpr::create(ctx, SourceLoc(), arg->getElements(),
7719+
{}, SourceLoc());
7720+
} else {
7721+
SmallVector<Identifier, 4> names;
7722+
SmallVector<Expr *, 4> dictElements;
7723+
for (unsigned i = 0, n = arg->getNumElements(); i < n; i++) {
7724+
Expr *labelExpr =
7725+
new (ctx) StringLiteralExpr(arg->getElementName(i).get(),
7726+
arg->getElementNameLoc(i),
7727+
/*Implicit*/ true);
7728+
Expr *pair =
7729+
TupleExpr::createImplicit(ctx, { labelExpr, arg->getElement(i) },
7730+
{});
7731+
dictElements.push_back(pair);
7732+
}
7733+
argument = DictionaryExpr::create(ctx, SourceLoc(), dictElements, {},
7734+
SourceLoc());
7735+
}
7736+
argument->setImplicit();
7737+
7738+
// Construct call to the `dynamicallyCall` method.
7739+
auto argumentName = methodType->getParams()[0].getLabel();
7740+
Expr *result = CallExpr::createImplicit(ctx, member, argument,
7741+
{ argumentName });
7742+
tc.typeCheckExpression(result, dc);
7743+
cs.cacheExprTypes(result);
7744+
return result;
76657745
}
76667746

76677747

lib/Sema/CSDiag.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5719,7 +5719,8 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
57195719
// If we resolved a concrete expression for the callee, and it has
57205720
// non-function/non-metatype type, then we cannot call it!
57215721
if (!isUnresolvedOrTypeVarType(fnType) &&
5722-
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
5722+
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()
5723+
&& !CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
57235724

57245725
auto arg = callExpr->getArg();
57255726

0 commit comments

Comments
 (0)