Skip to content

Commit e5ad72c

Browse files
committed
AST: Warn for non-existent platform versions in @available attributes.
1 parent f603eae commit e5ad72c

9 files changed

+164
-7
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ class AvailabilityDomain final {
209209
/// version ranges.
210210
bool isVersioned() const;
211211

212+
/// Returns true if the given version is a valid version number for this
213+
/// domain. It is an error to call this on an un-versioned domain.
214+
bool isVersionValid(const llvm::VersionTuple &version) const;
215+
212216
/// Returns true if availability of the domain can be refined using
213217
/// `@available` attributes and `if #available` queries. If not, then the
214218
/// domain's availability is fixed by compilation settings. For example,

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6929,6 +6929,9 @@ GROUPED_ERROR(availability_suggest_platform_name,
69296929
(Identifier, StringRef))
69306930
WARNING(availability_unsupported_version_number, none,
69316931
"'%0' is not a supported version number", (llvm::VersionTuple))
6932+
WARNING(availability_invalid_version_number_for_domain, none,
6933+
"'%0' is not a valid version number for %1",
6934+
(llvm::VersionTuple, AvailabilityDomain))
69326935

69336936
WARNING(attr_availability_expected_deprecated_version, none,
69346937
"expected version number with 'deprecated' in '%0' attribute for %1",

include/swift/AST/PlatformKind.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/Config.h"
2222
#include "llvm/ADT/StringRef.h"
2323
#include "llvm/Support/VersionTuple.h"
24+
#include "llvm/TargetParser/Triple.h"
2425
#include <optional>
2526

2627
namespace swift {
@@ -91,6 +92,10 @@ PlatformKind targetVariantPlatform(const LangOptions &LangOpts);
9192
/// an explicit attribute for the child.
9293
bool inheritsAvailabilityFromPlatform(PlatformKind Child, PlatformKind Parent);
9394

95+
/// Returns the LLVM triple OS type for the given platform, if there is one.
96+
std::optional<llvm::Triple::OSType>
97+
tripleOSTypeForPlatform(PlatformKind platform);
98+
9499
llvm::VersionTuple canonicalizePlatformVersion(
95100
PlatformKind platform, const llvm::VersionTuple &version);
96101

lib/AST/Availability.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,14 +832,27 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
832832

833833
auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
834834
SourceRange sourceRange) {
835-
if (version && !VersionRange::isValidVersion(*version)) {
835+
if (!version)
836+
return false;
837+
838+
if (!VersionRange::isValidVersion(*version)) {
836839
diags
837840
.diagnose(attrLoc, diag::availability_unsupported_version_number,
838841
*version)
839842
.highlight(sourceRange);
840843
return true;
841844
}
842845

846+
// Warn if the version is not a valid one for the domain. For example, macOS
847+
// 17 will never exist.
848+
if (domain->isVersioned() && !domain->isVersionValid(*version)) {
849+
diags
850+
.diagnose(attrLoc,
851+
diag::availability_invalid_version_number_for_domain,
852+
*version, *domain)
853+
.highlight(sourceRange);
854+
}
855+
843856
return false;
844857
};
845858

lib/AST/AvailabilityDomain.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ bool AvailabilityDomain::isVersioned() const {
109109
}
110110
}
111111

112+
bool AvailabilityDomain::isVersionValid(
113+
const llvm::VersionTuple &version) const {
114+
ASSERT(isVersioned());
115+
116+
switch (getKind()) {
117+
case Kind::Universal:
118+
case Kind::Embedded:
119+
llvm_unreachable("unexpected domain kind");
120+
case Kind::SwiftLanguage:
121+
case Kind::PackageDescription:
122+
return true;
123+
case Kind::Platform:
124+
if (auto osType = tripleOSTypeForPlatform(getPlatformKind()))
125+
return llvm::Triple::isValidVersionForOS(*osType, version);
126+
return true;
127+
case Kind::Custom:
128+
return true;
129+
}
130+
}
131+
112132
bool AvailabilityDomain::supportsContextRefinement() const {
113133
switch (getKind()) {
114134
case Kind::Universal:

lib/AST/PlatformKind.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child,
263263
return false;
264264
}
265265

266-
static std::optional<llvm::Triple::OSType>
267-
tripleOSTypeForPlatform(PlatformKind platform) {
266+
std::optional<llvm::Triple::OSType>
267+
swift::tripleOSTypeForPlatform(PlatformKind platform) {
268268
switch (platform) {
269269
case PlatformKind::macOS:
270270
case PlatformKind::macOSApplicationExtension:
@@ -296,8 +296,11 @@ tripleOSTypeForPlatform(PlatformKind platform) {
296296
llvm::VersionTuple
297297
swift::canonicalizePlatformVersion(PlatformKind platform,
298298
const llvm::VersionTuple &version) {
299-
if (auto osType = tripleOSTypeForPlatform(platform))
300-
return llvm::Triple::getCanonicalVersionForOS(*osType, version);
299+
if (auto osType = tripleOSTypeForPlatform(platform)) {
300+
bool isInValidRange = llvm::Triple::isValidVersionForOS(*osType, version);
301+
return llvm::Triple::getCanonicalVersionForOS(*osType, version,
302+
isInValidRange);
303+
}
301304

302305
return version;
303306
}

test/IDE/print_ast_tc_decls_canonical_versions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-swift-frontend -typecheck -verify %s
2+
// RUN: %target-swift-frontend -typecheck %s
33
// 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
44
// RUN: %FileCheck %s -check-prefix=PASS_COMMON -strict-whitespace < %t.printed.txt
55

test/Sema/availability_versions_canonical.swift

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple
1+
// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target x86_64-apple-macosx10.15 -verify-additional-prefix macos-
2+
// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-ios13 -verify-additional-prefix ios-
3+
// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-watchos6 -verify-additional-prefix watchos-
4+
// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-tvos13 -verify-additional-prefix tvos-
5+
// RUN: %swift -typecheck %s -verify -parse-stdlib -module-name Swift -target arm64-apple-xros1 -verify-additional-prefix visionos-
26

37
@available(OSX 10.16, *)
48
func introducedOnMacOS10_16() { }
@@ -12,7 +16,59 @@ func introducedInVersionsMappingTo26_0() { }
1216
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
1317
func introducedIn26_0() { }
1418

19+
@available(macOS 17.0, iOS 20.0, watchOS 13.0, tvOS 20.0, visionOS 4.0, *)
20+
// expected-warning@-1 {{'17.0' is not a valid version number for macOS}}
21+
// expected-warning@-2 {{'20.0' is not a valid version number for iOS}}
22+
// expected-warning@-3 {{'13.0' is not a valid version number for watchOS}}
23+
// expected-warning@-4 {{'20.0' is not a valid version number for tvOS}}
24+
// expected-warning@-5 {{'4.0' is not a valid version number for visionOS}}
25+
func introducedInVersionsMappingTo27_0() { }
26+
27+
@available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *)
28+
func introducedIn27_0() { }
29+
1530
func useUnderPoundAvailable() {
31+
// expected-note@-1 * {{add '@available' attribute to enclosing global function}}
32+
introducedOnMacOS10_16()
33+
// expected-macos-error@-1 {{'introducedOnMacOS10_16()' is only available in macOS 11.0 or newer}}
34+
// expected-macos-note@-2 {{add 'if #available' version check}}
35+
36+
introducedOnMacOS11_0()
37+
// expected-macos-error@-1 {{'introducedOnMacOS11_0()' is only available in macOS 11.0 or newer}}
38+
// expected-macos-note@-2 {{add 'if #available' version check}}
39+
40+
introducedInVersionsMappingTo26_0()
41+
// expected-macos-error@-1 {{'introducedInVersionsMappingTo26_0()' is only available in macOS 26.0 or newer}}
42+
// expected-ios-error@-2 {{'introducedInVersionsMappingTo26_0()' is only available in iOS 26.0 or newer}}
43+
// expected-watchos-error@-3 {{'introducedInVersionsMappingTo26_0()' is only available in watchOS 26.0 or newer}}
44+
// expected-tvos-error@-4 {{'introducedInVersionsMappingTo26_0()' is only available in tvOS 26.0 or newer}}
45+
// expected-visionos-error@-5 {{'introducedInVersionsMappingTo26_0()' is only available in visionOS 26.0 or newer}}
46+
// expected-note@-6 {{add 'if #available' version check}}
47+
48+
introducedIn26_0()
49+
// expected-macos-error@-1 {{'introducedIn26_0()' is only available in macOS 26.0 or newer}}
50+
// expected-ios-error@-2 {{'introducedIn26_0()' is only available in iOS 26.0 or newer}}
51+
// expected-watchos-error@-3 {{'introducedIn26_0()' is only available in watchOS 26.0 or newer}}
52+
// expected-tvos-error@-4 {{'introducedIn26_0()' is only available in tvOS 26.0 or newer}}
53+
// expected-visionos-error@-5 {{'introducedIn26_0()' is only available in visionOS 26.0 or newer}}
54+
// expected-note@-6 {{add 'if #available' version check}}
55+
56+
introducedInVersionsMappingTo27_0()
57+
// expected-macos-error@-1 {{'introducedInVersionsMappingTo27_0()' is only available in macOS 27.0 or newer}}
58+
// expected-ios-error@-2 {{'introducedInVersionsMappingTo27_0()' is only available in iOS 27.0 or newer}}
59+
// expected-watchos-error@-3 {{'introducedInVersionsMappingTo27_0()' is only available in watchOS 27.0 or newer}}
60+
// expected-tvos-error@-4 {{'introducedInVersionsMappingTo27_0()' is only available in tvOS 27.0 or newer}}
61+
// expected-visionos-error@-5 {{'introducedInVersionsMappingTo27_0()' is only available in visionOS 27.0 or newer}}
62+
// expected-note@-6 {{add 'if #available' version check}}
63+
64+
introducedIn27_0()
65+
// expected-macos-error@-1 {{'introducedIn27_0()' is only available in macOS 27.0 or newer}}
66+
// expected-ios-error@-2 {{'introducedIn27_0()' is only available in iOS 27.0 or newer}}
67+
// expected-watchos-error@-3 {{'introducedIn27_0()' is only available in watchOS 27.0 or newer}}
68+
// expected-tvos-error@-4 {{'introducedIn27_0()' is only available in tvOS 27.0 or newer}}
69+
// expected-visionos-error@-5 {{'introducedIn27_0()' is only available in visionOS 27.0 or newer}}
70+
// expected-note@-6 {{add 'if #available' version check}}
71+
1672
if #available(OSX 10.16, *) {
1773
introducedOnMacOS10_16()
1874
introducedOnMacOS11_0()
@@ -21,5 +77,24 @@ func useUnderPoundAvailable() {
2177
if #available(macOS 16.0, iOS 19.0, watchOS 12.0, tvOS 19.0, visionOS 3.0, *) {
2278
introducedInVersionsMappingTo26_0()
2379
introducedIn26_0()
80+
introducedInVersionsMappingTo27_0()
81+
// expected-macos-error@-1 {{'introducedInVersionsMappingTo27_0()' is only available in macOS 27.0 or newer}}
82+
// expected-ios-error@-2 {{'introducedInVersionsMappingTo27_0()' is only available in iOS 27.0 or newer}}
83+
// expected-watchos-error@-3 {{'introducedInVersionsMappingTo27_0()' is only available in watchOS 27.0 or newer}}
84+
// expected-tvos-error@-4 {{'introducedInVersionsMappingTo27_0()' is only available in tvOS 27.0 or newer}}
85+
// expected-visionos-error@-5 {{'introducedInVersionsMappingTo27_0()' is only available in visionOS 27.0 or newer}}
86+
// expected-note@-6 {{add 'if #available' version check}}
87+
introducedIn27_0()
88+
// expected-macos-error@-1 {{'introducedIn27_0()' is only available in macOS 27.0 or newer}}
89+
// expected-ios-error@-2 {{'introducedIn27_0()' is only available in iOS 27.0 or newer}}
90+
// expected-watchos-error@-3 {{'introducedIn27_0()' is only available in watchOS 27.0 or newer}}
91+
// expected-tvos-error@-4 {{'introducedIn27_0()' is only available in tvOS 27.0 or newer}}
92+
// expected-visionos-error@-5 {{'introducedIn27_0()' is only available in visionOS 27.0 or newer}}
93+
// expected-note@-6 {{add 'if #available' version check}}
94+
}
95+
96+
if #available(macOS 17.0, iOS 20.0, watchOS 13.0, tvOS 20.0, visionOS 4.0, *) {
97+
introducedInVersionsMappingTo27_0()
98+
introducedIn27_0()
2499
}
25100
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -parse-stdlib -module-name Swift %s
2+
3+
@available(macOS, introduced: 17) // expected-warning {{'17' is not a valid version number for macOS}}
4+
@available(iOS, introduced: 20) // expected-warning {{'20' is not a valid version number for iOS}}
5+
@available(macCatalyst, introduced: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}}
6+
@available(watchOS, introduced: 13) // expected-warning {{'13' is not a valid version number for watchOS}}
7+
@available(tvOS, introduced: 20) // expected-warning {{'20' is not a valid version number for tvOS}}
8+
@available(visionOS, introduced: 4) // expected-warning {{'4' is not a valid version number for visionOS}}
9+
func invalidIntroduced() { }
10+
11+
@available(macOS, deprecated: 17) // expected-warning {{'17' is not a valid version number for macOS}}
12+
@available(iOS, deprecated: 20) // expected-warning {{'20' is not a valid version number for iOS}}
13+
@available(macCatalyst, deprecated: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}}
14+
@available(watchOS, deprecated: 13) // expected-warning {{'13' is not a valid version number for watchOS}}
15+
@available(tvOS, deprecated: 20) // expected-warning {{'20' is not a valid version number for tvOS}}
16+
@available(visionOS, deprecated: 4) // expected-warning {{'4' is not a valid version number for visionOS}}
17+
func invalidDeprecated() { }
18+
19+
@available(macOS, obsoleted: 17) // expected-warning {{'17' is not a valid version number for macOS}}
20+
@available(iOS, obsoleted: 20) // expected-warning {{'20' is not a valid version number for iOS}}
21+
@available(macCatalyst, obsoleted: 20) // expected-warning {{'20' is not a valid version number for Mac Catalyst}}
22+
@available(watchOS, obsoleted: 13) // expected-warning {{'13' is not a valid version number for watchOS}}
23+
@available(tvOS, obsoleted: 20) // expected-warning {{'20' is not a valid version number for tvOS}}
24+
@available(visionOS, obsoleted: 4) // expected-warning {{'4' is not a valid version number for visionOS}}
25+
func invalidObsoleted() { }
26+
27+
@available(macOS 18, iOS 21, macCatalyst 22, watchOS 14, tvOS 23, visionOS 7, *)
28+
// expected-warning@-1 {{'18' is not a valid version number for macOS}}
29+
// expected-warning@-2 {{'21' is not a valid version number for iOS}}
30+
// expected-warning@-3 {{'22' is not a valid version number for Mac Catalyst}}
31+
// expected-warning@-4 {{'14' is not a valid version number for watchOS}}
32+
// expected-warning@-5 {{'23' is not a valid version number for tvOS}}
33+
// expected-warning@-6 {{'7' is not a valid version number for visionOS}}
34+
func invalidIntroducedShort() { }

0 commit comments

Comments
 (0)