Skip to content

Commit fcd94f1

Browse files
authored
Merge pull request #82308 from slavapestov/marker-protocol-conformance-availability-mismatch-tweak
Sema: Relax diagnosis of implied marker protocol conformances with mismatched availability
2 parents 09dc092 + dad3583 commit fcd94f1

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

lib/AST/ConformanceLookupTable.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,11 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
599599
}
600600
}
601601

602+
auto isUnavailable = [](DeclContext *dc) -> bool {
603+
auto *ext = dyn_cast<ExtensionDecl>(dc);
604+
return ext && ext->isUnavailable();
605+
};
606+
602607
// If only one of the conformances is unconditionally available on the
603608
// current deployment target, pick that one.
604609
//
@@ -610,7 +615,9 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
610615
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
611616
// Diagnose conflicting marker protocol conformances that differ in
612617
// un-availability.
613-
diagnoseSuperseded = lhs->getProtocol()->isMarkerProtocol();
618+
diagnoseSuperseded = (lhs->getProtocol()->isMarkerProtocol() &&
619+
isUnavailable(lhs->getDeclContext()) !=
620+
isUnavailable(rhs->getDeclContext()));
614621

615622
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
616623
? Ordering::Before
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
@available(macOS 200, *)
3+
extension Conformer1: Derived2 {}
4+
5+
@available(macOS 100, *)
6+
extension Conformer2: Derived1 {}
7+
// expected-error@-1 {{conformance of 'Conformer2' to 'Base' is only available in macOS 200 or newer}}
8+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 6
2+
// REQUIRES: OS=macosx
3+
4+
// Protocols:
5+
6+
protocol Base {
7+
func f() // expected-note {{protocol requirement here}}
8+
}
9+
10+
func takesBase<T: Base>(_: T.Type) {}
11+
12+
protocol Derived1: Base {}
13+
protocol Derived2: Base {}
14+
15+
@_marker protocol MarkerBase {}
16+
17+
func takesMarkerBase<T: MarkerBase>(_: T.Type) {}
18+
19+
protocol MarkerDerived1: MarkerBase {}
20+
protocol MarkerDerived2: MarkerBase {}
21+
22+
// Verify that the implied conformance is macOS 100:
23+
struct Conformer1 {}
24+
25+
@available(macOS 100, *)
26+
extension Conformer1: Derived1 {
27+
func f() {} // okay!
28+
}
29+
30+
@available(macOS 200, *)
31+
extension Conformer1: Derived2 {}
32+
33+
takesBase(Conformer1.self)
34+
// expected-error@-1 {{conformance of 'Conformer1' to 'Base' is only available in macOS 100 or newer}}
35+
// expected-note@-2 {{add 'if #available' version check}}
36+
37+
// No warning about redundant MarkerBase conformance (rdar://142873265):
38+
@available(macOS 100, *)
39+
extension Conformer1: MarkerDerived1 {}
40+
41+
@available(macOS 200, *)
42+
extension Conformer1: MarkerDerived2 {}
43+
44+
takesMarkerBase(Conformer1.self)
45+
// expected-error@-1 {{conformance of 'Conformer1' to 'MarkerBase' is only available in macOS 100 or newer}}
46+
// expected-note@-2 {{add 'if #available' version check}}
47+
48+
// Bad availability on the Base.f() witness:
49+
struct Conformer2 {}
50+
51+
@available(macOS 100, *)
52+
extension Conformer2: Derived1 {
53+
// expected-error@-1 {{protocol 'Base' requires 'f()' to be available in macOS 100 and newer}}
54+
}
55+
56+
@available(macOS 200, *)
57+
extension Conformer2: Derived2 {
58+
func f() {} // expected-note {{'f()' declared here}}
59+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s %S/Inputs/conformance_availability_implied_other.swift -swift-version 6
2+
// RUN: %target-swift-frontend -typecheck -verify %S/Inputs/conformance_availability_implied_other.swift %s -swift-version 6
3+
// REQUIRES: OS=macosx
4+
5+
protocol Base {
6+
func f()
7+
}
8+
9+
func takesBase<T: Base>(_: T.Type) {}
10+
11+
protocol Derived1: Base {}
12+
protocol Derived2: Base {}
13+
14+
// Verify that the implied conformance is macOS 100:
15+
struct Conformer1 {}
16+
17+
@available(macOS 100, *)
18+
extension Conformer1: Derived1 {
19+
func f() {} // okay!
20+
}
21+
22+
// Note that Conformer1: Derived2 is in the other file
23+
24+
func check1() {
25+
// expected-note@-1 {{add '@available' attribute to enclosing global function}}
26+
takesBase(Conformer1.self)
27+
// expected-error@-1 {{conformance of 'Conformer1' to 'Base' is only available in macOS 100 or newer}}
28+
// expected-note@-2 {{add 'if #available' version check}}
29+
}
30+
31+
// Verify that the implied conformance is macOS 200:
32+
// FIXME: This appears to be unsound!
33+
struct Conformer2 {}
34+
35+
@available(macOS 200, *)
36+
extension Conformer2: Derived2 {
37+
func f() {}
38+
}
39+
40+
// Note that Conformer2: Derived1 is in the other file
41+
42+
func check2() {
43+
// expected-note@-1 {{add '@available' attribute to enclosing global function}}
44+
takesBase(Conformer2.self)
45+
// expected-error@-1 {{conformance of 'Conformer2' to 'Base' is only available in macOS 200 or newer}}
46+
// expected-note@-2 {{add 'if #available' version check}}
47+
}
48+

0 commit comments

Comments
 (0)