https://github.com/ahatanak updated https://github.com/llvm/llvm-project/pull/190817
>From d5d371e6b2fd2b814fe2db2f8654472016a1da3f Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Mon, 6 Apr 2026 15:49:22 -0700 Subject: [PATCH 1/3] Redesign handling of anyAppleOS availability attribute Previously, when processing an anyAppleOS availability attribute, clang replaced it with an implicit platform-specific attribute (e.g., ios, macos) inferred for the current target. Only the introduced version of the original anyAppleOS attribute was preserved (as a field on the inferred attr). This was insufficient for clients such as Swift that need access to the full original attribute, including deprecated, obsoleted, and message fields. This patch preserves the original anyAppleOS attribute on the decl and attaches the inferred platform-specific attribute to it as a child via the new InferredAttr field. Most callers use getEffectiveAttr() to transparently get the inferred attr when present, preserving existing behavior. Fix-it hints use the presence of an inferred attr to decide whether to emit "anyAppleOS" or a platform-specific name in the @available expression. The one behavioral change is in documentation XML, where availability info is now emitted for both the anyAppleOS attr and the inferred platform-specific attr. When an explicit platform-specific attribute (e.g. ios(introduced=26.0)) conflicts with an anyAppleOS-derived attribute for the same platform, the explicit attribute wins: the anyAppleOS attribute is erased from the decl so only the explicit one is used. --- clang/include/clang/Basic/Attr.td | 22 ++++- clang/include/clang/Sema/Sema.h | 11 ++- clang/lib/AST/AttrImpl.cpp | 14 ++- clang/lib/AST/DeclBase.cpp | 11 ++- clang/lib/Index/CommentToXML.cpp | 83 ++++++++-------- clang/lib/Sema/SemaAPINotes.cpp | 3 +- clang/lib/Sema/SemaAvailability.cpp | 43 ++++----- clang/lib/Sema/SemaDecl.cpp | 12 ++- clang/lib/Sema/SemaDeclAttr.cpp | 94 +++++++++++++------ clang/lib/Sema/SemaHLSL.cpp | 5 +- ...otate-comments-availability-anyappleos.cpp | 26 +++++ .../Sema/attr-availability-anyappleos-ast.c | 48 ++++++---- clang/tools/libclang/CIndex.cpp | 2 +- clang/utils/TableGen/ClangAttrEmitter.cpp | 40 +++++++- 14 files changed, 282 insertions(+), 132 deletions(-) create mode 100644 clang/test/Index/annotate-comments-availability-anyappleos.cpp diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 328e70b3ed900..7b510fc748e08 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -341,7 +341,10 @@ class VariadicEnumArgument<string name, string type, bit is_string, } // Represents an attribute wrapped by another attribute. -class WrappedAttr<string name, bit opt = 0> : Argument<name, opt>; +class WrappedAttr<string name, bit opt = 0, string type = "Attr"> : Argument<name, opt> { + // C++ class name for the wrapped attr (without the ' *' suffix). + string AttrType = type; +} // This handles one spelling of an attribute. class Spelling<string name, string variety, int version = 1> { @@ -1108,7 +1111,7 @@ def Availability : InheritableAttr { BoolArgument<"unavailable">, StringArgument<"message">, BoolArgument<"strict">, StringArgument<"replacement">, IntArgument<"priority">, IdentifierArgument<"environment">, - VersionArgument<"origAnyAppleOSVersion", 0, 1>]; + WrappedAttr<"InferredAttr", 1, "AvailabilityAttr">]; let AdditionalMembers = [{static llvm::StringRef getPrettyPlatformName(llvm::StringRef Platform) { return llvm::StringSwitch<llvm::StringRef>(Platform) @@ -1246,6 +1249,21 @@ static llvm::Triple::OSType getOSType(llvm::StringRef Platform) { .Default(OSType::UnknownOS); } +const AvailabilityAttr *getEffectiveAttr() const { + if (auto *Inf = getInferredAttr()) + return Inf; + return this; +} +AvailabilityAttr *getEffectiveAttr() { + if (auto *Inf = getInferredAttr()) + return Inf; + return this; +} +VersionTuple getEffectiveIntroduced() const { return getEffectiveAttr()->getIntroduced(); } +const IdentifierInfo *getEffectiveEnvironment() const { + return getEffectiveAttr()->getEnvironment(); +} + }]; let HasCustomParsing = 1; let InheritEvenIfAlreadyPresent = 1; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1b8a9803be472..a22b0fa626ad8 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4996,7 +4996,16 @@ class Sema final : public SemaBase { StringRef Message, bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK, int Priority, const IdentifierInfo *IIEnvironment, - VersionTuple OrigAnyAppleOSVersion = {}); + const IdentifierInfo *InferredPlatformII = nullptr); + + AvailabilityAttr *mergeAndInferAvailabilityAttr( + NamedDecl *D, const AttributeCommonInfo &CI, + const IdentifierInfo *Platform, bool Implicit, VersionTuple Introduced, + VersionTuple Deprecated, VersionTuple Obsoleted, bool IsUnavailable, + StringRef Message, bool IsStrict, StringRef Replacement, + AvailabilityMergeKind AMK, int Priority, + const IdentifierInfo *IIEnvironment, + const IdentifierInfo *InferredPlatformII); TypeVisibilityAttr * mergeTypeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI, diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp index 0c7e81f1749cb..c89dd05327af3 100644 --- a/clang/lib/AST/AttrImpl.cpp +++ b/clang/lib/AST/AttrImpl.cpp @@ -292,8 +292,7 @@ namespace { #define USE_DEFAULT_EQUALITY \ (std::is_same_v<T, StringRef> || std::is_same_v<T, VersionTuple> || \ std::is_same_v<T, IdentifierInfo *> || std::is_same_v<T, ParamIdx> || \ - std::is_same_v<T, Attr *> || std::is_same_v<T, char *> || \ - std::is_enum_v<T> || std::is_integral_v<T>) + std::is_same_v<T, char *> || std::is_enum_v<T> || std::is_integral_v<T>) template <class T> typename std::enable_if_t<!USE_DEFAULT_EQUALITY, bool> @@ -320,9 +319,14 @@ bool equalAttrArgs(T *A1_B, T *A1_E, T *A2_B, T *A2_E, return true; } -template <> -bool equalAttrArgs<Attr *>(Attr *A1, Attr *A2, - StructuralEquivalenceContext &Context) { +// Handles Attr and its subclasses (e.g. AvailabilityAttr), which is needed for +// optional WrappedAttr fields like AvailabilityAttr::InferredAttr that can be +// null. +template <class T> +typename std::enable_if_t<std::is_base_of_v<Attr, T>, bool> +equalAttrArgs(T *A1, T *A2, StructuralEquivalenceContext &Context) { + if (!A1 || !A2) + return A1 == A2; return A1->isEquivalent(*A2, Context); } diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index 18b6a5b06bab8..8f72fd08dbb1f 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -802,6 +802,7 @@ AvailabilityResult Decl::getAvailability(std::string *Message, } if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) { + Availability = Availability->getEffectiveAttr(); AvailabilityResult AR = CheckAvailability(getASTContext(), Availability, Message, EnclosingVersion); @@ -830,10 +831,11 @@ VersionTuple Decl::getVersionIntroduced() const { StringRef TargetPlatform = Context.getTargetInfo().getPlatformName(); for (const auto *A : attrs()) { if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) { - if (getRealizedPlatform(Availability, Context) != TargetPlatform) - continue; - if (!Availability->getIntroduced().empty()) - return Availability->getIntroduced(); + Availability = Availability->getEffectiveAttr(); + if (getRealizedPlatform(Availability, Context) == TargetPlatform) { + if (!Availability->getIntroduced().empty()) + return Availability->getIntroduced(); + } } } return {}; @@ -878,6 +880,7 @@ bool Decl::isWeakImported() const { return true; if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) { + Availability = Availability->getEffectiveAttr(); if (CheckAvailability(getASTContext(), Availability, nullptr, VersionTuple()) == AR_NotYetIntroduced) return true; diff --git a/clang/lib/Index/CommentToXML.cpp b/clang/lib/Index/CommentToXML.cpp index a31176f68d0d4..52b4d66bd866d 100644 --- a/clang/lib/Index/CommentToXML.cpp +++ b/clang/lib/Index/CommentToXML.cpp @@ -1029,47 +1029,50 @@ void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { } // 'availability' attribute. - Result << "<Availability"; - StringRef Distribution; - if (AA->getPlatform()) { - Distribution = AvailabilityAttr::getPrettyPlatformName( - AA->getPlatform()->getName()); - if (Distribution.empty()) - Distribution = AA->getPlatform()->getName(); - } - Result << " distribution=\"" << Distribution << "\">"; - VersionTuple IntroducedInVersion = AA->getIntroduced(); - if (!IntroducedInVersion.empty()) { - Result << "<IntroducedInVersion>" - << IntroducedInVersion.getAsString() - << "</IntroducedInVersion>"; - } - VersionTuple DeprecatedInVersion = AA->getDeprecated(); - if (!DeprecatedInVersion.empty()) { - Result << "<DeprecatedInVersion>" - << DeprecatedInVersion.getAsString() - << "</DeprecatedInVersion>"; - } - VersionTuple RemovedAfterVersion = AA->getObsoleted(); - if (!RemovedAfterVersion.empty()) { - Result << "<RemovedAfterVersion>" - << RemovedAfterVersion.getAsString() - << "</RemovedAfterVersion>"; - } - StringRef DeprecationSummary = AA->getMessage(); - if (!DeprecationSummary.empty()) { - Result << "<DeprecationSummary>"; - appendToResultWithXMLEscaping(DeprecationSummary); - Result << "</DeprecationSummary>"; - } - if (AA->getUnavailable()) - Result << "<Unavailable/>"; + auto EmitAvailability = [&](const AvailabilityAttr *AA) { + Result << "<Availability"; + StringRef Distribution; + if (AA->getPlatform()) { + Distribution = AvailabilityAttr::getPrettyPlatformName( + AA->getPlatform()->getName()); + if (Distribution.empty()) + Distribution = AA->getPlatform()->getName(); + } + Result << " distribution=\"" << Distribution << "\">"; + VersionTuple IntroducedInVersion = AA->getIntroduced(); + if (!IntroducedInVersion.empty()) { + Result << "<IntroducedInVersion>" << IntroducedInVersion.getAsString() + << "</IntroducedInVersion>"; + } + VersionTuple DeprecatedInVersion = AA->getDeprecated(); + if (!DeprecatedInVersion.empty()) { + Result << "<DeprecatedInVersion>" << DeprecatedInVersion.getAsString() + << "</DeprecatedInVersion>"; + } + VersionTuple RemovedAfterVersion = AA->getObsoleted(); + if (!RemovedAfterVersion.empty()) { + Result << "<RemovedAfterVersion>" << RemovedAfterVersion.getAsString() + << "</RemovedAfterVersion>"; + } + StringRef DeprecationSummary = AA->getMessage(); + if (!DeprecationSummary.empty()) { + Result << "<DeprecationSummary>"; + appendToResultWithXMLEscaping(DeprecationSummary); + Result << "</DeprecationSummary>"; + } + if (AA->getUnavailable()) + Result << "<Unavailable/>"; + const IdentifierInfo *Environment = AA->getEnvironment(); + if (Environment) { + Result << "<Environment>" << Environment->getName() + << "</Environment>"; + } + Result << "</Availability>"; + }; - const IdentifierInfo *Environment = AA->getEnvironment(); - if (Environment) { - Result << "<Environment>" << Environment->getName() << "</Environment>"; - } - Result << "</Availability>"; + EmitAvailability(AA); + if (const AvailabilityAttr *Inf = AA->getInferredAttr()) + EmitAvailability(Inf); } } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index ab314a43abcca..a48edbbe027a4 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -271,8 +271,7 @@ static void ProcessAPINotes(Sema &S, Decl *D, /*Strict=*/false, /*Replacement=*/StringRef(), /*Priority=*/Sema::AP_Explicit, - /*Environment=*/nullptr, - /*OrigAnyAppleOSVersion=*/VersionTuple()); + /*Environment=*/nullptr); }, [](const Decl *D) { return llvm::find_if(D->attrs(), [](const Attr *next) -> bool { diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp index 41b2d0b5b5b34..6576d4c1f5445 100644 --- a/clang/lib/Sema/SemaAvailability.cpp +++ b/clang/lib/Sema/SemaAvailability.cpp @@ -58,9 +58,14 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context, // FIXME: this is copied from CheckAvailability. We should try to // de-duplicate. + // If this attr has an inferred platform-specific attr (e.g. anyappleos + // → ios/macos/...), use that for platform matching but return the + // original. + const AvailabilityAttr *EffectiveAvail = Avail->getEffectiveAttr(); + // Check if this is an App Extension "platform", and if so chop off // the suffix for matching with the actual platform. - StringRef ActualPlatform = Avail->getPlatform()->getName(); + StringRef ActualPlatform = EffectiveAvail->getPlatform()->getName(); StringRef RealizedPlatform = ActualPlatform; if (Context.getLangOpts().AppExt) { size_t suffix = RealizedPlatform.rfind("_app_extension"); @@ -73,7 +78,7 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context, // Match the platform name. if (RealizedPlatform == TargetPlatform) { // Find the best matching attribute for this environment - if (hasMatchingEnvironmentOrNone(Context, Avail)) + if (hasMatchingEnvironmentOrNone(Context, EffectiveAvail)) return Avail; PartialMatch = Avail; } @@ -207,8 +212,8 @@ static bool ShouldDiagnoseAvailabilityInContext( auto CheckContext = [&](const Decl *C) { if (K == AR_NotYetIntroduced) { if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C)) - if (AA->getIntroduced() >= DeclVersion && - AA->getEnvironment() == DeclEnv) + if (AA->getEffectiveIntroduced() >= DeclVersion && + AA->getEffectiveEnvironment() == DeclEnv) return true; } else if (K == AR_Deprecated) { if (C->isDeprecated()) @@ -429,8 +434,8 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl); const IdentifierInfo *IIEnv = nullptr; if (AA) { - DeclVersion = AA->getIntroduced(); - IIEnv = AA->getEnvironment(); + DeclVersion = AA->getEffectiveIntroduced(); + IIEnv = AA->getEffectiveEnvironment(); } if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, IIEnv, Ctx, @@ -462,9 +467,9 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or // later. assert(AA != nullptr && "expecting valid availability attribute"); - VersionTuple Introduced = AA->getIntroduced(); + VersionTuple Introduced = AA->getEffectiveIntroduced(); bool EnvironmentMatchesOrNone = - hasMatchingEnvironmentOrNone(S.getASTContext(), AA); + hasMatchingEnvironmentOrNone(S.getASTContext(), AA->getEffectiveAttr()); const TargetInfo &TI = S.getASTContext().getTargetInfo(); std::string PlatformName( @@ -898,9 +903,9 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( const AvailabilityAttr *AA = getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl); assert(AA != nullptr && "expecting valid availability attribute"); - bool EnvironmentMatchesOrNone = - hasMatchingEnvironmentOrNone(SemaRef.getASTContext(), AA); - VersionTuple Introduced = AA->getIntroduced(); + bool EnvironmentMatchesOrNone = hasMatchingEnvironmentOrNone( + SemaRef.getASTContext(), AA->getEffectiveAttr()); + VersionTuple Introduced = AA->getEffectiveIntroduced(); if (EnvironmentMatchesOrNone && AvailabilityStack.back() >= Introduced) return; @@ -908,7 +913,7 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( // If the context of this function is less available than D, we should not // emit a diagnostic. if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, - AA->getEnvironment(), Ctx, + AA->getEffectiveEnvironment(), Ctx, OffendingDecl)) return; @@ -988,20 +993,16 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( const char *ExtraIndentation = " "; std::string FixItString; llvm::raw_string_ostream FixItOS(FixItString); - // If the attr was derived from anyAppleOS, emit the fix-it using - // anyAppleOS and the original anyAppleOS version rather than the - // platform-specific name and version. - VersionTuple OrigAnyAppleOSVersion = AA->getOrigAnyAppleOSVersion(); StringRef FixItPlatformName; VersionTuple FixItVersion; - if (OrigAnyAppleOSVersion.empty()) { + if (AA->getInferredAttr()) { + FixItPlatformName = "anyAppleOS"; + FixItVersion = AA->getIntroduced(); + } else { FixItPlatformName = AvailabilityAttr::getPlatformNameSourceSpelling( SemaRef.getASTContext().getTargetInfo().getPlatformName()); - FixItVersion = Introduced; - } else { - FixItPlatformName = "anyAppleOS"; - FixItVersion = OrigAnyAppleOSVersion; + FixItVersion = AA->getEffectiveIntroduced(); } FixItOS << "if (" << (SemaRef.getLangOpts().ObjC ? "@available" diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 2334d3d89d57e..9609e145a1566 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2899,14 +2899,16 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D, // previous decl", for example if the attribute needs to be consistent // between redeclarations, you need to call a custom merge function here. InheritableAttr *NewAttr = nullptr; - if (const auto *AA = dyn_cast<AvailabilityAttr>(Attr)) - NewAttr = S.mergeAvailabilityAttr( + if (const auto *AA = dyn_cast<AvailabilityAttr>(Attr)) { + const IdentifierInfo *InferredPlatformII = nullptr; + if (AvailabilityAttr *Inf = AA->getInferredAttr()) + InferredPlatformII = Inf->getPlatform(); + NewAttr = S.mergeAndInferAvailabilityAttr( D, *AA, AA->getPlatform(), AA->isImplicit(), AA->getIntroduced(), AA->getDeprecated(), AA->getObsoleted(), AA->getUnavailable(), AA->getMessage(), AA->getStrict(), AA->getReplacement(), AMK, - AA->getPriority(), AA->getEnvironment(), - AA->getOrigAnyAppleOSVersion()); - else if (const auto *VA = dyn_cast<VisibilityAttr>(Attr)) + AA->getPriority(), AA->getEnvironment(), InferredPlatformII); + } else if (const auto *VA = dyn_cast<VisibilityAttr>(Attr)) NewAttr = S.mergeVisibilityAttr(D, *VA, VA->getVisibility()); else if (const auto *VA = dyn_cast<TypeVisibilityAttr>(Attr)) NewAttr = S.mergeTypeVisibilityAttr(D, *VA, VA->getVisibility()); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 8a856215a9627..92f224ad99897 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2360,7 +2360,7 @@ AvailabilityAttr *Sema::mergeAvailabilityAttr( VersionTuple Obsoleted, bool IsUnavailable, StringRef Message, bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK, int Priority, const IdentifierInfo *Environment, - VersionTuple OrigAnyAppleOSVersion) { + const IdentifierInfo *InferredPlatformII) { VersionTuple MergedIntroduced = Introduced; VersionTuple MergedDeprecated = Deprecated; VersionTuple MergedObsoleted = Obsoleted; @@ -2382,20 +2382,35 @@ AvailabilityAttr *Sema::mergeAvailabilityAttr( if (D->hasAttrs()) { AttrVec &Attrs = D->getAttrs(); for (unsigned i = 0, e = Attrs.size(); i != e;) { - const auto *OldAA = dyn_cast<AvailabilityAttr>(Attrs[i]); + auto *OldAA = dyn_cast<AvailabilityAttr>(Attrs[i]); if (!OldAA) { ++i; continue; } - const IdentifierInfo *OldPlatform = OldAA->getPlatform(); - if (OldPlatform != Platform) { + const IdentifierInfo *OldEnvironment = OldAA->getEnvironment(); + if (OldEnvironment != Environment) { ++i; continue; } - const IdentifierInfo *OldEnvironment = OldAA->getEnvironment(); - if (OldEnvironment != Environment) { + if (OldAA->getPlatform() != Platform) { + // If this new attr is for anyappleos and the old attr is for the + // inferred platform, the existing explicit platform attr wins. + if (InferredPlatformII) { + if (OldAA->getPlatform() == InferredPlatformII) + return nullptr; + } else { + // If this new attr is an explicit platform attr, check if the old + // attr is an existing anyAppleOS attr whose inferred attr is for this + // platform. If so, the explicit attr wins: erase the old attr. + if (AvailabilityAttr *Inf = OldAA->getInferredAttr(); + Inf && Inf->getPlatform() == Platform) { + Attrs.erase(Attrs.begin() + i); + --e; + continue; + } + } ++i; continue; } @@ -2520,14 +2535,41 @@ AvailabilityAttr *Sema::mergeAvailabilityAttr( auto *Avail = ::new (Context) AvailabilityAttr( Context, CI, Platform, Introduced, Deprecated, Obsoleted, IsUnavailable, Message, IsStrict, Replacement, Priority, Environment, - OrigAnyAppleOSVersion); + /*InferredAttr=*/nullptr); Avail->setImplicit(Implicit); return Avail; } return nullptr; } -/// Returns true if the given availability attribute should be inferred, and +AvailabilityAttr *Sema::mergeAndInferAvailabilityAttr( + NamedDecl *D, const AttributeCommonInfo &CI, const IdentifierInfo *Platform, + bool Implicit, VersionTuple Introduced, VersionTuple Deprecated, + VersionTuple Obsoleted, bool IsUnavailable, StringRef Message, + bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK, + int Priority, const IdentifierInfo *IIEnvironment, + const IdentifierInfo *InferredPlatformII) { + AvailabilityAttr *OrigAttr = mergeAvailabilityAttr( + D, CI, Platform, Implicit, Introduced, Deprecated, Obsoleted, + IsUnavailable, Message, IsStrict, Replacement, AMK, Priority, + IIEnvironment, InferredPlatformII); + if (!OrigAttr || !InferredPlatformII) + return OrigAttr; + + auto *InferredAttr = ::new (Context) AvailabilityAttr( + Context, CI, InferredPlatformII, OrigAttr->getIntroduced(), + OrigAttr->getDeprecated(), OrigAttr->getObsoleted(), + OrigAttr->getUnavailable(), OrigAttr->getMessage(), OrigAttr->getStrict(), + OrigAttr->getReplacement(), + Priority == AP_PragmaClangAttribute + ? AP_PragmaClangAttribute_InferredFromAnyAppleOS + : AP_InferredFromAnyAppleOS, + IIEnvironment, /*InferredAttr=*/nullptr); + InferredAttr->setImplicit(true); + OrigAttr->setInferredAttr(InferredAttr); + return OrigAttr; +} + /// adjusts the value of the attribute as necessary to facilitate that. static bool shouldInferAvailabilityAttribute(const ParsedAttr &AL, IdentifierInfo *&II, @@ -2706,8 +2748,8 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) { } } - // Handle anyAppleOS specially: create implicit platform-specific attributes - // instead of the original anyAppleOS attribute. + // Handle anyAppleOS: preserve the original anyappleos attr on the decl and + // store the inferred platform-specific attr as a field on it. if (II->getName() == "anyappleos") { // Validate anyAppleOS versions; reject versions older than 26.0. auto ValidateVersion = [&](const llvm::VersionTuple &Version, @@ -2742,24 +2784,20 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) { else // For iOS, tvOS, watchOS, visionOS, bridgeOS, etc. PlatformName = llvm::Triple::getOSTypeName(T.getOS()); - IdentifierInfo *NewII = &S.Context.Idents.get(PlatformName); - - // Use the special low-priority value for pragma push anyAppleOS. - int ExpandedPriority = - (PriorityModifier == Sema::AP_PragmaClangAttribute) - ? Sema::AP_PragmaClangAttribute_InferredFromAnyAppleOS - : Sema::AP_InferredFromAnyAppleOS; - - AvailabilityAttr *NewAttr = S.mergeAvailabilityAttr( - ND, AL, NewII, /*Implicit=*/true, Introduced.Version, - Deprecated.Version, Obsoleted.Version, IsUnavailable, Str, IsStrict, - Replacement, AvailabilityMergeKind::None, ExpandedPriority, - IIEnvironment, Introduced.Version); - if (NewAttr) - D->addAttr(NewAttr); - - // Don't add the original anyAppleOS attribute - only the implicit - // platform-specific attributes. + IdentifierInfo *InferredPlatformII = &S.Context.Idents.get(PlatformName); + + // Call mergeAvailabilityAttr for the original anyappleos attr. Pass + // InferredPlatformII so the dedup loop can detect a conflicting explicit + // platform attr (in which case mergeAvailabilityAttr returns null and we + // add neither attr). + AvailabilityAttr *OrigAttr = S.mergeAndInferAvailabilityAttr( + ND, AL, II, /*Implicit=*/false, Introduced.Version, Deprecated.Version, + Obsoleted.Version, IsUnavailable, Str, IsStrict, Replacement, + AvailabilityMergeKind::None, PriorityModifier, IIEnvironment, + InferredPlatformII); + if (!OrigAttr) + return; + D->addAttr(OrigAttr); return; } diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 5d7cec49c2b19..349151a9650b7 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -2954,14 +2954,15 @@ DiagnoseHLSLAvailability::FindAvailabilityAttr(const Decl *D) { // environment. for (const auto *A : D->attrs()) { if (const auto *Avail = dyn_cast<AvailabilityAttr>(A)) { - StringRef AttrPlatform = Avail->getPlatform()->getName(); + const AvailabilityAttr *EffectiveAvail = Avail->getEffectiveAttr(); + StringRef AttrPlatform = EffectiveAvail->getPlatform()->getName(); StringRef TargetPlatform = SemaRef.getASTContext().getTargetInfo().getPlatformName(); // Match the platform name. if (AttrPlatform == TargetPlatform) { // Find the best matching attribute for this environment - if (HasMatchingEnvironmentOrNone(Avail)) + if (HasMatchingEnvironmentOrNone(EffectiveAvail)) return Avail; PartialMatch = Avail; } diff --git a/clang/test/Index/annotate-comments-availability-anyappleos.cpp b/clang/test/Index/annotate-comments-availability-anyappleos.cpp new file mode 100644 index 0000000000000..5335575219aee --- /dev/null +++ b/clang/test/Index/annotate-comments-availability-anyappleos.cpp @@ -0,0 +1,26 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: c-index-test -test-load-source all -comments-xml-schema=%S/../../bindings/xml/comment-xml-schema.rng %s -- -triple x86_64-apple-macosx26.0 > %t/out +// RUN: FileCheck %s < %t/out + +// Ensure that XML we generate is not invalid. +// RUN: FileCheck %s -check-prefix=WRONG < %t/out +// WRONG-NOT: CommentXMLInvalid + +// Test that anyAppleOS availability emits two <Availability> entries in XML: +// one for the original anyAppleOS attr and one for the inferred platform attr. + +/// Aaa. +void attr_anyappleos_1() __attribute__((availability(anyAppleOS, introduced=26.0))); + +// CHECK: FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments-availability-anyappleos.cpp" line="[[@LINE-2]]" column="6"><Name>attr_anyappleos_1</Name><USR>c:@F@attr_anyappleos_1#</USR><Declaration>void attr_anyappleos_1()</Declaration><Abstract><Para> Aaa.</Para></Abstract><Availability distribution="any Apple OS"><IntroducedInVersion>26.0</IntroducedInVersion></Availability><Availability distribution="macOS"><IntroducedInVersion>26.0</IntroducedInVersion></Availability></Function>] + +/// Aaa. +void attr_anyappleos_2() __attribute__((availability(anyAppleOS, introduced=26.0, deprecated=27.0, message="use something else"))); + +// CHECK: FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments-availability-anyappleos.cpp" line="[[@LINE-2]]" column="6"><Name>attr_anyappleos_2</Name><USR>c:@F@attr_anyappleos_2#</USR><Declaration>void attr_anyappleos_2()</Declaration><Abstract><Para> Aaa.</Para></Abstract><Availability distribution="any Apple OS"><IntroducedInVersion>26.0</IntroducedInVersion><DeprecatedInVersion>27.0</DeprecatedInVersion><DeprecationSummary>use something else</DeprecationSummary></Availability><Availability distribution="macOS"><IntroducedInVersion>26.0</IntroducedInVersion><DeprecatedInVersion>27.0</DeprecatedInVersion><DeprecationSummary>use something else</DeprecationSummary></Availability></Function>] + +/// Aaa. +void attr_anyappleos_3() __attribute__((availability(anyAppleOS, unavailable, message="not available"))); + +// CHECK: FullCommentAsXML=[<Function file="{{[^"]+}}annotate-comments-availability-anyappleos.cpp" line="[[@LINE-2]]" column="6"><Name>attr_anyappleos_3</Name><USR>c:@F@attr_anyappleos_3#</USR><Declaration>void attr_anyappleos_3()</Declaration><Abstract><Para> Aaa.</Para></Abstract><Availability distribution="any Apple OS"><DeprecationSummary>not available</DeprecationSummary><Unavailable/></Availability><Availability distribution="macOS"><DeprecationSummary>not available</DeprecationSummary><Unavailable/></Availability></Function>] diff --git a/clang/test/Sema/attr-availability-anyappleos-ast.c b/clang/test/Sema/attr-availability-anyappleos-ast.c index 357c89ba1b0b3..a76f756ba532e 100644 --- a/clang/test/Sema/attr-availability-anyappleos-ast.c +++ b/clang/test/Sema/attr-availability-anyappleos-ast.c @@ -6,30 +6,44 @@ // RUN: %clang_cc1 -triple arm64-apple-ios26.0-macabi -fsyntax-only -ast-dump %s | FileCheck --check-prefix=CHECK-MACCATALYST %s // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsyntax-only -ast-dump %s | FileCheck --check-prefix=CHECK-LINUX %s -// Test that anyAppleOS availability creates implicit platform-specific -// attributes for the target platform. +// Test that anyAppleOS availability is preserved on the decl with the inferred +// platform-specific attr stored as a child. extern int func1 __attribute__((availability(anyAppleOS, introduced=26.0))); -// CHECK-IOS: AvailabilityAttr {{.*}} Implicit ios 26.0 0 0 "" "" 3 -// CHECK-MACOS: AvailabilityAttr {{.*}} Implicit macos 26.0 0 0 "" "" 3 -// CHECK-TVOS: AvailabilityAttr {{.*}} Implicit tvos 26.0 0 0 "" "" 3 -// CHECK-WATCHOS: AvailabilityAttr {{.*}} Implicit watchos 26.0 0 0 "" "" 3 -// CHECK-XROS: AvailabilityAttr {{.*}} Implicit xros 26.0 0 0 "" "" 3 -// CHECK-MACCATALYST: AvailabilityAttr {{.*}} Implicit maccatalyst 26.0 0 0 "" "" 3 -// CHECK-LINUX-NOT: AvailabilityAttr {{.*}} Implicit +// CHECK-IOS: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-IOS-NEXT: AvailabilityAttr {{.*}} Implicit ios 26.0 0 0 "" "" 3 +// CHECK-MACOS: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-MACOS-NEXT: AvailabilityAttr {{.*}} Implicit macos 26.0 0 0 "" "" 3 +// CHECK-TVOS: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-TVOS-NEXT: AvailabilityAttr {{.*}} Implicit tvos 26.0 0 0 "" "" 3 +// CHECK-WATCHOS: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-WATCHOS-NEXT: AvailabilityAttr {{.*}} Implicit watchos 26.0 0 0 "" "" 3 +// CHECK-XROS: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-XROS-NEXT: AvailabilityAttr {{.*}} Implicit xros 26.0 0 0 "" "" 3 +// CHECK-MACCATALYST: AvailabilityAttr {{.*}} anyappleos 26.0 0 0 "" "" 0 +// CHECK-MACCATALYST-NEXT: AvailabilityAttr {{.*}} Implicit maccatalyst 26.0 0 0 "" "" 3 +// CHECK-LINUX-NOT: AvailabilityAttr extern int func2 __attribute__((availability(anyAppleOS, introduced=26.0, deprecated=27.0))); -// CHECK-IOS: AvailabilityAttr {{.*}} Implicit ios 26.0 27.0 0 "" "" 3 -// CHECK-MACOS: AvailabilityAttr {{.*}} Implicit macos 26.0 27.0 0 "" "" 3 +// CHECK-IOS: AvailabilityAttr {{.*}} anyappleos 26.0 27.0 0 "" "" 0 +// CHECK-IOS-NEXT: AvailabilityAttr {{.*}} Implicit ios 26.0 27.0 0 "" "" 3 +// CHECK-MACOS: AvailabilityAttr {{.*}} anyappleos 26.0 27.0 0 "" "" 0 +// CHECK-MACOS-NEXT: AvailabilityAttr {{.*}} Implicit macos 26.0 27.0 0 "" "" 3 extern int func3 __attribute__((availability(anyAppleOS, introduced=26.0, deprecated=27.0, obsoleted=28.0))); -// CHECK-IOS: AvailabilityAttr {{.*}} Implicit ios 26.0 27.0 28.0 "" "" 3 -// CHECK-MACOS: AvailabilityAttr {{.*}} Implicit macos 26.0 27.0 28.0 "" "" 3 +// CHECK-IOS: AvailabilityAttr {{.*}} anyappleos 26.0 27.0 28.0 "" "" 0 +// CHECK-IOS-NEXT: AvailabilityAttr {{.*}} Implicit ios 26.0 27.0 28.0 "" "" 3 +// CHECK-MACOS: AvailabilityAttr {{.*}} anyappleos 26.0 27.0 28.0 "" "" 0 +// CHECK-MACOS-NEXT: AvailabilityAttr {{.*}} Implicit macos 26.0 27.0 28.0 "" "" 3 extern int func4 __attribute__((availability(anyAppleOS, unavailable))); -// CHECK-IOS: AvailabilityAttr {{.*}} Implicit ios 0 0 0 Unavailable "" "" 3 -// CHECK-MACOS: AvailabilityAttr {{.*}} Implicit macos 0 0 0 Unavailable "" "" 3 +// CHECK-IOS: AvailabilityAttr {{.*}} anyappleos 0 0 0 Unavailable "" "" 0 +// CHECK-IOS-NEXT: AvailabilityAttr {{.*}} Implicit ios 0 0 0 Unavailable "" "" 3 +// CHECK-MACOS: AvailabilityAttr {{.*}} anyappleos 0 0 0 Unavailable "" "" 0 +// CHECK-MACOS-NEXT: AvailabilityAttr {{.*}} Implicit macos 0 0 0 Unavailable "" "" 3 extern int func5 __attribute__((availability(anyAppleOS, unavailable, message="Use something else"))); -// CHECK-IOS: AvailabilityAttr {{.*}} Implicit ios 0 0 0 Unavailable "Use something else" "" 3 -// CHECK-MACOS: AvailabilityAttr {{.*}} Implicit macos 0 0 0 Unavailable "Use something else" "" 3 +// CHECK-IOS: AvailabilityAttr {{.*}} anyappleos 0 0 0 Unavailable "Use something else" "" 0 +// CHECK-IOS-NEXT: AvailabilityAttr {{.*}} Implicit ios 0 0 0 Unavailable "Use something else" "" 3 +// CHECK-MACOS: AvailabilityAttr {{.*}} anyappleos 0 0 0 Unavailable "Use something else" "" 0 +// CHECK-MACOS-NEXT: AvailabilityAttr {{.*}} Implicit macos 0 0 0 Unavailable "Use something else" "" 3 diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index 3ee37ed2dfc27..f8cdc79011a29 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -8929,7 +8929,7 @@ static void getCursorPlatformAvailabilityForDecl( } if (AvailabilityAttr *Avail = dyn_cast<AvailabilityAttr>(A)) { - AvailabilityAttrs.push_back(Avail); + AvailabilityAttrs.push_back(Avail->getEffectiveAttr()); HadAvailAttr = true; } } diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 61fb40e79ea91..c616e39cd176c 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -1469,25 +1469,57 @@ namespace { }; class WrappedAttr : public SimpleArgument { + std::string AttrType; // C++ class name for the wrapped attr + public: WrappedAttr(const Record &Arg, StringRef Attr) - : SimpleArgument(Arg, Attr, "Attr *") {} + : SimpleArgument(Arg, Attr, + (Arg.getValueAsString("AttrType") + " *").str()), + AttrType(Arg.getValueAsString("AttrType")) {} + + void writeAccessors(raw_ostream &OS) const override { + OS << " " << getType() << " get" << getUpperName() << "() const {\n"; + OS << " return " << getLowerName() << ";\n"; + OS << " }\n"; + OS << " void set" << getUpperName() << "(" << getType() << " V) {\n"; + OS << " " << getLowerName() << " = V;\n"; + OS << " }"; + } void writePCHReadDecls(raw_ostream &OS) const override { - OS << " Attr *" << getLowerName() << " = Record.readAttr();"; + OS << " " << getType() << " " << getLowerName() << " = cast_or_null<" + << AttrType << ">(Record.readAttr());"; } void writePCHWrite(raw_ostream &OS) const override { OS << " AddAttr(SA->get" << getUpperName() << "());"; } + std::string getIsOmitted() const override { + if (isOptional()) + return "!get" + getUpperName().str() + "()"; + return "false"; + } + + void writeValue(raw_ostream &OS) const override {} + void writeDump(raw_ostream &OS) const override {} void writeDumpChildren(raw_ostream &OS) const override { - OS << " Visit(SA->get" << getUpperName() << "());\n"; + if (isOptional()) { + OS << " if (auto *W = SA->get" << getUpperName() << "())\n"; + OS << " Visit(W);\n"; + } else { + OS << " Visit(SA->get" << getUpperName() << "());\n"; + } } - void writeHasChildren(raw_ostream &OS) const override { OS << "true"; } + void writeHasChildren(raw_ostream &OS) const override { + if (isOptional()) + OS << "SA->get" << getUpperName() << "() != nullptr"; + else + OS << "true"; + } }; } // end anonymous namespace >From 01584b8c128bd426e2b803328a6bd8c80de4e06c Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Tue, 7 Apr 2026 10:05:57 -0700 Subject: [PATCH 2/3] Add a specialization of equalAttrArgs for AvailabilityAttr std::is_base_of_v<Attr, T> doesn't work if T is an incomplete type. --- clang/lib/AST/AttrImpl.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp index c89dd05327af3..e2ea7ecbf48ee 100644 --- a/clang/lib/AST/AttrImpl.cpp +++ b/clang/lib/AST/AttrImpl.cpp @@ -292,7 +292,8 @@ namespace { #define USE_DEFAULT_EQUALITY \ (std::is_same_v<T, StringRef> || std::is_same_v<T, VersionTuple> || \ std::is_same_v<T, IdentifierInfo *> || std::is_same_v<T, ParamIdx> || \ - std::is_same_v<T, char *> || std::is_enum_v<T> || std::is_integral_v<T>) + std::is_same_v<T, Attr *> || std::is_same_v<T, char *> || \ + std::is_enum_v<T> || std::is_integral_v<T>) template <class T> typename std::enable_if_t<!USE_DEFAULT_EQUALITY, bool> @@ -319,12 +320,18 @@ bool equalAttrArgs(T *A1_B, T *A1_E, T *A2_B, T *A2_E, return true; } -// Handles Attr and its subclasses (e.g. AvailabilityAttr), which is needed for -// optional WrappedAttr fields like AvailabilityAttr::InferredAttr that can be +template <> +bool equalAttrArgs<Attr *>(Attr *A1, Attr *A2, + StructuralEquivalenceContext &Context) { + return A1->isEquivalent(*A2, Context); +} + +// AvailabilityAttr::InferredAttr is an optional WrappedAttr field that can be // null. -template <class T> -typename std::enable_if_t<std::is_base_of_v<Attr, T>, bool> -equalAttrArgs(T *A1, T *A2, StructuralEquivalenceContext &Context) { +template <> +bool equalAttrArgs<AvailabilityAttr *>(AvailabilityAttr *A1, + AvailabilityAttr *A2, + StructuralEquivalenceContext &Context) { if (!A1 || !A2) return A1 == A2; return A1->isEquivalent(*A2, Context); >From 86cc1bc7e7476e9b8aaf3dd601c69bfb0d19bca8 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Tue, 7 Apr 2026 11:45:45 -0700 Subject: [PATCH 3/3] Pass -target instead of -triple --- clang/test/Index/annotate-comments-availability-anyappleos.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Index/annotate-comments-availability-anyappleos.cpp b/clang/test/Index/annotate-comments-availability-anyappleos.cpp index 5335575219aee..d9fdf227b134e 100644 --- a/clang/test/Index/annotate-comments-availability-anyappleos.cpp +++ b/clang/test/Index/annotate-comments-availability-anyappleos.cpp @@ -1,6 +1,6 @@ // RUN: rm -rf %t // RUN: mkdir %t -// RUN: c-index-test -test-load-source all -comments-xml-schema=%S/../../bindings/xml/comment-xml-schema.rng %s -- -triple x86_64-apple-macosx26.0 > %t/out +// RUN: c-index-test -test-load-source all -comments-xml-schema=%S/../../bindings/xml/comment-xml-schema.rng -target x86_64-apple-macosx26.0 %s > %t/out // RUN: FileCheck %s < %t/out // Ensure that XML we generate is not invalid. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
