Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement @dynamicCallable. #20305

Merged
merged 4 commits into from
Nov 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
190 changes: 132 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,73 @@ 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