Skip to content

Sema: Relax diagnosis of implied marker protocol conformances with mismatched availability #82308

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: 8 additions & 1 deletion lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,11 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
}
}

auto isUnavailable = [](DeclContext *dc) -> bool {
auto *ext = dyn_cast<ExtensionDecl>(dc);
return ext && ext->isUnavailable();
};

// If only one of the conformances is unconditionally available on the
// current deployment target, pick that one.
//
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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}}

59 changes: 59 additions & 0 deletions test/Sema/conformance_availability_implied.swift
Original file line number Diff line number Diff line change
@@ -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: Base>(_: T.Type) {}

protocol Derived1: Base {}
protocol Derived2: Base {}

@_marker protocol MarkerBase {}

func takesMarkerBase<T: MarkerBase>(_: 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}}
}
48 changes: 48 additions & 0 deletions test/Sema/conformance_availability_implied_multifile.swift
Original file line number Diff line number Diff line change
@@ -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: Base>(_: 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}}
}