[clang] 75d4d4b - Add an attribute registry so plugins can add attributes

John Brawn via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 27 09:26:30 PST 2020


Author: John Brawn
Date: 2020-02-27T17:23:16Z
New Revision: 75d4d4bd028f6a5f24ef41dbbc45bed65b2305cf

URL: https://github.com/llvm/llvm-project/commit/75d4d4bd028f6a5f24ef41dbbc45bed65b2305cf
DIFF: https://github.com/llvm/llvm-project/commit/75d4d4bd028f6a5f24ef41dbbc45bed65b2305cf.diff

LOG: Add an attribute registry so plugins can add attributes

When constructing a ParsedAttr the ParsedAttrInfo gets looked up in the
AttrInfoMap, which is auto-generated using tablegen. If that lookup fails then
we look through the ParsedAttrInfos that plugins have added to the registry and
check if any has a spelling that matches.

Differential Revision: https://reviews.llvm.org/D31338

Added: 
    

Modified: 
    clang/include/clang/Basic/AttributeCommonInfo.h
    clang/include/clang/Sema/ParsedAttr.h
    clang/lib/Basic/Attributes.cpp
    clang/lib/Sema/ParsedAttr.cpp
    clang/utils/TableGen/ClangAttrEmitter.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/AttributeCommonInfo.h b/clang/include/clang/Basic/AttributeCommonInfo.h
