Skip to content

Commit fc5cb09

Browse files
committed
[CSRanking] Swift 4.1 compatibility hack for favouring properties on concrete types over protocols
Changes in shadowing behaviour by #15412 caused a property on a concrete type to no longer shadow a protocol property member, which created unintentional ambiguities in 4.2. This commit ensures we at least keep these cases unambiguous in Swift 5 under Swift 4 compatibility mode. This is intentionally narrow in order to best preserve source compatibility under Swift 4 mode by ensuring we don't introduce any new ambiguities. Resolves SR-7425, SR-7940 & SR-8343.
1 parent 4a9b30a commit fc5cb09

File tree

3 files changed

+117
-1
lines changed

3 files changed

+117
-1
lines changed

lib/Sema/CSRanking.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,9 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
775775
bool isStdlibOptionalMPlusOperator1 = false;
776776
bool isStdlibOptionalMPlusOperator2 = false;
777777

778+
bool isVarAndNotProtocol1 = false;
779+
bool isVarAndNotProtocol2 = false;
780+
778781
auto getWeight = [&](ConstraintLocator *locator) -> unsigned {
779782
if (auto *anchor = locator->getAnchor()) {
780783
auto weight = weights.find(anchor);
@@ -981,6 +984,32 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
981984
}
982985
}
983986

987+
// Swift 4.1 compatibility hack: If everything else is considered equal,
988+
// favour a property on a concrete type over a protocol property member.
989+
//
990+
// This hack is required due to changes in shadowing behaviour where a
991+
// protocol property member will no longer shadow a property on a concrete
992+
// type, which created unintentional ambiguities in 4.2. This hack ensures
993+
// we at least keep these cases unambiguous in Swift 5 under Swift 4
994+
// compatibility mode. Don't however apply this hack for decls found through
995+
// dynamic lookup, as we want the user to have to disambiguate those.
996+
//
997+
// This is intentionally narrow in order to best preserve source
998+
// compatibility under Swift 4 mode by ensuring we don't introduce any new
999+
// ambiguities. This will become a more general "is more specialised" rule
1000+
// in Swift 5 mode.
1001+
if (!tc.Context.isSwiftVersionAtLeast(5) &&
1002+
choice1.getKind() != OverloadChoiceKind::DeclViaDynamic &&
1003+
choice2.getKind() != OverloadChoiceKind::DeclViaDynamic &&
1004+
isa<VarDecl>(decl1) && isa<VarDecl>(decl2)) {
1005+
auto *nominal1 = dc1->getSelfNominalTypeDecl();
1006+
auto *nominal2 = dc2->getSelfNominalTypeDecl();
1007+
if (nominal1 && nominal2 && nominal1 != nominal2) {
1008+
isVarAndNotProtocol1 = !isa<ProtocolDecl>(nominal1);
1009+
isVarAndNotProtocol2 = !isa<ProtocolDecl>(nominal2);
1010+
}
1011+
}
1012+
9841013
// FIXME: Lousy hack for ?? to prefer the catamorphism (flattening)
9851014
// over the mplus (non-flattening) overload if all else is equal.
9861015
if (decl1->getBaseName() == "??") {
@@ -1134,6 +1163,14 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11341163
score1 += isStdlibOptionalMPlusOperator2;
11351164
}
11361165

1166+
// All other things being equal, apply the Swift 4.1 compatibility hack for
1167+
// preferring var members in concrete types over a protocol requirement
1168+
// (see the comment above for the rationale of this hack).
1169+
if (!tc.Context.isSwiftVersionAtLeast(5) && score1 == score2) {
1170+
score1 += isVarAndNotProtocol1;
1171+
score2 += isVarAndNotProtocol2;
1172+
}
1173+
11371174
// FIXME: There are type variables and overloads not common to both solutions
11381175
// that haven't been considered. They make the systems different, but don't
11391176
// affect ranking. We need to handle this.

test/Constraints/rdar39401774.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -swift-version 5
22

33
class A<T> {
44
var foo: Int? { return 42 } // expected-note {{found this candidate}}

test/Constraints/sr7425.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 4
2+
3+
protocol X {
4+
var foo: Int { get }
5+
var bar: Int { get }
6+
}
7+
8+
class Y {
9+
var foo: Int = 0
10+
}
11+
extension Y {
12+
var bar: Int { return foo }
13+
}
14+
15+
protocol Z : Y {
16+
var foo: Int { get }
17+
var bar: Int { get }
18+
}
19+
20+
class GenericClass<T> {
21+
var foo: T
22+
init(_ foo: T) { self.foo = foo }
23+
}
24+
extension GenericClass {
25+
var bar: T { return foo }
26+
}
27+
28+
// Make sure we keep all of the following cases unambiguous to retain source compatibility with Swift 4.1.
29+
30+
func testGenericPropertyProtocolClass<T : X & Y>(_ t: T) {
31+
_ = t.foo
32+
_ = t.bar
33+
}
34+
35+
func testExistentialPropertyProtocolClass(_ t: X & Y) {
36+
_ = t.foo
37+
_ = t.bar
38+
}
39+
40+
func testGenericPropertySubclassConstrainedProtocol<T : Z>(_ t: T) {
41+
_ = t.foo
42+
_ = t.bar
43+
}
44+
45+
func testExistentialPropertySubclassConstrainedProtocol(_ t: Z) {
46+
_ = t.foo
47+
_ = t.bar
48+
}
49+
50+
func testExistentialPropertyProtocolGenericClass(_ t: GenericClass<Int> & X) {
51+
_ = t.foo
52+
_ = t.bar
53+
}
54+
55+
func testExistentialPropertyProtocolGenericClass(_ t: GenericClass<String> & X) {
56+
_ = t.foo
57+
_ = t.bar
58+
}
59+
60+
extension X where Self : Y {
61+
func testGenericPropertyProtocolClass(_ x: Self) {
62+
_ = self.foo
63+
_ = self.bar
64+
}
65+
}
66+
67+
extension X where Self : GenericClass<Int> {
68+
func testGenericPropertyProtocolGenericClass(_ x: Self) {
69+
_ = self.foo
70+
_ = self.bar
71+
}
72+
}
73+
74+
extension X where Self : GenericClass<String> {
75+
func testGenericPropertyProtocolGenericClass(_ x: Self) {
76+
_ = self.foo
77+
_ = self.bar
78+
}
79+
}

0 commit comments

Comments
 (0)