Skip to content

Commit 1e764cc

Browse files
committed
[NameLookup] Swift 4 compatibility hack for cross-module var overloads in generic type extensions
In Swift 4, we only gave custom overload types to properties defined in extensions of generic types, using the null type for any other var decl. This meant that a property defined in such an extension would never shadow a property not defined in such an extension. As a result, this permitted cross-module overloads of properties of different types on generic types in certain cases. This patch adds an exception to the shadowing rules for properties defined in generic type extensions under Swift 4 mode. Permitting cross-module property overloads in the general case would also be source breaking (causing ambiguity errors), so this can be handled in a follow-up Swift 5 mode PR if desired. Resolves SR-7341.
1 parent 2770632 commit 1e764cc

File tree

6 files changed

+219
-0
lines changed

6 files changed

+219
-0
lines changed

lib/AST/NameLookup.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,25 @@ static void recordShadowedDeclsAfterSignatureMatch(
172172
for (unsigned firstIdx : indices(decls)) {
173173
auto firstDecl = decls[firstIdx];
174174
auto firstModule = firstDecl->getModuleContext();
175+
auto firstSig = firstDecl->getOverloadSignature();
175176
for (unsigned secondIdx : range(firstIdx + 1, decls.size())) {
176177
// Determine whether one module takes precedence over another.
177178
auto secondDecl = decls[secondIdx];
178179
auto secondModule = secondDecl->getModuleContext();
179180

181+
// Swift 4 compatibility hack: Don't shadow properties defined in
182+
// extensions of generic types with properties defined elsewhere.
183+
// This is due to the fact that in Swift 4, we only gave custom overload
184+
// types to properties in extensions of generic types, otherwise we
185+
// used the null type.
186+
if (!ctx.isSwiftVersionAtLeast(5)) {
187+
auto secondSig = secondDecl->getOverloadSignature();
188+
if (firstSig.IsVariable && secondSig.IsVariable)
189+
if (firstSig.InExtensionOfGenericType !=
190+
secondSig.InExtensionOfGenericType)
191+
continue;
192+
}
193+
180194
// If one declaration is in a protocol or extension thereof and the
181195
// other is not, prefer the one that is not.
182196
if ((bool)firstDecl->getDeclContext()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
public struct HasFooGeneric<T> {
3+
public var foo: Int = 0
4+
}
5+
6+
extension HasFooGeneric {
7+
public var bar: Int { return 0 }
8+
}
9+
10+
public class HasFooNonGeneric {
11+
public var foo: Int = 0
12+
}
13+
14+
extension HasFooNonGeneric {
15+
public var bar: Int { return 0 }
16+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -emit-module -o %t -module-name=library %S/Inputs/complete_import_overloads.swift
3+
4+
// RUN: %target-swift-ide-test -code-completion -swift-version 4 -source-filename %s -code-completion-token=SELF_DOT_1 -I %t | %FileCheck %s -check-prefix=SWIFT4_SELF_DOT_1
5+
// RUN: %target-swift-ide-test -code-completion -swift-version 4 -source-filename %s -code-completion-token=SELF_DOT_2 -I %t | %FileCheck %s -check-prefix=SWIFT4_SELF_DOT_2
6+
7+
// RUN: %target-swift-ide-test -code-completion -swift-version 5 -source-filename %s -code-completion-token=SELF_DOT_1 -I %t | %FileCheck %s -check-prefix=SWIFT5_SELF_DOT_1
8+
// RUN: %target-swift-ide-test -code-completion -swift-version 5 -source-filename %s -code-completion-token=SELF_DOT_2 -I %t | %FileCheck %s -check-prefix=SWIFT5_SELF_DOT_2
9+
10+
import library
11+
12+
// Ensure we maintain compatibility with Swift 4's overload signature rules.
13+
// Variables defined in extensions of generic types had different overload
14+
// signatures to other variables, so allow overloading in such cases (SR-7341).
15+
extension HasFooGeneric {
16+
var foo: String { return "" } // foo isn't defined in a generic extension in the other module, so allow overloading in Swift 4 mode.
17+
var bar: String { return "" } // bar is defined in a generic extension in the other module, so `bar: String` always shadows it.
18+
func baz() {
19+
self.#^SELF_DOT_1^#
20+
}
21+
}
22+
23+
// SWIFT4_SELF_DOT_1: Begin completions
24+
// SWIFT4_SELF_DOT_1-DAG: Decl[InstanceVar]/CurrNominal: foo[#Int#]; name=foo
25+
// SWIFT4_SELF_DOT_1-DAG: Decl[InstanceVar]/CurrNominal: foo[#String#]; name=foo
26+
// SWIFT4_SELF_DOT_1-NOT: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar
27+
// SWIFT4_SELF_DOT_1-DAG: Decl[InstanceVar]/CurrNominal: bar[#String#]; name=bar
28+
// SWIFT4_SELF_DOT_1: End completions
29+
30+
// But in Swift 5 mode, properties from this module currently always shadow
31+
// properties from the other module – therefore meaning that the properties from
32+
// the other module never show up in the overload set.
33+
// FIX-ME: It seems reasonable for both to show up in the overload set.
34+
// SWIFT5_SELF_DOT_1: Begin completions
35+
// SWIFT5_SELF_DOT_1-NOT: Decl[InstanceVar]/CurrNominal: foo[#Int#]; name=foo
36+
// SWIFT5_SELF_DOT_1-DAG: Decl[InstanceVar]/CurrNominal: foo[#String#]; name=foo
37+
// SWIFT5_SELF_DOT_1-NOT: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar
38+
// SWIFT5_SELF_DOT_1-DAG: Decl[InstanceVar]/CurrNominal: bar[#String#]; name=bar
39+
// SWIFT5_SELF_DOT_1: End completions
40+
41+
// For non-generic types, the variable overload signature was always the
42+
// null type, so `foo/bar: String` shadows `foo/bar: Int`.
43+
extension HasFooNonGeneric {
44+
var foo: String { return "" }
45+
var bar: String { return "" }
46+
func baz() {
47+
self.#^SELF_DOT_2^#
48+
}
49+
}
50+
51+
// SWIFT4_SELF_DOT_2: Begin completions
52+
// SWIFT4_SELF_DOT_2-NOT: Decl[InstanceVar]/CurrNominal: foo[#Int#]; name=foo
53+
// SWIFT4_SELF_DOT_2-DAG: Decl[InstanceVar]/CurrNominal: foo[#String#]; name=foo
54+
// SWIFT4_SELF_DOT_2-NOT: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar
55+
// SWIFT4_SELF_DOT_2-DAG: Decl[InstanceVar]/CurrNominal: bar[#String#]; name=bar
56+
// SWIFT4_SELF_DOT_2: End completions
57+
58+
// Again, in Swift 5 mode, we currently consistently shadow the properties from
59+
// the other module.
60+
// FIX-ME: It seems reasonable to not shadow them.
61+
// SWIFT5_SELF_DOT_2: Begin completions
62+
// SWIFT5_SELF_DOT_2-NOT: Decl[InstanceVar]/CurrNominal: foo[#Int#]; name=foo
63+
// SWIFT5_SELF_DOT_2-DAG: Decl[InstanceVar]/CurrNominal: foo[#String#]; name=foo
64+
// SWIFT5_SELF_DOT_2-NOT: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar
65+
// SWIFT5_SELF_DOT_2-DAG: Decl[InstanceVar]/CurrNominal: bar[#String#]; name=bar
66+
// SWIFT5_SELF_DOT_2: End completions

test/NameBinding/Inputs/overload_vars.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,19 @@ public protocol HasFoo {
1414
public protocol HasBar {
1515
var bar: Int { get }
1616
}
17+
18+
public class HasFooGeneric<T> {
19+
public var foo: Int = 0
20+
}
21+
22+
extension HasFooGeneric {
23+
public var bar: Int { return 0 }
24+
}
25+
26+
public class HasFooNonGeneric {
27+
public var foo: Int = 0
28+
}
29+
30+
extension HasFooNonGeneric {
31+
public var bar: Int { return 0 }
32+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/overload_vars.swift
3+
// RUN: %target-typecheck-verify-swift -swift-version 4 -I %t
4+
5+
import overload_vars
6+
7+
func useString(_ str: String) {}
8+
9+
// Ensure we maintain compatibility with Swift 4's overload signature rules.
10+
// Variables defined in extensions of generic types had different overload
11+
// signatures to other variables, so allow overloading in such cases (SR-7341).
12+
extension HasFooGeneric {
13+
var foo: String { return "" } // `foo` isn't defined in a generic extension in the other module, so allow overloading in Swift 4 mode.
14+
var bar: String { return "" } // `bar` is defined in a generic extension in the other module, so `bar: String` always shadows it.
15+
16+
func baz() {
17+
let x1: Int = foo // Make sure `foo: Int` is in the overload set.
18+
_ = x1
19+
20+
let x2: String = foo // Make sure `foo: String` is in the overload set.
21+
_ = x2
22+
23+
let y1 = bar // No ambiguity error.
24+
useString(y1) // Make sure we resolved to `bar: String`.
25+
26+
// Make sure `bar: Int` is not in the overload set.
27+
let y2: Int = bar // expected-error {{cannot convert}}
28+
_ = y2
29+
}
30+
}
31+
32+
// But for non-generic types, the variable overload signature was always the
33+
// null type, so `foo/bar: String` shadows `foo/bar: Int`.
34+
extension HasFooNonGeneric {
35+
var foo: String { return "" }
36+
var bar: String { return "" }
37+
38+
func baz() {
39+
let x1 = foo // No ambiguity error.
40+
useString(x1) // Make sure we resolved to `foo: String`.
41+
42+
// Make sure `foo: Int` is not in the overload set.
43+
let x2: Int = foo // expected-error {{cannot convert}}
44+
_ = x2
45+
46+
let y1 = bar // No ambiguity error.
47+
useString(y1) // Make sure we resolved to `bar: String`.
48+
49+
// Make sure `bar: Int` is not in the overload set.
50+
let y2: Int = bar // expected-error {{cannot convert}}
51+
_ = y2
52+
}
53+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/overload_vars.swift
3+
// RUN: %target-typecheck-verify-swift -swift-version 5 -I %t
4+
5+
import overload_vars
6+
7+
func useString(_ str: String) {}
8+
9+
// In Swift 5, properties from this module currently always shadow properties
10+
// from the other module – therefore meaning that the properties from the other
11+
// module never show up in the overload set.
12+
// FIX-ME: It seems reasonable for both to show up in the overload set.
13+
14+
extension HasFooGeneric {
15+
var foo: String { return "" }
16+
var bar: String { return "" }
17+
18+
func baz() {
19+
let x1 = foo // No ambiguity error.
20+
useString(x1) // Make sure we resolved to `foo: String`.
21+
22+
// Make sure `foo: Int` is not in the overload set.
23+
let x2: Int = foo // expected-error {{cannot convert}}
24+
_ = x2
25+
26+
let y1 = bar // No ambiguity error.
27+
useString(y1) // Make sure we resolved to `bar: String`.
28+
29+
// Make sure `bar: Int` is not in the overload set.
30+
let y2: Int = bar // expected-error {{cannot convert}}
31+
_ = y2
32+
}
33+
}
34+
35+
extension HasFooNonGeneric {
36+
var foo: String { return "" }
37+
var bar: String { return "" }
38+
39+
func baz() {
40+
let x1 = foo // No ambiguity error.
41+
useString(x1) // Make sure we resolved to `foo: String`.
42+
43+
// Make sure `foo: Int` is not in the overload set.
44+
let x2: Int = foo // expected-error {{cannot convert}}
45+
_ = x2
46+
47+
let y1 = bar // No ambiguity error.
48+
useString(y1) // Make sure we resolved to `bar: String`.
49+
50+
// Make sure `bar: Int` is not in the overload set.
51+
let y2: Int = bar // expected-error {{cannot convert}}
52+
_ = y2
53+
}
54+
}

0 commit comments

Comments
 (0)