[clang] [APINotes] Upstream Sema logic to apply API Notes to decls (PR #73017)

via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 21 09:01:38 PST 2023


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Egor Zhdan (egorzhdan)

<details>
<summary>Changes</summary>

This upstreams more of the Clang API Notes functionality that is currently implemented in the Apple fork: https://github.com/apple/llvm-project/tree/next/clang/lib/APINotes

This is the largest chunk of the API Notes functionality in the upstreaming process. I will soon submit a follow-up patch to actually enable usage of this functionality by having a Clang driver flag that enables API Notes, along with tests.

(it might be easier to review commit-by-commit)

---

Patch is 74.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/73017.diff


19 Files Affected:

- (modified) clang/include/clang/Basic/Attr.td (+45-1) 
- (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+3) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7) 
- (modified) clang/include/clang/Lex/Lexer.h (+1-1) 
- (modified) clang/include/clang/Parse/Parser.h (+12) 
- (modified) clang/include/clang/Sema/Sema.h (+36) 
- (modified) clang/lib/Parse/ParseDecl.cpp (+65) 
- (modified) clang/lib/Parse/Parser.cpp (+5) 
- (modified) clang/lib/Sema/CMakeLists.txt (+1) 
- (added) clang/lib/Sema/SemaAPINotes.cpp (+1014) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+35) 
- (modified) clang/lib/Sema/SemaDeclAttr.cpp (+23-14) 
- (modified) clang/lib/Sema/SemaDeclCXX.cpp (+5-1) 
- (modified) clang/lib/Sema/SemaDeclObjC.cpp (+4) 
- (modified) clang/lib/Sema/SemaObjCProperty.cpp (+5) 
- (modified) clang/lib/Sema/SemaTemplate.cpp (+7) 
- (modified) clang/lib/Sema/SemaType.cpp (+109-72) 
- (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+2) 
- (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+25-1) 


``````````diff
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index c2fbdfc66c540d6..acfb75a3dee3e7a 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -301,6 +301,9 @@ class VariadicEnumArgument<string name, string type, list<string> values,
   bit IsExternalType = isExternalType;
 }
 
+// Represents an attribute wrapped by another attribute.
+class AttrArgument<string name, bit opt = 0> : Argument<name, opt>;
+
 // This handles one spelling of an attribute.
 class Spelling<string name, string variety, int version = 1> {
   string Name = name;
@@ -2257,7 +2260,7 @@ def ObjCBridgeRelated : InheritableAttr {
 def NSErrorDomain : InheritableAttr {
   let Spellings = [GNU<"ns_error_domain">];
   let Subjects = SubjectList<[Enum], ErrorDiag>;
-  let Args = [DeclArgument<Var, "ErrorDomain">];
+  let Args = [IdentifierArgument<"ErrorDomain">];
   let Documentation = [NSErrorDomainDocs];
 }
 
@@ -2593,6 +2596,22 @@ def SwiftError : InheritableAttr {
   let Documentation = [SwiftErrorDocs];
 }
 
+def SwiftImportAsNonGeneric : InheritableAttr {
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+}
+
+def SwiftImportPropertyAsAccessors : InheritableAttr { 
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+}
+
 def SwiftName : InheritableAttr {
   let Spellings = [GNU<"swift_name">];
   let Args = [StringArgument<"Name">];
@@ -2614,6 +2633,31 @@ def SwiftPrivate : InheritableAttr {
   let SimpleHandler = 1;
 }
 
+def SwiftVersioned : Attr {
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let Args = [VersionArgument<"Version">, AttrArgument<"AttrToAdd">,
+              BoolArgument<"IsReplacedByActive">];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+}
+
+def SwiftVersionedRemoval : Attr {
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let Args = [VersionArgument<"Version">, UnsignedArgument<"RawKind">,
+              BoolArgument<"IsReplacedByActive">];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+  let AdditionalMembers = [{
+    attr::Kind getAttrKindToRemove() const {
+      return static_cast<attr::Kind>(getRawKind());
+    }
+  }];
+}
+
 def NoDeref : TypeAttr {
   let Spellings = [Clang<"noderef">];
   let Documentation = [NoDerefDocs];
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index c3753ca2828e25e..02c5b9527f5d5f3 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1606,6 +1606,9 @@ def err_pragma_invalid_keyword : Error<
 def err_pragma_pipeline_invalid_keyword : Error<
     "invalid argument; expected 'disable'">;
 
+// API notes.
+def err_type_unparsed : Error<"unparsed tokens following type">;
+
 // Pragma unroll support.
 def warn_pragma_unroll_cuda_value_in_parens : Warning<
   "argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 990692c06d7d3a8..f09f2899754690d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10667,6 +10667,13 @@ def warn_imp_cast_drops_unaligned : Warning<
 
 } // end of sema category
 
+let CategoryName = "API Notes Issue" in {
+
+def err_incompatible_replacement_type : Error<
+  "API notes replacement type %0 has a different size from original type %1">;
+
+} // end of API Notes category
+
 let CategoryName = "OpenMP Issue" in {
 // OpenMP support.
 def err_omp_expected_var_arg : Error<
diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h
index 899e665e7454652..b6ecc7e5ded9e2a 100644
--- a/clang/include/clang/Lex/Lexer.h
+++ b/clang/include/clang/Lex/Lexer.h
@@ -198,11 +198,11 @@ class Lexer : public PreprocessorLexer {
   /// from.  Currently this is only used by _Pragma handling.
   SourceLocation getFileLoc() const { return FileLoc; }
 
-private:
   /// Lex - Return the next token in the file.  If this is the end of file, it
   /// return the tok::eof token.  This implicitly involves the preprocessor.
   bool Lex(Token &Result);
 
+private:
   /// Called when the preprocessor is in 'dependency scanning lexing mode'.
   bool LexDependencyDirectiveToken(Token &Result);
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 465453826c0b982..034a8f90abbfaf7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3609,6 +3609,18 @@ class Parser : public CodeCompletionHandler {
   ParseConceptDefinition(const ParsedTemplateInfo &TemplateInfo,
                          SourceLocation &DeclEnd);
 
+  /// Parse the given string as a type.
+  ///
+  /// This is a dangerous utility function currently employed only by API notes.
+  /// It is not a general entry-point for safely parsing types from strings.
+  ///
+  /// \param TypeStr The string to be parsed as a type.
+  /// \param Context The name of the context in which this string is being
+  /// parsed, which will be used in diagnostics.
+  /// \param IncludeLoc The location at which this parse was triggered.
+  TypeResult ParseTypeFromString(StringRef TypeStr, StringRef Context,
+                                 SourceLocation IncludeLoc);
+
   //===--------------------------------------------------------------------===//
   // Modules
   DeclGroupPtrTy ParseModuleDecl(Sema::ModuleImportState &ImportState);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 59806bcbcbb2dbc..0b300df3ef5b666 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -955,6 +955,10 @@ class Sema final {
     OpaqueParser = P;
   }
 
+  /// Callback to the parser to parse a type expressed as a string.
+  std::function<TypeResult(StringRef, StringRef, SourceLocation)>
+      ParseTypeFromStringCallback;
+
   class DelayedDiagnostics;
 
   class DelayedDiagnosticsState {
@@ -3017,6 +3021,9 @@ class Sema final {
   ParmVarDecl *BuildParmVarDeclForTypedef(DeclContext *DC,
                                           SourceLocation Loc,
                                           QualType T);
+  QualType AdjustParameterTypeForObjCAutoRefCount(QualType T,
+                                                  SourceLocation NameLoc,
+                                                  TypeSourceInfo *TSInfo);
   ParmVarDecl *CheckParameter(DeclContext *DC, SourceLocation StartLoc,
                               SourceLocation NameLoc, IdentifierInfo *Name,
                               QualType T, TypeSourceInfo *TSInfo,
@@ -4742,6 +4749,12 @@ class Sema final {
   bool checkCommonAttributeFeatures(const Stmt *S, const ParsedAttr &A,
                                     bool SkipArgCountCheck = false);
 
+  /// Map any API notes provided for this declaration to attributes on the
+  /// declaration.
+  ///
+  /// Triggered by declaration-attribute processing.
+  void ProcessAPINotes(Decl *D);
+
   /// Determine if type T is a valid subject for a nonnull and similar
   /// attributes. By default, we look through references (the behavior used by
   /// nonnull), but if the second parameter is true, then we treat a reference
@@ -4802,6 +4815,29 @@ class Sema final {
   /// Valid types should not have multiple attributes with different CCs.
   const AttributedType *getCallingConvAttributedType(QualType T) const;
 
+  /// Check whether a nullability type specifier can be added to the given
+  /// type through some means not written in source (e.g. API notes).
+  ///
+  /// \param Type The type to which the nullability specifier will be
+  /// added. On success, this type will be updated appropriately.
+  ///
+  /// \param Nullability The nullability specifier to add.
+  ///
+  /// \param DiagLoc The location to use for diagnostics.
+  ///
+  /// \param AllowArrayTypes Whether to accept nullability specifiers on an
+  /// array type (e.g., because it will decay to a pointer).
+  ///
+  /// \param OverrideExisting Whether to override an existing, locally-specified
+  /// nullability specifier rather than complaining about the conflict.
+  ///
+  /// \returns true if nullability cannot be applied, false otherwise.
+  bool CheckImplicitNullabilityTypeSpecifier(QualType &Type,
+                                             NullabilityKind Nullability,
+                                             SourceLocation DiagLoc,
+                                             bool AllowArrayTypes,
+                                             bool OverrideExisting);
+
   /// Process the attributes before creating an attributed statement. Returns
   /// the semantic attributes that have been processed.
   void ProcessStmtAttributes(Stmt *Stmt, const ParsedAttributes &InAttrs,
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 8cb5b09fd3b0fa6..421fec3bed58534 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -8028,6 +8028,71 @@ bool Parser::TryAltiVecTokenOutOfLine(DeclSpec &DS, SourceLocation Loc,
   return false;
 }
 
+TypeResult Parser::ParseTypeFromString(StringRef TypeStr, StringRef Context,
+                                       SourceLocation IncludeLoc) {
+  // Consume (unexpanded) tokens up to the end-of-directive.
+  SmallVector<Token, 4> Tokens;
+  {
+    // Create a new buffer from which we will parse the type.
+    auto &SourceMgr = PP.getSourceManager();
+    FileID FID = SourceMgr.createFileID(
+        llvm::MemoryBuffer::getMemBufferCopy(TypeStr, Context), SrcMgr::C_User,
+        0, 0, IncludeLoc);
+
+    // Form a new lexer that references the buffer.
+    Lexer L(FID, SourceMgr.getBufferOrFake(FID), PP);
+    L.setParsingPreprocessorDirective(true);
+
+    // Lex the tokens from that buffer.
+    Token Tok;
+    do {
+      L.Lex(Tok);
+      Tokens.push_back(Tok);
+    } while (Tok.isNot(tok::eod));
+  }
+
+  // Replace the "eod" token with an "eof" token identifying the end of
+  // the provided string.
+  Token &EndToken = Tokens.back();
+  EndToken.startToken();
+  EndToken.setKind(tok::eof);
+  EndToken.setLocation(Tok.getLocation());
+  EndToken.setEofData(TypeStr.data());
+
+  // Add the current token back.
+  Tokens.push_back(Tok);
+
+  // Enter the tokens into the token stream.
+  PP.EnterTokenStream(Tokens, /*DisableMacroExpansion=*/false,
+                      /*IsReinject=*/false);
+
+  // Consume the current token so that we'll start parsing the tokens we
+  // added to the stream.
+  ConsumeAnyToken();
+
+  // Enter a new scope.
+  ParseScope LocalScope(this, 0);
+
+  // Parse the type.
+  TypeResult Result = ParseTypeName(nullptr);
+
+  // Check if we parsed the whole thing.
+  if (Result.isUsable() &&
+      (Tok.isNot(tok::eof) || Tok.getEofData() != TypeStr.data())) {
+    Diag(Tok.getLocation(), diag::err_type_unparsed);
+  }
+
+  // There could be leftover tokens (e.g. because of an error).
+  // Skip through until we reach the 'end of directive' token.
+  while (Tok.isNot(tok::eof))
+    ConsumeAnyToken();
+
+  // Consume the end token.
+  if (Tok.is(tok::eof) && Tok.getEofData() == TypeStr.data())
+    ConsumeAnyToken();
+  return Result;
+}
+
 void Parser::DiagnoseBitIntUse(const Token &Tok) {
   // If the token is for _ExtInt, diagnose it as being deprecated. Otherwise,
   // the token is about _BitInt and gets (potentially) diagnosed as use of an
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 1baeb2aeb021faa..97bcbb605dcbd75 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -70,6 +70,11 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies)
   PP.addCommentHandler(CommentSemaHandler.get());
 
   PP.setCodeCompletionHandler(*this);
+
+  Actions.ParseTypeFromStringCallback =
+      [this](StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc) {
+        return this->ParseTypeFromString(TypeStr, Context, IncludeLoc);
+      };
 }
 
 DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) {
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 1856a88e9a3271a..4f72bce98fbbec3 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -27,6 +27,7 @@ add_clang_library(clangSema
   Sema.cpp
   SemaAccess.cpp
   SemaAttr.cpp
+  SemaAPINotes.cpp
   SemaAvailability.cpp
   SemaCXXScopeSpec.cpp
   SemaCast.cpp
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
new file mode 100644
index 000000000000000..4f2ac1acfc01a9d
--- /dev/null
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -0,0 +1,1014 @@
+//===--- SemaAPINotes.cpp - API Notes Handling ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements the mapping from API notes to declaration attributes.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/APINotes/APINotesReader.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Sema/SemaInternal.h"
+
+using namespace clang;
+
+namespace {
+enum IsActive_t : bool { IsNotActive, IsActive };
+enum IsReplacement_t : bool { IsNotReplacement, IsReplacement };
+
+struct VersionedInfoMetadata {
+  /// An empty version refers to unversioned metadata.
+  VersionTuple Version;
+  unsigned IsActive : 1;
+  unsigned IsReplacement : 1;
+
+  VersionedInfoMetadata(VersionTuple Version, IsActive_t Active,
+                        IsReplacement_t Replacement)
+      : Version(Version), IsActive(Active == IsActive_t::IsActive),
+        IsReplacement(Replacement == IsReplacement_t::IsReplacement) {}
+};
+} // end anonymous namespace
+
+/// Determine whether this is a multi-level pointer type.
+static bool isMultiLevelPointerType(QualType Type) {
+  QualType Pointee = Type->getPointeeType();
+  if (Pointee.isNull())
+    return false;
+
+  return Pointee->isAnyPointerType() || Pointee->isObjCObjectPointerType() ||
+         Pointee->isMemberPointerType();
+}
+
+/// Apply nullability to the given declaration.
+static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability,
+                             VersionedInfoMetadata Metadata) {
+  if (!Metadata.IsActive)
+    return;
+
+  QualType Type;
+
+  // Nullability for a function/method appertains to the retain type.
+  if (auto Function = dyn_cast<FunctionDecl>(D))
+    Type = Function->getReturnType();
+  else if (auto Method = dyn_cast<ObjCMethodDecl>(D))
+    Type = Method->getReturnType();
+  else if (auto Value = dyn_cast<ValueDecl>(D))
+    Type = Value->getType();
+  else if (auto Property = dyn_cast<ObjCPropertyDecl>(D))
+    Type = Property->getType();
+  else
+    return;
+
+  // Check the nullability specifier on this type.
+  QualType OrigType = Type;
+  S.CheckImplicitNullabilityTypeSpecifier(Type, Nullability, D->getLocation(),
+                                          isa<ParmVarDecl>(D),
+                                          /*overrideExisting=*/true);
+  if (Type.getTypePtr() == OrigType.getTypePtr())
+    return;
+
+  if (auto Function = dyn_cast<FunctionDecl>(D)) {
+    const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
+    if (const FunctionProtoType *Proto = dyn_cast<FunctionProtoType>(FnType))
+      Function->setType(S.Context.getFunctionType(Type, Proto->getParamTypes(),
+                                                  Proto->getExtProtoInfo()));
+    else
+      Function->setType(
+          S.Context.getFunctionNoProtoType(Type, FnType->getExtInfo()));
+  } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
+    Method->setReturnType(Type);
+
+    // Make it a context-sensitive keyword if we can.
+    if (!isMultiLevelPointerType(Type))
+      Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+          Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+
+  } else if (auto Value = dyn_cast<ValueDecl>(D)) {
+    Value->setType(Type);
+
+    // Make it a context-sensitive keyword if we can.
+    if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
+      if (Parm->isObjCMethodParameter() && !isMultiLevelPointerType(Type))
+        Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+            Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+    }
+  } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
+    Property->setType(Type, Property->getTypeSourceInfo());
+
+    // Make it a property attribute if we can.
+    if (!isMultiLevelPointerType(Type))
+      Property->setPropertyAttributes(
+          ObjCPropertyAttribute::kind_null_resettable);
+
+  } else
+    llvm_unreachable("cannot handle nullability here");
+}
+
+/// Copy a string into ASTContext-allocated memory.
+static StringRef CopyString(ASTContext &Ctx, StringRef String) {
+  void *mem = Ctx.Allocate(String.size(), alignof(char));
+  memcpy(mem, String.data(), String.size());
+  return StringRef(static_cast<char *>(mem), String.size());
+}
+
+static AttributeCommonInfo getDummyAttrInfo() {
+  return AttributeCommonInfo(SourceRange(),
+                             AttributeCommonInfo::UnknownAttribute,
+                             {AttributeCommonInfo::AS_GNU,
+                              /*Spelling*/ 0, /*IsAlignas*/ false,
+                              /*IsRegularKeywordAttribute*/ false});
+}
+
+namespace {
+template <typename A> struct AttrKindFor {};
+
+#define ATTR(X)                                                                \
+  template <> struct AttrKindFor<X##Attr> {                                    \
+    static const attr::Kind value = attr::X;                                   \
+  };
+#include "clang/Basic/AttrList.inc"
+
+/// Handle an attribute introduced by API notes.
+///
+/// \param ShouldAddAttribute Whether we should add a new attribute
+/// (otherwise, we might remove an existing attribute).
+/// \param CreateAttr Create the new attribute to be added.
+template <typename A>
+void handleAPINotedAttribute(
+    Sema &S, Decl *D, bool ShouldAddAttribute, VersionedInfoMetadata Metadata,
+    llvm::function_ref<A *()> CreateAttr,
+    llvm::function_ref<Decl::attr_iterator(const Decl *)> GetExistingAttr) {
+  if (Metadata.IsActive) {
+    auto Existing = GetExistingAttr(D);
+    if (Existing != D->attr_end()) {
+      // Remove the existing attribute, and treat it as a superseded
+      // non-versioned attribute.
+      auto *Versioned = SwiftVersionedAttr::CreateImplicit(
+          S.Context, Metadata.Version, *Existing, /*IsReplacedByActive*/ true);
+
+      D->getAttrs().erase(Existing);
+      D->addAttr(Versioned);
+    }
+
+    // If we're supposed to add a new attribute, do so.
+    if (ShouldAddAttribute) {
+      if (auto Attr = CreateAttr())
+        D->addAttr(Attr);
+    }
+
+  } else {
+    if (ShouldAddAttribute) {
+      if (auto Attr = CreateAttr()) {
+        auto *Versioned = SwiftVersionedAttr::CreateImplicit(
+            S.Context, Metadata.Version, Attr,
+            /*IsReplacedByActive*/ Metadata.IsReplacement);
+        D->addAttr(Versioned);
+      }
+    } else {
+      // FIXME: This isn't preserving enough information for things like
+      // availability, where we're trying to remove a /specific/ kind of
+      // attribute.
+      auto *Versioned = SwiftVersionedRemovalAttr::CreateImplicit(
+          S.Context, Metadata.Version, AttrKindFor<A>::value,
+          /*IsReplacedByActive*/ Metadata.IsReplacement);
+      D->addAttr(Versioned);
+    }
+  }
+}
+
+template <typename A>
+void handleAPINotedAttribute(Sema &S, Decl *D, bool ShouldAddAttribute,
+                             VersionedInfoMeta...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/73017


More information about the cfe-commits mailing list