diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index f50db34a20296..af0a8c6eb6f6d 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -555,6 +555,9 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(TildeSendable, false) /// Allow use of protocol typed values in Embedded mode (`Any` and friends) EXPERIMENTAL_FEATURE(EmbeddedExistentials, false) +/// Allow use of the 'anyAppleOS' availability domain. +EXPERIMENTAL_FEATURE(AnyAppleOSAvailability, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index e9237a6c5d505..4ddb00084e4f4 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -684,9 +684,10 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator, if (!version) return false; + auto diagLoc = sourceRange.isValid() ? sourceRange.Start : attrLoc; if (!VersionRange::isValidVersion(*version)) { diags - .diagnose(attrLoc, diag::availability_unsupported_version_number, + .diagnose(diagLoc, diag::availability_unsupported_version_number, *version) .highlight(sourceRange); return true; @@ -696,7 +697,7 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator, // 17 will never exist. if (domain->isVersioned() && !domain->isVersionValid(*version)) { diags - .diagnose(attrLoc, + .diagnose(diagLoc, diag::availability_invalid_version_number_for_domain, *version, *domain) .highlight(sourceRange); diff --git a/lib/AST/AvailabilityDomain.cpp b/lib/AST/AvailabilityDomain.cpp index 5ee3a7fcb4ea1..989a23e487ad6 100644 --- a/lib/AST/AvailabilityDomain.cpp +++ b/lib/AST/AvailabilityDomain.cpp @@ -169,9 +169,17 @@ bool AvailabilityDomain::isVersionValid( return true; case Kind::Platform: + // If the platform kind corresponds to a specific OS, LLVM is the source of + // truth for version validity. if (auto osType = tripleOSTypeForPlatform(getPlatformKind())) return llvm::Triple::isValidVersionForOS(*osType, version); + + // Unified versioning for Apple's operating systems starts at 26.0. + if (getPlatformKind() == PlatformKind::anyAppleOS) + return (version.getMajor() >= 26); + return true; + case Kind::Custom: return true; } @@ -400,12 +408,36 @@ AvailabilityDomain AvailabilityDomain::getRootDomain() const { return *this; } +static std::optional +getApplePlatformKindForTarget(const llvm::Triple &target) { + if (target.isMacOSX()) + return PlatformKind::macOS; + if (target.isTvOS()) // Must be checked before isiOS. + return PlatformKind::tvOS; + if (target.isiOS()) + return PlatformKind::iOS; + if (target.isWatchOS()) + return PlatformKind::watchOS; + if (target.isXROS()) + return PlatformKind::visionOS; + + return std::nullopt; +} + std::optional AvailabilityDomain::getRemappedDomainOrNull(const ASTContext &ctx) const { if (getPlatformKind() == PlatformKind::iOS && isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) return AvailabilityDomain::forPlatform(PlatformKind::visionOS); + if (getPlatformKind() == PlatformKind::anyAppleOS) { + if (auto applePlatformKind = + getApplePlatformKindForTarget(ctx.LangOpts.Target)) + return AvailabilityDomain::forPlatform(*applePlatformKind); + + return std::nullopt; + } + return std::nullopt; } @@ -450,23 +482,29 @@ AvailabilityDomainAndRange AvailabilityDomain::getRemappedDomainAndRange( if (!remappedDomain) return {*this, AvailabilityRange{version}}; - std::optional remappedVersion; - switch (versionKind) { - case AvailabilityVersionKind::Introduced: - remappedVersion = - getRemappedIntroducedVersionForFallbackPlatform(ctx, version); - break; - case AvailabilityVersionKind::Deprecated: - case AvailabilityVersionKind::Obsoleted: - remappedVersion = - getRemappedDeprecatedObsoletedVersionForFallbackPlatform(ctx, version); - break; - } + if (getPlatformKind() == PlatformKind::anyAppleOS) + return {*remappedDomain, AvailabilityRange{version}}; + + if (getPlatformKind() == PlatformKind::iOS) { + std::optional remappedVersion; + switch (versionKind) { + case AvailabilityVersionKind::Introduced: + remappedVersion = + getRemappedIntroducedVersionForFallbackPlatform(ctx, version); + break; + case AvailabilityVersionKind::Deprecated: + case AvailabilityVersionKind::Obsoleted: + remappedVersion = + getRemappedDeprecatedObsoletedVersionForFallbackPlatform(ctx, + version); + break; + } - if (!remappedVersion) - return {*this, AvailabilityRange{version}}; + if (remappedVersion) + return {*remappedDomain, AvailabilityRange{*remappedVersion}}; + } - return {*remappedDomain, AvailabilityRange{*remappedVersion}}; + return {*this, AvailabilityRange{version}}; } bool IsCustomAvailabilityDomainPermanentlyEnabled::evaluate( @@ -627,6 +665,13 @@ AvailabilityDomainOrIdentifier::lookUpInDeclContext( return std::nullopt; } + if (domain->getPlatformKind() == PlatformKind::anyAppleOS && + !ctx.LangOpts.hasFeature(Feature::AnyAppleOSAvailability)) { + diags.diagnose(loc, diag::availability_domain_requires_feature, *domain, + "AnyAppleOSAvailability"); + return std::nullopt; + } + // Use of the 'Swift' domain requires the 'SwiftRuntimeAvailability' feature. if (!hasSwiftRuntimeAvailability && domain->isStandaloneSwiftRuntime()) { diags.diagnose(loc, diag::availability_domain_requires_feature, *domain, diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 3aa5e46bd2362..84c2d55645fe6 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -463,6 +463,7 @@ static bool usesFeatureTildeSendable(Decl *decl) { }); } +UNINTERESTING_FEATURE(AnyAppleOSAvailability) // ---------------------------------------------------------------------------- // MARK: - FeatureSet diff --git a/lib/AST/PlatformKindUtils.cpp b/lib/AST/PlatformKindUtils.cpp index 3e6406a6677d7..f878acb23b490 100644 --- a/lib/AST/PlatformKindUtils.cpp +++ b/lib/AST/PlatformKindUtils.cpp @@ -130,12 +130,12 @@ swift::basePlatformForExtensionPlatform(PlatformKind Platform) { static bool isPlatformActiveForTarget(PlatformKind Platform, const llvm::Triple &Target, - bool EnableAppExtensionRestrictions, + const LangOptions &LangOpts, bool ForRuntimeQuery) { if (Platform == PlatformKind::none) return true; - if (!EnableAppExtensionRestrictions && + if (!LangOpts.EnableAppExtensionRestrictions && isApplicationExtensionPlatform(Platform)) return false; @@ -186,12 +186,11 @@ bool swift::isPlatformActive(PlatformKind Platform, const LangOptions &LangOpts, if (ForTargetVariant) { assert(LangOpts.TargetVariant && "Must have target variant triple"); return isPlatformActiveForTarget(Platform, *LangOpts.TargetVariant, - LangOpts.EnableAppExtensionRestrictions, - ForRuntimeQuery); + LangOpts, ForRuntimeQuery); } - return isPlatformActiveForTarget(Platform, LangOpts.Target, - LangOpts.EnableAppExtensionRestrictions, ForRuntimeQuery); + return isPlatformActiveForTarget(Platform, LangOpts.Target, LangOpts, + ForRuntimeQuery); } static PlatformKind platformForTriple(const llvm::Triple &triple, @@ -250,6 +249,33 @@ PlatformKind swift::targetVariantPlatform(const LangOptions &LangOpts) { return PlatformKind::none; } +static bool inheritsAvailabilityFromAnyAppleOS(PlatformKind platform) { + switch (platform) { + case PlatformKind::macOSApplicationExtension: + case PlatformKind::iOSApplicationExtension: + case PlatformKind::macCatalystApplicationExtension: + case PlatformKind::tvOSApplicationExtension: + case PlatformKind::watchOSApplicationExtension: + case PlatformKind::visionOSApplicationExtension: + case PlatformKind::macOS: + case PlatformKind::iOS: + case PlatformKind::macCatalyst: + case PlatformKind::tvOS: + case PlatformKind::watchOS: + case PlatformKind::visionOS: + return true; + case PlatformKind::DriverKit: + case PlatformKind::anyAppleOS: + case PlatformKind::Swift: + case PlatformKind::FreeBSD: + case PlatformKind::OpenBSD: + case PlatformKind::Windows: + case PlatformKind::Android: + case PlatformKind::none: + return false; + } +} + bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child, PlatformKind Parent) { if (auto ChildPlatformBase = basePlatformForExtensionPlatform(Child)) { @@ -277,6 +303,10 @@ bool swift::inheritsAvailabilityFromPlatform(PlatformKind Child, } } + if (Parent == PlatformKind::anyAppleOS && + inheritsAvailabilityFromAnyAppleOS(Child)) + return true; + return false; } diff --git a/test/Availability/availability_any_apple_os.swift b/test/Availability/availability_any_apple_os.swift new file mode 100644 index 0000000000000..d86d746590f9c --- /dev/null +++ b/test/Availability/availability_any_apple_os.swift @@ -0,0 +1,104 @@ +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-macos26 -verify-additional-prefix apple- -verify-additional-prefix macos- +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-ios26 -verify-additional-prefix apple- -verify-additional-prefix ios- +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-watchos26 -verify-additional-prefix apple- -verify-additional-prefix watchos- +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-tvos26 -verify-additional-prefix apple- -verify-additional-prefix tvos- +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target %target-cpu-apple-visionos26 -verify-additional-prefix apple- -verify-additional-prefix visionos- +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target x86_64-unknown-linux-gnu +// RUN: %target-typecheck-verify-swift -parse-stdlib -enable-experimental-feature AnyAppleOSAvailability -target x86_64-unknown-windows-msvc + +// REQUIRES: swift_feature_AnyAppleOSAvailability + +@available(anyAppleOS 26.1, *) +func availableInAnyAppleOS26_1() { } + +@available(anyAppleOS, deprecated: 26) +func deprecatedInAnyAppleOS26() { } + +@available(anyAppleOS, obsoleted: 26) +func obsoletedInAnyAppleOS26() { } +// expected-macos-note@-1 {{'obsoletedInAnyAppleOS26()' was obsoleted in macOS 26}} +// expected-ios-note@-2 {{'obsoletedInAnyAppleOS26()' was obsoleted in iOS 26}} +// expected-watchos-note@-3 {{'obsoletedInAnyAppleOS26()' was obsoleted in watchOS 26}} +// expected-tvos-note@-4 {{'obsoletedInAnyAppleOS26()' was obsoleted in tvOS 26}} +// expected-visionos-note@-5 {{'obsoletedInAnyAppleOS26()' was obsoleted in visionOS 26}} + +@available(anyAppleOS 26, macOS 26.1, *) +func availableInAnyAppleOS26AndMacOS26_1() { } + +@available(macOS 26.1, anyAppleOS 26, *) +func availableInMacOS26_1AndAnyAppleOS26() { } + +@available(macOS 26.1, iOS 26.1, watchOS 26.1, tvOS 26.1, visionOS 26.1, *) +func availableInEveryAppleOS26_1() { } + +@available(anyAppleOS, unavailable) +func unavailableInAnyAppleOS() { } // expected-apple-note {{'unavailableInAnyAppleOS()' has been explicitly marked unavailable here}} + +// FIXME: [availability] Ensure the fix-it suggests @available(anyAppleOS ...) rdar://163819878 +func availableAtDeploymentTarget() { + // expected-apple-note@-1 {{add '@available' attribute to enclosing global function}} + // expected-macos-note@-2 2 {{add '@available' attribute to enclosing global function}} + + // FIXME: [availability] Ensure the fix-it suggests if #available(anyAppleOS ...) rdar://163819878 + availableInAnyAppleOS26_1() + // expected-macos-error@-1 {{'availableInAnyAppleOS26_1()' is only available in macOS 26.1 or newer}} + // expected-ios-error@-2 {{'availableInAnyAppleOS26_1()' is only available in iOS 26.1 or newer}} + // expected-watchos-error@-3 {{'availableInAnyAppleOS26_1()' is only available in watchOS 26.1 or newer}} + // expected-tvos-error@-4 {{'availableInAnyAppleOS26_1()' is only available in tvOS 26.1 or newer}} + // expected-visionos-error@-5 {{'availableInAnyAppleOS26_1()' is only available in visionOS 26.1 or newer}} + // expected-apple-note@-6 {{add 'if #available' version check}} + + // FIXME: [availability] Remap domain/version in deprecation diagnostics + deprecatedInAnyAppleOS26() + // expected-apple-warning@-1 {{'deprecatedInAnyAppleOS26()' was deprecated in any Apple OS 26}} + + obsoletedInAnyAppleOS26() + // expected-macos-error@-1 {{'obsoletedInAnyAppleOS26()' is unavailable in macOS}} + // expected-ios-error@-2 {{'obsoletedInAnyAppleOS26()' is unavailable in iOS}} + // expected-watchos-error@-3 {{'obsoletedInAnyAppleOS26()' is unavailable in watchOS}} + // expected-tvos-error@-4 {{'obsoletedInAnyAppleOS26()' is unavailable in tvOS}} + // expected-visionos-error@-5 {{'obsoletedInAnyAppleOS26()' is unavailable in visionOS}} + + availableInAnyAppleOS26AndMacOS26_1() + // expected-macos-error@-1 {{'availableInAnyAppleOS26AndMacOS26_1()' is only available in macOS 26.1 or newer}} + // 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 \}}} + availableInMacOS26_1AndAnyAppleOS26() + // expected-macos-error@-1 {{'availableInMacOS26_1AndAnyAppleOS26()' is only available in macOS 26.1 or newer}} + // 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 \}}} + + unavailableInAnyAppleOS() + // expected-macos-error@-1 {{'unavailableInAnyAppleOS()' is unavailable in macOS}} + // expected-ios-error@-2 {{'unavailableInAnyAppleOS()' is unavailable in iOS}} + // expected-watchos-error@-3 {{'unavailableInAnyAppleOS()' is unavailable in watchOS}} + // expected-tvos-error@-4 {{'unavailableInAnyAppleOS()' is unavailable in tvOS}} + // expected-visionos-error@-5 {{'unavailableInAnyAppleOS()' is unavailable in visionOS}} + + if #available(anyAppleOS 25, *) { } // expected-warning {{'25' is not a valid version number for any Apple OS}} + + if #available(anyAppleOS 26.1, *) { + availableInAnyAppleOS26_1() + availableInEveryAppleOS26_1() + availableInAnyAppleOS26AndMacOS26_1() + availableInMacOS26_1AndAnyAppleOS26() + } + if #available(macOS 26.1, *) { + availableInAnyAppleOS26AndMacOS26_1() + availableInMacOS26_1AndAnyAppleOS26() + } + if #available(macOS 26.1, iOS 26.1, watchOS 26.1, tvOS 26.1, visionOS 26.1, *) { + availableInAnyAppleOS26_1() + availableInEveryAppleOS26_1() + } +} + +@available(anyAppleOS 26.1, *) +struct AvailableInAnyAppleOS26_1 { + func method() { + availableInAnyAppleOS26_1() + } +} + +@available(anyAppleOS, unavailable) +func alsoUnavailableInAnyAppleOS() { + unavailableInAnyAppleOS() +} diff --git a/test/attr/attr_availability.swift b/test/attr/attr_availability.swift index ace37e0eab3ed..6125d5ce7aa1d 100644 --- a/test/attr/attr_availability.swift +++ b/test/attr/attr_availability.swift @@ -39,6 +39,15 @@ func incorrect_platform_not_similar() {} @available(HAL9000, unavailable) // expected-warning {{unrecognized platform name 'HAL9000'}} func availabilityUnknownPlatform() {} +@available(Swift 6.2, *) // expected-error {{Swift requires '-enable-experimental-feature SwiftRuntimeAvailability'}} +func swift6_2() {} + +@available(SwiftLanguageMode 6.0, *) // expected-error {{Swift requires '-enable-experimental-feature SwiftRuntimeAvailability'}} +func swiftLanguageMode6_0() {} + +@available(anyAppleOS 26, *) // expected-error {{any Apple OS requires '-enable-experimental-feature AnyAppleOSAvailability'}} +func anyAppleOS26() {} + // Availability can't appear on a typealias @available(*, unavailable, message: "oh no you don't") typealias int = Int // expected-note {{'int' has been explicitly marked unavailable here}} diff --git a/test/attr/attr_availability_any_apple_os.swift b/test/attr/attr_availability_any_apple_os.swift new file mode 100644 index 0000000000000..2006d0205a4c7 --- /dev/null +++ b/test/attr/attr_availability_any_apple_os.swift @@ -0,0 +1,36 @@ +// RUN: %target-typecheck-verify-swift -swift-version 5 -parse-as-library -enable-experimental-feature AnyAppleOSAvailability + +// REQUIRES: swift_feature_AnyAppleOSAvailability + +@available(anyAppleOS 26, *) +func availableIn26Short() { } + +@available(anyAppleOS 26.0, *) +func availableIn26_0Short() { } + +@available(AnyAppleOS 26, *) // expected-warning {{unrecognized platform name 'AnyAppleOS'; did you mean 'anyAppleOS'}} +func miscapitalized() { } + +@available(anyAppleOS 25, *) // expected-warning {{'25' is not a valid version number for any Apple OS}} +func availableIn25Short() { } + +@available(anyAppleOS 26, macOS 26, iOS 26, watchOS 26, tvOS 26, visionOS 26, *) +func availableIn26ShortWithPlatforms() { } + +@available(anyAppleOS, introduced: 26) +func availableIn26() { } + +@available(anyAppleOS, introduced: 26.0) +func availableIn26_0() { } + +@available(anyAppleOS, obsoleted: 26) +func obsoletedIn26() { } + +@available(anyAppleOS, deprecated: 26) +func deprecatedIn26() { } + +@available(anyAppleOS, deprecated) +func deprecated() { } + +@available(anyAppleOS, unavailable) +func unavailable() { }