diff --git a/lib/AST/ConformanceLookupTable.cpp b/lib/AST/ConformanceLookupTable.cpp index 7381bcf3cb607..ca4aa834dea50 100644 --- a/lib/AST/ConformanceLookupTable.cpp +++ b/lib/AST/ConformanceLookupTable.cpp @@ -599,6 +599,11 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances( } } + auto isUnavailable = [](DeclContext *dc) -> bool { + auto *ext = dyn_cast(dc); + return ext && ext->isUnavailable(); + }; + // If only one of the conformances is unconditionally available on the // current deployment target, pick that one. // @@ -610,7 +615,9 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances( rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) { // Diagnose conflicting marker protocol conformances that differ in // un-availability. - diagnoseSuperseded = lhs->getProtocol()->isMarkerProtocol(); + diagnoseSuperseded = (lhs->getProtocol()->isMarkerProtocol() && + isUnavailable(lhs->getDeclContext()) != + isUnavailable(rhs->getDeclContext())); return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() ? Ordering::Before diff --git a/test/Sema/Inputs/conformance_availability_implied_other.swift b/test/Sema/Inputs/conformance_availability_implied_other.swift new file mode 100644 index 0000000000000..7e045ec5cbea0 --- /dev/null +++ b/test/Sema/Inputs/conformance_availability_implied_other.swift @@ -0,0 +1,8 @@ + +@available(macOS 200, *) +extension Conformer1: Derived2 {} + +@available(macOS 100, *) +extension Conformer2: Derived1 {} +// expected-error@-1 {{conformance of 'Conformer2' to 'Base' is only available in macOS 200 or newer}} + diff --git a/test/Sema/conformance_availability_implied.swift b/test/Sema/conformance_availability_implied.swift new file mode 100644 index 0000000000000..00c8f0a4019ad --- /dev/null +++ b/test/Sema/conformance_availability_implied.swift @@ -0,0 +1,59 @@ +// RUN: %target-typecheck-verify-swift -swift-version 6 +// REQUIRES: OS=macosx + +// Protocols: + +protocol Base { + func f() // expected-note {{protocol requirement here}} +} + +func takesBase(_: T.Type) {} + +protocol Derived1: Base {} +protocol Derived2: Base {} + +@_marker protocol MarkerBase {} + +func takesMarkerBase(_: T.Type) {} + +protocol MarkerDerived1: MarkerBase {} +protocol MarkerDerived2: MarkerBase {} + +// Verify that the implied conformance is macOS 100: +struct Conformer1 {} + +@available(macOS 100, *) +extension Conformer1: Derived1 { + func f() {} // okay! +} + +@available(macOS 200, *) +extension Conformer1: Derived2 {} + +takesBase(Conformer1.self) +// expected-error@-1 {{conformance of 'Conformer1' to 'Base' is only available in macOS 100 or newer}} +// expected-note@-2 {{add 'if #available' version check}} + +// No warning about redundant MarkerBase conformance (rdar://142873265): +@available(macOS 100, *) +extension Conformer1: MarkerDerived1 {} + +@available(macOS 200, *) +extension Conformer1: MarkerDerived2 {} + +takesMarkerBase(Conformer1.self) +// expected-error@-1 {{conformance of 'Conformer1' to 'MarkerBase' is only available in macOS 100 or newer}} +// expected-note@-2 {{add 'if #available' version check}} + +// Bad availability on the Base.f() witness: +struct Conformer2 {} + +@available(macOS 100, *) +extension Conformer2: Derived1 { +// expected-error@-1 {{protocol 'Base' requires 'f()' to be available in macOS 100 and newer}} +} + +@available(macOS 200, *) +extension Conformer2: Derived2 { + func f() {} // expected-note {{'f()' declared here}} +} diff --git a/test/Sema/conformance_availability_implied_multifile.swift b/test/Sema/conformance_availability_implied_multifile.swift new file mode 100644 index 0000000000000..fccb6a9f94834 --- /dev/null +++ b/test/Sema/conformance_availability_implied_multifile.swift @@ -0,0 +1,48 @@ +// RUN: %target-swift-frontend -typecheck -verify %s %S/Inputs/conformance_availability_implied_other.swift -swift-version 6 +// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/conformance_availability_implied_other.swift %s -swift-version 6 +// REQUIRES: OS=macosx + +protocol Base { + func f() +} + +func takesBase(_: T.Type) {} + +protocol Derived1: Base {} +protocol Derived2: Base {} + +// Verify that the implied conformance is macOS 100: +struct Conformer1 {} + +@available(macOS 100, *) +extension Conformer1: Derived1 { + func f() {} // okay! +} + +// Note that Conformer1: Derived2 is in the other file + +func check1() { +// expected-note@-1 {{add '@available' attribute to enclosing global function}} + takesBase(Conformer1.self) + // expected-error@-1 {{conformance of 'Conformer1' to 'Base' is only available in macOS 100 or newer}} + // expected-note@-2 {{add 'if #available' version check}} +} + +// Verify that the implied conformance is macOS 200: +// FIXME: This appears to be unsound! +struct Conformer2 {} + +@available(macOS 200, *) +extension Conformer2: Derived2 { + func f() {} +} + +// Note that Conformer2: Derived1 is in the other file + +func check2() { +// expected-note@-1 {{add '@available' attribute to enclosing global function}} + takesBase(Conformer2.self) + // expected-error@-1 {{conformance of 'Conformer2' to 'Base' is only available in macOS 200 or newer}} + // expected-note@-2 {{add 'if #available' version check}} +} +