Skip to content

Commit f44064c

Browse files
authored
[SE-0253] Introduce callables. (#24299)
Introduce callables: values of types that declare `func callAsFunction` methods can be called like functions. The call syntax is shorthand for applying `func callAsFunction` methods. ```swift struct Adder { var base: Int func callAsFunction(_ x: Int) -> Int { return x + base } } var adder = Adder(base: 3) adder(10) // desugars to `adder.callAsFunction(10)` ``` `func callAsFunction` argument labels are required at call sites. Multiple `func callAsFunction` methods on a single type are supported. `mutating func callAsFunction` is supported. SR-11378 tracks improving `callAsFunction` diagnostics.
1 parent 1c2b8ba commit f44064c

12 files changed

+534
-73
lines changed

include/swift/AST/Decl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6018,6 +6018,7 @@ class FuncDecl : public AbstractFunctionDecl {
60186018
bool isConsuming() const {
60196019
return getSelfAccessKind() == SelfAccessKind::Consuming;
60206020
}
6021+
bool isCallAsFunctionMethod() const;
60216022

60226023
SelfAccessKind getSelfAccessKind() const;
60236024

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ IDENTIFIER(buildBlock)
3535
IDENTIFIER(buildDo)
3636
IDENTIFIER(buildEither)
3737
IDENTIFIER(buildIf)
38+
IDENTIFIER(callAsFunction)
3839
IDENTIFIER(Change)
3940
IDENTIFIER_WITH_NAME(code_, "_code")
4041
IDENTIFIER(CodingKeys)

lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7021,6 +7021,10 @@ SelfAccessKind FuncDecl::getSelfAccessKind() const {
70217021
SelfAccessKind::NonMutating);
70227022
}
70237023

7024+
bool FuncDecl::isCallAsFunctionMethod() const {
7025+
return getName() == getASTContext().Id_callAsFunction && isInstanceMember();
7026+
}
7027+
70247028
ConstructorDecl::ConstructorDecl(DeclName Name, SourceLoc ConstructorLoc,
70257029
bool Failable, SourceLoc FailabilityLoc,
70267030
bool Throws,

lib/Sema/CSApply.cpp

Lines changed: 123 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,12 @@ namespace {
10681068
Expr *finishApply(ApplyExpr *apply, Type openedType,
10691069
ConstraintLocatorBuilder locator);
10701070

1071-
// Resolve @dynamicCallable applications.
1072-
Expr *finishApplyDynamicCallable(const Solution &solution, ApplyExpr *apply,
1073-
ConstraintLocatorBuilder locator);
1071+
// Resolve `@dynamicCallable` applications.
1072+
Expr *finishApplyDynamicCallable(ApplyExpr *apply,
1073+
SelectedOverload selected,
1074+
FuncDecl *method,
1075+
AnyFunctionType *methodType,
1076+
ConstraintLocatorBuilder applyFunctionLoc);
10741077

10751078
private:
10761079
/// Simplify the given type by substituting all occurrences of
@@ -6828,11 +6831,62 @@ Expr *ExprRewriter::convertLiteralInPlace(Expr *literal,
68286831
return literal;
68296832
}
68306833

6831-
// Resolve @dynamicCallable applications.
6834+
// Returns true if the given method and method type are a valid
6835+
// `@dynamicCallable` required `func dynamicallyCall` method.
6836+
static bool isValidDynamicCallableMethod(FuncDecl *method,
6837+
AnyFunctionType *methodType) {
6838+
auto &ctx = method->getASTContext();
6839+
if (method->getName() != ctx.Id_dynamicallyCall)
6840+
return false;
6841+
if (methodType->getParams().size() != 1)
6842+
return false;
6843+
auto argumentLabel = methodType->getParams()[0].getLabel();
6844+
if (argumentLabel != ctx.Id_withArguments &&
6845+
argumentLabel != ctx.Id_withKeywordArguments)
6846+
return false;
6847+
return true;
6848+
}
6849+
6850+
// Resolve `callAsFunction` method applications.
6851+
static Expr *finishApplyCallAsFunctionMethod(
6852+
ExprRewriter &rewriter, ApplyExpr *apply, SelectedOverload selected,
6853+
AnyFunctionType *openedMethodType,
6854+
ConstraintLocatorBuilder applyFunctionLoc) {
6855+
auto &cs = rewriter.cs;
6856+
auto *fn = apply->getFn();
6857+
auto choice = selected.choice;
6858+
// Create direct reference to `callAsFunction` method.
6859+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
6860+
auto *declRef = rewriter.buildMemberRef(
6861+
fn, selected.openedFullType, /*dotLoc*/ SourceLoc(), choice,
6862+
DeclNameLoc(fn->getEndLoc()), selected.openedType, applyFunctionLoc,
6863+
applyFunctionLoc, /*implicit*/ true, choice.getFunctionRefKind(),
6864+
AccessSemantics::Ordinary, isDynamic);
6865+
if (!declRef)
6866+
return nullptr;
6867+
declRef->setImplicit(apply->isImplicit());
6868+
apply->setFn(declRef);
6869+
// Coerce argument to input type of the `callAsFunction` method.
6870+
SmallVector<Identifier, 2> argLabelsScratch;
6871+
auto *arg = rewriter.coerceCallArguments(
6872+
apply->getArg(), openedMethodType, apply,
6873+
apply->getArgumentLabels(argLabelsScratch), apply->hasTrailingClosure(),
6874+
applyFunctionLoc);
6875+
if (!arg)
6876+
return nullptr;
6877+
apply->setArg(arg);
6878+
cs.setType(apply, openedMethodType->getResult());
6879+
cs.cacheExprTypes(apply);
6880+
return apply;
6881+
}
6882+
6883+
// Resolve `@dynamicCallable` applications.
68326884
Expr *
6833-
ExprRewriter::finishApplyDynamicCallable(const Solution &solution,
6834-
ApplyExpr *apply,
6835-
ConstraintLocatorBuilder locator) {
6885+
ExprRewriter::finishApplyDynamicCallable(ApplyExpr *apply,
6886+
SelectedOverload selected,
6887+
FuncDecl *method,
6888+
AnyFunctionType *methodType,
6889+
ConstraintLocatorBuilder loc) {
68366890
auto &ctx = cs.getASTContext();
68376891
auto *fn = apply->getFn();
68386892

@@ -6841,27 +6895,16 @@ ExprRewriter::finishApplyDynamicCallable(const Solution &solution,
68416895
arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {});
68426896

68436897
// Get resolved `dynamicallyCall` method and verify it.
6844-
auto loc = locator.withPathElement(ConstraintLocator::ApplyFunction);
6845-
auto selected = solution.getOverloadChoice(cs.getConstraintLocator(loc));
6846-
auto *method = dyn_cast<FuncDecl>(selected.choice.getDecl());
6847-
auto methodType = simplifyType(selected.openedType)->castTo<AnyFunctionType>();
6848-
assert(method->getName() == ctx.Id_dynamicallyCall &&
6849-
"Expected 'dynamicallyCall' method");
6898+
assert(isValidDynamicCallableMethod(method, methodType));
68506899
auto params = methodType->getParams();
6851-
assert(params.size() == 1 &&
6852-
"Expected 'dynamicallyCall' method with one parameter");
68536900
auto argumentType = params[0].getParameterType();
6854-
auto argumentLabel = params[0].getLabel();
6855-
assert((argumentLabel == ctx.Id_withArguments ||
6856-
argumentLabel == ctx.Id_withKeywordArguments) &&
6857-
"Expected 'dynamicallyCall' method argument label 'withArguments' or "
6858-
"'withKeywordArguments'");
68596901

68606902
// Determine which method was resolved: a `withArguments` method or a
68616903
// `withKeywordArguments` method.
6904+
auto argumentLabel = methodType->getParams()[0].getLabel();
68626905
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;
68636906

6864-
// Construct expression referencing the `dynamicallyCall` method.
6907+
// Construct expression referencing the `dynamicallyCall` method.
68656908
bool isDynamic =
68666909
selected.choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
68676910
auto member = buildMemberRef(fn, selected.openedFullType,
@@ -7068,6 +7111,24 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
70687111
llvm_unreachable("Unhandled DeclTypeCheckingSemantics in switch.");
70697112
};
70707113

7114+
// Resolve `callAsFunction` and `@dynamicCallable` applications.
7115+
auto applyFunctionLoc =
7116+
locator.withPathElement(ConstraintLocator::ApplyFunction);
7117+
if (auto selected = solution.getOverloadChoiceIfAvailable(
7118+
cs.getConstraintLocator(applyFunctionLoc))) {
7119+
auto *method = dyn_cast<FuncDecl>(selected->choice.getDecl());
7120+
auto methodType =
7121+
simplifyType(selected->openedType)->getAs<AnyFunctionType>();
7122+
if (method && methodType) {
7123+
if (method->isCallAsFunctionMethod())
7124+
return finishApplyCallAsFunctionMethod(
7125+
*this, apply, *selected, methodType, applyFunctionLoc);
7126+
if (methodType && isValidDynamicCallableMethod(method, methodType))
7127+
return finishApplyDynamicCallable(
7128+
apply, *selected, method, methodType, applyFunctionLoc);
7129+
}
7130+
}
7131+
70717132
// The function is always an rvalue.
70727133
fn = cs.coerceToRValue(fn);
70737134

@@ -7168,57 +7229,51 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
71687229
}
71697230

71707231
// We have a type constructor.
7171-
if (auto metaTy = cs.getType(fn)->getAs<AnyMetatypeType>()) {
7172-
auto ty = metaTy->getInstanceType();
7173-
7174-
// If we're "constructing" a tuple type, it's simply a conversion.
7175-
if (auto tupleTy = ty->getAs<TupleType>()) {
7176-
// FIXME: Need an AST to represent this properly.
7177-
return coerceToType(apply->getArg(), tupleTy, locator);
7178-
}
7179-
7180-
// We're constructing a value of nominal type. Look for the constructor or
7181-
// enum element to use.
7182-
auto ctorLocator = cs.getConstraintLocator(
7183-
locator.withPathElement(ConstraintLocator::ApplyFunction)
7184-
.withPathElement(ConstraintLocator::ConstructorMember));
7185-
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7186-
if (!selected) {
7187-
assert(ty->hasError() || ty->hasUnresolvedType());
7188-
cs.setType(apply, ty);
7189-
return apply;
7190-
}
7191-
7192-
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7193-
ty->isExistentialType() || ty->is<ArchetypeType>());
7194-
7195-
// We have the constructor.
7196-
auto choice = selected->choice;
7197-
7198-
// Consider the constructor decl reference expr 'implicit', but the
7199-
// constructor call expr itself has the apply's 'implicitness'.
7200-
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7201-
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7202-
/*dotLoc=*/SourceLoc(), choice,
7203-
DeclNameLoc(fn->getEndLoc()),
7204-
selected->openedType, locator, ctorLocator,
7205-
/*Implicit=*/true,
7206-
choice.getFunctionRefKind(),
7207-
AccessSemantics::Ordinary, isDynamic);
7208-
if (!declRef)
7209-
return nullptr;
7210-
declRef->setImplicit(apply->isImplicit());
7211-
apply->setFn(declRef);
7232+
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
7233+
auto ty = metaTy->getInstanceType();
72127234

7213-
// Tail-recur to actually call the constructor.
7214-
return finishApply(apply, openedType, locator);
7235+
// If we're "constructing" a tuple type, it's simply a conversion.
7236+
if (auto tupleTy = ty->getAs<TupleType>()) {
7237+
// FIXME: Need an AST to represent this properly.
7238+
return coerceToType(apply->getArg(), tupleTy, locator);
72157239
}
72167240

7217-
// Handle @dynamicCallable applications.
7218-
// At this point, all other ApplyExpr cases have been handled.
7219-
return finishApplyDynamicCallable(solution, apply, locator);
7220-
}
7241+
// We're constructing a value of nominal type. Look for the constructor or
7242+
// enum element to use.
7243+
auto ctorLocator = cs.getConstraintLocator(
7244+
locator.withPathElement(ConstraintLocator::ApplyFunction)
7245+
.withPathElement(ConstraintLocator::ConstructorMember));
7246+
auto selected = solution.getOverloadChoiceIfAvailable(ctorLocator);
7247+
if (!selected) {
7248+
assert(ty->hasError() || ty->hasUnresolvedType());
7249+
cs.setType(apply, ty);
7250+
return apply;
7251+
}
72217252

7253+
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
7254+
ty->isExistentialType() || ty->is<ArchetypeType>());
7255+
7256+
// We have the constructor.
7257+
auto choice = selected->choice;
7258+
7259+
// Consider the constructor decl reference expr 'implicit', but the
7260+
// constructor call expr itself has the apply's 'implicitness'.
7261+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7262+
Expr *declRef = buildMemberRef(fn, selected->openedFullType,
7263+
/*dotLoc=*/SourceLoc(), choice,
7264+
DeclNameLoc(fn->getEndLoc()),
7265+
selected->openedType, locator, ctorLocator,
7266+
/*Implicit=*/true,
7267+
choice.getFunctionRefKind(),
7268+
AccessSemantics::Ordinary, isDynamic);
7269+
if (!declRef)
7270+
return nullptr;
7271+
declRef->setImplicit(apply->isImplicit());
7272+
apply->setFn(declRef);
7273+
7274+
// Tail-recur to actually call the constructor.
7275+
return finishApply(apply, openedType, locator);
7276+
}
72227277

72237278
// Return the precedence-yielding parent of 'expr', along with the index of
72247279
// 'expr' as the child of that parent. The precedence-yielding parent is the

lib/Sema/CSDiag.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3785,9 +3785,19 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
37853785
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
37863786

37873787
auto arg = callExpr->getArg();
3788+
auto isDynamicCallable =
3789+
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();
3790+
3791+
// Note: Consider caching `hasCallAsFunctionMethods` in `NominalTypeDecl`.
3792+
auto *nominal = fnType->getAnyNominal();
3793+
auto hasCallAsFunctionMethods = nominal &&
3794+
llvm::any_of(nominal->getMembers(), [](Decl *member) {
3795+
auto funcDecl = dyn_cast<FuncDecl>(member);
3796+
return funcDecl && funcDecl->isCallAsFunctionMethod();
3797+
});
37883798

37893799
// Diagnose @dynamicCallable errors.
3790-
if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
3800+
if (isDynamicCallable) {
37913801
auto dynamicCallableMethods =
37923802
CS.DynamicCallableCache[fnType->getCanonicalType()];
37933803

@@ -3843,7 +3853,8 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
38433853
}
38443854
}
38453855

3846-
return true;
3856+
if (!isDynamicCallable && !hasCallAsFunctionMethods)
3857+
return true;
38473858
}
38483859

