Skip to content

Commit 0dc9a0e

Browse files
authored
Merge pull request #28519 from hamishknight/callee-as-function
2 parents acb46cb + 2d7a088 commit 0dc9a0e

13 files changed

+178
-38
lines changed

include/swift/AST/TypeCheckRequests.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,26 @@ class CallerSideDefaultArgExprRequest
19101910
void cacheResult(Expr *expr) const;
19111911
};
19121912

1913+
/// Computes whether this is a type that supports being called through the
1914+
/// implementation of a \c callAsFunction method.
1915+
class IsCallableNominalTypeRequest
1916+
: public SimpleRequest<IsCallableNominalTypeRequest,
1917+
bool(CanType, DeclContext *), CacheKind::Cached> {
1918+
public:
1919+
using SimpleRequest::SimpleRequest;
1920+
1921+
private:
1922+
friend SimpleRequest;
1923+
1924+
// Evaluation.
1925+
llvm::Expected<bool> evaluate(Evaluator &evaluator, CanType ty,
1926+
DeclContext *dc) const;
1927+
1928+
public:
1929+
// Cached.
1930+
bool isCached() const { return true; }
1931+
};
1932+
19131933
// Allow AnyValue to compare two Type values, even though Type doesn't
19141934
// support ==.
19151935
template<>

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ SWIFT_REQUEST(TypeChecker, InterfaceTypeRequest,
8888
Type(ValueDecl *), SeparatelyCached, NoLocationInfo)
8989
SWIFT_REQUEST(TypeChecker, IsAccessorTransparentRequest, bool(AccessorDecl *),
9090
SeparatelyCached, NoLocationInfo)
91+
SWIFT_REQUEST(TypeChecker, IsCallableNominalTypeRequest,
92+
bool(CanType, DeclContext *), Cached, NoLocationInfo)
9193
SWIFT_REQUEST(TypeChecker, IsDynamicRequest, bool(ValueDecl *),
9294
SeparatelyCached, NoLocationInfo)
9395
SWIFT_REQUEST(TypeChecker, IsFinalRequest, bool(ValueDecl *), SeparatelyCached,

include/swift/AST/Types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,11 @@ class alignas(1 << TypeAlignInBits) TypeBase {
818818
getAnyNominal());
819819
}
820820

821+
/// Checks whether this is a type that supports being called through the
822+
/// implementation of a \c callAsFunction method. Note that this does not
823+
/// check access control.
824+
bool isCallableNominalType(DeclContext *dc);
825+
821826
/// Retrieve the superclass of this type.
822827
///
823828
/// \param useArchetypes Whether to use context archetypes for outer generic

lib/AST/Type.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,21 @@ bool TypeBase::satisfiesClassConstraint() {
14801480
return mayHaveSuperclass() || isObjCExistentialType();
14811481
}
14821482

