george.burgess.iv created this revision. Herald added a subscriber: javed.absar.
Attributes may gain features or added flexibility over time. This patch aims to add a simple and uniform way to directly add/query for arbitrary changes in attributes, instead of having to rely on other information (e.g. version numbers, the existence of other attributes added at around the same time as the feature you're interested in, ...). The only user of this at the moment will be https://reviews.llvm.org/D32332, so I won't tag people for review here until that lands. Better words than "enhancement" are welcome; I tried things like `__has_attribute_extension` and `__has_attribute_feature`, but we also have both `__has_extension` and `__has_feature`, so... :) https://reviews.llvm.org/D33904 Files: docs/LanguageExtensions.rst include/clang/Basic/Attr.td include/clang/Basic/Attributes.h include/clang/Lex/Preprocessor.h lib/Basic/Attributes.cpp lib/Lex/PPMacroExpansion.cpp test/Preprocessor/has_attribute.c utils/TableGen/ClangAttrEmitter.cpp
Index: utils/TableGen/ClangAttrEmitter.cpp =================================================================== --- utils/TableGen/ClangAttrEmitter.cpp +++ utils/TableGen/ClangAttrEmitter.cpp @@ -2664,6 +2664,7 @@ static void GenerateHasAttrSpellingStringSwitch( const std::vector<Record *> &Attrs, raw_ostream &OS, const std::string &Variety = "", const std::string &Scope = "") { + const StringRef CaseIndent = " "; for (const auto *Attr : Attrs) { // C++11-style attributes have specific version information associated with // them. If the attribute has no scope, the version information must not @@ -2700,15 +2701,35 @@ // present in the caller. Test = "LangOpts.CPlusPlus11"; + // Any enhancements must be checked for, as well. + if (!Test.empty()) + Test += " && "; + + std::vector<StringRef> Enhancements = + Attr->getValueAsListOfStrings("Enhancements"); + if (Enhancements.empty()) { + Test += "!Enhancement"; + } else { + Test += "(!Enhancement || llvm::StringSwitch<bool>(EnhancementName)"; + + std::string Preamble = ("\n" + CaseIndent + CaseIndent).str(); + for (const auto &Enhancement : Enhancements) { + assert(Enhancement.find('"') == std::string::npos && + "Quotes aren't allowed in enhancements!"); + Test += (Preamble + ".Case(\"" + Enhancement + "\", true)").str(); + } + Test += Preamble + ".Default(false))"; + } + std::string TestStr = !Test.empty() ? Test + " ? " + llvm::itostr(Version) + " : 0" : "1"; std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(*Attr); for (const auto &S : Spellings) if (Variety.empty() || (Variety == S.variety() && (Scope.empty() || Scope == S.nameSpace()))) - OS << " .Case(\"" << S.name() << "\", " << TestStr << ")\n"; + OS << CaseIndent << ".Case(\"" << S.name() << "\", " << TestStr << ")\n"; } - OS << " .Default(0);\n"; + OS << CaseIndent << ".Default(0);\n"; } // Emits the list of spellings for attributes. Index: test/Preprocessor/has_attribute.c =================================================================== --- test/Preprocessor/has_attribute.c +++ test/Preprocessor/has_attribute.c @@ -56,3 +56,59 @@ #if __has_cpp_attribute(selectany) // expected-error {{function-like macro '__has_cpp_attribute' is not defined}} #endif + +#if !defined(__has_attribute_enhancement) +#error "No extended has_attribute support?" +#endif + +#if __has_attribute_enhancement(attr_does_not_exist) // expected-error{{too few arguments}} +#error "We have an enhancement on an attr that doesn't exist?" +#endif + +#if __has_attribute_enhancement(attr_does_not_exist thing) // expected-error{{missing ',' after 'attr_does_not_exist'}} +#error "We have an enhancement on an attr that doesn't exist?" +#endif + +#if __has_attribute_enhancement(attr_does_not_exist, ____) +#error "We have an enhancement on an attr that doesn't exist?" +#endif + +#if __has_attribute_enhancement(always_inline, ____) +#error "Empty enhancement exists on always_inline?" +#endif + +#if __has_attribute_enhancement(always_inline, enhancement_does_not_exist) +#error "Enhancement checks on enhancementless attributes seem broken" +#endif + +#if !__has_attribute(overloadable) +#error "The following tests depend on having overloadable" +#endif + +#if __has_attribute_enhancement(overloadable,) // expected-error{{expected identifier}} +#error "Should assume false" +#endif + +#if __has_attribute_enhancement(overloadable, not a valid enhancement) // expected-error{{missing ')' after 'not'}} expected-note{{to match this}} +#error "Should assume false" +#endif + +#if __has_attribute_enhancement(overloadable, not_an_existing_enhancement) +#error "Should assume false" +#endif + +#if __has_attribute_enhancement(overloadable, __not_an_existing_enhancement__) +#error "Should assume false" +#endif + +#if !__has_attribute_enhancement(overloadable, unmarked_overloads) +#error "Unmarked overloads are supported" +#endif + +#if __has_attribute_enhancement(overloadable, __unmarked_overloads_) +#error "Underscores should be all-or-nothing" +#endif + +#if !__has_attribute_enhancement(overloadable, __unmarked_overloads__) +#error "Unmarked overloads are supported" +#endif Index: lib/Lex/PPMacroExpansion.cpp =================================================================== --- lib/Lex/PPMacroExpansion.cpp +++ lib/Lex/PPMacroExpansion.cpp @@ -351,9 +351,9 @@ Ident__has_cpp_attribute = nullptr; // GCC Extensions. - Ident__BASE_FILE__ = RegisterBuiltinMacro(*this, "__BASE_FILE__"); + Ident__BASE_FILE__ = RegisterBuiltinMacro(*this, "__BASE_FILE__"); Ident__INCLUDE_LEVEL__ = RegisterBuiltinMacro(*this, "__INCLUDE_LEVEL__"); - Ident__TIMESTAMP__ = RegisterBuiltinMacro(*this, "__TIMESTAMP__"); + Ident__TIMESTAMP__ = RegisterBuiltinMacro(*this, "__TIMESTAMP__"); // Microsoft Extensions. if (LangOpts.MicrosoftExt) { @@ -365,15 +365,17 @@ } // Clang Extensions. - Ident__has_feature = RegisterBuiltinMacro(*this, "__has_feature"); - Ident__has_extension = RegisterBuiltinMacro(*this, "__has_extension"); - Ident__has_builtin = RegisterBuiltinMacro(*this, "__has_builtin"); - Ident__has_attribute = RegisterBuiltinMacro(*this, "__has_attribute"); + Ident__has_feature = RegisterBuiltinMacro(*this, "__has_feature"); + Ident__has_extension = RegisterBuiltinMacro(*this, "__has_extension"); + Ident__has_builtin = RegisterBuiltinMacro(*this, "__has_builtin"); + Ident__has_attribute = RegisterBuiltinMacro(*this, "__has_attribute"); + Ident__has_attribute_enhancement = + RegisterBuiltinMacro(*this, "__has_attribute_enhancement"); Ident__has_declspec = RegisterBuiltinMacro(*this, "__has_declspec_attribute"); - Ident__has_include = RegisterBuiltinMacro(*this, "__has_include"); + Ident__has_include = RegisterBuiltinMacro(*this, "__has_include"); Ident__has_include_next = RegisterBuiltinMacro(*this, "__has_include_next"); - Ident__has_warning = RegisterBuiltinMacro(*this, "__has_warning"); - Ident__is_identifier = RegisterBuiltinMacro(*this, "__is_identifier"); + Ident__has_warning = RegisterBuiltinMacro(*this, "__has_warning"); + Ident__is_identifier = RegisterBuiltinMacro(*this, "__is_identifier"); // Modules. Ident__building_module = RegisterBuiltinMacro(*this, "__building_module"); @@ -1470,14 +1472,15 @@ return EvaluateHasIncludeCommon(Tok, II, PP, Lookup, LookupFromFile); } -/// \brief Process single-argument builtin feature-like macros that return -/// integer values. +/// \brief Process builtin feature-like macros that return integer values. Any +/// multi-arg handling should be done from `Op`. static void EvaluateFeatureLikeBuiltinMacro(llvm::raw_svector_ostream& OS, Token &Tok, IdentifierInfo *II, Preprocessor &PP, llvm::function_ref< int(Token &Tok, - bool &HasLexedNextTok)> Op) { + bool &HasLexedNextTok, + bool &SuppressDiags)> Op) { // Parse the initial '('. PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::l_paren)) { @@ -1550,7 +1553,9 @@ break; bool HasLexedNextToken = false; - Result = Op(Tok, HasLexedNextToken); + bool NoMoreDiags = false; + Result = Op(Tok, HasLexedNextToken, NoMoreDiags); + SuppressDiagnostic |= NoMoreDiags; ResultTok = Tok; if (HasLexedNextToken) goto already_lexed; @@ -1721,21 +1726,21 @@ Tok.setKind(tok::numeric_constant); } else if (II == Ident__has_feature) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); return II && HasFeature(*this, II->getName()); }); } else if (II == Ident__has_extension) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); return II && HasExtension(*this, II->getName()); }); } else if (II == Ident__has_builtin) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); if (!II) @@ -1753,28 +1758,66 @@ }); } else if (II == Ident__is_identifier) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [](Token &Tok, bool &HasLexedNextToken) -> int { + [](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { return Tok.is(tok::identifier); }); } else if (II == Ident__has_attribute) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); return II ? hasAttribute(AttrSyntax::GNU, nullptr, II, getTargetInfo(), getLangOpts()) : 0; }); + } else if (II == Ident__has_attribute_enhancement) { + EvaluateFeatureLikeBuiltinMacro( + OS, Tok, II, *this, + [this](Token &Tok, bool &HasLexedNextToken, + bool &SuppressDiags) -> int { + IdentifierInfo *AttrName = ExpectFeatureIdentifierInfo( + Tok, *this, diag::err_feature_check_malformed); + + if (!AttrName) + return 0; + + IdentifierInfo *EnhancementName = nullptr; + SourceLocation InitialIdentLoc = Tok.getLocation(); + LexUnexpandedToken(Tok); + if (Tok.isNot(tok::comma)) { + if (Tok.is(tok::r_paren)) + Diag(Tok.getLocation(), diag::err_too_few_args_in_macro_invoc); + else + Diag(Tok.getLocation(), diag::err_pp_expected_after) + << AttrName << tok::comma << InitialIdentLoc; + HasLexedNextToken = true; + SuppressDiags = true; + return 0; + } + + LexUnexpandedToken(Tok); + EnhancementName = ExpectFeatureIdentifierInfo( + Tok, *this, diag::err_pp_expected_ident_in_arg_list); + // If the enhancement name is invalid, pretend we don't have it. + if (!EnhancementName) { + SuppressDiags = true; + HasLexedNextToken = true; + return 0; + } + + return hasAttribute(AttrSyntax::GNU, nullptr, AttrName, + getTargetInfo(), getLangOpts(), EnhancementName); + }); } else if (II == Ident__has_declspec) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); return II ? hasAttribute(AttrSyntax::Declspec, nullptr, II, getTargetInfo(), getLangOpts()) : 0; }); } else if (II == Ident__has_cpp_attribute) { EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *ScopeII = nullptr; IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); @@ -1814,7 +1857,7 @@ } else if (II == Ident__has_warning) { // The argument should be a parenthesized string literal. EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { std::string WarningName; SourceLocation StrStartLoc = Tok.getLocation(); @@ -1845,7 +1888,7 @@ // builtin evaluates to 1 when that identifier names the module we are // currently building. EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, - [this](Token &Tok, bool &HasLexedNextToken) -> int { + [this](Token &Tok, bool &HasLexedNextToken, bool &SuppressDiags) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_expected_id_building_module); return getLangOpts().isCompilingModule() && II && Index: lib/Basic/Attributes.cpp =================================================================== --- lib/Basic/Attributes.cpp +++ lib/Basic/Attributes.cpp @@ -4,13 +4,20 @@ #include "llvm/ADT/StringSwitch.h" using namespace clang; +static StringRef stripSurroundingUnderscores(StringRef R) { + if (R.size() >= 4 && R.startswith("__") && R.endswith("__")) + return R.drop_front(2).drop_back(2); + return R; +} + int clang::hasAttribute(AttrSyntax Syntax, const IdentifierInfo *Scope, const IdentifierInfo *Attr, const TargetInfo &Target, - const LangOptions &LangOpts) { - StringRef Name = Attr->getName(); - // Normalize the attribute name, __foo__ becomes foo. - if (Name.size() >= 4 && Name.startswith("__") && Name.endswith("__")) - Name = Name.substr(2, Name.size() - 4); + const LangOptions &LangOpts, + const IdentifierInfo *Enhancement) { + StringRef Name = stripSurroundingUnderscores(Attr->getName()); + StringRef EnhancementName; + if (Enhancement) + EnhancementName = stripSurroundingUnderscores(Enhancement->getName()); #include "clang/Basic/AttrHasAttributeImpl.inc" Index: include/clang/Lex/Preprocessor.h =================================================================== --- include/clang/Lex/Preprocessor.h +++ include/clang/Lex/Preprocessor.h @@ -121,27 +121,29 @@ llvm::BumpPtrAllocator BP; /// Identifiers for builtin macros and other builtins. - IdentifierInfo *Ident__LINE__, *Ident__FILE__; // __LINE__, __FILE__ - IdentifierInfo *Ident__DATE__, *Ident__TIME__; // __DATE__, __TIME__ - IdentifierInfo *Ident__INCLUDE_LEVEL__; // __INCLUDE_LEVEL__ - IdentifierInfo *Ident__BASE_FILE__; // __BASE_FILE__ - IdentifierInfo *Ident__TIMESTAMP__; // __TIMESTAMP__ - IdentifierInfo *Ident__COUNTER__; // __COUNTER__ - IdentifierInfo *Ident_Pragma, *Ident__pragma; // _Pragma, __pragma - IdentifierInfo *Ident__identifier; // __identifier - IdentifierInfo *Ident__VA_ARGS__; // __VA_ARGS__ - IdentifierInfo *Ident__has_feature; // __has_feature - IdentifierInfo *Ident__has_extension; // __has_extension - IdentifierInfo *Ident__has_builtin; // __has_builtin - IdentifierInfo *Ident__has_attribute; // __has_attribute - IdentifierInfo *Ident__has_include; // __has_include - IdentifierInfo *Ident__has_include_next; // __has_include_next - IdentifierInfo *Ident__has_warning; // __has_warning - IdentifierInfo *Ident__is_identifier; // __is_identifier - IdentifierInfo *Ident__building_module; // __building_module - IdentifierInfo *Ident__MODULE__; // __MODULE__ - IdentifierInfo *Ident__has_cpp_attribute; // __has_cpp_attribute - IdentifierInfo *Ident__has_declspec; // __has_declspec_attribute + IdentifierInfo *Ident__LINE__, *Ident__FILE__; // __LINE__, __FILE__ + IdentifierInfo *Ident__DATE__, *Ident__TIME__; // __DATE__, __TIME__ + IdentifierInfo *Ident__INCLUDE_LEVEL__; // __INCLUDE_LEVEL__ + IdentifierInfo *Ident__BASE_FILE__; // __BASE_FILE__ + IdentifierInfo *Ident__TIMESTAMP__; // __TIMESTAMP__ + IdentifierInfo *Ident__COUNTER__; // __COUNTER__ + IdentifierInfo *Ident_Pragma, *Ident__pragma; // _Pragma, __pragma + IdentifierInfo *Ident__identifier; // __identifier + IdentifierInfo *Ident__VA_ARGS__; // __VA_ARGS__ + IdentifierInfo *Ident__has_feature; // __has_feature + IdentifierInfo *Ident__has_extension; // __has_extension + IdentifierInfo *Ident__has_builtin; // __has_builtin + IdentifierInfo *Ident__has_attribute; // __has_attribute + IdentifierInfo + *Ident__has_attribute_enhancement; // __has_attribute_enhancement + IdentifierInfo *Ident__has_include; // __has_include + IdentifierInfo *Ident__has_include_next; // __has_include_next + IdentifierInfo *Ident__has_warning; // __has_warning + IdentifierInfo *Ident__is_identifier; // __is_identifier + IdentifierInfo *Ident__building_module; // __building_module + IdentifierInfo *Ident__MODULE__; // __MODULE__ + IdentifierInfo *Ident__has_cpp_attribute; // __has_cpp_attribute + IdentifierInfo *Ident__has_declspec; // __has_declspec_attribute SourceLocation DATELoc, TIMELoc; unsigned CounterValue; // Next __COUNTER__ value. Index: include/clang/Basic/Attributes.h =================================================================== --- include/clang/Basic/Attributes.h +++ include/clang/Basic/Attributes.h @@ -34,7 +34,8 @@ /// recognize and implement the attribute specified by the given information. int hasAttribute(AttrSyntax Syntax, const IdentifierInfo *Scope, const IdentifierInfo *Attr, const TargetInfo &Target, - const LangOptions &LangOpts); + const LangOptions &LangOpts, + const IdentifierInfo *Enhancement = nullptr); } // end namespace clang Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -435,6 +435,10 @@ // attribute may be documented under multiple categories, more than one // Documentation entry may be listed. list<Documentation> Documentation; + // Any features that this attribute supports that were added since its + // introduction. These can be queried via the __has_attribute_enhancement + // builtin macro. + list<string> Enhancements = []; } /// A type attribute is not processed on a declaration or a statement. @@ -1581,6 +1585,7 @@ let Spellings = [GNU<"overloadable">]; let Subjects = SubjectList<[Function], ErrorDiag>; let Documentation = [OverloadableDocs]; + let Enhancements = ["unmarked_overloads"]; } def Override : InheritableAttr { Index: docs/LanguageExtensions.rst =================================================================== --- docs/LanguageExtensions.rst +++ docs/LanguageExtensions.rst @@ -164,6 +164,35 @@ (double underscore) to avoid interference from a macro with the same name. For instance, ``__always_inline__`` can be used instead of ``always_inline``. +``__has_attribute_enhancement`` +------------------------------- + +This function-like macro takes two arguments: the name of a GNU-style attribute, +and the name of an enhancement that was made to the attribute since its +introduction. It evaluates to 1 if clang supports this enhancement, and 0 if +not. + +For example, clang's ``overloadable`` attribute has existed since before Clang +3.5, but in Clang 5.0 it gained was modified to support so-called "unmarked +overloads". One can use ``__has_attribute_enhancement`` to query whether clang +supports this upgraded version of ``overloadable`` like so: + +.. code-block:: c++ + + #ifndef __has_attribute_enhancement // Optional. + #define __has_attribute_enhancement(x, y) 0 // Compatibility with non-clang + // compilers. + #endif + + #if !__has_attribute_enhancement(overloadable, unmarked_overloads) + #error "This library requires unmarked overload support." + #endif + +Since enhancements are attribute-specific, please consult the documentation to +see what enhancements each attribute has, if any. + +Much like in ``__has_attribute``, if either the first or second argument to +this macro has a leading and trailing ``__``, clang will ignore them. ``__has_declspec_attribute`` ----------------------------
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits