Skip to content

Commit 52804dd

Browse files
committed
[Type checker] Downgrade some "redundant conformance" errors to warnings.
When an extension introduces a conformance that already exists, the type checker will reject the conformance and then ignore it. In cases where the original conformance is in the same module as the type or protocol (but the new, redundant conformance is in some other module), downgrade this error to a warning. This helps with library-evolution cases where a library omitted a particular conformance for one of its own types/protocols in a previous version, then introduces it in a new version. The specific driver for this is the conformance of String to Collection, which affects source compatibility. Fixes rdar://problem/31104415. (cherry picked from commit f7bccb0)
1 parent e887eb2 commit 52804dd

File tree

8 files changed

+146
-18
lines changed

8 files changed

+146
-18
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,13 @@ WARNING(witness_unavailable,none,
15421542

15431543
ERROR(redundant_conformance,none,
15441544
"redundant conformance of %0 to protocol %1", (Type, DeclName))
1545+
WARNING(redundant_conformance_adhoc,none,
1546+
"conformance of %0 to protocol %1 was already stated in "
1547+
"%select{the protocol's|the type's}2 module %3",
1548+
(Type, DeclName, bool, Identifier))
1549+
NOTE(redundant_conformance_witness_ignored,none,
1550+
"%0 %1 will not be used to satisfy the conformance to %2",
1551+
(DescriptiveDeclKind, DeclName, DeclName))
15451552

15461553
// "Near matches"
15471554
WARNING(optional_req_near_match,none,

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5884,10 +5884,56 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
58845884
if (!existingDecl)
58855885
existingDecl = cast<ExtensionDecl>(diag.ExistingDC);
58865886

5887-
// Complain about redundant conformances.
5888-
diagnose(diag.Loc, diag::redundant_conformance,
5889-
dc->getDeclaredInterfaceType(),
5890-
diag.Protocol->getName());
5887+
// Complain about the redundant conformance.
5888+
5889+
// If we've redundantly stated a conformance for which the original
5890+
// conformance came from the module of the type or the module of the
5891+
// protocol, just warn; we'll pick up the original conformance.
5892+
auto existingModule = diag.ExistingDC->getParentModule();
5893+
auto extendedNominal =
5894+
diag.ExistingDC->getAsNominalTypeOrNominalTypeExtensionContext();
5895+
if (existingModule != dc->getParentModule() &&
5896+
(existingModule == extendedNominal->getParentModule() ||
5897+
existingModule == diag.Protocol->getParentModule())) {
5898+
// Warn about the conformance.
5899+
diagnose(diag.Loc, diag::redundant_conformance_adhoc,
5900+
dc->getDeclaredInterfaceType(),
5901+
diag.Protocol->getName(),
5902+
existingModule == extendedNominal->getParentModule(),
5903+
existingModule->getName());
5904+
5905+
// Complain about any declarations in this extension whose names match
5906+
// a requirement in that protocol.
5907+
SmallPtrSet<DeclName, 4> diagnosedNames;
5908+
for (auto decl : idc->getMembers()) {
5909+
if (decl->isImplicit())
5910+
continue;
5911+
5912+
auto value = dyn_cast<ValueDecl>(decl);
5913+
if (!value) continue;
5914+
5915+
if (!diagnosedNames.insert(value->getFullName()).second)
5916+
continue;
5917+
5918+
bool valueIsType = isa<TypeDecl>(value);
5919+
for (auto requirement
5920+
: diag.Protocol->lookupDirect(value->getFullName(),
5921+
/*ignoreNewExtensions=*/true)) {
5922+
auto requirementIsType = isa<TypeDecl>(requirement);
5923+
if (valueIsType != requirementIsType)
5924+
continue;
5925+
5926+
diagnose(value, diag::redundant_conformance_witness_ignored,
5927+
value->getDescriptiveKind(), value->getFullName(),
5928+
diag.Protocol->getFullName());
5929+
break;
5930+
}
5931+
}
5932+
} else {
5933+
diagnose(diag.Loc, diag::redundant_conformance,
5934+
dc->getDeclaredInterfaceType(),
5935+
diag.Protocol->getName());
5936+
}
58915937

58925938
// Special case: explain that 'RawRepresentable' conformance
58935939
// is implied for enums which already declare a raw type.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 3
2+
3+
extension String : Collection { // expected-warning{{conformance of 'String' to protocol 'Collection' was already stated in the type's module 'Swift'}}
4+
subscript (i: Index) -> String { // expected-note{{subscript 'subscript' will not be used to satisfy the conformance to 'Collection'}}
5+
get { return self }
6+
set { }
7+
}
8+
9+
var isEmpty: Bool { return false } // expected-note{{var 'isEmpty' will not be used to satisfy the conformance to 'Collection'}}
10+
11+
}
12+
13+
func testStringOps(s: String) {
14+
_ = s.isEmpty
15+
}

test/Sema/enum_post_hoc_raw_representable_with_raw_type.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
import enum_with_raw_type
77

8-
// expected-error@+1{{redundant conformance of 'Foo' to protocol 'RawRepresentable'}}
8+
// expected-warning@+1{{conformance of 'Foo' to protocol 'RawRepresentable' was already stated in the type's module 'enum_with_raw_type'}}
99
extension Foo: RawRepresentable {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
public protocol P1 {
2+
associatedtype A
3+
4+
func f() -> A
5+
}
6+
7+
public struct ConformsToP : P1 {
8+
public func f() -> Int { return 0 }
9+
}
10+
11+
public struct OtherConformsToP {
12+
public func f() -> Int { return 0 }
13+
}
14+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import redundant_conformance_A
2+
3+
public protocol P2 {
4+
associatedtype A
5+
6+
func f() -> A
7+
}
8+
9+
extension OtherConformsToP: P1 {
10+
}
11+
12+
extension ConformsToP: P2 {
13+
public func f() -> Int { return 0 }
14+
}

test/decl/protocol/conforms/placement.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -189,31 +189,31 @@ enum MFSynthesizedEnum2 : Int { case none = 0 }
189189
// ===========================================================================
190190
// Tests with conformances in imported modules
191191
// ===========================================================================
192-
extension MMExplicit1 : MMP1 { } // expected-error{{redundant conformance of 'MMExplicit1' to protocol 'MMP1'}}
192+
extension MMExplicit1 : MMP1 { } // expected-warning{{conformance of 'MMExplicit1' to protocol 'MMP1' was already stated in the type's module 'placement_module_A'}}
193193

194-
extension MMExplicit1 : MMP2a { } // expected-error{{redundant conformance of 'MMExplicit1' to protocol 'MMP2a'}}
195-
extension MMExplicit1 : MMP3a { } // expected-error{{redundant conformance of 'MMExplicit1' to protocol 'MMP3a'}}
194+
extension MMExplicit1 : MMP2a { } // expected-warning{{MMExplicit1' to protocol 'MMP2a' was already stated in the type's module 'placement_module_A}}
195+
extension MMExplicit1 : MMP3a { } // expected-warning{{conformance of 'MMExplicit1' to protocol 'MMP3a' was already stated in the type's module 'placement_module_A'}}
196196

197197
extension MMExplicit1 : MMP3b { } // okay
198198

199-
extension MMSuper1 : MMP1 { } // expected-error{{redundant conformance of 'MMSuper1' to protocol 'MMP1'}}
200-
extension MMSuper1 : MMP2a { } // expected-error{{redundant conformance of 'MMSuper1' to protocol 'MMP2a'}}
199+
extension MMSuper1 : MMP1 { } // expected-warning{{conformance of 'MMSuper1' to protocol 'MMP1' was already stated in the type's module 'placement_module_A'}}
200+
extension MMSuper1 : MMP2a { } // expected-warning{{conformance of 'MMSuper1' to protocol 'MMP2a' was already stated in the type's module 'placement_module_A'}}
201201
extension MMSuper1 : MMP3b { } // okay
202202

203-
extension MMSub1 : AnyObject { } // expected-error{{redundant conformance of 'MMSub1' to protocol 'AnyObject'}}
203+
extension MMSub1 : AnyObject { } // expected-warning{{conformance of 'MMSub1' to protocol 'AnyObject' was already stated in the type's module 'placement_module_A'}}
204204

205-
extension MMSub2 : MMP1 { } // expected-error{{redundant conformance of 'MMSub2' to protocol 'MMP1'}}
206-
extension MMSub2 : MMP2a { } // expected-error{{redundant conformance of 'MMSub2' to protocol 'MMP2a'}}
205+
extension MMSub2 : MMP1 { } // expected-warning{{conformance of 'MMSub2' to protocol 'MMP1' was already stated in the type's module 'placement_module_A'}}
206+
extension MMSub2 : MMP2a { } // expected-warning{{conformance of 'MMSub2' to protocol 'MMP2a' was already stated in the type's module 'placement_module_A'}}
207207
extension MMSub2 : MMP3b { } // okay
208208

209209
extension MMSub2 : MMAnyObjectRefinement { } // okay
210210

211-
extension MMSub3 : MMP1 { } // expected-error{{redundant conformance of 'MMSub3' to protocol 'MMP1'}}
212-
extension MMSub3 : MMP2a { } // expected-error{{redundant conformance of 'MMSub3' to protocol 'MMP2a'}}
211+
extension MMSub3 : MMP1 { } // expected-warning{{conformance of 'MMSub3' to protocol 'MMP1' was already stated in the type's module 'placement_module_A'}}
212+
extension MMSub3 : MMP2a { } // expected-warning{{conformance of 'MMSub3' to protocol 'MMP2a' was already stated in the type's module 'placement_module_A'}}
213213
extension MMSub3 : MMP3b { } // okay
214-
extension MMSub3 : AnyObject { } // expected-error{{redundant conformance of 'MMSub3' to protocol 'AnyObject'}}
214+
extension MMSub3 : AnyObject { } // expected-warning{{conformance of 'MMSub3' to protocol 'AnyObject' was already stated in the type's module 'placement_module_A'}}
215215

216-
extension MMSub4 : MMP1 { } // expected-error{{redundant conformance of 'MMSub4' to protocol 'MMP1'}}
217-
extension MMSub4 : MMP2a { } // expected-error{{redundant conformance of 'MMSub4' to protocol 'MMP2a'}}
216+
extension MMSub4 : MMP1 { } // expected-warning{{conformance of 'MMSub4' to protocol 'MMP1' was already stated in the type's module 'placement_module_B'}}
217+
extension MMSub4 : MMP2a { } // expected-warning{{conformance of 'MMSub4' to protocol 'MMP2a' was already stated in the type's module 'placement_module_B'}}
218218
extension MMSub4 : MMP3b { } // okay
219219
extension MMSub4 : AnyObjectRefinement { } // okay
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
4+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/redundant_conformance_A.swift
5+
// RUN: %target-swift-frontend -emit-module -o %t -I %t %S/Inputs/redundant_conformance_B.swift
6+
// RUN: %target-typecheck-verify-swift -I %t %s
7+
8+
import redundant_conformance_A
9+
import redundant_conformance_B
10+
11+
extension ConformsToP
12+
: P1 { // expected-warning{{conformance of 'ConformsToP' to protocol 'P1' was already stated in the type's module 'redundant_conformance_A'}}
13+
typealias A = Double // expected-note{{type alias 'A' will not be used to satisfy the conformance to 'P1'}}
14+
15+
func f() -> Double { return 0.0 } // expected-note{{instance method 'f()' will not be used to satisfy the conformance to 'P1'}}
16+
// expected-note@-1{{found this candidate}}
17+
}
18+
19+
extension ConformsToP
20+
: P2 { // expected-warning{{conformance of 'ConformsToP' to protocol 'P2' was already stated in the protocol's module 'redundant_conformance_B'}}
21+
}
22+
23+
extension OtherConformsToP : P1 { // expected-error{{redundant conformance of 'OtherConformsToP' to protocol 'P1'}}
24+
func f() -> Int { return 0 }
25+
}
26+
27+
func testConformsToP(cp1: ConformsToP, ocp1: OtherConformsToP) {
28+
// Note:
29+
let _ = cp1.f() // expected-error{{ambiguous use of 'f()'}}
30+
31+
let _ = ocp1.f() // okay: picks "our" OtherConformsToP.f()
32+
}

0 commit comments

Comments
 (0)