Skip to content
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

[Diagnostics] Add support for updating the existing 'available' attribute. #72524

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6705,6 +6705,9 @@ ERROR(availability_variadic_type_only_version_newer, none,
NOTE(availability_guard_with_version_check, none,
"add 'if #available' version check", ())

NOTE(availability_update_attribute, none,
"update @available attribute for %0 from '%1' to '%2' to meet the requirements of '%3'", (StringRef, StringRef, StringRef, StringRef))

NOTE(availability_add_attribute, none,
"add @available attribute to enclosing %0", (DescriptiveDeclKind))
FIXIT(insert_available_attr,
Expand Down
22 changes: 20 additions & 2 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1830,8 +1830,26 @@ static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
if (TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D).has_value())
return;

if (getActiveAvailableAttribute(D, Context)) {
// For QoI, in future should emit a fixit to update the existing attribute.
if (auto Attr = getActiveAvailableAttribute(D, Context)) {
// Check if the `range` of the attribute `Attr` overlaps with its
// `IntroducedRange`, which can be from a macro or the minimum version
// supported by the compiler, etc. If there's no explicit introduction range
// marked in the code, skip the update logic and return immediately.
if (!Attr->getRange().isValid() ||
!Attr->getRange().overlaps(Attr->IntroducedRange)) {
return;
}

auto Version = Attr->Introduced->getAsString();
auto Required = RequiredRange.getLowerEndpoint().getAsString();
auto ReferenceName =
Context.SourceMgr
.extractText(Lexer::getCharSourceRangeFromSourceRange(
Context.SourceMgr, ReferenceRange))
.str();
D->diagnose(diag::availability_update_attribute, Attr->platformString(),
Version, Required, ReferenceName)
.fixItReplace(Attr->IntroducedRange, Required);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it'll do the right thing for attributes like:

  • @available(*, unavailable) (just skips it)
  • @available(macOS, introduced: 10.15)
  • @available(macOS 10.15, iOS 13, *)

But what if you have something like these? Are there any tests cases like them?

  • @available(macOS, unavailable)
  • @available(macOS, deprecated: 12)

(You could just bail out for these like you do for unconditionally unavailable attributes, or you could make your fix-it insert , introduced: 11 after the platform name in these cases.)

Also:

  • @available(*, deprecated)

(For this, you probably either have to bail out or you have to act as though there is no active attribute and offer to insert a new one.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the deprecated, I personally believe it is unnecessary to continue expanding to support new capabilities.
The same for unavailable methods, unless a developer actively removes the unavailable attribute, we should not consider expanding to support new capabilities.
Therefore, I think return is a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. If you don't want to handle those cases (except by returning), I would file a follow-up issue suggesting that they could be supported in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest add test cases for those kind of attributes in this PR as extra scenarios if we don't already.

return;
}

Expand Down
1 change: 1 addition & 0 deletions test/Sema/api-availability-only.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public struct S {}

@available(macOS 10.51, *)
public func newFunc() {
// expected-note @-1 {{update @available attribute for macOS from '10.51' to '10.52' to meet the requirements of 'S'}} {{9:18-23=10.52}}
_ = S() // expected-error {{'S' is only available in}}
// expected-note @-1 {{add 'if #available' version check}}
}
1 change: 1 addition & 0 deletions test/Sema/availability_swiftui.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ class AnyColorBox: LessAvailable {} // Ok, exception specifically for AnyColorBo
@available(macOS 10.15, *)
@usableFromInline
class OtherClass: LessAvailable {} // expected-error {{'LessAvailable' is only available in macOS 11 or newer; clients of 'SwiftUI' may have a lower deployment target}}
// expected-note @-1 {{update @available attribute for macOS from '10.15' to '11' to meet the requirements of 'LessAvailable'}} {{13:18-23=11}}
25 changes: 19 additions & 6 deletions test/Sema/availability_versions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func functionWithoutAvailability() {
// Functions with annotations should refine their bodies.
@available(OSX, introduced: 10.51)
func functionAvailableOn10_51() {
// expected-note@-1 {{update @available attribute for macOS from '10.51' to '10.52' to meet the requirements of 'globalFuncAvailableOn10_52'}} {{43:29-34=10.52}}
let _: Int = globalFuncAvailableOn10_9()
let _: Int = globalFuncAvailableOn10_51()

Expand Down Expand Up @@ -324,10 +325,11 @@ class ClassWithPotentiallyUnavailableProperties {

@available(OSX, introduced: 10.9)
var availableOn10_9Computed: Int {
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'availableOn10_51Stored'}} {{326:31-35=10.51}}
get {
let _: Int = availableOn10_51Stored // expected-error {{'availableOn10_51Stored' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

if #available(OSX 10.51, *) {
let _: Int = availableOn10_51Stored
}
Expand Down Expand Up @@ -406,6 +408,7 @@ class ClassWithPotentiallyUnavailableProperties {

@available(OSX, introduced: 10.51)
class ClassWithReferencesInInitializers {
// expected-note@-1 2 {{update @available attribute for macOS from '10.51' to '10.52' to meet the requirements of 'globalFuncAvailableOn10_52'}} {{409:29-34=10.52}}
var propWithInitializer10_51: Int = globalFuncAvailableOn10_51()

var propWithInitializer10_52: Int = globalFuncAvailableOn10_52() // expected-error {{'globalFuncAvailableOn10_52()' is only available in macOS 10.52 or newer}}
Expand Down Expand Up @@ -522,6 +525,8 @@ enum EnumIntroducedOn10_52 {

@available(OSX, introduced: 10.51)
enum CompassPoint {
// expected-note@-1 3 {{update @available attribute for macOS from '10.51' to '10.52' to meet the requirements of 'EnumIntroducedOn10_52'}} {{526:29-34=10.52}}

case North
case South
case East
Expand All @@ -542,7 +547,7 @@ enum CompassPoint {
case WithPotentiallyUnavailablePayload(p : EnumIntroducedOn10_52) // expected-error {{'EnumIntroducedOn10_52' is only available in macOS 10.52 or newer}}

case WithPotentiallyUnavailablePayload1(p : EnumIntroducedOn10_52), WithPotentiallyUnavailablePayload2(p : EnumIntroducedOn10_52) // expected-error 2{{'EnumIntroducedOn10_52' is only available in macOS 10.52 or newer}}

@available(OSX, unavailable)
case WithPotentiallyUnavailablePayload3(p : EnumIntroducedOn10_52)
}
Expand Down Expand Up @@ -876,20 +881,21 @@ class SubWithLargerMemberAvailability : SuperWithLimitedMemberAvailability {
// expected-note@-1 2{{add @available attribute to enclosing class}}
@available(OSX, introduced: 10.9)
override func someMethod() {
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'someMethod'}} {{882:31-35=10.51}}
super.someMethod() // expected-error {{'someMethod()' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

if #available(OSX 10.51, *) {
super.someMethod()
}
}

@available(OSX, introduced: 10.9)
override var someProperty: Int {
get {
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'someProperty'}} {{893:31-35=10.51}}
get {
let _ = super.someProperty // expected-error {{'someProperty' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{add 'if #available' version check}}

// expected-note@-1 {{add 'if #available' version check}}
if #available(OSX 10.51, *) {
let _ = super.someProperty
}
Expand Down Expand Up @@ -961,6 +967,7 @@ protocol ProtocolAvailableOn10_51 {

@available(OSX, introduced: 10.9)
protocol ProtocolAvailableOn10_9InheritingFromProtocolAvailableOn10_51 : ProtocolAvailableOn10_51 { // expected-error {{'ProtocolAvailableOn10_51' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'ProtocolAvailableOn10_51'}} {{968:29-33=10.51}}
}

@available(OSX, introduced: 10.51)
Expand All @@ -973,6 +980,7 @@ protocol UnavailableProtocolInheritingFromProtocolAvailableOn10_51 : ProtocolAva

@available(OSX, introduced: 10.9)
class SubclassAvailableOn10_9OfClassAvailableOn10_51 : ClassAvailableOn10_51 { // expected-error {{'ClassAvailableOn10_51' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'ClassAvailableOn10_51'}} {{981:29-33=10.51}}
}

@available(OSX, unavailable)
Expand All @@ -997,12 +1005,14 @@ func castToPotentiallyUnavailableProtocol() {

@available(OSX, introduced: 10.9)
class SubclassAvailableOn10_9OfClassAvailableOn10_51AlsoAdoptingProtocolAvailableOn10_51 : ClassAvailableOn10_51, ProtocolAvailableOn10_51 { // expected-error {{'ClassAvailableOn10_51' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'ClassAvailableOn10_51'}} {{1006:29-33=10.51}}
}

class SomeGenericClass<T> { }

@available(OSX, introduced: 10.9)
class SubclassAvailableOn10_9OfSomeGenericClassOfProtocolAvailableOn10_51 : SomeGenericClass<ProtocolAvailableOn10_51> { // expected-error {{'ProtocolAvailableOn10_51' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'ProtocolAvailableOn10_51'}} {{1013:29-33=10.51}}
}

@available(OSX, unavailable)
Expand Down Expand Up @@ -1049,6 +1059,7 @@ extension ClassAvailableOn10_51 { }

@available(OSX, introduced: 10.51)
extension ClassAvailableOn10_51 {
// expected-note@-1 {{update @available attribute for macOS from '10.51' to '10.52' to meet the requirements of 'globalFuncAvailableOn10_52'}} {{1060:29-34=10.52}}
func m() {
// expected-note@-1 {{add @available attribute to enclosing instance method}}
let _ = globalFuncAvailableOn10_51()
Expand Down Expand Up @@ -1653,6 +1664,7 @@ class ClassWithShortFormAvailableOn10_54 {

@available(OSX 10.9, *)
func funcWithShortFormAvailableOn10_9() {
// expected-note@-1 {{update @available attribute for macOS from '10.9' to '10.51' to meet the requirements of 'ClassWithShortFormAvailableOn10_51'}} {{1665:16-20=10.51}}
let _ = ClassWithShortFormAvailableOn10_51() // expected-error {{'ClassWithShortFormAvailableOn10_51' is only available in macOS 10.51 or newer}}
// expected-note@-1 {{add 'if #available' version check}}
}
Expand Down Expand Up @@ -1685,6 +1697,7 @@ func funcWithMultipleShortFormAnnotationsForDifferentPlatforms() {
@available(OSX 10.53, *)
@available(OSX 10.52, *)
func funcWithMultipleShortFormAnnotationsForTheSamePlatform() {
// expected-note@-1 {{update @available attribute for macOS from '10.52' to '10.54' to meet the requirements of 'ClassWithShortFormAvailableOn10_54'}} {{1698:16-21=10.54}}
let _ = ClassWithShortFormAvailableOn10_53()

let _ = ClassWithShortFormAvailableOn10_54() // expected-error {{'ClassWithShortFormAvailableOn10_54' is only available in macOS 10.54 or newer}}
Expand Down
6 changes: 6 additions & 0 deletions test/Sema/availability_versions_multi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ let ignored3: Int = globalAvailableOn99_52 // expected-error {{'globalAvailableO

@available(OSX, introduced: 99.51)
func useFromOtherOn99_51() {
// expected-note@-1 {{update @available attribute for macOS from '99.51' to '99.52' to meet the requirements of 'returns99_52Introduced99_52'}} {{26:29-34=99.52}}
// expected-note@-2 {{update @available attribute for macOS from '99.51' to '99.52' to meet the requirements of 'OtherIntroduced99_52'}} {{26:29-34=99.52}}
// expected-note@-3 {{update @available attribute for macOS from '99.51' to '99.52' to meet the requirements of 'extensionMethodOnOtherIntroduced99_51AvailableOn99_52'}} {{26:29-34=99.52}}
// expected-note@-4 {{update @available attribute for macOS from '99.51' to '99.52' to meet the requirements of 'NestedIntroduced99_52'}} {{26:29-34=99.52}}

// This will trigger validation of OtherIntroduced99_51 in
// in availability_multi_other.swift
let o99_51 = OtherIntroduced99_51()
Expand All @@ -49,6 +54,7 @@ func useFromOtherOn99_51() {

@available(OSX, introduced: 99.52)
func useFromOtherOn99_52() {
// expected-note@-1 {{update @available attribute for macOS from '99.52' to '99.53' to meet the requirements of 'returns99_53'}} {{55:29-34=99.53}}
_ = OtherIntroduced99_52()

let n99_52 = OtherIntroduced99_51.NestedIntroduced99_52()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

@main // expected-error {{'main()' is only available in macOS 10.99 or newer}}
@available(OSX 10.0, *)
struct EntryPoint {
struct EntryPoint { // expected-note {{update @available attribute for macOS from '10.0' to '10.99' to meet the requirements of '@main'}} {{6:16-20=10.99}}
@available(OSX 10.99, *)
static func main() {
}
Expand Down
2 changes: 1 addition & 1 deletion test/attr/attr_availability_osx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ extension TestStruct {
}

@available(macOS 10.11, *)
func testMemberAvailability() {
func testMemberAvailability() { // expected-note {{update @available attribute for macOS from '10.11' to '10.12' to meet the requirements of 'doFourthThing'}} {{117:18-23=10.12}}
TestStruct().doTheThing() // expected-error {{'doTheThing()' is unavailable}}
TestStruct().doAnotherThing() // expected-error {{'doAnotherThing()' is unavailable}}
TestStruct().doThirdThing() // expected-error {{'doThirdThing()' is unavailable}}
Expand Down
69 changes: 69 additions & 0 deletions test/attr/attr_availability_update.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// RUN: %swift -typecheck -verify -parse-stdlib -module-name Swift -target x86_64-apple-macosx10.10 %s

@available(SwiftStdlib 5.1, *)
func FooForDefineAvailability() {}

@available(SwiftStdlib 5.0, *)
func FooForDefineAvailabilityTest() {
FooForDefineAvailability()
// expected-error@-1 {{'FooForDefineAvailability()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}

@available(*, unavailable)
func FooForUnavailable() {} // expected-note {{'FooForUnavailable()' has been explicitly marked unavailable here}}

func FooForUnavailableTest() {
FooForUnavailable()
// expected-error@-1 {{'FooForUnavailable()' is unavailable}}
}

@available(macOS, introduced: 10.15)
func FooForAvailability() {}

@available(macOS, introduced: 10.10)
func FooForAvailabilityTest() {
// expected-note@-1 {{update @available attribute for macOS from '10.10' to '10.15' to meet the requirements of 'FooForAvailability'}} {{24:31-36=10.15}}
FooForAvailability()
// expected-error@-1 {{'FooForAvailability()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}

@available(macOS 10.15, iOS 13, *)
func FooForAvailability2() {
}

@available(macOS 10.10, *)
func FooForAvailability2Test() {
// expected-note@-1 {{update @available attribute for macOS from '10.10' to '10.15' to meet the requirements of 'FooForAvailability2'}} {{36:18-23=10.15}}
FooForAvailability2()
// expected-error@-1 {{'FooForAvailability2()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}

@available(macOS 10.15, *)
func FooForUnavailable2() {}

@available(macOS, unavailable)
func FooForUnavailable2Test() {
FooForUnavailable2()
// expected-error@-1 {{'FooForUnavailable2()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}

@available(macOS 10.15, *)
func FooForDeprecated() {}

@available(macOS, deprecated: 12)
func FooForDeprecatedTest() {
FooForDeprecated()
// expected-error@-1 {{'FooForDeprecated()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}

@available(*, deprecated)
func FooForDeprecatedTest2() {
FooForDeprecated()
// expected-error@-1 {{'FooForDeprecated()' is only available in macOS 10.15 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}
Loading