Skip to content

[4.2] [Sema] Diagnose redundant conditional conformances more specifically. #16247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1697,10 +1697,19 @@ ERROR(witness_unavailable,none,

ERROR(redundant_conformance,none,
"redundant conformance of %0 to protocol %1", (Type, DeclName))
ERROR(redundant_conformance_conditional,none,
"conflicting conformance of %0 to protocol %1; there cannot be more "
"than one conformance, even with different conditional bounds",
(Type, DeclName))
WARNING(redundant_conformance_adhoc,none,
"conformance of %0 to protocol %1 was already stated in "
"%select{the protocol's|the type's}2 module %3",
(Type, DeclName, bool, Identifier))
WARNING(redundant_conformance_adhoc_conditional,none,
"conformance of %0 to protocol %1 conflicts with that stated in "
"%select{the protocol's|the type's}2 module %3 and will be ignored; "
"there cannot be more than one conformance, even with different conditional bounds",
(Type, DeclName, bool, Identifier))
NOTE(redundant_conformance_witness_ignored,none,
"%0 %1 will not be used to satisfy the conformance to %2",
(DescriptiveDeclKind, DeclName, DeclName))
Expand Down
20 changes: 15 additions & 5 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4603,6 +4603,12 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,

// Complain about the redundant conformance.

auto currentSig = dc->getGenericSignatureOfContext();
auto existingSig = diag.ExistingDC->getGenericSignatureOfContext();
auto differentlyConditional = currentSig && existingSig &&
currentSig->getCanonicalSignature() !=
existingSig->getCanonicalSignature();

// If we've redundantly stated a conformance for which the original
// conformance came from the module of the type or the module of the
// protocol, just warn; we'll pick up the original conformance.
Expand All @@ -4614,11 +4620,13 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
extendedNominal->getParentModule()->getName() ||
existingModule == diag.Protocol->getParentModule())) {
// Warn about the conformance.
diagnose(diag.Loc, diag::redundant_conformance_adhoc,
dc->getDeclaredInterfaceType(),
auto diagID = differentlyConditional
? diag::redundant_conformance_adhoc_conditional
: diag::redundant_conformance_adhoc;
diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
diag.Protocol->getName(),
existingModule->getName() ==
extendedNominal->getParentModule()->getName(),
extendedNominal->getParentModule()->getName(),
existingModule->getName());

// Complain about any declarations in this extension whose names match
Expand Down Expand Up @@ -4649,8 +4657,10 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
}
}
} else {
diagnose(diag.Loc, diag::redundant_conformance,
dc->getDeclaredInterfaceType(),
auto diagID = differentlyConditional
? diag::redundant_conformance_conditional
: diag::redundant_conformance;
diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
diag.Protocol->getName());
}

Expand Down
4 changes: 2 additions & 2 deletions test/Generics/conditional_conformances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,13 @@ struct TwoConformances<T> {}
extension TwoConformances: P2 where T: P1 {}
// expected-note@-1{{'TwoConformances<T>' declares conformance to protocol 'P2' here}}
extension TwoConformances: P2 where T: P3 {}
// expected-error@-1{{redundant conformance of 'TwoConformances<T>' to protocol 'P2'}}
// expected-error@-1{{conflicting conformance of 'TwoConformances<T>' to protocol 'P2'; there cannot be more than one conformance, even with different conditional bounds}}

struct TwoDisjointConformances<T> {}
extension TwoDisjointConformances: P2 where T == Int {}
// expected-note@-1{{'TwoDisjointConformances<T>' declares conformance to protocol 'P2' here}}
extension TwoDisjointConformances: P2 where T == String {}
// expected-error@-1{{redundant conformance of 'TwoDisjointConformances<T>' to protocol 'P2'}}
// expected-error@-1{{conflicting conformance of 'TwoDisjointConformances<T>' to protocol 'P2'; there cannot be more than one conformance, even with different conditional bounds}}


// FIXME: these cases should be equivalent (and both with the same output as the
Expand Down
19 changes: 19 additions & 0 deletions test/decl/protocol/conforms/Inputs/redundant_conformance_A.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,22 @@ public struct OtherConformsToP {
public func f() -> Int { return 0 }
}

// The downstream conformance is conditional
public struct GenericConformsToP<T> : P1 {
public func f() -> Int { return 0 }
}

public struct OtherGenericConformsToP<T> {
public func f() -> Int { return 0 }
}

// The upstream conformance is conditional
public struct GenericConditionalConformsToP<T> {}
extension GenericConditionalConformsToP: P1 where T == Int {
public typealias A = Int
public func f() -> Int { return 0 }
}

