Skip to content

Commit 3b77e2e

Browse files
committed
AST: Introduce experimental support for an anyAppleOS availability domain.
`anyAppleOS` represents a meta-platform for availability checks that can be used to check availability across all of Apple's operating systems. It supports versions 26.0 and up since version 26.0 is the first OS version number that is aligned accross macOS, iOS, watchOS, tvOS, and visionOS. Apple platform-specific availability specification take precedence over `anyAppleOS` availability specifications when specified simultaneously. Resolves rdar://153834380.
1 parent c978386 commit 3b77e2e

File tree

8 files changed

+252
-23
lines changed

8 files changed

+252
-23
lines changed

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,9 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(TildeSendable, false)
555555
/// Allow use of protocol typed values in Embedded mode (`Any` and friends)
556556
EXPERIMENTAL_FEATURE(EmbeddedExistentials, false)
557557

558+
/// Allow use of the 'anyAppleOS' availability domain.
559+
EXPERIMENTAL_FEATURE(AnyAppleOSAvailability, true)
560+
558561
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
559562
#undef EXPERIMENTAL_FEATURE
560563
#undef UPCOMING_FEATURE

lib/AST/Availability.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,9 +684,10 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
684684
if (!version)
685685
return false;
686686

687+
auto diagLoc = sourceRange.isValid() ? sourceRange.Start : attrLoc;
687688
if (!VersionRange::isValidVersion(*version)) {
688689
diags
689-
.diagnose(attrLoc, diag::availability_unsupported_version_number,
690+
.diagnose(diagLoc, diag::availability_unsupported_version_number,
690691
*version)
691692
.highlight(sourceRange);
692693
return true;
@@ -696,7 +697,7 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
696697
// 17 will never exist.
697698
if (domain->isVersioned() && !domain->isVersionValid(*version)) {
698699
diags
699-
.diagnose(attrLoc,
700+
.diagnose(diagLoc,
700701
diag::availability_invalid_version_number_for_domain,
701702
*version, *domain)
702703
.highlight(sourceRange);

lib/AST/AvailabilityDomain.cpp

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,17 @@ bool AvailabilityDomain::isVersionValid(
169169
return true;
170170

171171
case Kind::Platform:
172+
// If the platform kind corresponds to a specific OS, LLVM is the source of
173+
// truth for version validity.
172174
if (auto osType = tripleOSTypeForPlatform(getPlatformKind()))
173175
return llvm::Triple::isValidVersionForOS(*osType, version);
176+
177+
// Unified versioning for Apple's operating systems starts at 26.0.
178+
if (getPlatformKind() == PlatformKind::anyAppleOS)
179+
return (version.getMajor() >= 26);
180+
174181
return true;
182+
175183
case Kind::Custom:
176184
return true;
177185
}
@@ -400,12 +408,36 @@ AvailabilityDomain AvailabilityDomain::getRootDomain() const {
400408
return *this;
401409
}
402410

411+
static std::optional<PlatformKind>
412+
getApplePlatformKindForTarget(const llvm::Triple &target) {
413+
if (target.isMacOSX())
414+
return PlatformKind::macOS;
415+
if (target.isTvOS()) // Must be checked before isiOS.
416+
return PlatformKind::tvOS;
417+
if (target.isiOS())
418+
return PlatformKind::iOS;
419+
if (target.isWatchOS())
420+
return PlatformKind::watchOS;
421+
if (target.isXROS())
422+
return PlatformKind::visionOS;
423+
424+
return std::nullopt;
425+
}
426+
403427
std::optional<AvailabilityDomain>
404428
AvailabilityDomain::getRemappedDomainOrNull(const ASTContext &ctx) const {
405429
if (getPlatformKind() == PlatformKind::iOS &&
406430
isPlatformActive(PlatformKind::visionOS, ctx.LangOpts))
407431
return AvailabilityDomain::forPlatform(PlatformKind::visionOS);
408432

433+
if (getPlatformKind() == PlatformKind::anyAppleOS) {
434+
if (auto applePlatformKind =
435+
getApplePlatformKindForTarget(ctx.LangOpts.Target))
436+
return AvailabilityDomain::forPlatform(*applePlatformKind);
437+
438+
return std::nullopt;
439+
}
440+
409441
return std::nullopt;
410442
}
411443

@@ -450,23 +482,29 @@ AvailabilityDomainAndRange AvailabilityDomain::getRemappedDomainAndRange(
450482
if (!remappedDomain)
451483
return {*this, AvailabilityRange{version}};
452484

453-
std::optional<clang::VersionTuple> remappedVersion;
454-
switch (versionKind) {
455-
case AvailabilityVersionKind::Introduced:
456-
remappedVersion =
457-
getRemappedIntroducedVersionForFallbackPlatform(ctx, version);
458-
break;
459-
case AvailabilityVersionKind::Deprecated:
460-
case AvailabilityVersionKind::Obsoleted:
461-
remappedVersion =
462-
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(ctx, version);
463-
break;
464-
}
485+
if (getPlatformKind() == PlatformKind::anyAppleOS)
486+
return {*remappedDomain, AvailabilityRange{version}};
487+
488+
if (getPlatformKind() == PlatformKind::iOS) {
489+
std::optional<clang::VersionTuple> remappedVersion;
490+
switch (versionKind) {
491+
case AvailabilityVersionKind::Introduced:
492+
remappedVersion =
493+
getRemappedIntroducedVersionForFallbackPlatform(ctx, version);
494+
break;
495+
case AvailabilityVersionKind::Deprecated:
496+
case AvailabilityVersionKind::Obsoleted:
497+
remappedVersion =
498+
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(ctx,
499+
version);
500+
break;
501+
}
465502

466-
if (!remappedVersion)
467-
return {*this, AvailabilityRange{version}};
503+
if (remappedVersion)
504+
return {*remappedDomain, AvailabilityRange{*remappedVersion}};
505+
}
468506

469-
return {*remappedDomain, AvailabilityRange{*remappedVersion}};
507+
return {*this, AvailabilityRange{version}};
470508
}
471509

472510
bool IsCustomAvailabilityDomainPermanentlyEnabled::evaluate(
@@ -627,6 +665,13 @@ AvailabilityDomainOrIdentifier::lookUpInDeclContext(
627665
return std::nullopt;
628666
}
629667

668+
if (domain->getPlatformKind() == PlatformKind::anyAppleOS &&
669+
!ctx.LangOpts.hasFeature(Feature::AnyAppleOSAvailability)) {
670+
diags.diagnose(loc, diag::availability_domain_requires_feature, *domain,
671+
"AnyAppleOSAvailability");
672+
return std::nullopt;
673+
}
674+
630675
// Use of the 'Swift' domain requires the 'SwiftRuntimeAvailability' feature.
631676
if (!hasSwiftRuntimeAvailability && domain->isStandaloneSwiftRuntime()) {
632677
diags.diagnose(loc, diag::availability_domain_requires_feature, *domain,

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ static bool usesFeatureTildeSendable(Decl *decl) {
463463
});
464464
}
465465

466+
UNINTERESTING_FEATURE(AnyAppleOSAvailability)
466467

467468
// ----------------------------------------------------------------------------
468469
// MARK: - FeatureSet

lib/AST/PlatformKindUtils.cpp

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ swift::basePlatformForExtensionPlatform(PlatformKind Platform) {
130130

131131
static bool isPlatformActiveForTarget(PlatformKind Platform,
132132
const llvm::Triple &Target,
133-
bool EnableAppExtensionRestrictions,
133+
const LangOptions &LangOpts,
134134
bool ForRuntimeQuery) {
135135
if (Platform == PlatformKind::none)
136136
return true;
137137

138-
if (!EnableAppExtensionRestrictions &&
138+
if (!LangOpts.EnableAppExtensionRestrictions &&
139139
isApplicationExtensionPlatform(Platform))
140140
return false;
141141

@@ -186,12 +186,11 @@ bool swift::isPlatformActive(PlatformKind Platform, const LangOptions &LangOpts,
186186
if (ForTargetVariant) {
187187
assert(LangOpts.TargetVariant && "Must have target variant triple");
188188
return isPlatformActiveForTarget(Platform, *LangOpts.TargetVariant,
189-
LangOpts.EnableAppExtensionRestrictions,
190-
ForRuntimeQuery);
189+
LangOpts, ForRuntimeQuery);
191190
}
192191

193-
return isPlatformActiveForTarget(Platform, LangOpts.Target,
194-
LangOpts.EnableAppExtensionRestrictions, ForRuntimeQuery);
192+
return isPlatformActiveForTarget(Platform, LangOpts.Target, LangOpts,
193+
ForRuntimeQuery);
195194
}
196195

197196
static PlatformKind platformForTriple(const llvm::Triple &triple,
@@ -250,6 +249,33 @@ PlatformKind swift::targetVariantPlatform(const LangOptions &LangOpts) {
250249
return PlatformKind::none;
251250
}
252251

252+
static bool inheritsAvailabilityFromAnyAppleOS(PlatformKind platform) {
253+
switch (platform) {
254+
case PlatformKind::macOSApplicationExtension:
255+
case PlatformKind::iOSApplicationExtension:
256+
case PlatformKind::macCatalystApplicationExtension:
257+
case PlatformKind::tvOSApplicationExtension:
258+
case PlatformKind::watchOSApplicationExtension:
259+
case PlatformKind::visionOSApplicationExtension:
260+
case PlatformKind::macOS:
261+
case PlatformKind::iOS:
262+
case PlatformKind::macCatalyst:
263+
case PlatformKind::tvOS:
264+
case PlatformKind::watchOS:
265+
case PlatformKind::visionOS:
266+
return true;
267+
case PlatformKind::DriverKit:
268+
case PlatformKind::anyAppleOS:
269+
case PlatformKind::Swift:
270+
case PlatformKind::FreeBSD:
271+
case PlatformKind::OpenBSD:
272+
case PlatformKind::Windows:
273+
case PlatformKind::Android:
274+
case PlatformKind::none:
275+
return false;
276+
}
277+
}
278+
253279
bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child,
254280
PlatformKind Parent) {
255281
if (auto ChildPlatformBase = basePlatformForExtensionPlatform(Child)) {
@@ -277,6 +303,10 @@ bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child,
277303
}
278304
}
279305

306+
if (Parent == PlatformKind::anyAppleOS &&
307+
inheritsAvailabilityFromAnyAppleOS(Child))
308+
return true;
309+
280310
return false;
281311
}
282312

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-macos26 -verify-additional-prefix apple- -verify-additional-prefix macos-
2+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-ios26 -verify-additional-prefix apple- -verify-additional-prefix ios-
3+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-watchos26 -verify-additional-prefix apple- -verify-additional-prefix watchos-
4+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-tvos26 -verify-additional-prefix apple- -verify-additional-prefix tvos-
5+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-visionos26 -verify-additional-prefix apple- -verify-additional-prefix visionos-
6+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target x86_64-unknown-linux-gnu
7+
// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target x86_64-unknown-windows-msvc
8+
9+
// REQUIRES: swift_feature_AnyAppleOSAvailability
10+
11+
@available(anyAppleOS 26.1, *)
12+
func availableInAnyAppleOS26_1() { }
13+
14+
@available(anyAppleOS, deprecated: 26)
15+
func deprecatedInAnyAppleOS26() { }
16+
17+
@available(anyAppleOS, obsoleted: 26)
18+
func obsoletedInAnyAppleOS26() { }
19+
// expected-macos-note@-1 {{'obsoletedInAnyAppleOS26()' was obsoleted in macOS 26}}
20+
// expected-ios-note@-2 {{'obsoletedInAnyAppleOS26()' was obsoleted in iOS 26}}
21+
// expected-watchos-note@-3 {{'obsoletedInAnyAppleOS26()' was obsoleted in watchOS 26}}
22+
// expected-tvos-note@-4 {{'obsoletedInAnyAppleOS26()' was obsoleted in tvOS 26}}
23+
// expected-visionos-note@-5 {{'obsoletedInAnyAppleOS26()' was obsoleted in visionOS 26}}
24+
25+
@available(anyAppleOS 26, macOS 26.1, *)
26+
func availableInAnyAppleOS26AndMacOS26_1() { }
27+
28+
@available(macOS 26.1, anyAppleOS 26, *)
29+
func availableInMacOS26_1AndAnyAppleOS26() { }
30+
31+
@available(macOS 26.1, iOS 26.1, watchOS 26.1, tvOS 26.1, visionOS 26.1, *)
32+
func availableInEveryAppleOS26_1() { }
33+
34+
@available(anyAppleOS, unavailable)
35+
func unavailableInAnyAppleOS() { } // expected-apple-note {{'unavailableInAnyAppleOS()' has been explicitly marked unavailable here}}
36+
37+
// FIXME: [availability] Ensure the fix-it suggests @available(anyAppleOS ...) rdar://163819878
38+
func availableAtDeploymentTarget() {
39+
// expected-apple-note@-1 {{add '@available' attribute to enclosing global function}}
40+
// expected-macos-note@-2 2 {{add '@available' attribute to enclosing global function}}
41+
42+
// FIXME: [availability] Ensure the fix-it suggests if #available(anyAppleOS ...) rdar://163819878
43+
availableInAnyAppleOS26_1()
44+
// expected-macos-error@-1 {{'availableInAnyAppleOS26_1()' is only available in macOS 26.1 or newer}}
45+
// expected-ios-error@-2 {{'availableInAnyAppleOS26_1()' is only available in iOS 26.1 or newer}}
46+
// expected-watchos-error@-3 {{'availableInAnyAppleOS26_1()' is only available in watchOS 26.1 or newer}}
47+
// expected-tvos-error@-4 {{'availableInAnyAppleOS26_1()' is only available in tvOS 26.1 or newer}}
48+
// expected-visionos-error@-5 {{'availableInAnyAppleOS26_1()' is only available in visionOS 26.1 or newer}}
49+
// expected-apple-note@-6 {{add 'if #available' version check}}
50+
51+
// FIXME: [availability] Remap domain/version in deprecation diagnostics
52+
deprecatedInAnyAppleOS26()
53+
// expected-apple-warning@-1 {{'deprecatedInAnyAppleOS26()' was deprecated in any Apple OS 26}}
54+
55+
obsoletedInAnyAppleOS26()
56+
// expected-macos-error@-1 {{'obsoletedInAnyAppleOS26()' is unavailable in macOS}}
57+
// expected-ios-error@-2 {{'obsoletedInAnyAppleOS26()' is unavailable in iOS}}
58+
// expected-watchos-error@-3 {{'obsoletedInAnyAppleOS26()' is unavailable in watchOS}}
59+
// expected-tvos-error@-4 {{'obsoletedInAnyAppleOS26()' is unavailable in tvOS}}
60+
// expected-visionos-error@-5 {{'obsoletedInAnyAppleOS26()' is unavailable in visionOS}}
61+
62+
availableInAnyAppleOS26AndMacOS26_1()
63+
// expected-macos-error@-1 {{'availableInAnyAppleOS26AndMacOS26_1()' is only available in macOS 26.1 or newer}}
64+
// expected-macos-note@-2 {{add 'if #available' version check}}{{3-40=if #available(macOS 26.1, *) {\n availableInAnyAppleOS26AndMacOS26_1()\n \} else {\n // Fallback on earlier versions\n \}}}
65+
availableInMacOS26_1AndAnyAppleOS26()
66+
// expected-macos-error@-1 {{'availableInMacOS26_1AndAnyAppleOS26()' is only available in macOS 26.1 or newer}}
67+
// expected-macos-note@-2 {{add 'if #available' version check}}{{3-40=if #available(macOS 26.1, *) {\n availableInMacOS26_1AndAnyAppleOS26()\n \} else {\n // Fallback on earlier versions\n \}}}
68+
69+
unavailableInAnyAppleOS()
70+
// expected-macos-error@-1 {{'unavailableInAnyAppleOS()' is unavailable in macOS}}
71+
// expected-ios-error@-2 {{'unavailableInAnyAppleOS()' is unavailable in iOS}}
72+
// expected-watchos-error@-3 {{'unavailableInAnyAppleOS()' is unavailable in watchOS}}
73+
// expected-tvos-error@-4 {{'unavailableInAnyAppleOS()' is unavailable in tvOS}}
74+
// expected-visionos-error@-5 {{'unavailableInAnyAppleOS()' is unavailable in visionOS}}
75+
76+
if #available(anyAppleOS 25, *) { } // expected-warning {{'25' is not a valid version number for any Apple OS}}
77+
78+
if #available(anyAppleOS 26.1, *) {
79+
availableInAnyAppleOS26_1()
80+
availableInEveryAppleOS26_1()
81+
availableInAnyAppleOS26AndMacOS26_1()
82+
availableInMacOS26_1AndAnyAppleOS26()
83+
}
84+
if #available(macOS 26.1, *) {
85+
availableInAnyAppleOS26AndMacOS26_1()
86+
availableInMacOS26_1AndAnyAppleOS26()
87+
}
88+
if #available(macOS 26.1, iOS 26.1, watchOS 26.1, tvOS 26.1, visionOS 26.1, *) {
89+
availableInAnyAppleOS26_1()
90+
availableInEveryAppleOS26_1()
91+
}
92+
}
93+
94+
@available(anyAppleOS 26.1, *)
95+
struct AvailableInAnyAppleOS26_1 {
96+
func method() {
97+
availableInAnyAppleOS26_1()
98+
}
99+
}
100+
101+
@available(anyAppleOS, unavailable)
102+
func alsoUnavailableInAnyAppleOS() {
103+
unavailableInAnyAppleOS()
104+
}

test/attr/attr_availability.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ func incorrect_platform_not_similar() {}
3939
@available(HAL9000, unavailable) // expected-warning {{unrecognized platform name 'HAL9000'}}
4040
func availabilityUnknownPlatform() {}
4141

42+
@available(Swift 6.2, *) // expected-error {{Swift requires '-enable-experimental-feature SwiftRuntimeAvailability'}}
43+
func swift6_2() {}
44+
45+
@available(SwiftLanguageMode 6.0, *) // expected-error {{Swift requires '-enable-experimental-feature SwiftRuntimeAvailability'}}
46+
func swiftLanguageMode6_0() {}
47+
48+
@available(anyAppleOS 26, *) // expected-error {{any Apple OS requires '-enable-experimental-feature AnyAppleOSAvailability'}}
49+
func anyAppleOS26() {}
50+
4251
// <rdar://problem/17669805> Availability can't appear on a typealias
4352
@available(*, unavailable, message: "oh no you don't")
4453
typealias int = Int // expected-note {{'int' has been explicitly marked unavailable here}}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 5 -parse-as-library -enable-experimental-feature AnyAppleOSAvailability
2+
3+
// REQUIRES: swift_feature_AnyAppleOSAvailability
4+
5+
@available(anyAppleOS 26, *)
6+
func availableIn26Short() { }
7+
8+
@available(anyAppleOS 26.0, *)
9+
func availableIn26_0Short() { }
10+
11+
@available(AnyAppleOS 26, *) // expected-warning {{unrecognized platform name 'AnyAppleOS'; did you mean 'anyAppleOS'}}
12+
func miscapitalized() { }
13+
14+
@available(anyAppleOS 25, *) // expected-warning {{'25' is not a valid version number for any Apple OS}}
15+
func availableIn25Short() { }
16+
17+
@available(anyAppleOS 26, macOS 26, iOS 26, watchOS 26, tvOS 26, visionOS 26, *)
18+
func availableIn26ShortWithPlatforms() { }
19+
20+
@available(anyAppleOS, introduced: 26)
21+
func availableIn26() { }
22+
23+
@available(anyAppleOS, introduced: 26.0)
24+
func availableIn26_0() { }
25+
26+
@available(anyAppleOS, obsoleted: 26)
27+
func obsoletedIn26() { }
28+
29+
@available(anyAppleOS, deprecated: 26)
30+
func deprecatedIn26() { }
31+
32+
@available(anyAppleOS, deprecated)
33+
func deprecated() { }
34+
35+
@available(anyAppleOS, unavailable)
36+
func unavailable() { }

0 commit comments

Comments
 (0)