Skip to content

Commit 2d43be6

Browse files
Merge pull request #41978 from AnthonyLatsis/dyn-unbound-ref
CSApply: Handle unbound references to methods found via dynamic lookup
2 parents de41a58 + ad01750 commit 2d43be6

File tree

5 files changed

+182
-59
lines changed

5 files changed

+182
-59
lines changed

lib/Sema/CSApply.cpp

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -903,11 +903,6 @@ namespace {
903903
bool baseIsInstance) {
904904
ValueDecl *member = choice.getDecl();
905905

906-
// FIXME: We should finish plumbing this through for dynamic
907-
// lookup as well.
908-
if (choice.getKind() == OverloadChoiceKind::DeclViaDynamic)
909-
return false;
910-
911906
// If we're inside a selector expression, don't build the thunk.
912907
// Were not actually going to emit the member reference, just
913908
// look at the AST.
@@ -921,9 +916,11 @@ namespace {
921916
if (!baseIsInstance && member->isInstanceMember())
922917
return true;
923918

924-
// Bound optional method references are represented via
925-
// DynamicMemberRefExpr instead of a curry thunk.
926-
if (member->getAttrs().hasAttribute<OptionalAttr>())
919+
// Bound member references that are '@objc optional' or found via dynamic
920+
// lookup are always represented via DynamicMemberRefExpr instead of a
921+
// curry thunk.
922+
if (member->getAttrs().hasAttribute<OptionalAttr>() ||
923+
choice.getKind() == OverloadChoiceKind::DeclViaDynamic)
927924
return false;
928925

929926
// Figure out how many argument lists we need.
@@ -1002,7 +999,10 @@ namespace {
1002999
auto *calleeFnTy = fnTy;
10031000

10041001
if (baseExpr) {
1002+
// Coerce the base expression to the container type.
10051003
const auto calleeSelfParam = calleeFnTy->getParams().front();
1004+
baseExpr =
1005+
coerceToType(baseExpr, calleeSelfParam.getOldType(), locator);
10061006

10071007
// If the 'self' parameter has non-trivial ownership, adjust the
10081008
// argument type accordingly.
@@ -1159,7 +1159,33 @@ namespace {
11591159
Expr *thunkBody = buildSingleCurryThunkBodyCall(
11601160
baseExpr, fnExpr, declOrClosure, thunkParamList, locator);
11611161

1162-
// Coerce to the result type of the thunk.
1162+
// If we called a function with a dynamic 'Self' result, we may need some
1163+
// special handling.
1164+
if (baseExpr) {
1165+
if (auto *fnDecl = dyn_cast<AbstractFunctionDecl>(declOrClosure)) {
1166+
if (fnDecl->hasDynamicSelfResult()) {
1167+
Type convTy;
1168+
1169+
if (cs.getType(baseExpr)->hasOpenedExistential()) {
1170+
// FIXME: Sometimes we need to convert to an opened existential
1171+
// first, because CovariantReturnConversionExpr does not support
1172+
// direct conversions from a class C to an existential C & P.
1173+
convTy = cs.getType(baseExpr)->getMetatypeInstanceType();
1174+
convTy =
1175+
thunkTy->getResult()->replaceCovariantResultType(convTy, 0);
1176+
} else {
1177+
convTy = thunkTy->getResult();
1178+
}
1179+
1180+
if (!thunkBody->getType()->isEqual(convTy)) {
1181+
thunkBody = cs.cacheType(
1182+
new (ctx) CovariantReturnConversionExpr(thunkBody, convTy));
1183+
}
1184+
}
1185+
}
1186+
}
1187+
1188+
// Now, coerce to the result type of the thunk.
11631189
thunkBody = coerceToType(thunkBody, thunkTy->getResult(), locator);
11641190

11651191
if (thunkTy->getExtInfo().isThrowing()) {
@@ -1261,20 +1287,27 @@ namespace {
12611287
cs.cacheType(selfParamRef);
12621288
}
12631289

1264-
auto *const selfCalleeTy = cs.getType(memberRef)->castTo<FunctionType>();
1265-
const auto selfCalleeParam = selfCalleeTy->getParams().front();
1266-
const auto selfCalleeParamTy = selfCalleeParam.getPlainType();
1267-
1268-
// Open the 'self' parameter reference if warranted.
1290+
bool hasOpenedExistential = false;
12691291
Expr *selfOpenedRef = selfParamRef;
1270-
if (selfCalleeParamTy->hasOpenedExistential()) {
1292+
1293+
// If the 'self' parameter type is existential, it must be opened.
1294+
if (selfThunkParamTy->isAnyExistentialType()) {
1295+
Type openedTy = solution.OpenedExistentialTypes.lookup(
1296+
cs.getConstraintLocator(memberLocator));
1297+
assert(openedTy);
1298+
1299+
hasOpenedExistential = true;
1300+
12711301
// If we're opening an existential:
12721302
// - The type of 'memberRef' inside the thunk is written in terms of the
1273-
// open existental archetype.
1303+
// opened existental archetype.
12741304
// - The type of the thunk is written in terms of the
12751305
// erased existential bounds.
1276-
auto opaqueValueTy = selfCalleeParamTy;
1277-
if (selfCalleeParam.isInOut())
1306+
Type opaqueValueTy = openedTy;
1307+
if (selfThunkParamTy->is<ExistentialMetatypeType>())
1308+
opaqueValueTy = MetatypeType::get(opaqueValueTy);
1309+
1310+
if (selfThunkParam.isInOut())
12781311
opaqueValueTy = LValueType::get(opaqueValueTy);
12791312

12801313
selfOpenedRef = new (ctx) OpaqueValueExpr(SourceLoc(), opaqueValueTy);
@@ -1292,6 +1325,9 @@ namespace {
12921325
// build a dynamic member reference. Otherwise, build a nested
12931326
// "{ args... in self.member(args...) }" thunk that calls the member.
12941327
if (isDynamicLookup || member->getAttrs().hasAttribute<OptionalAttr>()) {
1328+
auto *const selfCalleeTy =
1329+
cs.getType(memberRef)->castTo<FunctionType>();
1330+
12951331
outerThunkBody = new (ctx) DynamicMemberRefExpr(
12961332
selfOpenedRef, SourceLoc(),
12971333
resolveConcreteDeclRef(member, memberLocator), memberLoc);
@@ -1303,10 +1339,11 @@ namespace {
13031339
memberLocator);
13041340

13051341
// Close the existential if warranted.
1306-
if (selfCalleeParamTy->hasOpenedExistential()) {
1342+
if (hasOpenedExistential) {
13071343
// If the callee's 'self' parameter has non-trivial ownership, adjust
13081344
// the argument type accordingly.
1309-
adjustExprOwnershipForParam(selfOpenedRef, selfCalleeParam);
1345+
adjustExprOwnershipForParam(selfOpenedRef,
1346+
selfCalleeTy->getParams().front());
13101347

13111348
outerThunkBody = new (ctx) OpenExistentialExpr(
13121349
selfParamRef, cast<OpaqueValueExpr>(selfOpenedRef),
@@ -1319,7 +1356,7 @@ namespace {
13191356
outerThunkTy->getResult()->castTo<FunctionType>(), memberLocator);
13201357

13211358
// Rewrite the body to close the existential if warranted.
1322-
if (selfCalleeParamTy->hasOpenedExistential()) {
1359+
if (hasOpenedExistential) {
13231360
auto *body = innerThunk->getSingleExpressionBody();
13241361
body = new (ctx) OpenExistentialExpr(
13251362
selfParamRef, cast<OpaqueValueExpr>(selfOpenedRef), body,
@@ -1438,7 +1475,7 @@ namespace {
14381475

14391476
const bool isUnboundInstanceMember =
14401477
(!baseIsInstance && member->isInstanceMember());
1441-
const bool isPartialApplication =
1478+
const bool needsCurryThunk =
14421479
shouldBuildCurryThunk(choice, baseIsInstance);
14431480

14441481
// The formal type of the 'self' value for the member's declaration.
@@ -1452,24 +1489,28 @@ namespace {
14521489
// the base accordingly.
14531490
bool openedExistential = false;
14541491

1455-
// For a partial application, we have to open the existential inside
1456-
// the thunk itself.
14571492
auto knownOpened = solution.OpenedExistentialTypes.find(
14581493
getConstraintSystem().getConstraintLocator(
14591494
memberLocator));
14601495
if (knownOpened != solution.OpenedExistentialTypes.end()) {
14611496
// Determine if we're going to have an OpenExistentialExpr around
14621497
// this member reference.
14631498
//
1464-
// If we have a partial application of a protocol method, we open
1465-
// the existential in the curry thunk, instead of opening it here,
1466-
// because we won't have a 'self' value until the curry thunk is
1467-
// applied.
1499+
// For an unbound reference to a method, always open the existential
1500+
// inside the curry thunk, because we won't have a 'self' value until
1501+
// the curry thunk is applied.
1502+
//
1503+
// For a partial application of a protocol method, open the existential
1504+
// inside the curry thunk as well. This reduces abstraction and
1505+
// post-factum function type conversions, and results in better SILGen.
14681506
//
1469-
// However, a partial application of a class method on a subclass
1470-
// existential does need to open the existential, so that it can be
1471-
// upcast to the appropriate class reference type.
1472-
if (!isPartialApplication || !containerTy->hasOpenedExistential()) {
1507+
// For a partial application of a class method, however, we always want
1508+
// the thunk to accept a class to avoid potential abstraction, so the
1509+
// existential base must be opened eagerly in order to be upcast to the
1510+
// appropriate class reference type before it is passed to the thunk.
1511+
if (!needsCurryThunk ||
1512+
(!member->getDeclContext()->getSelfProtocolDecl() &&
1513+
!isUnboundInstanceMember)) {
14731514
// Open the existential before performing the member reference.
14741515
base = openExistentialReference(base, knownOpened->second, member);
14751516
baseTy = knownOpened->second;
@@ -1508,13 +1549,15 @@ namespace {
15081549
base, selfParamTy, member,
15091550
locator.withPathElement(ConstraintLocator::MemberRefBase));
15101551
} else {
1511-
if (!isExistentialMetatype || openedExistential) {
1512-
// Convert the base to an rvalue of the appropriate metatype.
1513-
base = coerceToType(base,
1514-
MetatypeType::get(
1515-
isDynamic ? selfTy : containerTy),
1516-
locator.withPathElement(
1517-
ConstraintLocator::MemberRefBase));
1552+
// The base of an unbound reference is unused, and thus a conversion
1553+
// is not necessary.
1554+
if (!isUnboundInstanceMember) {
1555+
if (!isExistentialMetatype || openedExistential) {
1556+
// Convert the base to an rvalue of the appropriate metatype.
1557+
base = coerceToType(
1558+
base, MetatypeType::get(isDynamic ? selfTy : containerTy),
1559+
locator.withPathElement(ConstraintLocator::MemberRefBase));
1560+
}
15181561
}
15191562

15201563
if (!base)
@@ -1544,8 +1587,8 @@ namespace {
15441587
}
15451588

15461589
// Handle dynamic references.
1547-
if (isDynamic || (!isPartialApplication &&
1548-
member->getAttrs().hasAttribute<OptionalAttr>())) {
1590+
if (!needsCurryThunk &&
1591+
(isDynamic || member->getAttrs().hasAttribute<OptionalAttr>())) {
15491592
base = cs.coerceToRValue(base);
15501593
Expr *ref = new (context) DynamicMemberRefExpr(base, dotLoc, memberRef,
15511594
memberLoc);
@@ -1635,7 +1678,7 @@ namespace {
16351678
//
16361679
// { self in { args... in self.method(args...) } }(foo)
16371680
//
1638-
// This is done instead of just hoising the expression 'foo' up
1681+
// This is done instead of just hoisting the expression 'foo' up
16391682
// into the closure, which would change evaluation order.
16401683
//
16411684
// However, for a super method reference, eg, 'let fn = super.foo',
@@ -1644,12 +1687,12 @@ namespace {
16441687
// very specific shape, we only emit a single closure here and
16451688
// capture the original SuperRefExpr, since its evaluation does not
16461689
// have side effects, instead of abstracting out a 'self' parameter.
1647-
const auto isSuperPartialApplication = isPartialApplication && isSuper;
1690+
const auto isSuperPartialApplication = needsCurryThunk && isSuper;
16481691
if (isSuperPartialApplication) {
16491692
ref = buildSingleCurryThunk(base, declRefExpr,
16501693
cast<AbstractFunctionDecl>(member),
16511694
memberLocator);
1652-
} else if (isPartialApplication) {
1695+
} else if (needsCurryThunk) {
16531696
// Another case where we want to build a single closure is when
16541697
// we have a partial application of a constructor on a statically-
16551698
// derived metatype value. Again, there are no order of evaluation
@@ -1672,17 +1715,28 @@ namespace {
16721715
return closure;
16731716
}
16741717

1675-
auto *curryThunkTy = refTy->castTo<FunctionType>();
1676-
1677-
// Check if we need to open an existential stored inside 'self'.
1678-
auto knownOpened = solution.OpenedExistentialTypes.find(
1679-
getConstraintSystem().getConstraintLocator(
1680-
memberLocator));
1681-
if (knownOpened != solution.OpenedExistentialTypes.end()) {
1682-
curryThunkTy = curryThunkTy
1683-
->typeEraseOpenedArchetypesWithRoot(
1684-
knownOpened->second, dc)
1685-
->castTo<FunctionType>();
1718+
FunctionType *curryThunkTy = nullptr;
1719+
if (isUnboundInstanceMember) {
1720+
// For an unbound reference to a method, all conversions, including
1721+
// dynamic 'Self' handling, are done within the thunk to support
1722+
// the edge case of an unbound reference to a 'Self'-returning class
1723+
// method on a protocol metatype. The result of calling the method
1724+
// must be downcast to the opened archetype before being erased to the
1725+
// subclass existential to cope with the expectations placed
1726+
// on 'CovariantReturnConversionExpr'.
1727+
curryThunkTy = simplifyType(openedType)->castTo<FunctionType>();
1728+
} else {
1729+
curryThunkTy = refTy->castTo<FunctionType>();
1730+
1731+
// Check if we need to open an existential stored inside 'self'.
1732+
auto knownOpened = solution.OpenedExistentialTypes.find(
1733+
getConstraintSystem().getConstraintLocator(memberLocator));
1734+
if (knownOpened != solution.OpenedExistentialTypes.end()) {
1735+
curryThunkTy =
1736+
curryThunkTy
1737+
->typeEraseOpenedArchetypesWithRoot(knownOpened->second, dc)
1738+
->castTo<FunctionType>();
1739+
}
16861740
}
16871741

16881742
// Replace the DeclRefExpr with a closure expression which SILGen
@@ -1695,7 +1749,10 @@ namespace {
16951749
// implicit function type conversion around the resulting expression,
16961750
// with the destination type having 'Self' swapped for the appropriate
16971751
// replacement type -- usually the base object type.
1698-
if (!member->getDeclContext()->getSelfProtocolDecl()) {
1752+
//
1753+
// Note: For unbound references this is handled inside the thunk.
1754+
if (!isUnboundInstanceMember &&
1755+
!member->getDeclContext()->getSelfProtocolDecl()) {
16991756
if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
17001757
if (func->hasDynamicSelfResult() &&
17011758
!baseTy->getOptionalObjectType()) {

test/Interpreter/dynamic_lookup.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ func test_dynamic_lookup_f(_ obj: AnyObject) {
3939
}
4040
}
4141

42+
func test_dynamic_lookup_f_unbound(_ obj: AnyObject) {
43+
var of = AnyObject.f(obj)
44+
if of != nil {
45+
of!()
46+
} else {
47+
print("Object does not respond to the selector \"f\".\n", terminator: "")
48+
}
49+
}
50+
4251
func test_dynamic_lookup_g(_ obj: AnyObject) {
4352
var og = type(of: obj).g
4453
if og != nil {
@@ -64,6 +73,15 @@ test_dynamic_lookup_f(Y())
6473
// CHECK: Z.f()
6574
test_dynamic_lookup_f(Z())
6675

76+
// CHECK-NEXT: (AnyObject) -> Optional<() -> ()>
77+
print(type(of: AnyObject.f))
78+
// CHECK-NEXT: X.f()
79+
test_dynamic_lookup_f_unbound(X())
80+
// CHECK-NEXT: Object does not respond to the selector "f"
81+
test_dynamic_lookup_f_unbound(Y())
82+
// CHECK-NEXT: Z.f()
83+
test_dynamic_lookup_f_unbound(Z())
84+
6785
// CHECK: Class does not respond to the selector "g"
6886
test_dynamic_lookup_g(X())
6987
// CHECK: Y.g()

test/SILGen/dynamic_lookup.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ func opt_to_class(_ obj: AnyObject) {
117117
// CHECK: return [[RESULT]] : $()
118118
}
119119

120+
// CHECK-LABEL: sil hidden [ossa] @$s14dynamic_lookup20opt_to_class_unboundyyF : $@convention(thin) () -> () {
121+
// CHECK: bb0:
122+
// CHECK: metatype $@thin AnyObject.Protocol
123+
// CHECK: function_ref @$[[THUNK_NAME:[_a-zA-Z0-9]+]]
124+
// CHECK: } // end sil function '$s14dynamic_lookup20opt_to_class_unboundyyF'
125+
//
126+
// CHECK: sil private [ossa] @$[[THUNK_NAME]] : $@convention(thin) (@guaranteed AnyObject) -> @owned Optional<@callee_guaranteed () -> ()> {
127+
// CHECK: bb0(%0 : @guaranteed $AnyObject):
128+
// CHECK: [[OPENED:%[0-9]+]] = open_existential_ref %0 : $AnyObject to $[[OPENED_TY:@opened\("[-A-F0-9]+"\) AnyObject]]
129+
// CHECK: [[OPENED_COPY:%[0-9]+]] = copy_value [[OPENED]]
130+
// CHECK: alloc_stack $Optional<@callee_guaranteed () -> ()>
131+
// CHECK: dynamic_method_br [[OPENED_COPY]] : $[[OPENED_TY]], #X.f!foreign, bb1, bb2
132+
// CHECK: } // end sil function '$[[THUNK_NAME]]'
133+
func opt_to_class_unbound() {
134+
let f = AnyObject.f
135+
}
136+
120137
// CHECK-LABEL: sil hidden [ossa] @$s14dynamic_lookup20forced_without_outer{{[_0-9a-zA-Z]*}}F
121138
func forced_without_outer(_ obj: AnyObject) {
122139
// CHECK: dynamic_method_br

test/SILGen/subclass_existentials.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,36 @@ func methodCalls(
179179
// CHECK-NEXT: }
180180
}
181181

182+
183+
// CHECK-LABEL: sil hidden [ossa] @$s21subclass_existentials29methodCallsOnProtocolMetatypeyyF : $@convention(thin) () -> () {
184+
// CHECK: metatype $@thin (Base<Int> & P).Protocol
185+
// CHECK: function_ref @$[[THUNK1_NAME:[_a-zA-Z0-9]+]]
186+
// CHECK: } // end sil function '$s21subclass_existentials29methodCallsOnProtocolMetatypeyyF'
187+
//
188+
// CHECK: sil private [ossa] @$[[THUNK1_NAME]] : $@convention(thin) (@guaranteed Base<Int> & P) -> @owned @callee_guaranteed () -> @owned Base<Int> & P {
189+
// CHECK: bb0(%0 : @guaranteed $Base<Int> & P):
190+
// CHECK: [[THUNK2:%[0-9]+]] = function_ref @$[[THUNK2_NAME:[_a-zA-Z0-9]+]]
191+
// CHECK: [[SELF_COPY:%[0-9]+]] = copy_value %0
192+
// CHECK: [[RESULT:%[0-9]+]] = partial_apply [callee_guaranteed] [[THUNK2]]([[SELF_COPY]])
193+
// CHECK: return [[RESULT]]
194+
// CHECK: } // end sil function '$[[THUNK1_NAME]]'
195+
//
196+
// CHECK: sil private [ossa] @$[[THUNK2_NAME]] : $@convention(thin) (@guaranteed Base<Int> & P) -> @owned Base<Int> & P {
197+
// CHECK: bb0(%0 : @guaranteed $Base<Int> & P):
198+
// CHECK: [[OPENED:%[0-9]+]] = open_existential_ref %0 : $Base<Int> & P to $[[OPENED_TY:@opened\("[-A-F0-9]+"\) Base<Int> & P]]
199+
// CHECK: [[OPENED_COPY:%[0-9]+]] = copy_value [[OPENED]]
200+
// CHECK: [[CLASS:%[0-9]+]] = upcast [[OPENED_COPY]] : $[[OPENED_TY]] to $Base<Int>
201+
// CHECK: [[METHOD:%[0-9]+]] = class_method [[CLASS]] : $Base<Int>, #Base.classSelfReturn
202+
// CHECK: [[RESULT:%[0-9]+]] = apply [[METHOD]]<Int>([[CLASS]]) : $@convention(method) <τ_0_0> (@guaranteed Base<τ_0_0>) -> @owned Base<τ_0_0>
203+
// CHECK: destroy_value [[CLASS]]
204+
// CHECK: [[TO_OPENED:%[0-9]+]] = unchecked_ref_cast [[RESULT]] : $Base<Int> to $[[OPENED_TY]]
205+
// CHECK: [[ERASED:%[0-9]+]] = init_existential_ref [[TO_OPENED]] : $[[OPENED_TY]]
206+
// CHECK: return [[ERASED]]
207+
// CHECK: } // end sil function '$[[THUNK2_NAME]]'
208+
func methodCallsOnProtocolMetatype() {
209+
let _ = (Base<Int> & P).classSelfReturn
210+
}
211+
182212
protocol PropertyP {
183213
var p: PropertyP & PropertyC { get set }
184214

0 commit comments

Comments
 (0)