index 545e7e9a2b47..f4a5db84aa9f 100644
--- a/clang/include/clang/Basic/AttributeCommonInfo.h
+++ b/clang/include/clang/Basic/AttributeCommonInfo.h
@@ -134,6 +134,11 @@ class AttributeCommonInfo {
   const IdentifierInfo *getScopeName() const { return ScopeName; }
   SourceLocation getScopeLoc() const { return ScopeLoc; }
 
+  /// Gets the normalized full name, which consists of both scope and name and
+  /// with surrounding underscores removed as appropriate (e.g.
+  /// __gnu__::__attr__ will be normalized to gnu::attr).
+  std::string getNormalizedFullName() const;
+
   bool isDeclspecAttribute() const { return SyntaxUsed == AS_Declspec; }
   bool isMicrosoftAttribute() const { return SyntaxUsed == AS_Microsoft; }
 

diff  --git a/clang/include/clang/Sema/ParsedAttr.h b/clang/include/clang/Sema/ParsedAttr.h
index d9d8585970d9..0e0d5cce6d3c 100644
--- a/clang/include/clang/Sema/ParsedAttr.h
+++ b/clang/include/clang/Sema/ParsedAttr.h
@@ -24,6 +24,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/TinyPtrVector.h"
 #include "llvm/Support/Allocator.h"
+#include "llvm/Support/Registry.h"
 #include "llvm/Support/VersionTuple.h"
 #include <cassert>
 #include <cstddef>
@@ -37,6 +38,72 @@ class Decl;
 class Expr;
 class IdentifierInfo;
 class LangOptions;
+class ParsedAttr;
+class Sema;
+
+struct ParsedAttrInfo {
+  /// Corresponds to the Kind enum.
+  unsigned AttrKind : 16;
+  /// The number of required arguments of this attribute.
+  unsigned NumArgs : 4;
+  /// The number of optional arguments of this attributes.
+  unsigned OptArgs : 4;
+  /// True if the parsing does not match the semantic content.
+  unsigned HasCustomParsing : 1;
+  /// True if this attribute is only available for certain targets.
+  unsigned IsTargetSpecific : 1;
+  /// True if this attribute applies to types.
+  unsigned IsType : 1;
+  /// True if this attribute applies to statements.
+  unsigned IsStmt : 1;
+  /// True if this attribute has any spellings that are known to gcc.
+  unsigned IsKnownToGCC : 1;
+  /// True if this attribute is supported by #pragma clang attribute.
+  unsigned IsSupportedByPragmaAttribute : 1;
+  /// The syntaxes supported by this attribute and how they're spelled.
+  struct Spelling {
+    AttributeCommonInfo::Syntax Syntax;
+    std::string NormalizedFullName;
+  };
+  std::vector<Spelling> Spellings;
+
+  ParsedAttrInfo(AttributeCommonInfo::Kind AttrKind =
+                     AttributeCommonInfo::UnknownAttribute)
+      : AttrKind(AttrKind), NumArgs(0), OptArgs(0), HasCustomParsing(0),
+        IsTargetSpecific(0), IsType(0), IsStmt(0), IsKnownToGCC(0),
+        IsSupportedByPragmaAttribute(0) {}
+
+  virtual ~ParsedAttrInfo() = default;
+
+  /// Check if this attribute appertains to D, and issue a diagnostic if not.
+  virtual bool diagAppertainsToDecl(Sema &S, const ParsedAttr &Attr,
+                                    const Decl *D) const {
+    return true;
+  }
+  /// Check if this attribute is allowed by the language we are compiling, and
+  /// issue a diagnostic if not.
+  virtual bool diagLangOpts(Sema &S, const ParsedAttr &Attr) const {
+    return true;
+  }
+  /// Check if this attribute is allowed when compiling for the given target.
+  virtual bool existsInTarget(const TargetInfo &Target) const {
+    return true;
+  }
+  /// Convert the spelling index of Attr to a semantic spelling enum value.
+  virtual unsigned
+  spellingIndexToSemanticSpelling(const ParsedAttr &Attr) const {
+    return UINT_MAX;
+  }
+  /// Populate Rules with the match rules of this attribute.
+  virtual void getPragmaAttributeMatchRules(
+      llvm::SmallVectorImpl<std::pair<attr::SubjectMatchRule, bool>> &Rules,
+      const LangOptions &LangOpts) const {
+  }
+
+  static const ParsedAttrInfo &get(const AttributeCommonInfo &A);
+};
+
+typedef llvm::Registry<ParsedAttrInfo> ParsedAttrInfoRegistry;
 
 /// Represents information about a change in availability for
 /// an entity, which is part of the encoding of the 'availability'
@@ -181,6 +248,8 @@ class ParsedAttr final
 
   const Expr *MessageExpr;
 
+  const ParsedAttrInfo &Info;
+
   ArgsUnion *getArgsBuffer() { return getTrailingObjects<ArgsUnion>(); }
   ArgsUnion const *getArgsBuffer() const {
     return getTrailingObjects<ArgsUnion>();
@@ -207,7 +276,8 @@ class ParsedAttr final
         EllipsisLoc(ellipsisLoc), NumArgs(numArgs), Invalid(false),
         UsedAsTypeAttr(false), IsAvailability(false),
         IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
-        HasProcessingCache(false), IsPragmaClangAttribute(false) {
+        HasProcessingCache(false), IsPragmaClangAttribute(false),
+        Info(ParsedAttrInfo::get(*this)) {
     if (numArgs)
       memcpy(getArgsBuffer(), args, numArgs * sizeof(ArgsUnion));
   }
@@ -225,7 +295,8 @@ class ParsedAttr final
         NumArgs(1), Invalid(false), UsedAsTypeAttr(false), IsAvailability(true),
         IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
         HasProcessingCache(false), IsPragmaClangAttribute(false),
-        UnavailableLoc(unavailable), MessageExpr(messageExpr) {
+        UnavailableLoc(unavailable), MessageExpr(messageExpr),
+        Info(ParsedAttrInfo::get(*this)) {
     ArgsUnion PVal(Parm);
     memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
     new (getAvailabilityData()) detail::AvailabilityData(
@@ -242,7 +313,7 @@ class ParsedAttr final
         NumArgs(3), Invalid(false), UsedAsTypeAttr(false),
         IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false),
         HasParsedType(false), HasProcessingCache(false),
-        IsPragmaClangAttribute(false) {
+        IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
     ArgsUnion *Args = getArgsBuffer();
     Args[0] = Parm1;
     Args[1] = Parm2;
@@ -259,7 +330,7 @@ class ParsedAttr final
         NumArgs(1), Invalid(false), UsedAsTypeAttr(false),
         IsAvailability(false), IsTypeTagForDatatype(true), IsProperty(false),
         HasParsedType(false), HasProcessingCache(false),
-        IsPragmaClangAttribute(false) {
+        IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
     ArgsUnion PVal(ArgKind);
     memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
     detail::TypeTagForDatatypeData &ExtraData = getTypeTagForDatatypeDataSlot();
@@ -277,7 +348,7 @@ class ParsedAttr final
         NumArgs(0), Invalid(false), UsedAsTypeAttr(false),
         IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false),
         HasParsedType(true), HasProcessingCache(false),
-        IsPragmaClangAttribute(false) {
+        IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
     new (&getTypeBuffer()) ParsedType(typeArg);
   }
 
@@ -291,7 +362,7 @@ class ParsedAttr final
         NumArgs(0), Invalid(false), UsedAsTypeAttr(false),
         IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(true),
         HasParsedType(false), HasProcessingCache(false),
-        IsPragmaClangAttribute(false) {
+        IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
     new (&getPropertyDataBuffer()) detail::PropertyData(getterId, setterId);
   }
 
@@ -534,7 +605,10 @@ class ParsedAttr final
     }
   }
 
-  AttributeCommonInfo::Kind getKind() const { return getParsedKind(); }
+  AttributeCommonInfo::Kind getKind() const {
+    return AttributeCommonInfo::Kind(Info.AttrKind);
+  }
+  const ParsedAttrInfo &getInfo() const { return Info; }
 };
 
 class AttributePool;

diff  --git a/clang/lib/Basic/Attributes.cpp b/clang/lib/Basic/Attributes.cpp
index 74cc3d1d03da..a860c9dba2ef 100644
--- a/clang/lib/Basic/Attributes.cpp
+++ b/clang/lib/Basic/Attributes.cpp
@@ -36,10 +36,14 @@ const char *attr::getSubjectMatchRuleSpelling(attr::SubjectMatchRule Rule) {
 }
 
 static StringRef
-normalizeAttrScopeName(StringRef ScopeName,
+normalizeAttrScopeName(const IdentifierInfo *Scope,
                        AttributeCommonInfo::Syntax SyntaxUsed) {
+  if (!Scope)
+    return "";
+
   // Normalize the "__gnu__" scope name to be "gnu" and the "_Clang" scope name
   // to be "clang".
+  StringRef ScopeName = Scope->getName();
   if (SyntaxUsed == AttributeCommonInfo::AS_CXX11 ||
       SyntaxUsed == AttributeCommonInfo::AS_C2x) {
     if (ScopeName == "__gnu__")
@@ -50,7 +54,7 @@ normalizeAttrScopeName(StringRef ScopeName,
   return ScopeName;
 }
 
-static StringRef normalizeAttrName(StringRef AttrName,
+static StringRef normalizeAttrName(const IdentifierInfo *Name,
                                    StringRef NormalizedScopeName,
                                    AttributeCommonInfo::Syntax SyntaxUsed) {
   // Normalize the attribute name, __foo__ becomes foo. This is only allowable
@@ -61,6 +65,7 @@ static StringRef normalizeAttrName(StringRef AttrName,
         SyntaxUsed == AttributeCommonInfo::AS_C2x) &&
        (NormalizedScopeName.empty() || NormalizedScopeName == "gnu" ||
         NormalizedScopeName == "clang"));
+  StringRef AttrName = Name->getName();
   if (ShouldNormalize && AttrName.size() >= 4 && AttrName.startswith("__") &&
       AttrName.endswith("__"))
     AttrName = AttrName.slice(2, AttrName.size() - 2);
@@ -74,35 +79,41 @@ bool AttributeCommonInfo::isGNUScope() const {
 
 #include "clang/Sema/AttrParsedAttrKinds.inc"
 
-AttributeCommonInfo::Kind
-AttributeCommonInfo::getParsedKind(const IdentifierInfo *Name,
-                                   const IdentifierInfo *ScopeName,
-                                   Syntax SyntaxUsed) {
-  StringRef AttrName = Name->getName();
-
-  SmallString<64> FullName;
-  if (ScopeName)
-    FullName += normalizeAttrScopeName(ScopeName->getName(), SyntaxUsed);
-
-  AttrName = normalizeAttrName(AttrName, FullName, SyntaxUsed);
+static SmallString<64> normalizeName(const IdentifierInfo *Name,
+                                     const IdentifierInfo *Scope,
+                                     AttributeCommonInfo::Syntax SyntaxUsed) {
+  StringRef ScopeName = normalizeAttrScopeName(Scope, SyntaxUsed);
+  StringRef AttrName = normalizeAttrName(Name, ScopeName, SyntaxUsed);
 
   // Ensure that in the case of C++11 attributes, we look for '::foo' if it is
   // unscoped.
-  if (ScopeName || SyntaxUsed == AS_CXX11 || SyntaxUsed == AS_C2x)
+  SmallString<64> FullName = ScopeName;
+  if (Scope || SyntaxUsed == AttributeCommonInfo::AS_CXX11 ||
+      SyntaxUsed == AttributeCommonInfo::AS_C2x)
     FullName += "::";
   FullName += AttrName;
 
-  return ::getAttrKind(FullName, SyntaxUsed);
+  return FullName;
+}
+
+AttributeCommonInfo::Kind
+AttributeCommonInfo::getParsedKind(const IdentifierInfo *Name,
+                                   const IdentifierInfo *ScopeName,
+                                   Syntax SyntaxUsed) {
+  return ::getAttrKind(normalizeName(Name, ScopeName, SyntaxUsed), SyntaxUsed);
+}
+
+std::string AttributeCommonInfo::getNormalizedFullName() const {
+  return static_cast<std::string>(
+      normalizeName(getAttrName(), getScopeName(), getSyntax()));
 }
 
 unsigned AttributeCommonInfo::calculateAttributeSpellingListIndex() const {
   // Both variables will be used in tablegen generated
   // attribute spell list index matching code.
   auto Syntax = static_cast<AttributeCommonInfo::Syntax>(getSyntax());
-  StringRef Scope =
-      getScopeName() ? normalizeAttrScopeName(getScopeName()->getName(), Syntax)
-                     : "";
-  StringRef Name = normalizeAttrName(getAttrName()->getName(), Scope, Syntax);
+  StringRef Scope = normalizeAttrScopeName(getScopeName(), Syntax);
+  StringRef Name = normalizeAttrName(getAttrName(), Scope, Syntax);
 
 #include "clang/Sema/AttrSpellingListIndex.inc"
 }

diff  --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index c814639f00ea..45df8cf5a85c 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -25,6 +25,8 @@
 
 using namespace clang;
 
+LLVM_INSTANTIATE_REGISTRY(ParsedAttrInfoRegistry)
+
 IdentifierLoc *IdentifierLoc::create(ASTContext &Ctx, SourceLocation Loc,
                                      IdentifierInfo *Ident) {
   IdentifierLoc *Result = new (Ctx) IdentifierLoc;
@@ -100,66 +102,58 @@ void AttributePool::takePool(AttributePool &pool) {
   pool.Attrs.clear();
 }
 
-struct ParsedAttrInfo {
-  unsigned NumArgs : 4;
-  unsigned OptArgs : 4;
-  unsigned HasCustomParsing : 1;
-  unsigned IsTargetSpecific : 1;
-  unsigned IsType : 1;
-  unsigned IsStmt : 1;
-  unsigned IsKnownToGCC : 1;
-  unsigned IsSupportedByPragmaAttribute : 1;
-
-  virtual ~ParsedAttrInfo() = default;
-
-  virtual bool diagAppertainsToDecl(Sema &S, const ParsedAttr &Attr,
-                                    const Decl *) const {
-    return true;
-  }
-  virtual bool diagLangOpts(Sema &S, const ParsedAttr &Attr) const {
-    return true;
-  }
-  virtual bool existsInTarget(const TargetInfo &Target) const {
-    return true;
-  }
-  virtual unsigned
-  spellingIndexToSemanticSpelling(const ParsedAttr &Attr) const {
-    return UINT_MAX;
-  }
-  virtual void getPragmaAttributeMatchRules(
-    llvm::SmallVectorImpl<std::pair<attr::SubjectMatchRule, bool>> &Rules,
-    const LangOptions &LangOpts) const {
-  }
-};
-
 namespace {
 
 #include "clang/Sema/AttrParsedAttrImpl.inc"
 
 } // namespace
 
-static const ParsedAttrInfo &getInfo(const ParsedAttr &A) {
-  // If we have a ParsedAttrInfo for this ParsedAttr then return that,
-  // otherwise return a default ParsedAttrInfo.
-  if (A.getKind() < llvm::array_lengthof(AttrInfoMap))
-    return *AttrInfoMap[A.getKind()];
-
+const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
+  // If we have a ParsedAttrInfo for this ParsedAttr then return that.
+  if (A.getParsedKind() < llvm::array_lengthof(AttrInfoMap))
+    return *AttrInfoMap[A.getParsedKind()];
+
+  // If this is an ignored attribute then return an appropriate ParsedAttrInfo.
+  static ParsedAttrInfo IgnoredParsedAttrInfo(
+      AttributeCommonInfo::IgnoredAttribute);
+  if (A.getParsedKind() == AttributeCommonInfo::IgnoredAttribute)
+    return IgnoredParsedAttrInfo;
+
+  // Otherwise this may be an attribute defined by a plugin. First instantiate
+  // all plugin attributes if we haven't already done so.
+  static std::list<std::unique_ptr<ParsedAttrInfo>> PluginAttrInstances;
+  if (PluginAttrInstances.empty())
+    for (auto It : ParsedAttrInfoRegistry::entries())
+      PluginAttrInstances.emplace_back(It.instantiate());
+
+  // Search for a ParsedAttrInfo whose name and syntax match.
+  std::string FullName = A.getNormalizedFullName();
+  AttributeCommonInfo::Syntax SyntaxUsed = A.getSyntax();
+  if (SyntaxUsed == AttributeCommonInfo::AS_ContextSensitiveKeyword)
+    SyntaxUsed = AttributeCommonInfo::AS_Keyword;
+
+  for (auto &Ptr : PluginAttrInstances)
+    for (auto &S : Ptr->Spellings)
+      if (S.Syntax == SyntaxUsed && S.NormalizedFullName == FullName)
+        return *Ptr;
+
+  // If we failed to find a match then return a default ParsedAttrInfo.
   static ParsedAttrInfo DefaultParsedAttrInfo;
   return DefaultParsedAttrInfo;
 }
 
-unsigned ParsedAttr::getMinArgs() const { return getInfo(*this).NumArgs; }
+unsigned ParsedAttr::getMinArgs() const { return getInfo().NumArgs; }
 
 unsigned ParsedAttr::getMaxArgs() const {
-  return getMinArgs() + getInfo(*this).OptArgs;
+  return getMinArgs() + getInfo().OptArgs;
 }
 
 bool ParsedAttr::hasCustomParsing() const {
-  return getInfo(*this).HasCustomParsing;
+  return getInfo().HasCustomParsing;
 }
 
 bool ParsedAttr::diagnoseAppertainsTo(Sema &S, const Decl *D) const {
-  return getInfo(*this).diagAppertainsToDecl(S, *this, D);
+  return getInfo().diagAppertainsToDecl(S, *this, D);
 }
 
 bool ParsedAttr::appliesToDecl(const Decl *D,
@@ -171,33 +165,33 @@ void ParsedAttr::getMatchRules(
     const LangOptions &LangOpts,
     SmallVectorImpl<std::pair<attr::SubjectMatchRule, bool>> &MatchRules)
     const {
-  return getInfo(*this).getPragmaAttributeMatchRules(MatchRules, LangOpts);
+  return getInfo().getPragmaAttributeMatchRules(MatchRules, LangOpts);
 }
 
 bool ParsedAttr::diagnoseLangOpts(Sema &S) const {
-  return getInfo(*this).diagLangOpts(S, *this);
+  return getInfo().diagLangOpts(S, *this);
 }
 
 bool ParsedAttr::isTargetSpecificAttr() const {
-  return getInfo(*this).IsTargetSpecific;
+  return getInfo().IsTargetSpecific;
 }
 
-bool ParsedAttr::isTypeAttr() const { return getInfo(*this).IsType; }
+bool ParsedAttr::isTypeAttr() const { return getInfo().IsType; }
 
-bool ParsedAttr::isStmtAttr() const { return getInfo(*this).IsStmt; }
+bool ParsedAttr::isStmtAttr() const { return getInfo().IsStmt; }
 
 bool ParsedAttr::existsInTarget(const TargetInfo &Target) const {
-  return getInfo(*this).existsInTarget(Target);
+  return getInfo().existsInTarget(Target);
 }
 
-bool ParsedAttr::isKnownToGCC() const { return getInfo(*this).IsKnownToGCC; }
+bool ParsedAttr::isKnownToGCC() const { return getInfo().IsKnownToGCC; }
 
 bool ParsedAttr::isSupportedByPragmaAttribute() const {
-  return getInfo(*this).IsSupportedByPragmaAttribute;
+  return getInfo().IsSupportedByPragmaAttribute;
 }
 
 unsigned ParsedAttr::getSemanticSpelling() const {
-  return getInfo(*this).spellingIndexToSemanticSpelling(*this);
+  return getInfo().spellingIndexToSemanticSpelling(*this);
 }
 
 bool ParsedAttr::hasVariadicArg() const {
@@ -205,5 +199,5 @@ bool ParsedAttr::hasVariadicArg() const {
   // claim that as being variadic. If we someday get an attribute that
   // legitimately bumps up against that maximum, we can use another bit to track
   // whether it's truly variadic or not.
-  return getInfo(*this).OptArgs == 15;
+  return getInfo().OptArgs == 15;
 }

diff  --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 251cbecd1c45..d8b5480c2b26 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -3625,9 +3625,11 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
 
     // We need to generate struct instances based off ParsedAttrInfo from
     // ParsedAttr.cpp.
+    const std::string &AttrName = I->first;
     const Record &Attr = *I->second;
     OS << "struct ParsedAttrInfo" << I->first << " : public ParsedAttrInfo {\n";
     OS << "  ParsedAttrInfo" << I->first << "() {\n";
+    OS << "    AttrKind = ParsedAttr::AT_" << AttrName << ";\n";
     emitArgInfo(Attr, OS);
     OS << "    HasCustomParsing = ";
     OS << Attr.getValueAsBit("HasCustomParsing") << ";\n";
@@ -3642,6 +3644,20 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
     OS << IsKnownToGCC(Attr) << ";\n";
     OS << "    IsSupportedByPragmaAttribute = ";
     OS << PragmaAttributeSupport.isAttributedSupported(*I->second) << ";\n";
+    for (const auto &S : GetFlattenedSpellings(Attr)) {
+      const std::string &RawSpelling = S.name();
+      std::string Spelling;
+      if (S.variety() == "CXX11" || S.variety() == "C2x") {
+        Spelling += S.nameSpace();
+        Spelling += "::";
+      }
+      if (S.variety() == "GNU")
+        Spelling += NormalizeGNUAttrSpelling(RawSpelling);
+      else
+        Spelling += RawSpelling;
+      OS << "    Spellings.push_back({AttributeCommonInfo::AS_" << S.variety();
+      OS << ",\"" << Spelling << "\"});\n";
+    }
     OS << "  }\n";
     GenerateAppertainsTo(Attr, OS);
     GenerateLangOptRequirements(Attr, OS);


        


More information about the cfe-commits mailing list