AST: Warn for non-existent platform versions in @available attributes.

This commit is contained in:
Allan Shortlidge
2025-06-08 12:20:27 -07:00
parent e358cd6309
commit e16c638fc3
9 changed files with 164 additions and 7 deletions

View File

@@ -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,

View File

@@ -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",

View File

@@ -21,6 +21,7 @@
#include "swift/Config.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/TargetParser/Triple.h"
#include <optional>
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<llvm::Triple::OSType>
tripleOSTypeForPlatform(PlatformKind platform);
llvm::VersionTuple canonicalizePlatformVersion(
PlatformKind platform, const llvm::VersionTuple &version);

View File

@@ -832,7 +832,10 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
auto checkVersion = [&](std::optional<llvm::VersionTuple> 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;
};

View File

@@ -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:

View File

@@ -263,8 +263,8 @@ bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child,
return false;
}
static std::optional<llvm::Triple::OSType>
tripleOSTypeForPlatform(PlatformKind platform) {
std::optional<llvm::Triple::OSType>
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;
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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() { }