From 7f0dd3c73e03309f76e5c5a7df3f8e5a5db202a5 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Thu, 22 Jan 2026 17:34:46 -0800 Subject: [PATCH] Infer visionOS availability from iOS availability (#176990) Automatically infer and apply availability or unavailable attributes for visionOS based on the corresponding iOS availability of the same declaration using the version mapping information provided in SDKSettings.json. rdar://162148639 --- clang/lib/Sema/SemaDeclAttr.cpp | 93 ++++++++++++++ .../Sema/Inputs/XROS.sdk/SDKSettings.json | 21 ++++ .../xrOS/Sema/infer-ios-availability.c | 117 ++++++++++++++++++ ...le-from-missing-ios-availability-mapping.c | 12 ++ 4 files changed, 243 insertions(+) create mode 100644 clang/test/PlatformSpecific/xrOS/Sema/Inputs/XROS.sdk/SDKSettings.json create mode 100644 clang/test/PlatformSpecific/xrOS/Sema/infer-ios-availability.c create mode 100644 clang/test/PlatformSpecific/xrOS/Sema/infer-unavailable-from-missing-ios-availability-mapping.c diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 79f3a0ca29e81..b881e43f83601 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2484,6 +2484,80 @@ static void handleFeatureAvailabilityAttr(Sema &S, Decl *D, IsUnavailable, AL)); } +/// Returns true if the given availability attribute should be inferred, and +/// adjusts the value of the attribute as necessary to facilitate that. +static bool shouldInferAvailabilityAttribute(const ParsedAttr &AL, + IdentifierInfo *&II, + bool &IsUnavailable, + VersionTuple &Introduced, + VersionTuple &Deprecated, + VersionTuple &Obsolete, Sema &S) { + const llvm::Triple &TT = S.Context.getTargetInfo().getTriple(); + const ASTContext &Context = S.Context; + if (TT.getOS() != llvm::Triple::XROS) + return false; + IdentifierInfo *NewII = nullptr; + if (II->getName() == "ios") + NewII = &Context.Idents.get("xros"); + else if (II->getName() == "ios_app_extension") + NewII = &Context.Idents.get("xros_app_extension"); + if (!NewII) + return false; + II = NewII; + + auto MakeUnavailable = [&]() { + IsUnavailable = true; + // Reset introduced, deprecated, obsoleted. + Introduced = VersionTuple(); + Deprecated = VersionTuple(); + Obsolete = VersionTuple(); + }; + + const DarwinSDKInfo *SDKInfo = S.getDarwinSDKInfoForAvailabilityChecking( + AL.getRange().getBegin(), "ios"); + + if (!SDKInfo) { + MakeUnavailable(); + return true; + } + // Map from the fallback platform availability to the current platform + // availability. + const auto *Mapping = SDKInfo->getVersionMapping(DarwinSDKInfo::OSEnvPair( + llvm::Triple::IOS, llvm::Triple::UnknownEnvironment, llvm::Triple::XROS, + llvm::Triple::UnknownEnvironment)); + if (!Mapping) { + MakeUnavailable(); + return true; + } + + if (!Introduced.empty()) { + auto NewIntroduced = Mapping->mapIntroducedAvailabilityVersion(Introduced); + if (!NewIntroduced) { + MakeUnavailable(); + return true; + } + Introduced = *NewIntroduced; + } + + if (!Obsolete.empty()) { + auto NewObsolete = + Mapping->mapDeprecatedObsoletedAvailabilityVersion(Obsolete); + if (!NewObsolete) { + MakeUnavailable(); + return true; + } + Obsolete = *NewObsolete; + } + + if (!Deprecated.empty()) { + auto NewDeprecated = + Mapping->mapDeprecatedObsoletedAvailabilityVersion(Deprecated); + Deprecated = NewDeprecated ? *NewDeprecated : VersionTuple(); + } + + return true; +} + static void handleAvailabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (!AL.isAvailabilityAttribute()) { handleFeatureAvailabilityAttr(S, D, AL); @@ -2601,6 +2675,25 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (NewAttr) D->addAttr(NewAttr); + if (S.Context.getTargetInfo().getTriple().getOS() == llvm::Triple::XROS) { + IdentifierInfo *NewII = II; + bool NewIsUnavailable = IsUnavailable; + VersionTuple NewIntroduced = Introduced.Version; + VersionTuple NewDeprecated = Deprecated.Version; + VersionTuple NewObsoleted = Obsoleted.Version; + if (shouldInferAvailabilityAttribute(AL, NewII, NewIsUnavailable, + NewIntroduced, NewDeprecated, + NewObsoleted, S)) { + AvailabilityAttr *NewAttr = S.mergeAvailabilityAttr( + ND, AL, NewII, true /*Implicit*/, NewIntroduced, NewDeprecated, + NewObsoleted, NewIsUnavailable, Str, IsStrict, Replacement, + AvailabilityMergeKind::None, + PriorityModifier + Sema::AP_InferredFromOtherPlatform, IIEnvironment); + if (NewAttr) + D->addAttr(NewAttr); + } + } + // Transcribe "ios" to "watchos" (and add a new attribute) if the versioning // matches before the start of the watchOS platform. if (S.Context.getTargetInfo().getTriple().isWatchOS()) { diff --git a/clang/test/PlatformSpecific/xrOS/Sema/Inputs/XROS.sdk/SDKSettings.json b/clang/test/PlatformSpecific/xrOS/Sema/Inputs/XROS.sdk/SDKSettings.json new file mode 100644 index 0000000000000..e4f7c87519805 --- /dev/null +++ b/clang/test/PlatformSpecific/xrOS/Sema/Inputs/XROS.sdk/SDKSettings.json @@ -0,0 +1,21 @@ +{ + "DefaultVariant": "xrOS", "DisplayName": "xrOS", + "Version": "1.0", + "CanonicalName": "xros1.0", + "MaximumDeploymentTarget": "1.0.99", + "SupportedTargets": { + "xros": { + "Archs": ["arm64e", "arm64"], + "LLVMTargetTripleVendor": "apple", + "LLVMTargetTripleSys": "xros", + "LLVMTargetTripleEnvironment": "", + "SystemPrefix": "" + } + }, + "VersionMap": { + "iOS_visionOS": {"17.1": "1.0"}, + "iOS_xrOS": {"17.1": "1.0"}, + "visionOS_iOS": {"1.0": "17.1"}, + "xrOS_iOS": {"1.0": "17.1"} + } +} diff --git a/clang/test/PlatformSpecific/xrOS/Sema/infer-ios-availability.c b/clang/test/PlatformSpecific/xrOS/Sema/infer-ios-availability.c new file mode 100644 index 0000000000000..76be32370e745 --- /dev/null +++ b/clang/test/PlatformSpecific/xrOS/Sema/infer-ios-availability.c @@ -0,0 +1,117 @@ +// RUN: %clang_cc1 -triple arm64-apple-xros1 -verify=ios -isysroot %S/Inputs/XROS.sdk %s 2>&1 +// RUN: %clang_cc1 -triple arm64-apple-xros1 -fapplication-extension -verify=ios,ext -isysroot %S/Inputs/XROS.sdk %s 2>&1 + +// RUN: %clang_cc1 -triple arm64-apple-xros2 -DXROS2 -verify=ios -isysroot %S/Inputs/XROS.sdk %s 2>&1 + +__attribute__((availability(ios, unavailable))) +void ios_unavail(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, unavailable))) +void ios_ext_unavail(); // ext-note {{}} + +void use() { + ios_unavail(); // ios-error {{'ios_unavail' is unavailable: not available on }} + ios_ext_unavail(); // ext-error {{'ios_ext_unavail' is unavailable: not available on }} +} + +__attribute__((availability(ios, introduced=10))) +void ios_introduced_10(); + +__attribute__((availability(ios_app_extension, introduced=10))) +void ios_ext_introduced_10(); + +__attribute__((availability(ios, introduced=17.1))) +void ios_introduced_17(); + +__attribute__((availability(ios_app_extension, introduced=17.1))) +void ios_ext_introduced_17(); + +__attribute__((availability(ios, introduced=18))) +void ios_introduced_18(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, introduced=18))) +void ios_ext_introduced_18(); // ext-note {{}} + +void useIntroduced() { + // introduced iOS < 10 => introduced xrOS 1 + ios_introduced_10(); + ios_ext_introduced_10(); + // introduced iOS 17.1 => introduced xrOS 1 + ios_introduced_17(); + ios_ext_introduced_17(); + // introduced iOS 18 => xros unavailable (no mapping) + ios_introduced_18(); // ios-error {{is unavailable: not available on }} + ios_ext_introduced_18(); // ext-error {{is unavailable: not available on }} +} + +__attribute__((availability(ios, deprecated=10))) +void ios_deprecated_10(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, deprecated=10))) +void ios_ext_deprecated_10(); // ext-note {{}} + +__attribute__((availability(ios, deprecated=17.1))) +void ios_deprecated_17(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, deprecated=17.1))) +void ios_ext_deprecated_17(); // ext-note {{}} + +__attribute__((availability(ios, deprecated=18))) +void ios_deprecated_18(); +#ifdef XROS2 +// ios-note@-2 {{}} +#endif + +__attribute__((availability(ios_app_extension, deprecated=18))) +void ios_ext_deprecated_18(); + +void useDeprecated() { + // deprecated iOS < 10 => deprecated xrOS 1 + ios_deprecated_10(); // ios-warning {{is deprecated: first deprecated in}} + ios_ext_deprecated_10(); // ext-warning {{is deprecated: first deprecated in}} + // deprecated iOS 17.1 => deprecated xrOS 1 + ios_deprecated_17(); // ios-warning {{is deprecated: first deprecated in}} + ios_ext_deprecated_17(); // ext-warning {{is deprecated: first deprecated in}} + // deprecated iOS 18 => deprecated xrOS 1.0.99 + ios_deprecated_18(); +#ifdef XROS2 + // ios-warning@-2 {{is deprecated: first deprecated in}} +#endif + ios_ext_deprecated_18(); +} + +__attribute__((availability(ios, obsoleted=10))) +void ios_obsoleted_10(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, obsoleted=10))) +void ios_ext_obsoleted_10(); // ext-note {{}} + +__attribute__((availability(ios, obsoleted=17.1))) +void ios_obsoleted_17(); // ios-note {{}} + +__attribute__((availability(ios_app_extension, obsoleted=17.1))) +void ios_ext_obsoleted_17(); // ext-note {{}} + +__attribute__((availability(ios, obsoleted=18))) +void ios_obsoleted_18(); +#ifdef XROS2 +// ios-note@-2 {{}} +#endif + +__attribute__((availability(ios_app_extension, obsoleted=18))) +void ios_ext_obsoleted_18(); + +void useObsoleted() { + // deprecated iOS < 10 => deprecated xrOS 1 + ios_obsoleted_10(); // ios-error {{is unavailable: obsoleted in}} + ios_ext_obsoleted_10(); // ext-error {{is unavailable: obsoleted in}} + // deprecated iOS 17.1 => deprecated xrOS 1 + ios_obsoleted_17(); // ios-error {{is unavailable: obsoleted in}} + ios_ext_obsoleted_17(); // ext-error {{is unavailable: obsoleted in}} + // obsoleted iOS 18 => obsoleted xrOS 1.0.99 + ios_obsoleted_18(); +#ifdef XROS2 + // ios-error@-2 {{is unavailable: obsoleted in}} +#endif + ios_ext_obsoleted_18(); +} diff --git a/clang/test/PlatformSpecific/xrOS/Sema/infer-unavailable-from-missing-ios-availability-mapping.c b/clang/test/PlatformSpecific/xrOS/Sema/infer-unavailable-from-missing-ios-availability-mapping.c new file mode 100644 index 0000000000000..2645b487ffd72 --- /dev/null +++ b/clang/test/PlatformSpecific/xrOS/Sema/infer-unavailable-from-missing-ios-availability-mapping.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -triple arm64-apple-xros1 -verify=ios -DNOSDK %s 2>&1 +// RUN: %clang_cc1 -triple arm64-apple-xros1 -verify=ios -isysroot %S/Inputs/XROS.sdk %s 2>&1 + +#ifdef NOSDK +// ios-warning@+2 {{ios availability is ignored without a valid 'SDKSettings.json' in the SDK}} +#endif +__attribute__((availability(ios, introduced=18))) // note the version introduced has to be higher than the versions in SDKSettings +void ios_introduced_10(); // ios-note {{}} + +void useIntroduced() { + ios_introduced_10(); // ios-error {{is unavailable: not available on }} +}