public struct OtherGenericConditionalConformsToP<T> {
public func f() -> Int { return 0 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ public protocol P2 {

extension OtherConformsToP: P1 {
}
extension OtherGenericConformsToP: P1 {}
extension OtherGenericConditionalConformsToP: P1 where T: P2 {}

extension ConformsToP: P2 {
public func f() -> Int { return 0 }
}

extension GenericConformsToP: P2 {
public func f() -> Int { return 0 }
}
extension GenericConditionalConformsToP: P2 where T: P1 {
public func f() -> Int { return 0 }
}
38 changes: 37 additions & 1 deletion test/decl/protocol/conforms/redundant_conformance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/redundant_conformance_A.swift
// RUN: %target-swift-frontend -emit-module -o %t -I %t %S/Inputs/redundant_conformance_B.swift
// RUN: %target-typecheck-verify-swift -I %t %s
// RUN: %target-typecheck-verify-swift -I %t

import redundant_conformance_A
import redundant_conformance_B
Expand All @@ -29,3 +29,39 @@ func testConformsToP(cp1: ConformsToP, ocp1: OtherConformsToP) {

let _ = ocp1.f() // okay: picks "our" OtherConformsToP.f()
}

// slightly different error messages for conditional conformances:

extension GenericConformsToP: P1 where T: P1 {
// expected-warning@-1{{conformance of 'GenericConformsToP<T>' to protocol 'P1' conflicts with that stated in the type's module 'redundant_conformance_A' and will be ignored; there cannot be more than one conformance, even with different conditional bounds}}
typealias A = Double
// expected-note@-1{{type alias 'A' will not be used to satisfy the conformance to 'P1'}}
func f() -> Double { return 0.0 }
// expected-note@-1{{instance method 'f()' will not be used to satisfy the conformance to 'P1'}}
}
extension GenericConformsToP: P2 where T: P1 {
// expected-warning@-1{{conformance of 'GenericConformsToP<T>' to protocol 'P2' conflicts with that stated in the protocol's module 'redundant_conformance_B' and will be ignored; there cannot be more than one conformance, even with different conditional bounds}}
}

extension OtherGenericConformsToP: P1 where T: P1 {
// expected-error@-1{{conflicting conformance of 'OtherGenericConformsToP<T>' to protocol 'P1'; there cannot be more than one conformance, even with different conditional bounds}}
typealias A = Double
func f() -> Double { return 0.0 }
}

extension GenericConditionalConformsToP: P1 {
// expected-warning@-1{{conformance of 'GenericConditionalConformsToP<T>' to protocol 'P1' conflicts with that stated in the type's module 'redundant_conformance_A' and will be ignored; there cannot be more than one conformance, even with different conditional bounds}}
typealias A = Double
// expected-note@-1{{type alias 'A' will not be used to satisfy the conformance to 'P1'}}
func f() -> Double { return 0.0 }
// expected-note@-1{{instance method 'f()' will not be used to satisfy the conformance to 'P1'}}
}
extension GenericConditionalConformsToP: P2 {
// expected-warning@-1{{conformance of 'GenericConditionalConformsToP<T>' to protocol 'P2' conflicts with that stated in the protocol's module 'redundant_conformance_B' and will be ignored; there cannot be more than one conformance, even with different conditional bounds}}
}

extension OtherGenericConditionalConformsToP: P1 {
// expected-error@-1{{conflicting conformance of 'OtherGenericConditionalConformsToP<T>' to protocol 'P1'; there cannot be more than one conformance, even with different conditional bounds}}
typealias A = Double
func f() -> Double { return 0.0 }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/redundant_conformance_A.swift
// RUN: %target-swift-frontend -emit-module -o %t -I %t %S/Inputs/redundant_conformance_B.swift
// RUN: %target-typecheck-verify-swift -I %t

import redundant_conformance_A
import redundant_conformance_B

// These have identical requirements to the original conformances

extension GenericConditionalConformsToP: P1 where T == Int {
// expected-warning@-1{{conformance of 'GenericConditionalConformsToP<T>' to protocol 'P1' was already stated in the type's module 'redundant_conformance_A'}}
func f() -> Int { return 0 }
// expected-note@-1{{instance method 'f()' will not be used to satisfy the conformance to 'P1'}}
}
extension GenericConditionalConformsToP: P2 where T: P1 {
// expected-warning@-1{{conformance of 'GenericConditionalConformsToP<T>' to protocol 'P2' was already stated in the protocol's module 'redundant_conformance_B'}}
func f() -> Int { return 0 }
// expected-note@-1{{instance method 'f()' will not be used to satisfy the conformance to 'P2'}}
}
extension OtherGenericConditionalConformsToP: P1 where T: P2 {
// expected-error@-1{{redundant conformance of 'OtherGenericConditionalConformsToP<T>' to protocol 'P1'}}
func f() -> Int { return 0 }
}