https://github.com/delcypher updated https://github.com/llvm/llvm-project/pull/88596
>From 31e3269126569e43cae3e4b7aba159f9a32d3c70 Mon Sep 17 00:00:00 2001 From: Dan Liew <d...@su-root.co.uk> Date: Fri, 12 Apr 2024 17:36:19 -0700 Subject: [PATCH] [Attributes] Support Attributes being declared as only supporting late parsing when passing an experimental feature flag This patch changes the `LateParsed` field of `Attr` in `Attr.td` to be an instantiation of the new `LateAttrParseKind` class. The instation can be one of the following: * `LateAttrParsingNever` - Corresponds with the false value of `LateParsed` prior to this patch (the default for an attribute). * `LateAttrParsingAlways` - Corresponds with the true value of `LateParsed` prior to this patch. * `LateAttrParsingExperimentalOnly` - A new mode described below. `LateAttrParsingExperimentalOnly` means that the attribute will be late parsed if the new the `ExperimentalLateParseAttributes` language option (also introduced in this patch) is enabled and will **not** be late parsed if the language option is disabled. The new `ExperimentalLateParseAttributes` language option is controlled by a new driver and frontend flag (`-fexperimental-late-parse-attributes`). A driver test is included to check that the driver passes the flag to CC1. In this patch the `LateAttrParsingExperimentalOnly` is not adopted by any attribute so `-fexperimental-late-pase-attributes` and the corresponding language option currently have no effect on compilation. This is why this patch does not include any test cases that test `LateAttrParsingExperimentalOnly`'s behavior. The motivation for this patch is to be able to late parse the new `counted_by` attribute but only do so when a feature flag is passed. This was discussed during the [bounds-safety RFC](https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-c-fbounds-safety/70854/68). Adoption of `LateAttrParsingExperimentalOnly` for the `counted_by` attributed will be handled separately in another patch (likely https://github.com/llvm/llvm-project/pull/87596). --- clang/include/clang/Basic/Attr.td | 46 +++--- clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Driver/Options.td | 7 + clang/include/clang/Parse/Parser.h | 4 + clang/lib/Driver/ToolChains/Clang.cpp | 3 + clang/lib/Parse/ParseDecl.cpp | 19 ++- .../experimental-late-parse-attributes.c | 12 ++ clang/utils/TableGen/ClangAttrEmitter.cpp | 138 ++++++++++++++++-- 8 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 clang/test/Driver/experimental-late-parse-attributes.c diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index dc87a8c6f022dc..597a8fb11e51b8 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -592,6 +592,16 @@ class AttrSubjectMatcherAggregateRule<AttrSubject subject> { def SubjectMatcherForNamed : AttrSubjectMatcherAggregateRule<Named>; +// Late Attribute parsing mode enum +class LateAttrParseKind <int val> { + int Kind = val; +} +def LateAttrParseNever : LateAttrParseKind<0>; // Never late parsed +def LateAttrParseAlways: LateAttrParseKind<1>; // Always late parsed +// Late parsed if `-fexperimental-late-parse-attributes` is on and parsed +// normally if off. +def LateAttrParseExperimentalOnly : LateAttrParseKind<2>; + class Attr { // The various ways in which an attribute can be spelled in source list<Spelling> Spellings; @@ -603,8 +613,8 @@ class Attr { list<Accessor> Accessors = []; // Specify targets for spellings. list<TargetSpecificSpelling> TargetSpecificSpellings = []; - // Set to true for attributes with arguments which require delayed parsing. - bit LateParsed = 0; + // Specifies the late parsing kind. + LateAttrParseKind LateParsed = LateAttrParseNever; // Set to false to prevent an attribute from being propagated from a template // to the instantiation. bit Clone = 1; @@ -3173,7 +3183,7 @@ def DiagnoseIf : InheritableAttr { BoolArgument<"ArgDependent", 0, /*fake*/ 1>, DeclArgument<Named, "Parent", 0, /*fake*/ 1>]; let InheritEvenIfAlreadyPresent = 1; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let AdditionalMembers = [{ bool isError() const { return diagnosticType == DT_Error; } bool isWarning() const { return diagnosticType == DT_Warning; } @@ -3472,7 +3482,7 @@ def AssertCapability : InheritableAttr { let Spellings = [Clang<"assert_capability", 0>, Clang<"assert_shared_capability", 0>]; let Subjects = SubjectList<[Function]>; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3488,7 +3498,7 @@ def AcquireCapability : InheritableAttr { GNU<"exclusive_lock_function">, GNU<"shared_lock_function">]; let Subjects = SubjectList<[Function]>; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3504,7 +3514,7 @@ def TryAcquireCapability : InheritableAttr { Clang<"try_acquire_shared_capability", 0>]; let Subjects = SubjectList<[Function], ErrorDiag>; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3520,7 +3530,7 @@ def ReleaseCapability : InheritableAttr { Clang<"release_generic_capability", 0>, Clang<"unlock_function", 0>]; let Subjects = SubjectList<[Function]>; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3539,7 +3549,7 @@ def RequiresCapability : InheritableAttr { Clang<"requires_shared_capability", 0>, Clang<"shared_locks_required", 0>]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3559,7 +3569,7 @@ def NoThreadSafetyAnalysis : InheritableAttr { def GuardedBy : InheritableAttr { let Spellings = [GNU<"guarded_by">]; let Args = [ExprArgument<"Arg">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3570,7 +3580,7 @@ def GuardedBy : InheritableAttr { def PtGuardedBy : InheritableAttr { let Spellings = [GNU<"pt_guarded_by">]; let Args = [ExprArgument<"Arg">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3581,7 +3591,7 @@ def PtGuardedBy : InheritableAttr { def AcquiredAfter : InheritableAttr { let Spellings = [GNU<"acquired_after">]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3592,7 +3602,7 @@ def AcquiredAfter : InheritableAttr { def AcquiredBefore : InheritableAttr { let Spellings = [GNU<"acquired_before">]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3603,7 +3613,7 @@ def AcquiredBefore : InheritableAttr { def AssertExclusiveLock : InheritableAttr { let Spellings = [GNU<"assert_exclusive_lock">]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3614,7 +3624,7 @@ def AssertExclusiveLock : InheritableAttr { def AssertSharedLock : InheritableAttr { let Spellings = [GNU<"assert_shared_lock">]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3627,7 +3637,7 @@ def AssertSharedLock : InheritableAttr { def ExclusiveTrylockFunction : InheritableAttr { let Spellings = [GNU<"exclusive_trylock_function">]; let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3640,7 +3650,7 @@ def ExclusiveTrylockFunction : InheritableAttr { def SharedTrylockFunction : InheritableAttr { let Spellings = [GNU<"shared_trylock_function">]; let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; @@ -3651,7 +3661,7 @@ def SharedTrylockFunction : InheritableAttr { def LockReturned : InheritableAttr { let Spellings = [GNU<"lock_returned">]; let Args = [ExprArgument<"Arg">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let Subjects = SubjectList<[Function]>; @@ -3661,7 +3671,7 @@ def LockReturned : InheritableAttr { def LocksExcluded : InheritableAttr { let Spellings = [GNU<"locks_excluded">]; let Args = [VariadicExprArgument<"Args">]; - let LateParsed = 1; + let LateParsed = LateAttrParseAlways; let TemplateDependent = 1; let ParseArgumentsAsUnevaluated = 1; let InheritEvenIfAlreadyPresent = 1; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 8ef6700ecdc78e..55c81eab1ec150 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -164,6 +164,7 @@ LANGOPT(ExperimentalLibrary, 1, 0, "enable unstable and experimental library fea LANGOPT(PointerAuthIntrinsics, 1, 0, "pointer authentication intrinsics") LANGOPT(DoubleSquareBracketAttributes, 1, 0, "'[[]]' attributes extension for all language standard modes") +LANGOPT(ExperimentalLateParseAttributes, 1, 0, "experimental late parsing of attributes") COMPATIBLE_LANGOPT(RecoveryAST, 1, 1, "Preserve expressions in AST when encountering errors") COMPATIBLE_LANGOPT(RecoveryASTType, 1, 1, "Preserve the type in recovery expressions") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 9a0b5d3304ca6e..711291c0caa6bb 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1603,6 +1603,13 @@ defm double_square_bracket_attributes : BoolFOption<"double-square-bracket-attri LangOpts<"DoubleSquareBracketAttributes">, DefaultTrue, PosFlag<SetTrue>, NegFlag<SetFalse>>; +defm experimental_late_parse_attributes : BoolFOption<"experimental-late-parse-attributes", + LangOpts<"ExperimentalLateParseAttributes">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption], "Enable">, + NegFlag<SetFalse, [], [ClangOption], "Disable">, + BothFlags<[], [ClangOption, CC1Option], + " experimental late parsing of attributes">>; + defm autolink : BoolFOption<"autolink", CodeGenOpts<"Autolink">, DefaultTrue, NegFlag<SetFalse, [], [ClangOption, CC1Option], diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index c719218731c35b..d9bdb07c9e5743 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2959,6 +2959,10 @@ class Parser : public CodeCompletionHandler { SourceLocation AttrNameLoc, SourceLocation *EndLoc); + /// isAttributeLateParsed - Return true if the attribute has arguments that + /// require late parsing. + bool isAttributeLateParsed(const IdentifierInfo &II); + IdentifierInfo *TryParseCXX11AttributeIdentifier( SourceLocation &Loc, Sema::AttributeCompletion Completion = Sema::AttributeCompletion::None, diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 766a9b91e3c0ad..539861601f359f 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7480,6 +7480,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.addOptInFlag(CmdArgs, options::OPT_fsafe_buffer_usage_suggestions, options::OPT_fno_safe_buffer_usage_suggestions); + Args.addOptInFlag(CmdArgs, options::OPT_fexperimental_late_parse_attributes, + options::OPT_fno_experimental_late_parse_attributes); + // Setup statistics file output. SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D); if (!StatsFile.empty()) { diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 583232f2d610d0..26e62054b92c12 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -91,11 +91,24 @@ static StringRef normalizeAttrName(StringRef Name) { /// isAttributeLateParsed - Return true if the attribute has arguments that /// require late parsing. -static bool isAttributeLateParsed(const IdentifierInfo &II) { +bool Parser::isAttributeLateParsed(const IdentifierInfo &II) { + // Some attributes might only be late parsed when in the experimental + // language mode. + if (getLangOpts().ExperimentalLateParseAttributes) { +#define CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST + bool IsExperimentalLateParseAttr = + llvm::StringSwitch<bool>(normalizeAttrName(II.getName())) +#include "clang/Parse/AttrParserStringSwitches.inc" + .Default(false); +#undef CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST + if (IsExperimentalLateParseAttr) + return true; + } + #define CLANG_ATTR_LATE_PARSED_LIST - return llvm::StringSwitch<bool>(normalizeAttrName(II.getName())) + return llvm::StringSwitch<bool>(normalizeAttrName(II.getName())) #include "clang/Parse/AttrParserStringSwitches.inc" - .Default(false); + .Default(false); #undef CLANG_ATTR_LATE_PARSED_LIST } diff --git a/clang/test/Driver/experimental-late-parse-attributes.c b/clang/test/Driver/experimental-late-parse-attributes.c new file mode 100644 index 00000000000000..6b54b898afa749 --- /dev/null +++ b/clang/test/Driver/experimental-late-parse-attributes.c @@ -0,0 +1,12 @@ +// RUN: %clang %s -c -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON +// RUN: %clang %s -c -fno-experimental-late-parse-attributes -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON + +// CHECK-ON: -cc1 +// CHECK-ON: -fexperimental-late-parse-attributes + +// RUN: %clang %s -c 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF +// RUN: %clang %s -c -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF +// RUN: %clang %s -c -fexperimental-late-parse-attributes -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF + +// CHECK-OFF: -cc1 +// CHECK-OFF-NOT: -fexperimental-late-parse-attributes diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 6c56f99f503df4..0d2dadc2246618 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -1822,28 +1822,106 @@ void WriteSemanticSpellingSwitch(const std::string &VarName, OS << " }\n"; } +enum class LateAttrParseKind { + Never = 0, + Always = 1, + ExperimentalOnly = 2 +}; + +static LateAttrParseKind getLateAttrParseKind(const Record *Attr) { + // This function basically does + // `Attr->getValueAsDef("LateParsed")->getValueAsInt("Mode")` but does + // a bunch of sanity checking to ensure that + // `LateAttrParseMode` in `Attr.td` is in sync with the `LateAttrParseKind` + // enum in this source file. + + static constexpr StringRef LateParsedStr = "LateParsed"; + static constexpr StringRef LateAttrParseKindStr = "LateAttrParseKind"; + static constexpr StringRef KindFieldStr = "Kind"; + + auto *LAPK = Attr->getValueAsDef(LateParsedStr); + + // Typecheck the `LateParsed` field. + SmallVector<Record *, 1> SuperClasses; + LAPK->getDirectSuperClasses(SuperClasses); + if (SuperClasses.size() != 1) + PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) + + "`should only have one super class"); + + if (SuperClasses[0]->getName().compare(LateAttrParseKindStr) != 0) + PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) + + "`should only have type `" + + llvm::Twine(LateAttrParseKindStr) + + "` but found type `" + + SuperClasses[0]->getName() + "`"); + + // Get Kind and verify the enum name matches the name in `Attr.td`. + unsigned Kind = LAPK->getValueAsInt(KindFieldStr); + switch (LateAttrParseKind(Kind)) { +#define CASE(X) \ + case LateAttrParseKind::X: \ + if (LAPK->getName().compare("LateAttrParse" #X) != 0) { \ + PrintFatalError(Attr, \ + "Field `" + llvm::Twine(LateParsedStr) + "` set to `" + \ + LAPK->getName() + \ + "` but this converts to `LateAttrParseKind::" + \ + llvm::Twine(#X) + "`"); \ + } \ + return LateAttrParseKind::X; + + CASE(Never) + CASE(Always) + CASE(ExperimentalOnly) +#undef CASE + } + + // The Kind value is completely invalid + auto KindValueStr = llvm::utostr(Kind); + PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) + "` set to `" + + LAPK->getName() + "` has unexpected `" + + llvm::Twine(KindFieldStr) + "` value of " + + KindValueStr); +} + // Emits the LateParsed property for attributes. -static void emitClangAttrLateParsedList(RecordKeeper &Records, raw_ostream &OS) { - OS << "#if defined(CLANG_ATTR_LATE_PARSED_LIST)\n"; - std::vector<Record*> Attrs = Records.getAllDerivedDefinitions("Attr"); +static void emitClangAttrLateParsedListImpl(RecordKeeper &Records, + raw_ostream &OS, + LateAttrParseKind LateParseMode) { + std::vector<Record *> Attrs = Records.getAllDerivedDefinitions("Attr"); for (const auto *Attr : Attrs) { - bool LateParsed = Attr->getValueAsBit("LateParsed"); + auto LateParsed = getLateAttrParseKind(Attr); - if (LateParsed) { - std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(*Attr); + if (LateParsed != LateParseMode) + continue; - // FIXME: Handle non-GNU attributes - for (const auto &I : Spellings) { - if (I.variety() != "GNU") - continue; - OS << ".Case(\"" << I.name() << "\", " << LateParsed << ")\n"; - } + std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(*Attr); + + // FIXME: Handle non-GNU attributes + for (const auto &I : Spellings) { + if (I.variety() != "GNU") + continue; + OS << ".Case(\"" << I.name() << "\", 1)\n"; } } +} + +static void emitClangAttrLateParsedList(RecordKeeper &Records, + raw_ostream &OS) { + OS << "#if defined(CLANG_ATTR_LATE_PARSED_LIST)\n"; + emitClangAttrLateParsedListImpl(Records, OS, + LateAttrParseKind::Always); OS << "#endif // CLANG_ATTR_LATE_PARSED_LIST\n\n"; } +static void emitClangAttrLateParsedExperimentalList(RecordKeeper &Records, + raw_ostream &OS) { + OS << "#if defined(CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST)\n"; + emitClangAttrLateParsedListImpl( + Records, OS, LateAttrParseKind::ExperimentalOnly); + OS << "#endif // CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST\n\n"; +} + static bool hasGNUorCXX11Spelling(const Record &Attribute) { std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(Attribute); for (const auto &I : Spellings) { @@ -2101,9 +2179,20 @@ bool PragmaClangAttributeSupport::isAttributedSupported( return SpecifiedResult; // Opt-out rules: - // An attribute requires delayed parsing (LateParsed is on) - if (Attribute.getValueAsBit("LateParsed")) + + // An attribute requires delayed parsing (LateParsed is on). + switch (getLateAttrParseKind(&Attribute)) { + case LateAttrParseKind::Never: + break; + case LateAttrParseKind::Always: + return false; + case LateAttrParseKind::ExperimentalOnly: + // This is only late parsed when `LangOpts.ExperimentalLateParseAttributes` + // is true. Unfortunately `LangOpts` is not available in this method so + // just opt this attribute out. return false; + } + // An attribute has no GNU/CXX11 spelling if (!hasGNUorCXX11Spelling(Attribute)) return false; @@ -2885,8 +2974,24 @@ static void emitAttributes(RecordKeeper &Records, raw_ostream &OS, return; } OS << "\n : " << SuperName << "(Ctx, CommonInfo, "; - OS << "attr::" << R.getName() << ", " - << (R.getValueAsBit("LateParsed") ? "true" : "false"); + OS << "attr::" << R.getName() << ", "; + + // Handle different late parsing modes. + OS << "/*IsLateParsed=*/"; + switch (getLateAttrParseKind(&R)) { + case LateAttrParseKind::Never: + OS << "false"; + break; + case LateAttrParseKind::Always: + OS << "true"; + break; + case LateAttrParseKind::ExperimentalOnly: + // Emit code that determines if late parsing is enabled based on + // the LangOpts when the attribute's constructor gets called. + OS << "Ctx.getLangOpts().ExperimentalLateParseAttributes"; + break; + } + if (Inheritable) { OS << ", " << (R.getValueAsBit("InheritEvenIfAlreadyPresent") ? "true" @@ -4843,6 +4948,7 @@ void EmitClangAttrParserStringSwitches(RecordKeeper &Records, raw_ostream &OS) { emitClangAttrAcceptsExprPack(Records, OS); emitClangAttrTypeArgList(Records, OS); emitClangAttrLateParsedList(Records, OS); + emitClangAttrLateParsedExperimentalList(Records, OS); } void EmitClangAttrSubjectMatchRulesParserStringSwitches(RecordKeeper &Records, _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits