Skip to content

Commit 932f108

Browse files
committed
Introduce callables.
Introduce callables: values that can be called like functions. Methods named `func callFunction` act as call-syntax delegate methods. ``` struct Adder { var base: Int func callFunction(_ x: Int) -> Int { return x + base } } var adder = Adder(base: 3) adder(10) // desugars to `adder.callFunction(10)` ```
1 parent 49b19f5 commit 932f108

File tree

10 files changed

+440
-2
lines changed

10 files changed

+440
-2
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5958,6 +5958,9 @@ class FuncDecl : public AbstractFunctionDecl {
59585958
bool isConsuming() const {
59595959
return getSelfAccessKind() == SelfAccessKind::__Consuming;
59605960
}
5961+
bool isCallable() const {
5962+
return getName().str() == "call" && isInstanceMember();
5963+
}
59615964

59625965
SelfAccessKind getSelfAccessKind() const {
59635966
return static_cast<SelfAccessKind>(Bits.FuncDecl.SelfAccess);

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ IDENTIFIER(Any)
3131
IDENTIFIER(ArrayLiteralElement)
3232
IDENTIFIER(atIndexedSubscript)
3333
IDENTIFIER_(bridgeToObjectiveC)
34+
IDENTIFIER(call)
3435
IDENTIFIER_WITH_NAME(code_, "_code")
3536
IDENTIFIER(CodingKeys)
3637
IDENTIFIER(combine)

lib/Sema/CSApply.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7067,6 +7067,9 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
70677067
llvm_unreachable("Unhandled DeclTypeCheckingSemantics in switch.");
70687068
};
70697069

7070+
// Save the original potentially lvalue function for rewriting call method
7071+
// applications.
7072+
auto *originalFn = fn;
70707073
// The function is always an rvalue.
70717074
fn = cs.coerceToRValue(fn);
70727075

@@ -7213,6 +7216,59 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
72137216
return finishApply(apply, openedType, locator);
72147217
}
72157218

7219+
// Handle call method applications.
7220+
auto &ctx = cs.getASTContext();
7221+
7222+
TupleExpr *arg = dyn_cast<TupleExpr>(apply->getArg());
7223+
if (auto parenExpr = dyn_cast<ParenExpr>(apply->getArg()))
7224+
arg = TupleExpr::createImplicit(ctx, parenExpr->getSubExpr(), {});
7225+
7226+
// Get resolved call method and verify it.
7227+
auto loc = locator.withPathElement(ConstraintLocator::ApplyFunction);
7228+
auto selected = solution.getOverloadChoice(cs.getConstraintLocator(loc));
7229+
auto choice = selected.choice;
7230+
auto *callMethod = dyn_cast<FuncDecl>(selected.choice.getDecl());
7231+
if (callMethod && callMethod->isCallable()) {
7232+
auto methodType =
7233+
simplifyType(selected.openedType)->castTo<AnyFunctionType>();
7234+
auto selfParam = callMethod->getImplicitSelfDecl();
7235+
// Diagnose `mutating` method call on immutable value.
7236+
// FIXME(TF-444): This logic for `mutating` method calls incorrectly rejects
7237+
// IUOs. Performing this ad-hoc check using `originalFn` feels hacky;
7238+
// rewrite if possible.
7239+
if (!cs.getType(originalFn)->hasLValueType() && selfParam->isInOut()) {
7240+
AssignmentFailure failure(originalFn, cs, originalFn->getLoc(),
7241+
diag::cannot_pass_rvalue_mutating_subelement,
7242+
diag::cannot_pass_rvalue_mutating);
7243+
failure.diagnose();
7244+
return nullptr;
7245+
}
7246+
// Create direct reference to call method.
7247+
bool isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
7248+
Expr *declRef = buildMemberRef(originalFn, selected.openedFullType,
7249+
/*dotLoc=*/SourceLoc(), choice,
7250+
DeclNameLoc(fn->getEndLoc()),
7251+
selected.openedType, loc, loc,
7252+
/*Implicit=*/true,
7253+
choice.getFunctionRefKind(),
7254+
AccessSemantics::Ordinary, isDynamic);
7255+
if (!declRef)
7256+
return nullptr;
7257+
declRef->setImplicit(apply->isImplicit());
7258+
apply->setFn(declRef);
7259+
// Coerce argument to input type of call method.
7260+
SmallVector<Identifier, 2> argLabelsScratch;
7261+
auto *arg = coerceCallArguments(apply->getArg(), methodType, apply,
7262+
apply->getArgumentLabels(argLabelsScratch),
7263+
apply->hasTrailingClosure(), loc);
7264+
if (!arg)
7265+
return nullptr;
7266+
apply->setArg(arg);
7267+
cs.setType(apply, methodType->getResult());
7268+
cs.cacheExprTypes(apply);
7269+
return apply;
7270+
}
7271+
72167272
// Handle @dynamicCallable applications.
72177273
// At this point, all other ApplyExpr cases have been handled.
72187274
return finishApplyDynamicCallable(cs, solution, apply, locator);

lib/Sema/CSDiag.cpp

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

47984798
auto arg = callExpr->getArg();
4799+
auto isDynamicCallable =
4800+
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();
4801+
4802+
// TODO: Consider caching?
4803+
auto *nominal = fnType->getAnyNominal();
4804+
auto hasCallMethods = nominal &&
4805+
llvm::any_of(nominal->getMembers(), [](Decl *member) {
4806+
auto funcDecl = dyn_cast<FuncDecl>(member);
4807+
return funcDecl && funcDecl->isCallable();
4808+
});
47994809

48004810
// Diagnose @dynamicCallable errors.
4801-
if (CS.DynamicCallableCache[fnType->getCanonicalType()].isValid()) {
4811+
if (isDynamicCallable) {
48024812
auto dynamicCallableMethods =
48034813
CS.DynamicCallableCache[fnType->getCanonicalType()];
48044814

@@ -4854,7 +4864,8 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
48544864
}
48554865
}
48564866

4857-
return true;
4867+
if (!isDynamicCallable && !hasCallMethods)
4868+
return true;
48584869
}
48594870

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

lib/Sema/CSSimplify.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5788,6 +5788,60 @@ ConstraintSystem::simplifyApplicableFnConstraint(
57885788
return simplified;
57895789
}
57905790

5791+
// Handle applications of types with call methods.
5792+
if (desugar2->mayHaveMembers()) {
5793+
auto &ctx = getASTContext();
5794+
// Get all call methods of the nominal type.
5795+
// TODO: Consider caching?
5796+
SmallVector<FuncDecl *, 4> callMethods;
5797+
auto candidates = lookupMember(desugar2, DeclName(ctx.Id_call));
5798+
for (auto entry : candidates) {
5799+
auto callMethod = dyn_cast<FuncDecl>(entry.getValueDecl());
5800+
if (!callMethod)
5801+
continue;
5802+
callMethods.push_back(callMethod);
5803+
}
5804+
5805+
// Handle call methods calls.
5806+
if (!callMethods.empty()) {
5807+
// Create a type variable for the call method.
5808+
auto loc = getConstraintLocator(locator);
5809+
auto tv = createTypeVariable(loc, TVO_CanBindToLValue);
5810+
5811+
// Record the call method overload set.
5812+
SmallVector<OverloadChoice, 4> choices;
5813+
for (auto candidate : callMethods) {
5814+
TC.validateDecl(candidate);
5815+
if (candidate->isInvalid()) continue;
5816+
choices.push_back(
5817+
OverloadChoice(type2, candidate, FunctionRefKind::SingleApply));
5818+
}
5819+
if (choices.empty()) return SolutionKind::Error;
5820+
addOverloadSet(tv, choices, DC, loc);
5821+
5822+
// Create type variables for each parameter type.
5823+
SmallVector<AnyFunctionType::Param, 4> tvParams;
5824+
for (unsigned i : range(func1->getNumParams())) {
5825+
auto param = func1->getParams()[i];
5826+
auto paramType = param.getPlainType();
5827+
5828+
auto *tvParam = createTypeVariable(loc, TVO_CanBindToNoEscape);
5829+
auto locatorBuilder =
5830+
locator.withPathElement(LocatorPathElt::getTupleElement(i));
5831+
addConstraint(ConstraintKind::ArgumentConversion, paramType,
5832+
tvParam, locatorBuilder);
5833+
tvParams.push_back(AnyFunctionType::Param(
5834+
tvParam, Identifier(), param.getParameterFlags()));
5835+
}
5836+
// Create target function type and an applicable function constraint.
5837+
AnyFunctionType *funcType =
5838+
FunctionType::get(tvParams, func1->getResult());
5839+
addConstraint(ConstraintKind::ApplicableFunction, funcType, tv, locator);
5840+
5841+
return SolutionKind::Solved;
5842+
}
5843+
}
5844+
57915845
// Handle applications of @dynamicCallable types.
57925846
return simplifyDynamicCallableApplicableFnConstraint(type1, origType2,
57935847
subflags, locator);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public protocol Layer {
2+
func call(_ input: Float) -> Float
3+
}
4+
5+
public struct Dense {
6+
public init() {}
7+
8+
public func call(_ 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_method_other_module.swift -emit-module-path %t/call_method_other_module.swiftmodule
3+
// RUN: %target-swift-frontend -typecheck -I %t -primary-file %s -verify
4+
5+
import call_method_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+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P0 {
4+
func call(x: Self)
5+
}
6+
7+
struct ConcreteType {
8+
func call<T, U>(_ x: T, _ y: U) -> (T, U) {
9+
return (x, y)
10+
}
11+
12+
func call<T, U>(_ fn: @escaping (T) -> U) -> (T) -> U {
13+
return fn
14+
}
15+
}
16+
17+
let concrete = ConcreteType()
18+
_ = concrete(1, 3.0)
19+
_ = concrete(concrete, concrete.call as ([Int], Float) -> ([Int], Float))
20+
21+
func generic<T, U>(_ x: T, _ y: U) {
22+
_ = concrete(x, x)
23+
_ = concrete(x, y)
24+
}
25+
26+
struct GenericType<T : Collection> {
27+
let collection: T
28+
func call<U>(_ x: U) -> Bool where U == T.Element, U : Equatable {
29+
return collection.contains(x)
30+
}
31+
}
32+
33+
// Test conditional conformance.
34+
extension GenericType where T.Element : Numeric {
35+
func call(initialValue: T.Element) -> T.Element {
36+
return collection.reduce(initialValue, +)
37+
}
38+
}
39+
40+
let genericString = GenericType<[String]>(collection: ["Hello", "world", "!"])
41+
_ = genericString("Hello")
42+
let genericInt = GenericType<Set<Int>>(collection: [1, 2, 3])
43+
_ = genericInt(initialValue: 1)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P0 {
4+
// expected-note @+1 {{protocol requires function 'call()' with type '() -> Missing'; do you want to add a stub?}}
5+
func call() -> Self
6+
}
7+
func testProtocol(_ x: P0) {
8+
_ = x()
9+
}
10+
func testGeneric<T : P0>(_ x: T) {
11+
_ = x()
12+
}
13+
14+
protocol P1 {
15+
func call() -> Self
16+
}
17+
extension P1 {
18+
// expected-note @+1 {{found this candidate}}
19+
func call() -> Self {
20+
return self
21+
}
22+
}
23+
protocol P2 {}
24+
extension P2 {
25+
// expected-note @+1 {{found this candidate}}
26+
func call(x: Int, y: Int) -> Int {
27+
return x + y
28+
}
29+
}
30+
31+
// expected-error @+1 {{type 'Missing' does not conform to protocol 'P0'}}
32+
struct Missing : P0 {}
33+
struct S0 : P0 {
34+
@discardableResult
35+
func call() -> S0 { return self }
36+
}
37+
let s0 = S0()
38+
s0()
39+
40+
struct S1 : P1 {
41+
func call() -> S1 { return self }
42+
}
43+
44+
let s1 = S1()
45+
_ = s1()()
46+
47+
struct Conforming : P0 & P1 & P2 {}
48+
let conforming = Conforming()
49+
_ = conforming(x: 1, y: 2)
50+
_ = conforming().call(x:y:)(1, 2)
51+
_ = conforming.call(x:y:)
52+
_ = conforming.call // expected-error {{ambiguous use of 'call'}}
53+
54+
protocol P3 {}
55+
extension P3 {
56+
func call() -> Self { return self }
57+
}
58+
struct S3 : P3 {}
59+
60+
let s3 = S3()
61+
_ = s3()()

0 commit comments

Comments
 (0)