Skip to content

[Sema] Check protocol conformance availability for member reference and type erasing expressions #41054

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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ _**Note:** This is in reverse chronological order, so newer entries are added to

## Swift 5.7

* The compiler now correctly emits warnings for more kinds of expressions where a protocol conformance is used and may be unavailable at runtime. Previously, member reference expressions and type erasing expressions that used potentially unavailable conformances were not diagnosed, leading to potential crashes at runtime.

```swift
struct Pancake {}
protocol Food {}

extension Food {
var isGlutenFree: Bool { false }
}

@available(macOS 12.0, *)
extension Pancake: Food {}

@available(macOS 11.0, *)
func eatPancake(_ pancake: Pancake) {
if (pancake.isGlutenFree) { // warning: conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer
eatFood(pancake) // warning: conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer
}
}

func eatFood(_ food: Food) {}
```

* [SE-0328][]:

Opaque types (expressed with 'some') can now be used in structural positions
Expand Down
17 changes: 12 additions & 5 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,12 @@ class ExprAvailabilityWalker : public ASTWalker {
return skipChildren();
}
}

if (auto EE = dyn_cast<ErasureExpr>(E)) {
for (ProtocolConformanceRef C : EE->getConformances()) {
diagnoseConformanceAvailability(E->getLoc(), C, Where);
}
}

return visitChildren();
}
Expand Down Expand Up @@ -2950,15 +2956,15 @@ class ExprAvailabilityWalker : public ASTWalker {
// writeback. The AST should have this information.
walkInContext(E, E->getBase(), MemberAccessContext::Getter);

ValueDecl *D = E->getMember().getDecl();
ConcreteDeclRef DR = E->getMember();
// Diagnose for the member declaration itself.
if (diagnoseDeclAvailability(D, E->getNameLoc().getSourceRange(),
nullptr, Where))
if (diagnoseDeclRefAvailability(DR, E->getNameLoc().getSourceRange(),
getEnclosingApplyExpr(), None))
return;

// Diagnose for appropriate accessors, given the access context.
auto *DC = Where.getDeclContext();
maybeDiagStorageAccess(D, E->getSourceRange(), DC);
maybeDiagStorageAccess(DR.getDecl(), E->getSourceRange(), DC);
}

/// Walk a keypath expression, checking all of its components for
Expand Down Expand Up @@ -3099,7 +3105,8 @@ bool ExprAvailabilityWalker::diagnoseDeclRefAvailability(
return true;
}

diagnoseDeclAvailability(D, R, call, Where, Flags);
if (diagnoseDeclAvailability(D, R, call, Where, Flags))
return true;

if (R.isValid()) {
if (diagnoseSubstitutionMapAvailability(R.Start, declRef.getSubstitutions(),
Expand Down
79 changes: 72 additions & 7 deletions test/Sema/conformance_availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

public protocol Horse {}
func takesHorse<T : Horse>(_: T) {}
func takesHorseExistential(_: Horse) {}

extension Horse {
func giddyUp() {}
var isGalloping: Bool { true }
}

struct UsesHorse<T : Horse> {}
Expand All @@ -16,18 +18,24 @@ public struct HasUnavailableConformance1 {}

@available(*, unavailable)
extension HasUnavailableConformance1 : Horse {}
// expected-note@-1 4{{conformance of 'HasUnavailableConformance1' to 'Horse' has been explicitly marked unavailable here}}
// expected-note@-1 7{{conformance of 'HasUnavailableConformance1' to 'Horse' has been explicitly marked unavailable here}}

func passUnavailableConformance1(x: HasUnavailableConformance1) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance1>.self // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
}

@available(*, unavailable)
func passUnavailableConformance1a(x: HasUnavailableConformance1) {
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
_ = UsesHorse<HasUnavailableConformance1>.self
}

Expand All @@ -36,38 +44,50 @@ public struct HasUnavailableConformance2 {}

@available(macOS, unavailable)
extension HasUnavailableConformance2 : Horse {}
// expected-note@-1 3{{conformance of 'HasUnavailableConformance2' to 'Horse' has been explicitly marked unavailable here}}
// expected-note@-1 6{{conformance of 'HasUnavailableConformance2' to 'Horse' has been explicitly marked unavailable here}}

func passUnavailableConformance2(x: HasUnavailableConformance2) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
_ = UsesHorse<HasUnavailableConformance2>.self // expected-error {{conformance of 'HasUnavailableConformance2' to 'Horse' is unavailable in macOS}}
}

@available(macOS, unavailable)
func passUnavailableConformance2a(x: HasUnavailableConformance2) {
// This is allowed
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
}

// Swift version unavailability
public struct HasUnavailableConformance3 {}

@available(swift 12)
extension HasUnavailableConformance3 : Horse {}
// expected-note@-1 6{{conformance of 'HasUnavailableConformance3' to 'Horse' was introduced in Swift 12}}
// expected-note@-1 12{{conformance of 'HasUnavailableConformance3' to 'Horse' was introduced in Swift 12}}

func passUnavailableConformance3(x: HasUnavailableConformance3) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance3>.self // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
}

@available(swift 12)
func passUnavailableConformance3a(x: HasUnavailableConformance3) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance3>.self // expected-error {{conformance of 'HasUnavailableConformance3' to 'Horse' is unavailable}}
}

Expand All @@ -76,18 +96,24 @@ public struct HasUnavailableConformance4 {}

