Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -225,6 +225,9 @@ - The ``__declspec(naked)`` attribute can no longer be written on a member function in Microsoft compatibility mode, matching the behavior of cl.exe. +- Added the `clang::annotate_type` attribute, which can be used to add + annotations to types (see documentation for details). + Windows Support --------------- Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -804,6 +804,14 @@ let Documentation = [Undocumented]; } +def AnnotateType : TypeAttr { + let Spellings = [CXX11<"clang", "annotate_type">, C2x<"clang", "annotate_type">]; + let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">]; + let HasCustomParsing = 1; + let AcceptsExprPack = 1; + let Documentation = [AnnotateTypeDocs]; +} + def ARMInterrupt : InheritableAttr, TargetSpecificAttr { // NOTE: If you add any additional spellings, M68kInterrupt's, // MSP430Interrupt's, MipsInterrupt's and AnyX86Interrupt's spellings Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -6443,3 +6443,37 @@ The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-groupindex }]; } + +def AnnotateTypeDocs : Documentation { + let Category = DocCatType; + let Heading = "annotate_type"; + let Content = [{ +This attribute is used to add annotations to types, typically for use by static +analysis tools that are not integrated into the core Clang compiler (e.g., +Clang-Tidy checks or out-of-tree Clang-based tools). It is a counterpart to the +`annotate` attribute, which serves the same purpose, but for declarations. + +The attribute takes a mandatory string literal argument specifying the +annotation category and an arbitrary number of optional arguments that provide +additional information specific to the annotation category. The optional +arguments must be constant expressions of arbitrary type. + +For example: + +.. code-block:: c++ + + int* [[clang::annotate("category1", "foo", 1)]] f(int[[clang::annotate("category2")]] *); + +The attribute does not have any effect on the semantics of C++ code, neither +type checking rules, nor runtime semantics. In particular: + +- ``std::is_same Args); + /// ConstantFoldAttrArgs - Folds attribute arguments into ConstantExprs + /// (unless they are value dependent or type dependent). Returns false + /// and emits a diagnostic if one or more of the arguments could not be + /// folded into a constant. + bool ConstantFoldAttrArgs(const AttributeCommonInfo &CI, + MutableArrayRef Args); + /// AddLaunchBoundsAttr - Adds a launch_bounds attribute to a particular /// declaration. void AddLaunchBoundsAttr(Decl *D, const AttributeCommonInfo &CI, Index: clang/lib/AST/TypePrinter.cpp =================================================================== --- clang/lib/AST/TypePrinter.cpp +++ clang/lib/AST/TypePrinter.cpp @@ -1681,6 +1681,15 @@ if (T->getAttrKind() == attr::AddressSpace) return; + if (T->getAttrKind() == attr::AnnotateType) { + // FIXME: Print the attribute arguments once we have a way to retrieve these + // here. For the meantime, we just print `[[clang::annotate_type(...)]]` + // without the arguments so that we know at least that we had _some_ + // annotation on the type. + OS << " [[clang::annotate_type(...)]]"; + return; + } + OS << " __attribute__(("; switch (T->getAttrKind()) { #define TYPE_ATTR(NAME) @@ -1718,6 +1727,7 @@ case attr::UPtr: case attr::AddressSpace: case attr::CmseNSCall: + case attr::AnnotateType: llvm_unreachable("This attribute should have been handled already"); case attr::NSReturnsRetained: Index: clang/lib/Parse/ParseDecl.cpp =================================================================== --- clang/lib/Parse/ParseDecl.cpp +++ clang/lib/Parse/ParseDecl.cpp @@ -3164,10 +3164,23 @@ if (!AttrsLastTime) ProhibitAttributes(attrs); else { - // Reject C++11 attributes that appertain to decl specifiers as - // we don't support any C++11 attributes that appertain to decl - // specifiers. This also conforms to what g++ 4.8 is doing. - ProhibitCXX11Attributes(attrs, diag::err_attribute_not_type_attr); + // Reject C++11 / C2x attributes that aren't type attributes. + for (const ParsedAttr &PA : attrs) { + if (!PA.isCXX11Attribute() && !PA.isC2xAttribute()) + continue; + if (PA.getKind() == ParsedAttr::UnknownAttribute) + // We will warn about the unused attribute elsewhere (in + // SemaDeclAttr.cpp) + continue; + // We reject AT_LifetimeBound and AT_AnyX86NoCfCheck, even though they + // are type attributes, because we historically haven't allowed these + // to be used as type attributes in C++11 / C2x syntax. + if (PA.isTypeAttr() && PA.getKind() != ParsedAttr::AT_LifetimeBound && + PA.getKind() != ParsedAttr::AT_AnyX86NoCfCheck) + continue; + Diag(PA.getLoc(), diag::err_attribute_not_type_attr) << PA; + PA.setInvalid(); + } DS.takeAttributesFrom(attrs); } Index: clang/lib/Sema/SemaAttr.cpp =================================================================== --- clang/lib/Sema/SemaAttr.cpp +++ clang/lib/Sema/SemaAttr.cpp @@ -384,6 +384,54 @@ AlignPackStack.Act(PragmaLoc, Action, SlotLabel, Info); } +bool Sema::ConstantFoldAttrArgs(const AttributeCommonInfo &CI, + MutableArrayRef Args) { + llvm::SmallVector Notes; + for (unsigned Idx = 0; Idx < Args.size(); Idx++) { + Expr *&E = Args.begin()[Idx]; + assert(E && "error are handled before"); + if (E->isValueDependent() || E->isTypeDependent()) + continue; + + // FIXME: Use DefaultFunctionArrayLValueConversion() in place of the logic + // that adds implicit casts here. + if (E->getType()->isArrayType()) + E = ImpCastExprToType(E, Context.getPointerType(E->getType()), + clang::CK_ArrayToPointerDecay) + .get(); + if (E->getType()->isFunctionType()) + E = ImplicitCastExpr::Create(Context, + Context.getPointerType(E->getType()), + clang::CK_FunctionToPointerDecay, E, nullptr, + VK_PRValue, FPOptionsOverride()); + if (E->isLValue()) + E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(), + clang::CK_LValueToRValue, E, nullptr, + VK_PRValue, FPOptionsOverride()); + + Expr::EvalResult Eval; + Notes.clear(); + Eval.Diag = &Notes; + + bool Result = E->EvaluateAsConstantExpr(Eval, Context); + + /// Result means the expression can be folded to a constant. + /// Note.empty() means the expression is a valid constant expression in the + /// current language mode. + if (!Result || !Notes.empty()) { + Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type) + << CI << (Idx + 1) << AANT_ArgumentConstantExpr; + for (auto &Note : Notes) + Diag(Note.first, Note.second); + return false; + } + assert(Eval.Val.hasValue()); + E = ConstantExpr::Create(Context, E, Eval.Val); + } + + return true; +} + void Sema::DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind, SourceLocation IncludeLoc) { if (Kind == PragmaAlignPackDiagnoseKind::NonDefaultStateAtInclude) { Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -4155,48 +4155,10 @@ void Sema::AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI, StringRef Str, MutableArrayRef Args) { auto *Attr = AnnotateAttr::Create(Context, Str, Args.data(), Args.size(), CI); - llvm::SmallVector Notes; - for (unsigned Idx = 0; Idx < Attr->args_size(); Idx++) { - Expr *&E = Attr->args_begin()[Idx]; - assert(E && "error are handled before"); - if (E->isValueDependent() || E->isTypeDependent()) - continue; - - if (E->getType()->isArrayType()) - E = ImpCastExprToType(E, Context.getPointerType(E->getType()), - clang::CK_ArrayToPointerDecay) - .get(); - if (E->getType()->isFunctionType()) - E = ImplicitCastExpr::Create(Context, - Context.getPointerType(E->getType()), - clang::CK_FunctionToPointerDecay, E, nullptr, - VK_PRValue, FPOptionsOverride()); - if (E->isLValue()) - E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(), - clang::CK_LValueToRValue, E, nullptr, - VK_PRValue, FPOptionsOverride()); - - Expr::EvalResult Eval; - Notes.clear(); - Eval.Diag = &Notes; - - bool Result = - E->EvaluateAsConstantExpr(Eval, Context); - - /// Result means the expression can be folded to a constant. - /// Note.empty() means the expression is a valid constant expression in the - /// current language mode. - if (!Result || !Notes.empty()) { - Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type) - << CI << (Idx + 1) << AANT_ArgumentConstantExpr; - for (auto &Note : Notes) - Diag(Note.first, Note.second); - return; - } - assert(Eval.Val.hasValue()); - E = ConstantExpr::Create(Context, E, Eval.Val); - } - D->addAttr(Attr); + if (ConstantFoldAttrArgs( + CI, MutableArrayRef(Attr->args_begin(), Attr->args_end()))) { + D->addAttr(Attr); + } } static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) { Index: clang/lib/Sema/SemaType.cpp =================================================================== --- clang/lib/Sema/SemaType.cpp +++ clang/lib/Sema/SemaType.cpp @@ -8146,6 +8146,34 @@ CurType = T; } +static void HandleAnnotateTypeAttr(TypeProcessingState &State, + QualType &CurType, const ParsedAttr &PA) { + Sema &S = State.getSema(); + + if (PA.getNumArgs() < 1) { + S.Diag(PA.getLoc(), diag::err_attribute_too_few_arguments) << PA << 1; + return; + } + + // Make sure that there is a string literal as the annotation's first + // argument. + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(PA, 0, Str)) + return; + + llvm::SmallVector Args; + Args.reserve(PA.getNumArgs() - 1); + for (unsigned Idx = 1; Idx < PA.getNumArgs(); Idx++) { + assert(!PA.isArgIdent(Idx)); + Args.push_back(PA.getArgAsExpr(Idx)); + } + if (!S.ConstantFoldAttrArgs(PA, Args)) + return; + auto *AnnotateTypeAttr = + AnnotateTypeAttr::Create(S.Context, Str, Args.data(), Args.size(), PA); + CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType); +} + static void HandleLifetimeBoundAttr(TypeProcessingState &State, QualType &CurType, ParsedAttr &Attr) { @@ -8208,10 +8236,11 @@ if (!IsTypeAttr) continue; } - } else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr)) { + } else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr) && + attr.getKind() != ParsedAttr::AT_AnnotateType) { // Otherwise, only consider type processing for a C++11 attribute if // it's actually been applied to a type. - // We also allow C++11 address_space and + // We also allow C++11 address_space and annotate_type and // OpenCL language address space attributes to pass through. continue; } @@ -8409,6 +8438,11 @@ attr.setUsedAsTypeAttr(); break; } + case ParsedAttr::AT_AnnotateType: { + HandleAnnotateTypeAttr(state, type, attr); + attr.setUsedAsTypeAttr(); + break; + } } // Handle attributes that are defined in a macro. We do not want this to be Index: clang/test/AST/attr-annotate-type.c =================================================================== --- /dev/null +++ clang/test/AST/attr-annotate-type.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 %s -ast-dump -fdouble-square-bracket-attributes | FileCheck %s + +// Verify that we print the [[clang::annotate_type]] attribute. +// FIXME: The arguments are currently not printed -- see also comments in +// TypePrinter.cpp. + +// Need to escape the `[[` as a regex to avoid it being interpreted as a +// substitution block. +// CHECK: VarDecl {{.*}} x1 'int {{\[\[}}clang::annotate_type(...){{]]}}':'int' +int [[clang::annotate_type("bar")]] x1; +// CHECK: VarDecl {{.*}} x2 'int * {{\[\[}}clang::annotate_type(...){{]]}}':'int *' +int *[[clang::annotate_type("bar")]] x2; Index: clang/test/CodeGenCXX/annotate-type.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCXX/annotate-type.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm-only %s -emit-llvm -o - | FileCheck %s + +// Test that `annotate_type` does not affect mangled names. + +int *[[clang::annotate_type("foo")]] f(int *[[clang::annotate_type("foo")]], + int [[clang::annotate_type("foo")]]) { + return nullptr; +} +// CHECK: @_Z1fPii + +template struct S {}; + +S +g(S) { + return {}; +} +// CHECK: @_Z1g1SIPiE Index: clang/test/Sema/annotate-type.c =================================================================== --- /dev/null +++ clang/test/Sema/annotate-type.c @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 %s -fsyntax-only -fdouble-square-bracket-attributes -verify + +const char *some_function(); + +void foo(float *[[clang::annotate_type("foo")]] a) { + int [[clang::annotate_type("bar")]] x1; + int *[[clang::annotate_type("bar")]] x2; + int *[[clang::annotate_type("bar", 1)]] x3; + int *[[clang::annotate_type("bar", 1 + 2)]] x4; + struct {} [[clang::annotate_type("foo")]] x5; + int [[clang::annotate_type("int")]] *[[clang::annotate_type("ptr")]] array[10] [[clang::annotate_type("arr")]]; + + typedef int [[clang::annotate_type("bar")]] my_typedef; + + // GNU spelling is not supported + int __attribute__((annotate_type("bar"))) y1; // expected-warning {{unknown attribute 'annotate_type' ignored}} + int *__attribute__((annotate_type("bar"))) y2; // expected-warning {{unknown attribute 'annotate_type' ignored}} + + // Various error cases + // FIXME: We would want to prohibit the attribute on the following two lines. + // However, Clang currently generally doesn't prohibit type-only C++11 + // attributes on declarations. This should be fixed more generally. + [[clang::annotate_type("bar")]] int *z1; + int *z2 [[clang::annotate_type("bar")]]; + [[clang::annotate_type("bar")]]; // expected-error {{'annotate_type' attribute cannot be applied to a statement}} + int *[[clang::annotate_type(1)]] z3; // expected-error {{'annotate_type' attribute requires a string}} + int *[[clang::annotate_type()]] z4; // expected-error {{'annotate_type' attribute takes at least 1 argument}} + int *[[clang::annotate_type]] z5; // expected-error {{'annotate_type' attribute takes at least 1 argument}} + int *[[clang::annotate_type(some_function())]] z6; // expected-error {{'annotate_type' attribute requires a string}} + int *[[clang::annotate_type("bar", some_function())]] z7; // expected-error {{'annotate_type' attribute requires parameter 1 to be a constant expression}} expected-note{{subexpression not valid in a constant expression}} + int *[[clang::annotate_type("bar", z7)]] z8; // expected-error {{'annotate_type' attribute requires parameter 1 to be a constant expression}} expected-note{{subexpression not valid in a constant expression}} + int *[[clang::annotate_type("bar", int)]] z9; // expected-error {{expected expression}} +} +// More error cases: Prohibit adding the attribute to declarations. +// Different declarations hit different code paths, so they need separate tests. +// FIXME: Clang currently generally doesn't prohibit type-only C++11 +// attributes on declarations. +[[clang::annotate_type("bar")]] int *global; +void annotated_function([[clang::annotate_type("bar")]] int); +void g([[clang::annotate_type("bar")]] int); +struct [[clang::annotate_type("foo")]] S; +struct [[clang::annotate_type("foo")]] S{ + [[clang::annotate_type("foo")]] int member; + [[clang::annotate_type("foo")]] union { + int i; + float f; + }; +}; Index: clang/test/SemaCXX/annotate-type.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/annotate-type.cpp @@ -0,0 +1,70 @@ +// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -fcxx-exceptions -verify + +struct S1 { + void f() [[clang::annotate_type("foo")]]; + // FIXME: We would want to prohibit the attribute in the following location. + // However, Clang currently generally doesn't prohibit type-only C++11 + // attributes on declarations. This should be fixed more generally. + [[clang::annotate_type("foo")]] void g(); +}; + +template struct is_same { + static constexpr bool value = false; +}; + +template struct is_same { + static constexpr bool value = true; +}; + +static_assert(is_same::value); +static_assert(is_same::value); +static_assert(is_same::value); + +// Cannot overload on types that only differ by `annotate_type` attribute. +void f(int) {} // expected-note {{previous definition is here}} +void f(int [[clang::annotate_type("foo")]]) {} // expected-error {{redefinition of 'f'}} + +// Cannot specialize on types that only differ by `annotate_type` attribute. +template struct S2 {}; + +template <> struct S2 {}; // expected-note {{previous definition is here}} + +template <> +struct S2 {}; // expected-error {{redefinition of 'S2'}} + +// Test that the attribute supports parameter pack expansion. +template void variadic_func_template() { + int [[clang::annotate_type("foo", Is...)]] val; +} +int f2() { variadic_func_template<1, 2, 3>(); } + +// Make sure we correctly diagnose wrong number of arguments for +// [[clang::annotate_type]] inside a template argument. +template void func_template(); +void f3() { + func_template(); // expected-error {{'annotate_type' attribute takes at least 1 argument}} +} + +// More error cases: Prohibit adding the attribute to declarations. +// Different declarations hit different code paths, so they need separate tests. +// FIXME: Clang currently generally doesn't prohibit type-only C++11 +// attributes on declarations. +namespace [[clang::annotate_type("foo")]] my_namespace {} +struct [[clang::annotate_type("foo")]] S3; +struct [[clang::annotate_type("foo")]] S3{ + [[clang::annotate_type("foo")]] int member; +}; +void f4() { + for ([[clang::annotate_type("foo")]] int i = 0; i < 42; ++i) {} + for (; [[clang::annotate_type("foo")]] bool b = false;) {} + while ([[clang::annotate_type("foo")]] bool b = false) {} + if ([[clang::annotate_type("foo")]] bool b = false) {} + try { + } catch ([[clang::annotate_type("foo")]] int i) { + } +} +template +[[clang::annotate_type("foo")]] T var_template; +[[clang::annotate_type("foo")]] extern "C" int extern_c_func(); +extern "C" [[clang::annotate_type("foo")]] int extern_c_func(); Index: clang/unittests/AST/AttrTest.cpp =================================================================== --- clang/unittests/AST/AttrTest.cpp +++ clang/unittests/AST/AttrTest.cpp @@ -7,7 +7,10 @@ //===----------------------------------------------------------------------===// #include "clang/AST/Attr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/AttrKinds.h" +#include "clang/Tooling/Tooling.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -15,10 +18,154 @@ namespace { +using clang::ast_matchers::constantExpr; +using clang::ast_matchers::equals; +using clang::ast_matchers::functionDecl; +using clang::ast_matchers::has; +using clang::ast_matchers::hasDescendant; +using clang::ast_matchers::hasName; +using clang::ast_matchers::integerLiteral; +using clang::ast_matchers::match; +using clang::ast_matchers::selectFirst; +using clang::ast_matchers::stringLiteral; +using clang::ast_matchers::varDecl; +using clang::tooling::buildASTFromCode; +using clang::tooling::buildASTFromCodeWithArgs; + TEST(Attr, Doc) { EXPECT_THAT(Attr::getDocumentation(attr::Used).str(), testing::HasSubstr("The compiler must emit the definition even " "if it appears to be unused")); } +const FunctionDecl *getFunctionNode(ASTUnit *AST, const std::string &Name) { + auto Result = + match(functionDecl(hasName(Name)).bind("fn"), AST->getASTContext()); + EXPECT_EQ(Result.size(), 1u); + return Result[0].getNodeAs("fn"); +} + +const VarDecl *getVariableNode(ASTUnit *AST, const std::string &Name) { + auto Result = match(varDecl(hasName(Name)).bind("var"), AST->getASTContext()); + EXPECT_EQ(Result.size(), 1u); + return Result[0].getNodeAs("var"); +} + +template +void AssertAnnotatedAs(TypeLoc TL, llvm::StringRef annotation, + ModifiedTypeLoc &ModifiedTL, + const AnnotateTypeAttr **AnnotateOut = nullptr) { + const auto AttributedTL = TL.getAs(); + ASSERT_FALSE(AttributedTL.isNull()); + ModifiedTL = AttributedTL.getModifiedLoc().getAs(); + ASSERT_TRUE(ModifiedTL); + + ASSERT_NE(AttributedTL.getAttr(), nullptr); + const auto *Annotate = dyn_cast(AttributedTL.getAttr()); + ASSERT_NE(Annotate, nullptr); + EXPECT_EQ(Annotate->getAnnotation(), annotation); + if (AnnotateOut) { + *AnnotateOut = Annotate; + } +} + +TEST(Attr, AnnotateType) { + + // Test that the AnnotateType attribute shows up in the right places and that + // it stores its arguments correctly. + + auto AST = buildASTFromCode(R"cpp( + void f(int* [[clang::annotate_type("foo", "arg1", 2)]] *, + int [[clang::annotate_type("bar")]]); + + int [[clang::annotate_type("int")]] * [[clang::annotate_type("ptr")]] + array[10] [[clang::annotate_type("arr")]]; + + void (* [[clang::annotate_type("funcptr")]] fp)(void); + + struct S { int mem; }; + int [[clang::annotate_type("int")]] + S::* [[clang::annotate_type("ptr_to_mem")]] ptr_to_member = &S::mem; + )cpp"); + + { + const FunctionDecl *Func = getFunctionNode(AST.get(), "f"); + + // First parameter. + const auto PointerTL = Func->getParamDecl(0) + ->getTypeSourceInfo() + ->getTypeLoc() + .getAs(); + ASSERT_FALSE(PointerTL.isNull()); + PointerTypeLoc PointerPointerTL; + const AnnotateTypeAttr *Annotate; + AssertAnnotatedAs(PointerTL.getPointeeLoc(), "foo", PointerPointerTL, + &Annotate); + + EXPECT_EQ(Annotate->args_size(), 2); + const auto *StringLit = selectFirst( + "str", match(constantExpr(hasDescendant(stringLiteral().bind("str"))), + *Annotate->args_begin()[0], AST->getASTContext())); + ASSERT_NE(StringLit, nullptr); + EXPECT_EQ(StringLit->getString(), "arg1"); + EXPECT_EQ(match(constantExpr(has(integerLiteral(equals(2)).bind("int"))), + *Annotate->args_begin()[1], AST->getASTContext()) + .size(), + 1); + + // Second parameter. + BuiltinTypeLoc IntTL; + AssertAnnotatedAs(Func->getParamDecl(1)->getTypeSourceInfo()->getTypeLoc(), + "bar", IntTL); + EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy); + } + + { + const VarDecl *Var = getVariableNode(AST.get(), "array"); + + ArrayTypeLoc ArrayTL; + AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "arr", ArrayTL); + PointerTypeLoc PointerTL; + AssertAnnotatedAs(ArrayTL.getElementLoc(), "ptr", PointerTL); + BuiltinTypeLoc IntTL; + AssertAnnotatedAs(PointerTL.getPointeeLoc(), "int", IntTL); + EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy); + } + + { + const VarDecl *Var = getVariableNode(AST.get(), "fp"); + + PointerTypeLoc PointerTL; + AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "funcptr", + PointerTL); + ASSERT_TRUE( + PointerTL.getPointeeLoc().IgnoreParens().getAs()); + } + + { + const VarDecl *Var = getVariableNode(AST.get(), "ptr_to_member"); + + MemberPointerTypeLoc MemberPointerTL; + AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "ptr_to_mem", + MemberPointerTL); + BuiltinTypeLoc IntTL; + AssertAnnotatedAs(MemberPointerTL.getPointeeLoc(), "int", IntTL); + EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy); + } + + // Test type annotation on an `__auto_type` type in C mode. + AST = buildASTFromCodeWithArgs(R"c( + __auto_type [[clang::annotate_type("auto")]] auto_var = 1; + )c", + {"-fdouble-square-bracket-attributes"}, + "input.c"); + + { + const VarDecl *Var = getVariableNode(AST.get(), "auto_var"); + + AutoTypeLoc AutoTL; + AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "auto", AutoTL); + } +} + } // namespace