Skip to content

Commit 0a14858

Browse files
committed
[CSApply] Hack around existential closing for callables
The current existential closing logic relies on maintaining a stack of expressions, and closing the existential when the size of the stack goes below a certain threshold. This works fine for cases where we open the existential as a part of an user-written member reference, as we push it onto the stack when walking over the AST. However it doesn't handle cases where we generate an implicit member reference when visiting a part of the AST higher up in the tree. We therefore run into problems with both implicit `callAsFunction` and `@dynamicCallable`, both of which generate implicit member refs when we visit the apply. To hack around this issue, push the apply's fn expr onto the stack before building the member reference, such that we don't try to prematurely close the existential as a part of applying the curried member ref. The good news at least is that this hack allows us to remove the `extraUncurryLevel` param which was previously working around this issue for the `shouldBuildCurryThunk` function. To properly fix this, we'll need to refactor existential opening to not rely on the shape of the AST prior to re-writing. Resolves SR-12590.
1 parent 379d73c commit 0a14858

File tree

3 files changed

+79
-34
lines changed

3 files changed

+79
-34
lines changed

lib/Sema/CSApply.cpp

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,7 @@ namespace {
491491

492492
// Handle operator requirements found in protocols.
493493
if (auto proto = dyn_cast<ProtocolDecl>(decl->getDeclContext())) {
494-
bool isCurried = shouldBuildCurryThunk(choice,
495-
/*baseIsInstance=*/false,
496-
/*extraUncurryLevel=*/false);
494+
bool isCurried = shouldBuildCurryThunk(choice, /*baseIsInstance=*/false);
497495

498496
// If we have a concrete conformance, build a call to the witness.
499497
//
@@ -558,8 +556,7 @@ namespace {
558556
cs.cacheExprTypes(base);
559557

560558
return buildMemberRef(base, SourceLoc(), overload, loc, locator,
561-
locator, implicit, /*extraUncurryLevel=*/false,
562-
semantics);
559+
locator, implicit, semantics);
563560
}
564561

565562
if (isa<TypeDecl>(decl) && !isa<ModuleDecl>(decl)) {
@@ -669,6 +666,10 @@ namespace {
669666

670667
/// Calculates the nesting depth of the current application.
671668
unsigned getArgCount(unsigned maxArgCount) {
669+
// FIXME: Walking over the ExprStack to figure out the number of argument
670+
// lists being applied is brittle. We should instead be checking
671+
// hasAppliedSelf to figure out if the self param is applied, and looking
672+
// at the FunctionRefKind to see if the parameter list is applied.
672673
unsigned e = ExprStack.size();
673674
unsigned argCount;
674675

@@ -812,8 +813,7 @@ namespace {
812813
/// converted into a fully-applied member reference with a pair of
813814
/// closures.
814815
bool shouldBuildCurryThunk(OverloadChoice choice,
815-
bool baseIsInstance,
816-
bool extraUncurryLevel) {
816+
bool baseIsInstance) {
817817
ValueDecl *member = choice.getDecl();
818818
auto isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
819819

@@ -848,29 +848,20 @@ namespace {
848848
isa<CallExpr>(prev) &&
849849
isa<TypeExpr>(cast<CallExpr>(prev)->getFn())) {
850850
assert(maxArgCount == 2);
851-
return 1;
851+
return 2;
852852
}
853853

854854
// Similarly, ".foo(...)" really applies two argument lists.
855855
if (auto *unresolvedMemberExpr = dyn_cast<UnresolvedMemberExpr>(prev)) {
856856
if (unresolvedMemberExpr->hasArguments() ||
857857
unresolvedMemberExpr->hasTrailingClosure())
858-
return 1;
859-
return 0;
858+
return 2;
859+
return 1;
860860
}
861861

862862
return getArgCount(maxArgCount);
863863
}();
864864

865-
// Sometimes we build a member reference that has an implicit
866-
// level of function application in the AST. For example,
867-
// @dynamicCallable and callAsFunction are handled this way.
868-
//
869-
// FIXME: It would be nice to simplify this and the argCount
870-
// computation above somehow.
871-
if (extraUncurryLevel)
872-
argCount++;
873-
874865
// If we have fewer argument lists than expected, build a thunk.
875866
if (argCount < maxArgCount)
876867
return true;
@@ -1062,7 +1053,7 @@ namespace {
10621053
SelectedOverload overload, DeclNameLoc memberLoc,
10631054
ConstraintLocatorBuilder locator,
10641055
ConstraintLocatorBuilder memberLocator, bool Implicit,
1065-
bool extraUncurryLevel, AccessSemantics semantics) {
1056+
AccessSemantics semantics) {
10661057
auto choice = overload.choice;
10671058
auto openedType = overload.openedType;
10681059
auto openedFullType = overload.openedFullType;
@@ -1116,8 +1107,7 @@ namespace {
11161107

11171108
bool isUnboundInstanceMember =
11181109
(!baseIsInstance && member->isInstanceMember());
1119-
bool isPartialApplication =
1120-
shouldBuildCurryThunk(choice, baseIsInstance, extraUncurryLevel);
1110+
bool isPartialApplication = shouldBuildCurryThunk(choice, baseIsInstance);
11211111

11221112
auto refTy = simplifyType(openedFullType);
11231113

@@ -2760,7 +2750,7 @@ namespace {
27602750
return buildMemberRef(
27612751
expr->getBase(), expr->getDotLoc(), selected, expr->getNameLoc(),
27622752
cs.getConstraintLocator(expr), memberLocator, expr->isImplicit(),
2763-
/*extraUncurryLevel=*/false, expr->getAccessSemantics());
2753+
expr->getAccessSemantics());
27642754
}
27652755

27662756
Expr *visitDynamicMemberRefExpr(DynamicMemberRefExpr *expr) {
@@ -2803,8 +2793,7 @@ namespace {
28032793
auto *exprLoc = cs.getConstraintLocator(expr);
28042794
auto result = buildMemberRef(
28052795
base, expr->getDotLoc(), selected, expr->getNameLoc(), exprLoc,
2806-
memberLocator, expr->isImplicit(), /*extraUncurryLevel=*/true,
2807-
AccessSemantics::Ordinary);
2796+
memberLocator, expr->isImplicit(), AccessSemantics::Ordinary);
28082797
if (!result)
28092798
return nullptr;
28102799

@@ -2952,8 +2941,7 @@ namespace {
29522941
if (cs.getType(base)->is<AnyMetatypeType>()) {
29532942
return buildMemberRef(
29542943
base, dotLoc, overload, nameLoc, cs.getConstraintLocator(expr),
2955-
ctorLocator, implicit, /*extraUncurryLevel=*/true,
2956-
AccessSemantics::Ordinary);
2944+
ctorLocator, implicit, AccessSemantics::Ordinary);
29572945
}
29582946

29592947
// The subexpression must be either 'self' or 'super'.
@@ -3126,8 +3114,7 @@ namespace {
31263114
case OverloadChoiceKind::DeclViaDynamic:
31273115
return buildMemberRef(base, dotLoc, selected, nameLoc,
31283116
cs.getConstraintLocator(expr), memberLocator,
3129-
implicit, /*extraUncurryLevel=*/false,
3130-
AccessSemantics::Ordinary);
3117+
implicit, AccessSemantics::Ordinary);
31313118

31323119
case OverloadChoiceKind::TupleIndex: {
31333120
Type toType = simplifyType(cs.getType(expr));
@@ -7141,10 +7128,19 @@ static Expr *buildCallAsFunctionMethodRef(
71417128
// Create direct reference to `callAsFunction` method.
71427129
auto *fn = apply->getFn();
71437130
auto *arg = apply->getArg();
7131+
7132+
// HACK: Temporarily push the fn expr onto the expr stack to make sure we
7133+
// don't try to prematurely close an existential when applying the curried
7134+
// member ref. This can be removed once existential opening is refactored not
7135+
// to rely on the shape of the AST prior to rewriting.
7136+
rewriter.ExprStack.push_back(fn);
7137+
SWIFT_DEFER {
7138+
rewriter.ExprStack.pop_back();
7139+
};
7140+
71447141
auto *declRef = rewriter.buildMemberRef(
71457142
fn, /*dotLoc*/ SourceLoc(), selected, DeclNameLoc(arg->getStartLoc()),
7146-
calleeLoc, calleeLoc, /*implicit*/ true,
7147-
/*extraUncurryLevel=*/true, AccessSemantics::Ordinary);
7143+
calleeLoc, calleeLoc, /*implicit*/ true, AccessSemantics::Ordinary);
71487144
if (!declRef)
71497145
return nullptr;
71507146
declRef->setImplicit(apply->isImplicit());
@@ -7174,11 +7170,19 @@ ExprRewriter::buildDynamicCallable(ApplyExpr *apply, SelectedOverload selected,
71747170
auto argumentLabel = methodType->getParams()[0].getLabel();
71757171
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;
71767172

7173+
// HACK: Temporarily push the fn expr onto the expr stack to make sure we
7174+
// don't try to prematurely close an existential when applying the curried
7175+
// member ref. This can be removed once existential opening is refactored not
7176+
// to rely on the shape of the AST prior to rewriting.
7177+
ExprStack.push_back(fn);
7178+
SWIFT_DEFER {
7179+
ExprStack.pop_back();
7180+
};
7181+
71777182
// Construct expression referencing the `dynamicallyCall` method.
71787183
auto member = buildMemberRef(fn, SourceLoc(), selected,
71797184
DeclNameLoc(method->getNameLoc()), loc, loc,
7180-
/*implicit=*/true, /*extraUncurryLevel=*/true,
7181-
AccessSemantics::Ordinary);
7185+
/*implicit=*/true, AccessSemantics::Ordinary);
71827186

71837187
// Construct argument to the method (either an array or dictionary
71847188
// expression).
@@ -7536,7 +7540,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
75367540
Expr *declRef = buildMemberRef(fn, /*dotLoc=*/SourceLoc(), *selected,
75377541
DeclNameLoc(fn->getEndLoc()), locator,
75387542
ctorLocator, /*implicit=*/true,
7539-
/*extraUncurryLevel=*/true,
75407543
AccessSemantics::Ordinary);
75417544
if (!declRef)
75427545
return nullptr;

test/SILGen/call_as_function.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s
2+
3+
struct S {
4+
func callAsFunction(_ x: Int) -> Int! { nil }
5+
}
6+
7+
protocol P1 {
8+
func callAsFunction()
9+
}
10+
11+
protocol P2 {
12+
func callAsFunction() -> Self
13+
}
14+
15+
class C {
16+
func callAsFunction(_ x: String) -> Self { return self }
17+
}
18+
19+
// CHECK-LABEL: sil hidden [ossa] @$s16call_as_function05test_a1_b1_C0yyAA1SV_AA2P1_pAA2P2_pxtAA1CCRbzlF : $@convention(thin) <T where T : C> (S, @in_guaranteed P1, @in_guaranteed P2, @guaranteed T) -> ()
20+
func test_call_as_function<T : C>(_ s: S, _ p1: P1, _ p2: P2, _ t: T) {
21+
// CHECK: function_ref @$s16call_as_function1SV0A10AsFunctionySiSgSiF : $@convention(method) (Int, S) -> Optional<Int>
22+
// CHECK: switch_enum %{{.+}} : $Optional<Int>
23+
let _: Int = s(0)
24+
25+
// SR-12590: SILGen crash on existential callAsFunction.
26+
// CHECK: witness_method $@opened({{.+}}) P1, #P1.callAsFunction : <Self where Self : P1> (Self) -> () -> ()
27+
p1()
28+
29+
// CHECK: witness_method $@opened({{.+}}) P2, #P2.callAsFunction : <Self where Self : P2> (Self) -> () -> Self
30+
_ = p2()
31+
32+
// CHECK: class_method %{{.+}} : $C, #C.callAsFunction : (C) -> (String) -> @dynamic_self C, $@convention(method) (@guaranteed String, @guaranteed C) -> @owned C
33+
// CHECK: unchecked_ref_cast %{{.+}} : $C to $T
34+
_ = t("")
35+
}
36+

test/SILGen/dynamic_callable_attribute.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ func test_dynamic_callables<T : C>(_ s: S, _ p1: P1, _ p2: P2, _ t: T) {
6666
// CHECK: switch_enum %{{.+}} : $Optional<Int>
6767
let _: Int = s(0)
6868

69+
// CHECK: witness_method $@opened({{.+}}) P1, #P1.dynamicallyCall : <Self where Self : P1> (Self) -> ([String : Any]) -> ()
70+
p1(x: 5)
71+
72+
// CHECK: witness_method $@opened({{.+}}) P2, #P2.dynamicallyCall : <Self where Self : P2> (Self) -> ([Int]) -> Self
73+
_ = p2()
74+
6975
// CHECK: class_method %{{.+}} : $C, #C.dynamicallyCall : (C) -> ([String : String]) -> @dynamic_self C, $@convention(method) (@guaranteed Dictionary<String, String>, @guaranteed C) -> @owned C
7076
// CHECK: unchecked_ref_cast %{{.+}} : $C to $T
7177
_ = t("")

0 commit comments

Comments
 (0)