Skip to content

Commit

Permalink
Implement dynamically callable types (@dynamicCallable).
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
dan-zheng committed Nov 3, 2018
1 parent b020ee7 commit e651ed8
Show file tree
Hide file tree
Showing 17 changed files with 1,062 additions and 68 deletions.
4 changes: 3 additions & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,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))
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ IDENTIFIER(decode)
IDENTIFIER(decodeIfPresent)
IDENTIFIER(Decoder)
IDENTIFIER(decoder)
IDENTIFIER(dynamicallyCall)
IDENTIFIER(dynamicMember)
IDENTIFIER(Element)
IDENTIFIER(Encodable)
Expand Down Expand Up @@ -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")
Expand Down
189 changes: 131 additions & 58 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7072,6 +7072,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<TupleExpr>(apply->getArg());
if (auto parenExpr = dyn_cast<ParenExpr>(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<FuncDecl>(selected.choice.getDecl());
auto methodType = selected.openedType->castTo<AnyFunctionType>();
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<Identifier, 4> names;
SmallVector<Expr *, 4> 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();
Expand Down Expand Up @@ -7366,67 +7434,72 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
}

// We have a type constructor.
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
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<MetatypeType>()) {
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<TupleType>()) {
// FIXME: Need an AST to represent this properly.
return coerceToType(apply->getArg(), tupleTy, locator);
}
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
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<MetatypeType>()) {
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<TupleType>()) {
// 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<DynamicSelfType>() ||
ty->isExistentialType() || ty->is<ArchetypeType>());

// 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<DynamicSelfType>() ||
ty->isExistentialType() || ty->is<ArchetypeType>());

// 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);
}


Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -575,6 +576,7 @@ ConstraintSystem::getPotentialBindings(TypeVariableType *typeVar) {
}

case ConstraintKind::ApplicableFunction:
case ConstraintKind::DynamicCallableApplicableFunction:
case ConstraintKind::BindOverload: {
if (result.FullyBound && result.InvolvesTypeVariables)
continue;
Expand Down
21 changes: 20 additions & 1 deletion lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5357,9 +5357,28 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
// non-function/non-metatype type, then we cannot call it!
if (!isUnresolvedOrTypeVarType(fnType) &&
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {

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<TupleExpr>(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<ExistentialMetatypeType>()) {
auto diag = diagnose(arg->getStartLoc(),
diag::missing_init_on_metatype_initialization);
Expand Down
Loading

0 comments on commit e651ed8

Please sign in to comment.