Skip to content

Commit a1cf598

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 31e99e5 commit a1cf598

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
@@ -484,9 +484,7 @@ namespace {
484484

485485
// Handle operator requirements found in protocols.
486486
if (auto proto = dyn_cast<ProtocolDecl>(decl->getDeclContext())) {
487-
bool isCurried = shouldBuildCurryThunk(choice,
488-
/*baseIsInstance=*/false,
489-
/*extraUncurryLevel=*/false);
487+
bool isCurried = shouldBuildCurryThunk(choice, /*baseIsInstance=*/false);
490488

491489
// If we have a concrete conformance, build a call to the witness.
492490
//
@@ -551,8 +549,7 @@ namespace {
551549
cs.cacheExprTypes(base);
552550

553551
return buildMemberRef(base, SourceLoc(), overload, loc, locator,
554-
locator, implicit, /*extraUncurryLevel=*/false,
555-
semantics);
552+
locator, implicit, semantics);
556553
}
557554

558555
if (isa<TypeDecl>(decl) && !isa<ModuleDecl>(decl)) {
@@ -662,6 +659,10 @@ namespace {
662659

663660
/// Calculates the nesting depth of the current application.
664661
unsigned getArgCount(unsigned maxArgCount) {
662+
// FIXME: Walking over the ExprStack to figure out the number of argument
663+
// lists being applied is brittle. We should instead be checking
664+
// hasAppliedSelf to figure out if the self param is applied, and looking
665+
// at the FunctionRefKind to see if the parameter list is applied.
665666
unsigned e = ExprStack.size();
666667
unsigned argCount;
667668

@@ -805,8 +806,7 @@ namespace {
805806
/// converted into a fully-applied member reference with a pair of
806807
/// closures.
807808
bool shouldBuildCurryThunk(OverloadChoice choice,
808-
bool baseIsInstance,
809-
bool extraUncurryLevel) {
809+
bool baseIsInstance) {
810810
ValueDecl *member = choice.getDecl();
811811
auto isDynamic = choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
812812

@@ -841,29 +841,20 @@ namespace {
841841
isa<CallExpr>(prev) &&
842842
isa<TypeExpr>(cast<CallExpr>(prev)->getFn())) {
843843
assert(maxArgCount == 2);
844-
return 1;
844+
return 2;
845845
}
846846

847847
// Similarly, ".foo(...)" really applies two argument lists.
848848
if (auto *unresolvedMemberExpr = dyn_cast<UnresolvedMemberExpr>(prev)) {
849849
if (unresolvedMemberExpr->hasArguments() ||
850850
unresolvedMemberExpr->hasTrailingClosure())
851-
return 1;
852-
return 0;
851+
return 2;
852+
return 1;
853853
}
854854

855855
return getArgCount(maxArgCount);
856856
}();
857857

858-
// Sometimes we build a member reference that has an implicit
859-
// level of function application in the AST. For example,
860-
// @dynamicCallable and callAsFunction are handled this way.
861-
//
862-
// FIXME: It would be nice to simplify this and the argCount
863-
// computation above somehow.
864-
if (extraUncurryLevel)
865-
argCount++;
866-
867858
// If we have fewer argument lists than expected, build a thunk.
868859
if (argCount < maxArgCount)
869860
return true;
@@ -1055,7 +1046,7 @@ namespace {
10551046
SelectedOverload overload, DeclNameLoc memberLoc,
10561047
ConstraintLocatorBuilder locator,
10571048
ConstraintLocatorBuilder memberLocator, bool Implicit,
1058-
bool extraUncurryLevel, AccessSemantics semantics) {
1049+
AccessSemantics semantics) {
10591050
auto choice = overload.choice;
10601051
auto openedType = overload.openedType;
10611052
auto openedFullType = overload.openedFullType;
@@ -1109,8 +1100,7 @@ namespace {
11091100

11101101
bool isUnboundInstanceMember =
11111102
(!baseIsInstance && member->isInstanceMember());
1112-
bool isPartialApplication =
1113-
shouldBuildCurryThunk(choice, baseIsInstance, extraUncurryLevel);
1103+
bool isPartialApplication = shouldBuildCurryThunk(choice, baseIsInstance);
11141104

11151105
auto refTy = simplifyType(openedFullType);
11161106

@@ -2753,7 +2743,7 @@ namespace {
27532743
return buildMemberRef(
27542744
expr->getBase(), expr->getDotLoc(), selected, expr->getNameLoc(),
27552745
cs.getConstraintLocator(expr), memberLocator, expr->isImplicit(),
2756-
/*extraUncurryLevel=*/false, expr->getAccessSemantics());
2746+
expr->getAccessSemantics());
27572747
}
27582748

27592749
Expr *visitDynamicMemberRefExpr(DynamicMemberRefExpr *expr) {
@@ -2796,8 +2786,7 @@ namespace {
27962786
auto *exprLoc = cs.getConstraintLocator(expr);
27972787
auto result = buildMemberRef(
27982788
base, expr->getDotLoc(), selected, expr->getNameLoc(), exprLoc,
2799-
memberLocator, expr->isImplicit(), /*extraUncurryLevel=*/true,
2800-
AccessSemantics::Ordinary);
2789+
memberLocator, expr->isImplicit(), AccessSemantics::Ordinary);
28012790
if (!result)
28022791
return nullptr;
28032792

@@ -2945,8 +2934,7 @@ namespace {
29452934
if (cs.getType(base)->is<AnyMetatypeType>()) {
29462935
return buildMemberRef(
29472936
base, dotLoc, overload, nameLoc, cs.getConstraintLocator(expr),
2948-
ctorLocator, implicit, /*extraUncurryLevel=*/true,
2949-
AccessSemantics::Ordinary);
2937+
ctorLocator, implicit, AccessSemantics::Ordinary);
29502938
}
29512939

29522940
// The subexpression must be either 'self' or 'super'.
@@ -3119,8 +3107,7 @@ namespace {
31193107
case OverloadChoiceKind::DeclViaDynamic:
31203108
return buildMemberRef(base, dotLoc, selected, nameLoc,
31213109
cs.getConstraintLocator(expr), memberLocator,
3122-
implicit, /*extraUncurryLevel=*/false,
3123-
AccessSemantics::Ordinary);
3110+
implicit, AccessSemantics::Ordinary);
31243111

31253112
case OverloadChoiceKind::TupleIndex: {
31263113
Type toType = simplifyType(cs.getType(expr));
@@ -7134,10 +7121,19 @@ static Expr *buildCallAsFunctionMethodRef(
71347121
// Create direct reference to `callAsFunction` method.
71357122
auto *fn = apply->getFn();
71367123
auto *arg = apply->getArg();
7124+
7125+
// HACK: Temporarily push the fn expr onto the expr stack to make sure we
7126+
// don't try to prematurely close an existential when applying the curried
7127+
// member ref. This can be removed once existential opening is refactored not
7128+
// to rely on the shape of the AST prior to rewriting.
7129+
rewriter.ExprStack.push_back(fn);
7130+
SWIFT_DEFER {
7131+
rewriter.ExprStack.pop_back();
7132+
};
7133+
71377134
auto *declRef = rewriter.buildMemberRef(
71387135
fn, /*dotLoc*/ SourceLoc(), selected, DeclNameLoc(arg->getStartLoc()),
7139-
calleeLoc, calleeLoc, /*implicit*/ true,
7140-
/*extraUncurryLevel=*/true, AccessSemantics::Ordinary);
7136+
calleeLoc, calleeLoc, /*implicit*/ true, AccessSemantics::Ordinary);
71417137
if (!declRef)
71427138
return nullptr;
71437139
declRef->setImplicit(apply->isImplicit());
@@ -7167,11 +7163,19 @@ ExprRewriter::buildDynamicCallable(ApplyExpr *apply, SelectedOverload selected,
71677163
auto argumentLabel = methodType->getParams()[0].getLabel();
71687164
bool useKwargsMethod = argumentLabel == ctx.Id_withKeywordArguments;
71697165

7166+
// HACK: Temporarily push the fn expr onto the expr stack to make sure we
7167+
// don't try to prematurely close an existential when applying the curried
7168+
// member ref. This can be removed once existential opening is refactored not
7169+
// to rely on the shape of the AST prior to rewriting.
7170+
ExprStack.push_back(fn);
7171+
SWIFT_DEFER {
7172+
ExprStack.pop_back();
7173+
};
7174+
71707175
// Construct expression referencing the `dynamicallyCall` method.
71717176
auto member = buildMemberRef(fn, SourceLoc(), selected,
71727177
DeclNameLoc(method->getNameLoc()), loc, loc,
7173-
/*implicit=*/true, /*extraUncurryLevel=*/true,
7174-
AccessSemantics::Ordinary);
7178+
/*implicit=*/true, AccessSemantics::Ordinary);
71757179

71767180
// Construct argument to the method (either an array or dictionary
71777181
// expression).
@@ -7529,7 +7533,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
75297533
Expr *declRef = buildMemberRef(fn, /*dotLoc=*/SourceLoc(), *selected,
75307534
DeclNameLoc(fn->getEndLoc()), locator,
75317535
ctorLocator, /*implicit=*/true,
7532-
/*extraUncurryLevel=*/true,
75337536
AccessSemantics::Ordinary);
75347537
if (!declRef)
75357538
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)