1483+
bool TypeBase::isCallableNominalType(DeclContext *dc) {
1484+
// Don't allow callAsFunction to be used with dynamic lookup.
1485+
if (isAnyObject())
1486+
return false;
1487+
1488+
// If the type cannot have members, we're done.
1489+
if (!mayHaveMembers())
1490+
return false;
1491+
1492+
auto canTy = getCanonicalType();
1493+
auto &ctx = canTy->getASTContext();
1494+
return evaluateOrDefault(ctx.evaluator,
1495+
IsCallableNominalTypeRequest{canTy, dc}, false);
1496+
}
1497+
14831498
Type TypeBase::getSuperclass(bool useArchetypes) {
14841499
auto *nominalDecl = getAnyNominal();
14851500
auto *classDecl = dyn_cast_or_null<ClassDecl>(nominalDecl);

lib/Sema/CSApply.cpp

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6495,12 +6495,10 @@ static bool isValidDynamicCallableMethod(FuncDecl *method,
64956495
return true;
64966496
}
64976497

6498-
// Resolve `callAsFunction` method applications.
6499-
static Expr *finishApplyCallAsFunctionMethod(
6498+
// Build a reference to a `callAsFunction` method.
6499+
static Expr *buildCallAsFunctionMethodRef(
65006500
ExprRewriter &rewriter, ApplyExpr *apply, SelectedOverload selected,
6501-
AnyFunctionType *openedMethodType,
65026501
ConstraintLocatorBuilder applyFunctionLoc) {
6503-
auto &cs = rewriter.cs;
65046502
auto *fn = apply->getFn();
65056503
auto choice = selected.choice;
65066504
// Create direct reference to `callAsFunction` method.
@@ -6512,21 +6510,7 @@ static Expr *finishApplyCallAsFunctionMethod(
65126510
if (!declRef)
65136511
return nullptr;
65146512
declRef->setImplicit(apply->isImplicit());
6515-
apply->setFn(declRef);
6516-
// Coerce argument to input type of the `callAsFunction` method.
6517-
auto callee = rewriter.resolveConcreteDeclRef(choice.getDecl(),
6518-
applyFunctionLoc);
6519-
SmallVector<Identifier, 2> argLabelsScratch;
6520-
auto *arg = rewriter.coerceCallArguments(
6521-
apply->getArg(), openedMethodType, callee, apply,
6522-
apply->getArgumentLabels(argLabelsScratch), apply->hasTrailingClosure(),
6523-
applyFunctionLoc);
6524-
if (!arg)
6525-
return nullptr;
6526-
apply->setArg(arg);
6527-
cs.setType(apply, openedMethodType->getResult());
6528-
cs.cacheExprTypes(apply);
6529-
return apply;
6513+
return declRef;
65306514
}
65316515

65326516
// Resolve `@dynamicCallable` applications.
@@ -6768,12 +6752,19 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, ConcreteDeclRef callee,
67686752
auto methodType =
67696753
simplifyType(selected->openedType)->getAs<AnyFunctionType>();
67706754
if (method && methodType) {
6771-
if (method->isCallAsFunctionMethod())
6772-
return finishApplyCallAsFunctionMethod(
6773-
*this, apply, *selected, methodType, applyFunctionLoc);
6774-
if (methodType && isValidDynamicCallableMethod(method, methodType))
6755+
// Handle a call to a @dynamicCallable method.
6756+
if (isValidDynamicCallableMethod(method, methodType))
67756757
return finishApplyDynamicCallable(
67766758
apply, *selected, method, methodType, applyFunctionLoc);
6759+
6760+
// If this is an implicit call to a callAsFunction method, build the
6761+
// appropriate member reference.
6762+
if (method->isCallAsFunctionMethod()) {
6763+
fn = buildCallAsFunctionMethodRef(*this, apply, *selected,
6764+
applyFunctionLoc);
6765+
if (!fn)
6766+
return nullptr;
6767+
}
67776768
}
67786769
}
67796770

lib/Sema/CSDiag.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,13 +2616,7 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
26162616
auto isDynamicCallable =
26172617
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();
26182618

2619-
// Note: Consider caching `hasCallAsFunctionMethods` in `NominalTypeDecl`.
2620-
auto *nominal = fnType->getAnyNominal();
2621-
auto hasCallAsFunctionMethods = nominal &&
2622-
llvm::any_of(nominal->getMembers(), [](Decl *member) {
2623-
auto funcDecl = dyn_cast<FuncDecl>(member);
2624-
return funcDecl && funcDecl->isCallAsFunctionMethod();
2625-
});
2619+
auto hasCallAsFunctionMethods = fnType->isCallableNominalType(CS.DC);
26262620

26272621
// Diagnose specific @dynamicCallable errors.
26282622
if (isDynamicCallable) {

lib/Sema/CSSimplify.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7334,20 +7334,16 @@ ConstraintSystem::simplifyApplicableFnConstraint(
73347334
// Handle applications of types with `callAsFunction` methods.
73357335
// Do this before stripping optional types below, when `shouldAttemptFixes()`
73367336
// is true.
7337-
auto hasCallAsFunctionMethods =
7338-
desugar2->mayHaveMembers() &&
7339-
llvm::any_of(lookupMember(desugar2, DeclName(ctx.Id_callAsFunction)),
7340-
[](LookupResultEntry entry) {
7341-
return isa<FuncDecl>(entry.getValueDecl());
7342-
});
7343-
if (hasCallAsFunctionMethods) {
7337+
if (desugar2->isCallableNominalType(DC)) {
73447338
auto memberLoc = getConstraintLocator(
73457339
outerLocator.withPathElement(ConstraintLocator::Member));
73467340
// Add a `callAsFunction` member constraint, binding the member type to a
73477341
// type variable.
73487342
auto memberTy = createTypeVariable(memberLoc, /*options=*/0);
73497343
// TODO: Revisit this if `static func callAsFunction` is to be supported.
73507344
// Static member constraint requires `FunctionRefKind::DoubleApply`.
7345+
// TODO: Use a custom locator element to identify this member constraint
7346+
// instead of just pointing to the function expr.
73517347
addValueMemberConstraint(origLValueType2, DeclName(ctx.Id_callAsFunction),
73527348
memberTy, DC, FunctionRefKind::SingleApply,
73537349
/*outerAlternatives*/ {}, locator);

lib/Sema/ConstraintSystem.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,17 +478,29 @@ ConstraintSystem::getCalleeLocator(ConstraintLocator *locator,
478478
if (lookThroughApply) {
479479
if (auto *applyExpr = dyn_cast<ApplyExpr>(anchor)) {
480480
auto *fnExpr = applyExpr->getFn();
481+
482+
// FIXME: We should probably assert that we don't get a type variable
483+
// here to make sure we only retrieve callee locators for resolved calls,
484+
// ensuring that callee locators don't change after binding a type.
485+
// Unfortunately CSDiag currently calls into getCalleeLocator, so all bets
486+
// are off. Once we remove that legacy diagnostic logic, we should be able
487+
// to assert here.
488+
auto fnTy = getFixedTypeRecursive(getType(fnExpr), /*wantRValue*/ true);
489+
481490
// For an apply of a metatype, we have a short-form constructor. Unlike
482491
// other locators to callees, these are anchored on the apply expression
483492
// rather than the function expr.
484-
auto fnTy = getFixedTypeRecursive(getType(fnExpr), /*wantRValue*/ true);
485493
if (fnTy->is<AnyMetatypeType>()) {
486494
auto *fnLocator =
487495
getConstraintLocator(applyExpr, ConstraintLocator::ApplyFunction);
488496
return getConstraintLocator(fnLocator,
489497
ConstraintLocator::ConstructorMember);
490498
}
491499

500+
// Handle an apply of a nominal type which supports callAsFunction.
501+
if (fnTy->isCallableNominalType(DC))
502+
return getConstraintLocator(anchor, ConstraintLocator::ApplyFunction);
503+
492504
// Otherwise fall through and look for locators anchored on the function
493505
// expr. For CallExprs, this can look through things like parens and
494506
// optional chaining.

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "swift/AST/ProtocolConformance.h"
3737
#include "swift/AST/SubstitutionMap.h"
3838
#include "swift/AST/TypeCheckerDebugConsumer.h"
39+
#include "swift/AST/TypeCheckRequests.h"
3940
#include "swift/Basic/Statistic.h"
4041
#include "swift/Parse/Confusables.h"
4142
#include "swift/Parse/Lexer.h"
@@ -4569,3 +4570,22 @@ ForcedCheckedCastExpr *swift::findForcedDowncast(ASTContext &ctx, Expr *expr) {
45694570

45704571
return nullptr;
45714572
}
4573+
4574+
llvm::Expected<bool>
4575+
IsCallableNominalTypeRequest::evaluate(Evaluator &evaluator, CanType ty,
4576+
DeclContext *dc) const {
4577+
auto options = defaultMemberLookupOptions;
4578+
options |= NameLookupFlags::IgnoreAccessControl;
4579+
if (isa<AbstractFunctionDecl>(dc))
4580+
options |= NameLookupFlags::KnownPrivate;
4581+
4582+
// Look for a callAsFunction method.
4583+
auto &ctx = ty->getASTContext();
4584+
auto results =
4585+
TypeChecker::lookupMember(dc, ty, ctx.Id_callAsFunction, options);
4586+
return llvm::any_of(results, [](LookupResultEntry entry) -> bool {
4587+
if (auto *fd = dyn_cast<FuncDecl>(entry.getValueDecl()))
4588+
return fd->isCallAsFunctionMethod();
4589+
return false;
4590+
});
4591+
}

test/Constraints/dynamic_lookup.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,13 @@ class HasMethodWithDefault {
398398
func testAnyObjectWithDefault(_ x: AnyObject) {
399399
x.hasDefaultParam()
400400
}
401+
402+
// SR-11829: Don't perform dynamic lookup for callAsFunction.
403+
class ClassWithObjcCallAsFunction {
404+
@objc func callAsFunction() {}
405+
}
406+
407+
func testCallAsFunctionAnyObject(_ x: AnyObject) {
408+
x() // expected-error {{cannot call value of non-function type 'AnyObject'}}
409+
x.callAsFunction() // Okay.
410+
}

test/SILGen/default_arguments.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,26 @@ func genericMagic<T : ExpressibleByStringLiteral>(x: T = #file) -> T {
408408
}
409409

410410
let _: String = genericMagic()
411+
412+
// SR-11778
413+
struct CallableWithDefault {
414+
func callAsFunction(x: Int = 4) {}
415+
func callAsFunction(y: Int, z: String = #function) {}
416+
}
417+
418+
// CHECK-LABEL: sil hidden [ossa] @$s17default_arguments23testCallableWithDefaultyyAA0deF0VF : $@convention(thin) (CallableWithDefault) -> ()
419+
func testCallableWithDefault(_ x: CallableWithDefault) {
420+
// CHECK: [[DEF_FN:%[0-9]+]] = function_ref @$s17default_arguments19CallableWithDefaultV14callAsFunction1xySi_tFfA_ : $@convention(thin) () -> Int
421+
// CHECK: [[DEF:%[0-9]+]] = apply [[DEF_FN]]() : $@convention(thin) () -> Int
422+
// CHECK: [[CALL_AS_FN:%[0-9]+]] = function_ref @$s17default_arguments19CallableWithDefaultV14callAsFunction1xySi_tF : $@convention(method) (Int, CallableWithDefault) -> ()
423+
// CHECK: apply [[CALL_AS_FN]]([[DEF]], {{%[0-9]+}})
424+
x()
425+
426+
// CHECK: [[RAW_I:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 5
427+
// CHECK: [[I:%[0-9]+]] = apply {{%[0-9]+}}([[RAW_I]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
428+
// CHECK: [[RAW_STR:%[0-9]+]] = string_literal utf8 "testCallableWithDefault(_:)"
429+
// CHECK: [[STR:%[0-9]+]] = apply {{%[0-9]+}}([[RAW_STR]], {{%[0-9]+}}, {{%[0-9]+}}, {{%[0-9]+}}) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
430+
// CHECK: [[CALL_AS_FN:%[0-9]+]] = function_ref @$s17default_arguments19CallableWithDefaultV14callAsFunction1y1zySi_SStF : $@convention(method) (Int, @guaranteed String, CallableWithDefault) -> ()
431+
// CHECK: apply [[CALL_AS_FN]]([[I]], [[STR]], {{%[0-9]+}})
432+
x(y: 5)
433+
}

test/Sema/call_as_function_generic.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,19 @@ let genericString = GenericType<[String]>(collection: ["Hello", "world", "!"])
4141
_ = genericString("Hello")
4242
let genericInt = GenericType<Set<Int>>(collection: [1, 2, 3])
4343
_ = genericInt(initialValue: 1)
44+
45+
// SR-11386
46+
class C<T> {}
47+
protocol P1 {}
48+
extension C where T : P1 { // expected-note {{where 'T' = 'Int'}}
49+
func callAsFunction(t: T) {}
50+
}
51+
52+
struct S0 : P1 {}
53+
54+
func testCallAsFunctionWithWhereClause(_ c1: C<Int>, _ c2: C<S0>, _ s0: S0) {
55+
c1(42) // expected-error {{referencing instance method 'callAsFunction(t:)' on 'C' requires that 'Int' conform to 'P1'}}
56+
// expected-error@-1 {{missing argument label 't:' in call}}
57+
58+
c2(t: s0) // Okay.
59+
}

test/Sema/call_as_function_simple.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,39 @@ func testIUO(a: SimpleCallable!, b: MultipleArgsCallable!, c: Extended!,
192192
_ = try? h()
193193
_ = try? h { throw DummyError() }
194194
}
195+
196+
// SR-11778
197+
struct DoubleANumber {
198+
func callAsFunction(_ x: Int, completion: (Int) -> Void = { _ in }) {
199+
completion(x + x)
200+
}
201+
}
202+
203+
func testDefaults(_ x: DoubleANumber) {
204+
x(5)
205+
x(5, completion: { _ in })
206+
}
207+
208+
// SR-11881
209+
struct IUOCallable {
210+
static var callable: IUOCallable { IUOCallable() }
211+
func callAsFunction(_ x: Int) -> IUOCallable! { nil }
212+
}
213+
214+
func testIUOCallAsFunction(_ x: IUOCallable) {
215+
let _: IUOCallable = x(5)
216+
let _: IUOCallable? = x(5)
217+
let _ = x(5)
218+
219+
let _: IUOCallable = .callable(5)
220+
let _: IUOCallable? = .callable(5)
221+
}
222+
223+
// Test access control.
224+
struct PrivateCallable {
225+
private func callAsFunction(_ x: Int) {} // expected-note {{'callAsFunction' declared here}}
226+
}
227+
228+
func testAccessControl(_ x: PrivateCallable) {
229+
x(5) // expected-error {{'callAsFunction' is inaccessible due to 'private' protection level}}
230+
}

0 commit comments

Comments
 (0)