@available(macOS, obsoleted: 10.1)
extension HasUnavailableConformance4 : Horse {}
// expected-note@-1 6{{conformance of 'HasUnavailableConformance4' to 'Horse' was obsoleted in macOS 10.1}}
// expected-note@-1 12{{conformance of 'HasUnavailableConformance4' to 'Horse' was obsoleted in macOS 10.1}}

func passUnavailableConformance4(x: HasUnavailableConformance4) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance4>.self // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
}

@available(macOS, obsoleted: 10.1)
func passUnavailableConformance4a(x: HasUnavailableConformance4) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance4>.self // expected-error {{conformance of 'HasUnavailableConformance4' to 'Horse' is unavailable in macOS}}
}

Expand All @@ -96,18 +122,24 @@ public struct HasUnavailableConformance5 {}

@available(swift, obsoleted: 4)
extension HasUnavailableConformance5 : Horse {}
// expected-note@-1 6{{conformance of 'HasUnavailableConformance5' to 'Horse' was obsoleted in Swift 4}}
// expected-note@-1 12{{conformance of 'HasUnavailableConformance5' to 'Horse' was obsoleted in Swift 4}}

func passUnavailableConformance5(x: HasUnavailableConformance5) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance5>.self // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
}

@available(swift, obsoleted: 4)
func passUnavailableConformance5a(x: HasUnavailableConformance5) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
_ = UsesHorse<HasUnavailableConformance5>.self // expected-error {{conformance of 'HasUnavailableConformance5' to 'Horse' is unavailable}}
}

Expand All @@ -116,11 +148,14 @@ public struct HasUnavailableConformance6 {}

@available(*, unavailable, message: "This conformance is bad")
extension HasUnavailableConformance6 : Horse {}
// expected-note@-1 3{{conformance of 'HasUnavailableConformance6' to 'Horse' has been explicitly marked unavailable here}}
// expected-note@-1 6{{conformance of 'HasUnavailableConformance6' to 'Horse' has been explicitly marked unavailable here}}

func passUnavailableConformance6(x: HasUnavailableConformance6) {
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
takesHorseExistential(x) // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
x.giddyUp() // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
_ = x.isGalloping // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
_ = UsesHorse<HasUnavailableConformance6>.self // expected-error {{conformance of 'HasUnavailableConformance6' to 'Horse' is unavailable: This conformance is bad}}
}

Expand All @@ -132,14 +167,20 @@ extension HasDeprecatedConformance1 : Horse {}

func passDeprecatedConformance1(x: HasDeprecatedConformance1) {
takesHorse(x) // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
takesHorseExistential(x) // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
x.giddyUp() // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
_ = x.isGalloping // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
_ = x[keyPath: \.isGalloping] // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
_ = UsesHorse<HasDeprecatedConformance1>.self // expected-warning {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
}

@available(*, deprecated)
func passDeprecatedConformance1a(x: HasDeprecatedConformance1) {
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
_ = UsesHorse<HasDeprecatedConformance1>.self
}

Expand All @@ -151,14 +192,20 @@ extension HasDeprecatedConformance2 : Horse {}

func passDeprecatedConformance2(x: HasDeprecatedConformance2) {
takesHorse(x) // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
takesHorseExistential(x) // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
x.giddyUp() // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
_ = x.isGalloping // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
_ = x[keyPath: \.isGalloping] // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
_ = UsesHorse<HasDeprecatedConformance2>.self // expected-warning {{conformance of 'HasDeprecatedConformance2' to 'Horse' is deprecated: This conformance is deprecated}}
}

@available(*, deprecated)
func passDeprecatedConformance2a(x: HasDeprecatedConformance2) {
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
_ = UsesHorse<HasDeprecatedConformance2>.self
}

Expand All @@ -170,7 +217,10 @@ extension HasDeprecatedConformance3 : Horse {}

func passDeprecatedConformance3(x: HasDeprecatedConformance3) {
takesHorse(x) // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
takesHorseExistential(x) // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
x.giddyUp() // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
_ = x.isGalloping // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
_ = x[keyPath: \.isGalloping] // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
_ = UsesHorse<HasDeprecatedConformance3>.self // expected-warning {{conformance of 'HasDeprecatedConformance3' to 'Horse' was deprecated in macOS 10.8}}
}

Expand All @@ -180,7 +230,10 @@ func passDeprecatedConformance3a(x: HasDeprecatedConformance3) {
// This branch is dead with our minimum deployment target, so don't emit
// deprecation diagnostics in it.
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
_ = UsesHorse<HasDeprecatedConformance3>.self
}
}
Expand All @@ -196,21 +249,33 @@ extension HasAvailableConformance1 : Horse {}
// in test/Sema/conformance_availability_warn.swift for the same example
// but without this flag.

func passAvailableConformance1(x: HasAvailableConformance1) { // expected-note 3{{add @available attribute to enclosing global function}}
func passAvailableConformance1(x: HasAvailableConformance1) { // expected-note 6{{add @available attribute to enclosing global function}}
takesHorse(x) // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

takesHorseExistential(x) // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

x.giddyUp() // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

_ = x.isGalloping // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

_ = x[keyPath: \.isGalloping] // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

_ = UsesHorse<HasAvailableConformance1>.self // expected-error {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
// expected-note@-1 {{add 'if #available' version check}}
}

@available(macOS 100, *)
func passAvailableConformance1a(x: HasAvailableConformance1) {
takesHorse(x)
takesHorseExistential(x)
x.giddyUp()
_ = x.isGalloping
_ = x[keyPath: \.isGalloping]
_ = UsesHorse<HasAvailableConformance1>.self
}

Expand Down
Loading