Skip to content

Commit 4c4e2b4

Browse files
committed
CSApply: Fix unbound ref to class method on protocol composition metatype
1 parent ecf8a35 commit 4c4e2b4

File tree

3 files changed

+137
-46
lines changed

3 files changed

+137
-46
lines changed

lib/Sema/CSApply.cpp

Lines changed: 103 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,10 @@ namespace {
10021002
auto *calleeFnTy = fnTy;
10031003

10041004
if (baseExpr) {
1005+
// Coerce the base expression to the container type.
10051006
const auto calleeSelfParam = calleeFnTy->getParams().front();
1007+
baseExpr =
1008+
coerceToType(baseExpr, calleeSelfParam.getOldType(), locator);
10061009

10071010
// If the 'self' parameter has non-trivial ownership, adjust the
10081011
// argument type accordingly.
@@ -1159,7 +1162,33 @@ namespace {
11591162
Expr *thunkBody = buildSingleCurryThunkBodyCall(
11601163
baseExpr, fnExpr, declOrClosure, thunkParamList, locator);
11611164

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

11651194
if (thunkTy->getExtInfo().isThrowing()) {
@@ -1261,20 +1290,27 @@ namespace {
12611290
cs.cacheType(selfParamRef);
12621291
}
12631292

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.
1293+
bool hasOpenedExistential = false;
12691294
Expr *selfOpenedRef = selfParamRef;
1270-
if (selfCalleeParamTy->hasOpenedExistential()) {
1295+
1296+
// If the 'self' parameter type is existential, it must be opened.
1297+
if (selfThunkParamTy->isAnyExistentialType()) {
1298+
Type openedTy = solution.OpenedExistentialTypes.lookup(
1299+
cs.getConstraintLocator(memberLocator));
1300+
assert(openedTy);
1301+
1302+
hasOpenedExistential = true;
1303+
12711304
// If we're opening an existential:
12721305
// - The type of 'memberRef' inside the thunk is written in terms of the
1273-
// open existental archetype.
1306+
// opened existental archetype.
12741307
// - The type of the thunk is written in terms of the
12751308
// erased existential bounds.
1276-
auto opaqueValueTy = selfCalleeParamTy;
1277-
if (selfCalleeParam.isInOut())
1309+
Type opaqueValueTy = openedTy;
1310+
if (selfThunkParamTy->is<ExistentialMetatypeType>())
1311+
opaqueValueTy = MetatypeType::get(opaqueValueTy);
1312+
1313+
if (selfThunkParam.isInOut())
12781314
opaqueValueTy = LValueType::get(opaqueValueTy);
12791315

12801316
selfOpenedRef = new (ctx) OpaqueValueExpr(SourceLoc(), opaqueValueTy);
@@ -1292,6 +1328,9 @@ namespace {
12921328
// build a dynamic member reference. Otherwise, build a nested
12931329
// "{ args... in self.member(args...) }" thunk that calls the member.
12941330
if (isDynamicLookup || member->getAttrs().hasAttribute<OptionalAttr>()) {
1331+
auto *const selfCalleeTy =
1332+
cs.getType(memberRef)->castTo<FunctionType>();
1333+
12951334
outerThunkBody = new (ctx) DynamicMemberRefExpr(
12961335
selfOpenedRef, SourceLoc(),
12971336
resolveConcreteDeclRef(member, memberLocator), memberLoc);
@@ -1303,10 +1342,11 @@ namespace {
13031342
memberLocator);
13041343

13051344
// Close the existential if warranted.
1306-
if (selfCalleeParamTy->hasOpenedExistential()) {
1345+
if (hasOpenedExistential) {
13071346
// If the callee's 'self' parameter has non-trivial ownership, adjust
13081347
// the argument type accordingly.
1309-
adjustExprOwnershipForParam(selfOpenedRef, selfCalleeParam);
1348+
adjustExprOwnershipForParam(selfOpenedRef,
1349+
selfCalleeTy->getParams().front());
13101350

13111351
outerThunkBody = new (ctx) OpenExistentialExpr(
13121352
selfParamRef, cast<OpaqueValueExpr>(selfOpenedRef),
@@ -1319,7 +1359,7 @@ namespace {
13191359
outerThunkTy->getResult()->castTo<FunctionType>(), memberLocator);
13201360

13211361
// Rewrite the body to close the existential if warranted.
1322-
if (selfCalleeParamTy->hasOpenedExistential()) {
1362+
if (hasOpenedExistential) {
13231363
auto *body = innerThunk->getSingleExpressionBody();
13241364
body = new (ctx) OpenExistentialExpr(
13251365
selfParamRef, cast<OpaqueValueExpr>(selfOpenedRef), body,
@@ -1452,24 +1492,28 @@ namespace {
14521492
// the base accordingly.
14531493
bool openedExistential = false;
14541494

1455-
// For a partial application, we have to open the existential inside
1456-
// the thunk itself.
14571495
auto knownOpened = solution.OpenedExistentialTypes.find(
14581496
getConstraintSystem().getConstraintLocator(
14591497
memberLocator));
14601498
if (knownOpened != solution.OpenedExistentialTypes.end()) {
14611499
// Determine if we're going to have an OpenExistentialExpr around
14621500
// this member reference.
14631501
//
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.
1502+
// For an unbound reference to a method, always open the existential
1503+
// inside the curry thunk, because we won't have a 'self' value until
1504+
// the curry thunk is applied.
1505+
//
1506+
// For a partial application of a protocol method, open the existential
1507+
// inside the curry thunk as well. This reduces abstraction and
1508+
// post-factum function type conversions, and results in better SILGen.
14681509
//
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()) {
1510+
// For a partial application of a class method, however, we always want
1511+
// the thunk to accept a class to avoid potential abstraction, so the
1512+
// existential base must be opened eagerly in order to be upcast to the
1513+
// appropriate class reference type before it is passed to the thunk.
1514+
if (!isPartialApplication ||
1515+
(!member->getDeclContext()->getSelfProtocolDecl() &&
1516+
!isUnboundInstanceMember)) {
14731517
// Open the existential before performing the member reference.
14741518
base = openExistentialReference(base, knownOpened->second, member);
14751519
baseTy = knownOpened->second;
@@ -1508,13 +1552,15 @@ namespace {
15081552
base, selfParamTy, member,
15091553
locator.withPathElement(ConstraintLocator::MemberRefBase));
15101554
} 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));
1555+
// The base of an unbound reference is unused, and thus a conversion
1556+
// is not necessary.
1557+
if (!isUnboundInstanceMember) {
1558+
if (!isExistentialMetatype || openedExistential) {
1559+
// Convert the base to an rvalue of the appropriate metatype.
1560+
base = coerceToType(
1561+
base, MetatypeType::get(isDynamic ? selfTy : containerTy),
1562+
locator.withPathElement(ConstraintLocator::MemberRefBase));
1563+
}
15181564
}
15191565

15201566
if (!base)
@@ -1635,7 +1681,7 @@ namespace {
16351681
//
16361682
// { self in { args... in self.method(args...) } }(foo)
16371683
//
1638-
// This is done instead of just hoising the expression 'foo' up
1684+
// This is done instead of just hoisting the expression 'foo' up
16391685
// into the closure, which would change evaluation order.
16401686
//
16411687
// However, for a super method reference, eg, 'let fn = super.foo',
@@ -1672,17 +1718,28 @@ namespace {
16721718
return closure;
16731719
}
16741720

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

16881745
// Replace the DeclRefExpr with a closure expression which SILGen
@@ -1695,7 +1752,10 @@ namespace {
16951752
// implicit function type conversion around the resulting expression,
16961753
// with the destination type having 'Self' swapped for the appropriate
16971754
// replacement type -- usually the base object type.
1698-
if (!member->getDeclContext()->getSelfProtocolDecl()) {
1755+
//
1756+
// Note: For unbound references this is handled inside the thunk.
1757+
if (!isUnboundInstanceMember &&
1758+
!member->getDeclContext()->getSelfProtocolDecl()) {
16991759
if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
17001760
if (func->hasDynamicSelfResult() &&
17011761
!baseTy->getOptionalObjectType()) {

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

test/Sema/dynamic_self_implicit_conversions.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %target-swift-frontend -typecheck -dump-ast %s | %FileCheck %s
22

3+
// FIXME: Make this a SILGen test instead.
34
// Even though redundant conversions are eventually optimized away, test from
45
// the get-go that we build these implicit conversions only when necessary.
56
protocol P {}
@@ -35,9 +36,9 @@ class B: A {
3536
_ = super.staticMethod()
3637
// CHECK: covariant_function_conversion_expr implicit type='() -> Self' location={{.*}}.swift:[[@LINE+1]]
3738
_ = super.staticMethod
38-
// FIXME: This could be a single conversion.
39-
// CHECK: function_conversion_expr implicit type='(Self) -> () -> Self' location={{.*}}.swift:[[@LINE+2]]
40-
// CHECK: covariant_function_conversion_expr implicit type='(A) -> () -> Self' location={{.*}}.swift:[[@LINE+1]]
39+
// CHECK-NOT: function_conversion_expr {{.*}} location={{.*}}.swift:[[@LINE+3]]
40+
// CHECK-NOT: covariant_function_conversion_expr {{.*}} location={{.*}}.swift:[[@LINE+2]]
41+
// CHECK: covariant_return_conversion_expr implicit type='Self' location={{.*}}.swift:[[@LINE+1]]
4142
_ = self.method
4243
// CHECK: covariant_function_conversion_expr implicit type='() -> Self' location={{.*}}.swift:[[@LINE+1]]
4344
_ = self.init

0 commit comments

Comments
 (0)