From e16c638fc3b28c03b9898960bb273200691814db Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 8 Jun 2025 12:20:27 -0700 Subject: [PATCH] AST: Warn for non-existent platform versions in @available attributes. --- include/swift/AST/AvailabilityDomain.h | 4 + include/swift/AST/DiagnosticsSema.def | 3 + include/swift/AST/PlatformKind.h | 5 ++ lib/AST/Availability.cpp | 15 +++- lib/AST/AvailabilityDomain.cpp | 20 +++++ lib/AST/PlatformKind.cpp | 11 ++- ...rint_ast_tc_decls_canonical_versions.swift | 2 +- .../availability_versions_canonical.swift | 77 ++++++++++++++++++- ...ailability_invalid_platform_versions.swift | 34 ++++++++ 9 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 test/attr/attr_availability_invalid_platform_versions.swift diff --git a/include/swift/AST/AvailabilityDomain.h b/include/swift/AST/AvailabilityDomain.h index 87b5fb46c42..e16f5c6b805 100644 --- a/include/swift/AST/AvailabilityDomain.h +++ b/include/swift/AST/AvailabilityDomain.h @@ -209,6 +209,10 @@ public: /// version ranges. bool isVersioned() const; + /// Returns true if the given version is a valid version number for this + /// domain. It is an error to call this on an un-versioned domain. + bool isVersionValid(const llvm::VersionTuple &version) const; + /// Returns true if availability of the domain can be refined using /// `@available` attributes and `if #available` queries. If not, then the /// domain's availability is fixed by compilation settings. For example, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e907814f7c4..e9922c7c334 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6929,6 +6929,9 @@ GROUPED_ERROR(availability_suggest_platform_name, (Identifier, StringRef)) WARNING(availability_unsupported_version_number, none, "'%0' is not a supported version number", (llvm::VersionTuple)) +WARNING(availability_invalid_version_number_for_domain, none, + "'%0' is not a valid version number for %1", + (llvm::VersionTuple, AvailabilityDomain)) WARNING(attr_availability_expected_deprecated_version, none, "expected version number with 'deprecated' in '%0' attribute for %1", diff --git a/include/swift/AST/PlatformKind.h b/include/swift/AST/PlatformKind.h index a8a869a3677..34323bae9ba 100644 --- a/include/swift/AST/PlatformKind.h +++ b/include/swift/AST/PlatformKind.h @@ -21,6 +21,7 @@ #include "swift/Config.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/VersionTuple.h" +#include "llvm/TargetParser/Triple.h" #include namespace swift { @@ -91,6 +92,10 @@ PlatformKind targetVariantPlatform(const LangOptions &LangOpts); /// an explicit attribute for the child. bool inheritsAvailabilityFromPlatform(PlatformKind Child, PlatformKind Parent); +/// Returns the LLVM triple OS type for the given platform, if there is one. +std::optional +tripleOSTypeForPlatform(PlatformKind platform); + llvm::VersionTuple canonicalizePlatformVersion( PlatformKind platform, const llvm::VersionTuple &version); diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 2f4c07b294e..e8a6fc7d76d 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -832,7 +832,10 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator, auto checkVersion = [&](std::optional version, SourceRange sourceRange) { - if (version && !VersionRange::isValidVersion(*version)) { + if (!version) + return false; + + if (!VersionRange::isValidVersion(*version)) { diags .diagnose(attrLoc, diag::availability_unsupported_version_number, *version) @@ -840,6 +843,16 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator, return true; } + // Warn if the version is not a valid one for the domain. For example, macOS + // 17 will never exist. + if (domain->isVersioned() && !domain->isVersionValid(*version)) { + diags + .diagnose(attrLoc, + diag::availability_invalid_version_number_for_domain, + *version, *domain) + .highlight(sourceRange); + } + return false; }; diff --git a/lib/AST/AvailabilityDomain.cpp b/lib/AST/AvailabilityDomain.cpp index bc2e0a2fa92..feffdb338c7 100644 --- a/lib/AST/AvailabilityDomain.cpp +++ b/lib/AST/AvailabilityDomain.cpp @@ -109,6 +109,26 @@ bool AvailabilityDomain::isVersioned() const { } } +bool AvailabilityDomain::isVersionValid( + const llvm::VersionTuple &version) const { + ASSERT(isVersioned()); + + switch (getKind()) { + case Kind::Universal: + case Kind::Embedded: + llvm_unreachable("unexpected domain kind"); + case Kind::SwiftLanguage: + case Kind::PackageDescription: + return true; + case Kind::Platform: + if (auto osType = tripleOSTypeForPlatform(getPlatformKind())) + return llvm::Triple::isValidVersionForOS(*osType, version); + return true; + case Kind::Custom: + return true; + } +} + bool AvailabilityDomain::supportsContextRefinement() const { switch (getKind()) { case Kind::Universal: diff --git a/lib/AST/PlatformKind.cpp b/lib/AST/PlatformKind.cpp index 348dc647190..b8026af51a0 100644 --- a/lib/AST/PlatformKind.cpp +++ b/lib/AST/PlatformKind.cpp @@ -263,8 +263,8 @@ bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child, return false; } -static std::optional -tripleOSTypeForPlatform(PlatformKind platform) { +std::optional +swift::tripleOSTypeForPlatform(PlatformKind platform) { switch (platform) { case PlatformKind::macOS: case PlatformKind::macOSApplicationExtension: @@ -296,8 +296,11 @@ tripleOSTypeForPlatform(PlatformKind platform) { llvm::VersionTuple swift::canonicalizePlatformVersion(PlatformKind platform, const llvm::VersionTuple &version) { - if (auto osType = tripleOSTypeForPlatform(platform)) - return llvm::Triple::getCanonicalVersionForOS(*osType, version); + if (auto osType = tripleOSTypeForPlatform(platform)) { + bool isInValidRange = llvm::Triple::isValidVersionForOS(*osType, version); + return llvm::Triple::getCanonicalVersionForOS(*osType, version, + isInValidRange); + } return version; } diff --git a/test/IDE/print_ast_tc_decls_canonical_versions.swift b/test/IDE/print_ast_tc_decls_canonical_versions.swift index 2571c593f8c..ca30d5d63e5 100644 --- a/test/IDE/print_ast_tc_decls_canonical_versions.swift +++ b/test/IDE/print_ast_tc_decls_canonical_versions.swift @@ -1,5 +1,5 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -typecheck -verify %s +// RUN: %target-swift-frontend -typecheck %s // RUN: %target-swift-ide-test -skip-deinit=false -print-ast-typechecked -source-filename %s -function-definitions=false -prefer-type-repr=false -print-implicit-attrs=true > %t.printed.txt // RUN: %FileCheck %s -check-prefix=PASS_COMMON -strict-whitespace < %t.printed.txt diff --git a/test/Sema/availability_versions_canonical.swift b/test/Sema/availability_versions_canonical.swift index 0f80ad205de..e8257660968 100644 --- a/test/Sema/availability_versions_canonical.swift +++ b/test/Sema/availability_versions_canonical.swift @@ -1,4 +1,8 @@ -// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple +// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target x86_64-apple-macosx10.15 -verify-additional-prefix macos- +// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-ios13 -verify-additional-prefix ios- +// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-watchos6 -verify-additional-prefix watchos- +// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-tvos13 -verify-additional-prefix tvos- +// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-xros1 -verify-additional-prefix visionos- @available(OSX 10.16, *) func introducedOnMacOS10_16() { } @@ -12,7 +16,59 @@ func introducedInVersionsMappingTo26_0() { } @available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *) func introducedIn26_0() { } +@available(macOS 17.0, iOS 20.0, watchOS 13.0, tvOS 20.0, visionOS 4.0, *) +// expected-warning@-1 {{'17.0' is not a valid version number for macOS}} +// expected-warning@-2 {{'20.0' is not a valid version number for iOS}} +// expected-warning@-3 {{'13.0' is not a valid version number for watchOS}} +// expected-warning@-4 {{'20.0' is not a valid version number for tvOS}} +// expected-warning@-5 {{'4.0' is not a valid version number for visionOS}} +func introducedInVersionsMappingTo27_0() { } + +@available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *) +func introducedIn27_0() { } + func useUnderPoundAvailable() { + // expected-note@-1 * {{add '@available' attribute to enclosing global function}} + introducedOnMacOS10_16() + // expected-macos-error@-1 {{'introducedOnMacOS10_16()' is only available in macOS 11.0 or newer}} + // expected-macos-note@-2 {{add 'if #available' version check}} + + introducedOnMacOS11_0() + // expected-macos-error@-1 {{'introducedOnMacOS11_0()' is only available in macOS 11.0 or newer}} + // expected-macos-note@-2 {{add 'if #available' version check}} + + introducedInVersionsMappingTo26_0() + // expected-macos-error@-1 {{'introducedInVersionsMappingTo26_0()' is only available in macOS 26.0 or newer}} + // expected-ios-error@-2 {{'introducedInVersionsMappingTo26_0()' is only available in iOS 26.0 or newer}} + // expected-watchos-error@-3 {{'introducedInVersionsMappingTo26_0()' is only available in watchOS 26.0 or newer}} + // expected-tvos-error@-4 {{'introducedInVersionsMappingTo26_0()' is only available in tvOS 26.0 or newer}} + // expected-visionos-error@-5 {{'introducedInVersionsMappingTo26_0()' is only available in visionOS 26.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + + introducedIn26_0() + // expected-macos-error@-1 {{'introducedIn26_0()' is only available in macOS 26.0 or newer}} + // expected-ios-error@-2 {{'introducedIn26_0()' is only available in iOS 26.0 or newer}} + // expected-watchos-error@-3 {{'introducedIn26_0()' is only available in watchOS 26.0 or newer}} + // expected-tvos-error@-4 {{'introducedIn26_0()' is only available in tvOS 26.0 or newer}} + // expected-visionos-error@-5 {{'introducedIn26_0()' is only available in visionOS 26.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + + introducedInVersionsMappingTo27_0() + // expected-macos-error@-1 {{'introducedInVersionsMappingTo27_0()' is only available in macOS 27.0 or newer}} + // expected-ios-error@-2 {{'introducedInVersionsMappingTo27_0()' is only available in iOS 27.0 or newer}} + // expected-watchos-error@-3 {{'introducedInVersionsMappingTo27_0()' is only available in watchOS 27.0 or newer}} + // expected-tvos-error@-4 {{'introducedInVersionsMappingTo27_0()' is only available in tvOS 27.0 or newer}} + // expected-visionos-error@-5 {{'introducedInVersionsMappingTo27_0()' is only available in visionOS 27.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + + introducedIn27_0() + // expected-macos-error@-1 {{'introducedIn27_0()' is only available in macOS 27.0 or newer}} + // expected-ios-error@-2 {{'introducedIn27_0()' is only available in iOS 27.0 or newer}} + // expected-watchos-error@-3 {{'introducedIn27_0()' is only available in watchOS 27.0 or newer}} + // expected-tvos-error@-4 {{'introducedIn27_0()' is only available in tvOS 27.0 or newer}} + // expected-visionos-error@-5 {{'introducedIn27_0()' is only available in visionOS 27.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + if #available(OSX 10.16, *) { introducedOnMacOS10_16() introducedOnMacOS11_0() @@ -21,5 +77,24 @@ func useUnderPoundAvailable() { if #available(macOS 16.0, iOS 19.0, watchOS 12.0, tvOS 19.0, visionOS 3.0, *) { introducedInVersionsMappingTo26_0() introducedIn26_0() + introducedInVersionsMappingTo27_0() + // expected-macos-error@-1 {{'introducedInVersionsMappingTo27_0()' is only available in macOS 27.0 or newer}} + // expected-ios-error@-2 {{'introducedInVersionsMappingTo27_0()' is only available in iOS 27.0 or newer}} + // expected-watchos-error@-3 {{'introducedInVersionsMappingTo27_0()' is only available in watchOS 27.0 or newer}} + // expected-tvos-error@-4 {{'introducedInVersionsMappingTo27_0()' is only available in tvOS 27.0 or newer}} + // expected-visionos-error@-5 {{'introducedInVersionsMappingTo27_0()' is only available in visionOS 27.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + introducedIn27_0() + // expected-macos-error@-1 {{'introducedIn27_0()' is only available in macOS 27.0 or newer}} + // expected-ios-error@-2 {{'introducedIn27_0()' is only available in iOS 27.0 or newer}} + // expected-watchos-error@-3 {{'introducedIn27_0()' is only available in watchOS 27.0 or newer}} + // expected-tvos-error@-4 {{'introducedIn27_0()' is only available in tvOS 27.0 or newer}} + // expected-visionos-error@-5 {{'introducedIn27_0()' is only available in visionOS 27.0 or newer}} + // expected-note@-6 {{add 'if #available' version check}} + } + + if #available(macOS 17.0, iOS 20.0, watchOS 13.0, tvOS 20.0, visionOS 4.0, *) { + introducedInVersionsMappingTo27_0() + introducedIn27_0() } } diff --git a/test/attr/attr_availability_invalid_platform_versions.swift b/test/attr/attr_availability_invalid_platform_versions.swift new file mode 100644 index 00000000000..8ddb3ce1ac4 --- /dev/null +++ b/test/attr/attr_availability_invalid_platform_versions.swift @@ -0,0 +1,34 @@ +// RUN: %target-swift-frontend -typecheck -verify -parse-stdlib -module-name Swift %s + +@available(macOS, introduced: 17) // expected-warning {{'17' is not a valid version number for macOS}} +@available(iOS, introduced: 20) // expected-warning {{'20' is not a valid version number for iOS}} +@available(macCatalyst, introduced: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}} +@available(watchOS, introduced: 13) // expected-warning {{'13' is not a valid version number for watchOS}} +@available(tvOS, introduced: 20) // expected-warning {{'20' is not a valid version number for tvOS}} +@available(visionOS, introduced: 4) // expected-warning {{'4' is not a valid version number for visionOS}} +func invalidIntroduced() { } + +@available(macOS, deprecated: 17) // expected-warning {{'17' is not a valid version number for macOS}} +@available(iOS, deprecated: 20) // expected-warning {{'20' is not a valid version number for iOS}} +@available(macCatalyst, deprecated: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}} +@available(watchOS, deprecated: 13) // expected-warning {{'13' is not a valid version number for watchOS}} +@available(tvOS, deprecated: 20) // expected-warning {{'20' is not a valid version number for tvOS}} +@available(visionOS, deprecated: 4) // expected-warning {{'4' is not a valid version number for visionOS}} +func invalidDeprecated() { } + +@available(macOS, obsoleted: 17) // expected-warning {{'17' is not a valid version number for macOS}} +@available(iOS, obsoleted: 20) // expected-warning {{'20' is not a valid version number for iOS}} +@available(macCatalyst, obsoleted: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}} +@available(watchOS, obsoleted: 13) // expected-warning {{'13' is not a valid version number for watchOS}} +@available(tvOS, obsoleted: 20) // expected-warning {{'20' is not a valid version number for tvOS}} +@available(visionOS, obsoleted: 4) // expected-warning {{'4' is not a valid version number for visionOS}} +func invalidObsoleted() { } + +@available(macOS 18, iOS 21, macCatalyst 22, watchOS 14, tvOS 23, visionOS 7, *) +// expected-warning@-1 {{'18' is not a valid version number for macOS}} +// expected-warning@-2 {{'21' is not a valid version number for iOS}} +// expected-warning@-3 {{'22' is not a valid version number for Mac Catalyst}} +// expected-warning@-4 {{'14' is not a valid version number for watchOS}} +// expected-warning@-5 {{'23' is not a valid version number for tvOS}} +// expected-warning@-6 {{'7' is not a valid version number for visionOS}} +func invalidIntroducedShort() { }