38493860
bool hasTrailingClosure = callArgHasTrailingClosure(callExpr->getArg());

lib/Sema/CSSimplify.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6269,6 +6269,7 @@ ConstraintSystem::simplifyApplicableFnConstraint(
62696269
Type type2,
62706270
TypeMatchOptions flags,
62716271
ConstraintLocatorBuilder locator) {
6272+
auto &ctx = getASTContext();
62726273

62736274
// By construction, the left hand side is a type that looks like the
62746275
// following: $T1 -> $T2.
@@ -6293,6 +6294,16 @@ ConstraintSystem::simplifyApplicableFnConstraint(
62936294
}
62946295
}
62956296

6297+
// Before stripping lvalue-ness and optional types, save the original second
6298+
// type for handling `func callAsFunction` and `@dynamicCallable`
6299+
// applications. This supports the following cases:
6300+
// - Generating constraints for `mutating func callAsFunction`. The nominal
6301+
// type (`type2`) should be an lvalue type.
6302+
// - Extending `Optional` itself with `func callAsFunction` or
6303+
// `@dynamicCallable` functionality. Optional types are stripped below if
6304+
// `shouldAttemptFixes()` is true.
6305+
auto origLValueType2 =
6306+
getFixedTypeRecursive(type2, flags, /*wantRValue=*/false);
62966307
// Drill down to the concrete type on the right hand side.
62976308
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
62986309
auto desugar2 = type2->getDesugaredType();
@@ -6362,9 +6373,34 @@ ConstraintSystem::simplifyApplicableFnConstraint(
63626373
ConstraintLocatorBuilder outerLocator =
63636374
getConstraintLocator(anchor, parts, locator.getSummaryFlags());
63646375

6365-
// Before stripping optional types, save original type for handling
6366-
// @dynamicCallable applications. This supports the fringe case where
6367-
// `Optional` itself is extended with @dynamicCallable functionality.
6376+
// Handle applications of types with `callAsFunction` methods.
6377+
// Do this before stripping optional types below, when `shouldAttemptFixes()`
6378+
// is true.
6379+
auto hasCallAsFunctionMethods =
6380+
desugar2->mayHaveMembers() &&
6381+
llvm::any_of(lookupMember(desugar2, DeclName(ctx.Id_callAsFunction)),
6382+
[](LookupResultEntry entry) {
6383+
return isa<FuncDecl>(entry.getValueDecl());
6384+
});
6385+
if (hasCallAsFunctionMethods) {
6386+
auto memberLoc = getConstraintLocator(
6387+
outerLocator.withPathElement(ConstraintLocator::Member));
6388+
// Add a `callAsFunction` member constraint, binding the member type to a
6389+
// type variable.
6390+
auto memberTy = createTypeVariable(memberLoc, /*options=*/0);
6391+
// TODO: Revisit this if `static func callAsFunction` is to be supported.
6392+
// Static member constraint requires `FunctionRefKind::DoubleApply`.
6393+
addValueMemberConstraint(origLValueType2, DeclName(ctx.Id_callAsFunction),
6394+
memberTy, DC, FunctionRefKind::SingleApply,
6395+
/*outerAlternatives*/ {}, locator);
6396+
// Add new applicable function constraint based on the member type
6397+
// variable.
6398+
addConstraint(ConstraintKind::ApplicableFunction, func1, memberTy,
6399+
locator);
6400+
return SolutionKind::Solved;
6401+
}
6402+
6403+
// Record the second type before unwrapping optionals.
63686404
auto origType2 = desugar2;
63696405
unsigned unwrapCount = 0;
63706406
if (shouldAttemptFixes()) {
@@ -6587,6 +6623,13 @@ getDynamicCallableMethods(Type type, ConstraintSystem &CS,
65876623
return result;
65886624
}
65896625

6626+
// TODO: Refactor/simplify this function.
6627+
// - It should perform less duplicate work with its caller
6628+
// `ConstraintSystem::simplifyApplicableFnConstraint`.
6629+
// - It should generate a member constraint instead of manually forming an
6630+
// overload set for `func dynamicallyCall` candidates.
6631+
// - It should support `mutating func dynamicallyCall`. This should fall out of
6632+
// using member constraints with an lvalue base type.
65906633
ConstraintSystem::SolutionKind
65916634
ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
65926635
Type type1,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public protocol Layer {
2+
func callAsFunction(_ input: Float) -> Float
3+
}
4+
5+
public struct Dense {
6+
public init() {}
7+
8+
public func callAsFunction(_ input: Float) -> Float {
9+
return input * 2
10+
}
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %empty-directory(%t)
2+
// 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
3+
// RUN: %target-swift-frontend -typecheck -I %t -primary-file %s -verify
4+
5+
import call_as_function_other_module
6+
7+
func testLayer<L: Layer>(_ layer: L) -> Float {
8+
return layer(1)
9+
}
10+
11+
func testDense() -> Float {
12+
let dense = Dense()
13+
return dense(1)
14+
}

0 commit comments

Comments
 (0)