[CS] Only use unsolved applicable fn for @dynamicMemberLookup cases

This works around the fact that existential opening does not currently
work correctly in cases where the argument isn't resolved before the
applicable fn is solved.
This commit is contained in:
Hamish Knight
2025-10-19 13:14:00 +01:00
parent 9147adc7d5
commit 0a1905acba
4 changed files with 67 additions and 10 deletions

View File

@@ -1162,12 +1162,27 @@ namespace {
// Add the constraint that the index expression's type be convertible
// to the input type of the subscript operator.
// We add this as an unsolved constraint before adding the member
// constraint since some member constraints such as key-path dynamic
// member require the applicable function constraint to be available.
CS.addUnsolvedConstraint(Constraint::createApplicableFunction(
CS, FunctionType::get(params, outputTy), memberTy,
/*trailingClosureMatching=*/std::nullopt, CurDC, fnLocator));
auto addApplicableFn = [&]() {
CS.addApplicationConstraint(
FunctionType::get(params, outputTy), memberTy,
/*trailingClosureMatching=*/std::nullopt, CurDC, fnLocator);
};
// If we have a dynamic member base we need to add the applicable function
// first since solving the member constraint requires the constraint to be
// available since it may retire it. We can't yet do this in the general
// case since the simplifying of the applicable fn in CSGen is currently
// load-bearing for existential opening.
// FIXME: Once existential opening is no longer sensitive to solving
// order, we ought to be able to just always record the applicable fn as
// an unsolved constraint before the member.
auto hasDynamicMemberLookup = CS.simplifyType(baseTy)
->getRValueType()
->getMetatypeInstanceType()
->eraseDynamicSelfType()
->hasDynamicMemberLookupAttribute();
if (hasDynamicMemberLookup)
addApplicableFn();
// FIXME: synthesizeMaterializeForSet() wants to statically dispatch to
// a known subscript here. This might be cleaner if we split off a new
@@ -1184,6 +1199,11 @@ namespace {
/*outerAlternatives=*/{}, memberLocator);
}
// If we don't have a dynamic member, we add the application after the
// member, see the above comment.
if (!hasDynamicMemberLookup)
addApplicableFn();
if (CS.performanceHacksEnabled()) {
Type fixedOutputType =
CS.getFixedTypeRecursive(outputTy, /*wantRValue=*/false);

View File

@@ -115,11 +115,11 @@ func f_45262(block: () -> (), other: () -> Int) {
struct S {
init<T>(_ x: T, _ y: T) {} // expected-note {{generic parameters are always considered '@escaping'}}
subscript<T>() -> (T, T) -> Void { { _, _ in } }
subscript<T>() -> (T, T) -> Void { { _, _ in } } // expected-note {{generic parameters are always considered '@escaping'}}
init(fn: () -> Int) { // expected-note {{parameter 'fn' is implicitly non-escaping}}
init(fn: () -> Int) {
self.init({ 0 }, fn) // expected-error {{converting non-escaping parameter 'fn' to generic parameter 'T' may allow it to escape}}
_ = self[]({ 0 }, fn) // expected-error {{passing non-escaping parameter 'fn' to function expecting an '@escaping' closure}}
_ = self[]({ 0 }, fn) // expected-error {{converting non-escaping parameter 'fn' to generic parameter 'T' may allow it to escape}}
}
}

View File

@@ -601,3 +601,18 @@ struct TestIssue56837 {
_ = value[type: Int8.max]
}
}
@dynamicMemberLookup
class TestDynamicSelf {
struct S {
subscript() -> Int { 0 }
}
func foo() -> Self {
// Make sure we can do dynamic member lookup on a dynamic self.
_ = self[]
return self
}
subscript<T>(dynamicMember dynamicMember: KeyPath<S, T>) -> T {
fatalError()
}
}

View File

@@ -3,7 +3,7 @@
protocol P {}
func g(_: some P) {}
// expected-note@-1 {{required by global function 'g' where 'some P' = 'any P'}}
// expected-note@-1 2{{required by global function 'g' where 'some P' = 'any P'}}
// rdar://problem/160389221
func good(_ x: Array<any P>) {
@@ -28,3 +28,25 @@ func bad(_ x: Array<any P>) {
// expected-error@-1 {{type 'any P' cannot conform to 'P'}}
// expected-note@-2 {{only concrete types such as structs, enums and classes can conform to protocols}}
}
func testSubscript() {
struct S1<T> {
subscript() -> T { fatalError() }
}
func foo(_ xs: S1<any P>) {
g(xs[])
}
struct S2<T> {
subscript() -> Int { fatalError() }
subscript() -> T { fatalError() }
}
func foo(_ xs: S2<any P>) {
// FIXME: This should work, if you fix this you can also remove the
// dynamic member lookup hack in `addSubscriptConstraints`, we should always
// just add the applicable fn as an unsolved constraint before the member.
g(xs[])
// expected-error@-1 {{type 'any P' cannot conform to 'P'}}
// expected-note@-2 {{only concrete types such as structs, enums and classes can conform to protocols}}
}
}