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

[SE-0253] Introduce callables. #24299

Merged
merged 12 commits into from
Aug 27, 2019
1 change: 1 addition & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6016,6 +6016,7 @@ class FuncDecl : public AbstractFunctionDecl {
bool isConsuming() const {
return getSelfAccessKind() == SelfAccessKind::__Consuming;
}
bool isCallAsFunctionMethod() const;
dan-zheng marked this conversation as resolved.
Show resolved Hide resolved

SelfAccessKind getSelfAccessKind() const;

Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ IDENTIFIER(buildBlock)
IDENTIFIER(buildDo)
IDENTIFIER(buildEither)
IDENTIFIER(buildIf)
IDENTIFIER(callAsFunction)
IDENTIFIER(Change)
IDENTIFIER_WITH_NAME(code_, "_code")
IDENTIFIER(CodingKeys)
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6929,6 +6929,10 @@ SelfAccessKind FuncDecl::getSelfAccessKind() const {
SelfAccessKind::NonMutating);
}

bool FuncDecl::isCallAsFunctionMethod() const {
return getName() == getASTContext().Id_callAsFunction && isInstanceMember();
}

ConstructorDecl::ConstructorDecl(DeclName Name, SourceLoc ConstructorLoc,
OptionalTypeKind Failability,
SourceLoc FailabilityLoc,
Expand Down
191 changes: 123 additions & 68 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1069,9 +1069,12 @@ namespace {
Expr *finishApply(ApplyExpr *apply, Type openedType,
ConstraintLocatorBuilder locator);

// Resolve @dynamicCallable applications.
Expr *finishApplyDynamicCallable(const Solution &solution, ApplyExpr *apply,
ConstraintLocatorBuilder locator);
// Resolve `@dynamicCallable` applications.
Expr *finishApplyDynamicCallable(ApplyExpr *apply,
SelectedOverload selected,
FuncDecl *method,
AnyFunctionType *methodType,
ConstraintLocatorBuilder applyFunctionLoc);

private:
/// Simplify the given type by substituting all occurrences of
Expand Down Expand Up @@ -6748,11 +6751,62 @@ Expr *ExprRewriter::convertLiteralInPlace(Expr *literal,
return literal;
}

// Resolve @dynamicCallable applications.
// Returns true if the given method and method type are a valid
// `@dynamicCallable` required `func dynamicallyCall` method.
static bool isValidDynamicCallableMethod(FuncDecl *method,
AnyFunctionType *methodType) {
dan-zheng marked this conversation as resolved.
Show resolved Hide resolved
auto &ctx = method->getASTContext();
if (method->getName() != ctx.Id_dynamicallyCall)
return false;
if (methodType->getParams().size() != 1)
return false;
auto argumentLabel = methodType->getParams()[0].getLabel();
if (argumentLabel != ctx.Id_withArguments &&
argumentLabel != ctx.Id_withKeywordArguments)
return false;
return true;
}

// Resolve `callAsFunction` method applications.
static Expr *finishApplyCallAsFunctionMethod(
ExprRewriter &rewriter, ApplyExpr *apply, SelectedOverload selected,
AnyFunctionType *openedMethodType,
ConstraintLocatorBuilder applyFunctionLoc) {
auto &cs = rewriter.cs;
auto *fn = apply->getFn();
auto choice = selected.choice;
// Create direct reference to `callAsFunction` method.
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
auto *declRef = rewriter.buildMemberRef(
fn, selected.openedFullType, /*dotLoc*/ SourceLoc(), choice,
DeclNameLoc(fn->getEndLoc()), selected.openedType, applyFunctionLoc,
applyFunctionLoc, /*implicit*/ true, choice.getFunctionRefKind(),
AccessSemantics::Ordinary, isDynamic);
if (!declRef)
return nullptr;
declRef->setImplicit(apply->isImplicit());
apply->setFn(declRef);
// Coerce argument to input type of the `callAsFunction` method.
SmallVector<Identifier, 2> argLabelsScratch;
auto *arg = rewriter.coerceCallArguments(
apply->getArg(), openedMethodType, apply,
apply->getArgumentLabels(argLabelsScratch), apply->hasTrailingClosure(),
applyFunctionLoc);
if (!arg)
return nullptr;
apply->setArg(arg);
cs.setType(apply, openedMethodType->getResult());
cs.cacheExprTypes(apply);
return apply;
}

// Resolve `@dynamicCallable` applications.
Expr *
ExprRewriter::finishApplyDynamicCallable(const Solution &solution,
ApplyExpr *apply,
ConstraintLocatorBuilder locator) {
ExprRewriter::finishApplyDynamicCallable(ApplyExpr *apply,
SelectedOverload selected,
FuncDecl *method,
AnyFunctionType *methodType,
ConstraintLocatorBuilder loc) {
auto &ctx = cs.getASTContext();
auto *fn = apply->getFn();

Expand All @@ -6761,27 +6815,16 @@ ExprRewriter::finishApplyDynamicCallable(const Solution &solution,
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 = simplifyType(selected.openedType)->castTo<AnyFunctionType>();
assert(method->getName() == ctx.Id_dynamicallyCall &&
"Expected 'dynamicallyCall' method");
assert(isValidDynamicCallableMethod(method, methodType));
auto params = methodType->getParams();
assert(params.size() == 1 &&
"Expected 'dynamicallyCall' method with one parameter");
auto argumentType = params[0].getParameterType();
auto argumentLabel = params[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.
auto argumentLabel = methodType->getParams()[0].getLabel();
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;

// Construct expression referencing the `dynamicallyCall` method.
// Construct expression referencing the `dynamicallyCall` method.
bool isDynamic =
selected.choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
auto member = buildMemberRef(fn, selected.openedFullType,
Expand Down Expand Up @@ -6988,6 +7031,24 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
llvm_unreachable("Unhandled DeclTypeCheckingSemantics in switch.");
};

// Resolve `callAsFunction` and `@dynamicCallable` applications.
auto applyFunctionLoc =
locator.withPathElement(ConstraintLocator::ApplyFunction);
if (auto selected = solution.getOverloadChoiceIfAvailable(
cs.getConstraintLocator(applyFunctionLoc))) {
auto *method = dyn_cast<FuncDecl>(selected->choice.getDecl());
auto methodType =
simplifyType(selected->openedType)->getAs<AnyFunctionType>();
if (method && methodType) {
if (method->isCallAsFunctionMethod())
return finishApplyCallAsFunctionMethod(
*this, apply, *selected, methodType, applyFunctionLoc);
if (methodType && isValidDynamicCallableMethod(method, methodType))
return finishApplyDynamicCallable(
apply, *selected, method, methodType, applyFunctionLoc);
}
}

// The function is always an rvalue.
fn = cs.coerceToRValue(fn);

Expand Down Expand Up @@ -7088,57 +7149,51 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
}

// We have a type constructor.
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
auto ty = metaTy->getInstanceType();

// 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);
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
auto ty = metaTy->getInstanceType();

// Tail-recur to actually call the constructor.
return finishApply(apply, openedType, locator);
// 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);
}

// Handle @dynamicCallable applications.
// At this point, all other ApplyExpr cases have been handled.
return finishApplyDynamicCallable(solution, apply, 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;
dan-zheng marked this conversation as resolved.
Show resolved Hide resolved
}

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

// Return the precedence-yielding parent of 'expr', along with the index of
// 'expr' as the child of that parent. The precedence-yielding parent is the
Expand Down
15 changes: 13 additions & 2 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4462,9 +4462,19 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {

auto arg = callExpr->getArg();
auto isDynamicCallable =
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();

// Note: Consider caching `hasCallAsFunctionMethods` in `NominalTypeDecl`.
auto *nominal = fnType->getAnyNominal();
auto hasCallAsFunctionMethods = nominal &&
llvm::any_of(nominal->getMembers(), [](Decl *member) {
auto funcDecl = dyn_cast<FuncDecl>(member);
return funcDecl && funcDecl->isCallAsFunctionMethod();
});

// Diagnose @dynamicCallable errors.
if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
if (isDynamicCallable) {
auto dynamicCallableMethods =
CS.DynamicCallableCache[fnType->getCanonicalType()];

Expand Down Expand Up @@ -4520,7 +4530,8 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
}
}

return true;
if (!isDynamicCallable && !hasCallAsFunctionMethods)
return true;
}

bool hasTrailingClosure = callArgHasTrailingClosure(callExpr->getArg());
Expand Down
49 changes: 46 additions & 3 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5987,6 +5987,7 @@ ConstraintSystem::simplifyApplicableFnConstraint(
Type type2,
TypeMatchOptions flags,
ConstraintLocatorBuilder locator) {
auto &ctx = getASTContext();

// By construction, the left hand side is a type that looks like the
// following: $T1 -> $T2.
Expand All @@ -6011,6 +6012,16 @@ ConstraintSystem::simplifyApplicableFnConstraint(
}
}

// Before stripping lvalue-ness and optional types, save the original second
// type for handling `func callAsFunction` and `@dynamicCallable`
// applications. This supports the following cases:
// - Generating constraints for `mutating func callAsFunction`. The nominal
// type (`type2`) should be an lvalue type.
// - Extending `Optional` itself with `func callAsFunction` or
// `@dynamicCallable` functionality. Optional types are stripped below if
// `shouldAttemptFixes()` is true.
auto origLValueType2 =
getFixedTypeRecursive(type2, flags, /*wantRValue=*/false);
// Drill down to the concrete type on the right hand side.
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
auto desugar2 = type2->getDesugaredType();
Expand Down Expand Up @@ -6080,9 +6091,34 @@ ConstraintSystem::simplifyApplicableFnConstraint(
ConstraintLocatorBuilder outerLocator =
getConstraintLocator(anchor, parts, locator.getSummaryFlags());

// Before stripping optional types, save original type for handling
// @dynamicCallable applications. This supports the fringe case where
// `Optional` itself is extended with @dynamicCallable functionality.
// Handle applications of types with `callAsFunction` methods.
// Do this before stripping optional types below, when `shouldAttemptFixes()`
// is true.
auto hasCallAsFunctionMethods =
desugar2->mayHaveMembers() &&
llvm::any_of(lookupMember(desugar2, DeclName(ctx.Id_callAsFunction)),
[](LookupResultEntry entry) {
return isa<FuncDecl>(entry.getValueDecl());
});
if (hasCallAsFunctionMethods) {
auto memberLoc = getConstraintLocator(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that usages of memberLoc or locator are correct/ideal here.

@xedin: could you please check these ConstraintLocator * usages?
You suggested adding an "implicit call" path element, but I'm not sure where to use it - please advise!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for addValueMemberConstraint it makes more sense to use memberLoc rather than locator, and for ApplicableFunction indeed locator is best.

Regarding implicit call you don't have to worry about it right now, we can do it in the follow-up instead. I was thinking that it would be great to associate such new element with locator which belongs to ApplicableFunction something like <call anchor> -> implicit call -> apply function so when argument conversions are expanded by matchCallArguments they'd get <call anchor> -> implicit call -> apply arg-to-param -> comparing #N to #M locators.

outerLocator.withPathElement(ConstraintLocator::Member));
// Add a `callAsFunction` member constraint, binding the member type to a
// type variable.
auto memberTy = createTypeVariable(memberLoc, /*options=*/0);
// TODO: Revisit this if `static func callAsFunction` is to be supported.
// Static member constraint requires `FunctionRefKind::DoubleApply`.
addValueMemberConstraint(origLValueType2, DeclName(ctx.Id_callAsFunction),
memberTy, DC, FunctionRefKind::SingleApply,
/*outerAlternatives*/ {}, locator);
dan-zheng marked this conversation as resolved.
Show resolved Hide resolved
// Add new applicable function constraint based on the member type
// variable.
addConstraint(ConstraintKind::ApplicableFunction, func1, memberTy,
locator);
return SolutionKind::Solved;
}

// Record the second type before unwrapping optionals.
auto origType2 = desugar2;
unsigned unwrapCount = 0;
if (shouldAttemptFixes()) {
Expand Down Expand Up @@ -6305,6 +6341,13 @@ getDynamicCallableMethods(Type type, ConstraintSystem &CS,
return result;
}

// TODO: Refactor/simplify this function.
// - It should perform less duplicate work with its caller
// `ConstraintSystem::simplifyApplicableFnConstraint`.
// - It should generate a member constraint instead of manually forming an
// overload set for `func dynamicallyCall` candidates.
// - It should support `mutating func dynamicallyCall`. This should fall out of
// using member constraints with an lvalue base type.
ConstraintSystem::SolutionKind
ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
Type type1,
Expand Down
11 changes: 11 additions & 0 deletions test/Sema/Inputs/call_as_function_other_module.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public protocol Layer {
func callAsFunction(_ input: Float) -> Float
}

public struct Dense {
public init() {}

public func callAsFunction(_ input: Float) -> Float {
return input * 2
}
}
14 changes: 14 additions & 0 deletions test/Sema/call_as_function_cross_module.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -primary-file %S/Inputs/call_as_function_other_module.swift -emit-module-path %t/call_as_function_other_module.swiftmodule
// RUN: %target-swift-frontend -typecheck -I %t -primary-file %s -verify

import call_as_function_other_module

func testLayer<L: Layer>(_ layer: L) -> Float {
return layer(1)
}

func testDense() -> Float {
let dense = Dense()
return dense(1)
}
Loading