diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -56,6 +56,14 @@ : Out(Out), Policy(Policy), Context(Context), Indentation(Indentation), PrintInstantiation(PrintInstantiation) {} + enum class AttrLocation { + Unspecified, + Pragma, + BeforeDecl, + AfterDecl, + Ignore, + }; + void VisitDeclContext(DeclContext *DC, bool Indent = true); void VisitTranslationUnitDecl(TranslationUnitDecl *D); @@ -117,7 +125,9 @@ const TemplateParameterList *Params); void printTemplateArguments(llvm::ArrayRef Args, const TemplateParameterList *Params); - void prettyPrintAttributes(Decl *D); + AttrLocation getAttrLocation(Decl *D, Attr *A); + void prettyPrintAttributes(Decl *D, + AttrLocation AL = AttrLocation::Unspecified); void prettyPrintPragmas(Decl *D); void printDeclType(QualType T, StringRef DeclName, bool Pack = false); }; @@ -234,49 +244,71 @@ return Out; } -void DeclPrinter::prettyPrintAttributes(Decl *D) { - if (Policy.PolishForDeclaration) - return; +DeclPrinter::AttrLocation DeclPrinter::getAttrLocation(Decl *D, Attr *A) { + auto S = A->getSyntax(); + if (S == Attr::AS_Pragma) + return AttrLocation::Pragma; - if (D->hasAttrs()) { - AttrVec &Attrs = D->getAttrs(); - for (auto *A : Attrs) { - if (A->isInherited() || A->isImplicit()) - continue; - switch (A->getKind()) { -#define ATTR(X) -#define PRAGMA_SPELLING_ATTR(X) case attr::X: -#include "clang/Basic/AttrList.inc" - break; - default: - A->printPretty(Out, Policy); - break; - } + if (A->isInherited() || A->isImplicit()) + return AttrLocation::Ignore; + + if (auto *FD = dyn_cast(D)) { + switch (A->getKind()) { + case attr::AsmLabel: + case attr::Final: + case attr::Override: + return AttrLocation::AfterDecl; + + default: + break; + } + + // C2x/C++11-style attributes must appear before the declarator. + if (S == Attr::AS_CXX11 || S == Attr::AS_C2x) + return AttrLocation::BeforeDecl; + + // The __declspec keywords should be placed at the beginning of a simple + // declaration, see + // https://learn.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-170 + if (S == Attr::AS_Declspec) + return AttrLocation::BeforeDecl; + + if (FD->doesThisDeclarationHaveABody()) { + return AttrLocation::BeforeDecl; + } else { + return AttrLocation::AfterDecl; } + } else { + return AttrLocation::Unspecified; } } -void DeclPrinter::prettyPrintPragmas(Decl *D) { +void DeclPrinter::prettyPrintAttributes(Decl *D, AttrLocation AL) { if (Policy.PolishForDeclaration) return; if (D->hasAttrs()) { AttrVec &Attrs = D->getAttrs(); + bool PrintAttr = false; for (auto *A : Attrs) { - switch (A->getKind()) { -#define ATTR(X) -#define PRAGMA_SPELLING_ATTR(X) case attr::X: -#include "clang/Basic/AttrList.inc" - A->printPretty(Out, Policy); + if (getAttrLocation(D, A) != AL) + continue; + + A->printPretty(Out, Policy); + if (AL == AttrLocation::Pragma) Indent(); - break; - default: - break; - } + PrintAttr = true; } + + if (PrintAttr && AL == AttrLocation::BeforeDecl) + Out << " "; } } +void DeclPrinter::prettyPrintPragmas(Decl *D) { + prettyPrintAttributes(D, AttrLocation::Pragma); +} + void DeclPrinter::printDeclType(QualType T, StringRef DeclName, bool Pack) { // Normally, a PackExpansionType is written as T[3]... (for instance, as a // template argument), but if it is the type of a declaration, the ellipsis @@ -613,6 +645,9 @@ printTemplateParameters(D->getTemplateParameterList(I)); } + // [[...]] attributes must be printed before static, inline, virtual, etc. + prettyPrintAttributes(D, AttrLocation::BeforeDecl); + CXXConstructorDecl *CDecl = dyn_cast(D); CXXConversionDecl *ConversionDecl = dyn_cast(D); CXXDeductionGuideDecl *GuideDecl = dyn_cast(D); @@ -774,7 +809,7 @@ Ty.print(Out, Policy, Proto); } - prettyPrintAttributes(D); + prettyPrintAttributes(D, AttrLocation::AfterDecl); if (D->isPure()) Out << " = 0"; diff --git a/clang/test/AST/ast-print-method-decl.cpp b/clang/test/AST/ast-print-method-decl.cpp --- a/clang/test/AST/ast-print-method-decl.cpp +++ b/clang/test/AST/ast-print-method-decl.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -ast-print -triple i386-linux-gnu %s -o - -std=c++20 | FileCheck %s +// RUN: %clang_cc1 -ast-print -triple i386-linux-gnu -fdeclspec %s -o - -std=c++20 | FileCheck %s // CHECK: struct DelegatingCtor1 { struct DelegatingCtor1 { @@ -123,3 +123,51 @@ // CHECK-NEXT: }; }; + + +// CHECK: struct MethodAttr1 { +struct MethodAttr1 { + // CHECK-NEXT: virtual void f1() & = 0; + virtual void f1() & = 0; + + // CHECK-NEXT: virtual void f2(); + virtual void f2(); + + // CHECK-NEXT: }; +}; + + // CHECK-NEXT: struct MethodAttr2 : MethodAttr1 { +struct MethodAttr2 : MethodAttr1 { + // CHECK-NEXT: MethodAttr2() = default; + MethodAttr2() = default; + + // CHECK-NEXT: explicit MethodAttr2(int); + explicit MethodAttr2(int); + + // CHECK-NEXT: void f1() & override; + void f1() & override; + + // CHECK-NEXT: virtual void f2() final; + virtual void f2() final; + + // CHECK-NEXT: static void f3(); + static void f3(); + + // CHECK-NEXT: {{\[\[}}noreturn]] static inline void f4(); + [[noreturn]] static inline void f4(); + + // CHECK-NEXT: void f5() noexcept(10 > 1); + void f5() noexcept(10 > 1); + + // CHECK-NEXT: void f6() asm("f6.2"); + void f6() asm("f6.2"); + + // FIXME: Noreturn attribute is attached to FunctionProtoType and not print + // CHECK-NEXT: void f7(); + void f7() __attribute__((noreturn)); + + // CHECK-NEXT: __declspec(deprecated("bad")) void f8(); + __declspec(deprecated("bad")) void f8(); + + // CHECK-NEXT: }; +}; diff --git a/clang/test/AST/ast-print-no-sanitize.cpp b/clang/test/AST/ast-print-no-sanitize.cpp --- a/clang/test/AST/ast-print-no-sanitize.cpp +++ b/clang/test/AST/ast-print-no-sanitize.cpp @@ -4,4 +4,4 @@ [[clang::no_sanitize_memory]] void should_not_crash_2(); // CHECK: void should_not_crash_1() __attribute__((no_sanitize("memory"))); -// CHECK: void should_not_crash_2() {{\[\[}}clang::no_sanitize("memory"){{\]\]}}; +// CHECK: {{\[\[}}clang::no_sanitize("memory"){{\]\]}} void should_not_crash_2(); diff --git a/clang/test/OpenMP/assumes_codegen.cpp b/clang/test/OpenMP/assumes_codegen.cpp --- a/clang/test/OpenMP/assumes_codegen.cpp +++ b/clang/test/OpenMP/assumes_codegen.cpp @@ -67,41 +67,41 @@ } #pragma omp end assumes -// AST: void foo() __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST: __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) void foo() { // AST-NEXT: } // AST-NEXT: class BAR { // AST-NEXT: public: -// AST-NEXT: BAR() __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) BAR() { // AST-NEXT: } -// AST-NEXT: void bar1() __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) void bar1() { // AST-NEXT: } -// AST-NEXT: static void bar2() __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) static void bar2() { // AST-NEXT: } // AST-NEXT: }; -// AST-NEXT: void bar() __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) void bar() { // AST-NEXT: BAR b; // AST-NEXT: } // AST-NEXT: void baz() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))); // AST-NEXT: template class BAZ { // AST-NEXT: public: -// AST-NEXT: BAZ() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) BAZ() { // AST-NEXT: } -// AST-NEXT: void baz1() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) void baz1() { // AST-NEXT: } -// AST-NEXT: static void baz2() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) static void baz2() { // AST-NEXT: } // AST-NEXT: }; // AST-NEXT: template<> class BAZ { // AST-NEXT: public: -// AST-NEXT: BAZ() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) BAZ() { // AST-NEXT: } // AST-NEXT: void baz1() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))); // AST-NEXT: static void baz2() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))); // AST-NEXT: }; -// AST-NEXT: void baz() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) void baz() { // AST-NEXT: BAZ b; // AST-NEXT: } -// AST-NEXT: int lambda_outer() __attribute__((assume("ompx_lambda_assumption"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) { +// AST-NEXT: __attribute__((assume("ompx_lambda_assumption"))) __attribute__((assume("omp_no_openmp_routines,ompx_another_warning,ompx_after_invalid_clauses"))) __attribute__((assume("omp_no_openmp"))) int lambda_outer() { // AST-NEXT: auto lambda_inner = []() { // AST-NEXT: return 42; // AST-NEXT: }; diff --git a/clang/test/OpenMP/assumes_print.cpp b/clang/test/OpenMP/assumes_print.cpp --- a/clang/test/OpenMP/assumes_print.cpp +++ b/clang/test/OpenMP/assumes_print.cpp @@ -37,8 +37,8 @@ } #pragma omp end assumes -// CHECK: void foo() __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) -// CHECK: void bar() __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) -// CHECK: void baz() __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) +// CHECK: __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) void foo() +// CHECK: __attribute__((assume("ompx_range_bar_only"))) __attribute__((assume("ompx_range_bar_only_2"))) __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) void bar() +// CHECK: __attribute__((assume("ompx_1234"))) __attribute__((assume("omp_no_openmp_routines"))) __attribute__((assume("omp_no_openmp"))) void baz() #endif diff --git a/clang/test/OpenMP/assumes_template_print.cpp b/clang/test/OpenMP/assumes_template_print.cpp --- a/clang/test/OpenMP/assumes_template_print.cpp +++ b/clang/test/OpenMP/assumes_template_print.cpp @@ -17,7 +17,7 @@ struct S { int a; // CHECK: template struct S { -// CHECK: void foo() __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) void foo() { void foo() { #pragma omp parallel {} @@ -25,15 +25,15 @@ }; // CHECK: template<> struct S { -// CHECK: void foo() __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) void foo() { #pragma omp begin assumes no_openmp -// CHECK: void S_with_assumes_no_call() __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) void S_with_assumes_no_call() { void S_with_assumes_no_call() { S s; s.a = 0; } -// CHECK: void S_with_assumes_call() __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) void S_with_assumes_call() { void S_with_assumes_call() { S s; s.a = 0; @@ -42,7 +42,7 @@ } #pragma omp end assumes -// CHECK: void S_without_assumes() __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) void S_without_assumes() { void S_without_assumes() { S s; s.foo(); @@ -54,7 +54,7 @@ template struct P { // CHECK: template struct P { -// CHECK: void foo() __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) void foo() { int a; void foo() { #pragma omp parallel @@ -65,21 +65,21 @@ // TODO: Avoid the duplication here: // CHECK: template<> struct P { -// CHECK: void foo() __attribute__((assume("ompx_global_assumption"))) __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) __attribute__((assume("ompx_global_assumption"))) void foo() { -// CHECK: void P_without_assumes() __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("ompx_global_assumption"))) void P_without_assumes() { void P_without_assumes() { P p; p.foo(); } #pragma omp begin assumes no_openmp -// CHECK: void P_with_assumes_no_call() __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) void P_with_assumes_no_call() { void P_with_assumes_no_call() { P p; p.a = 0; } -// CHECK: void P_with_assumes_call() __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) { +// CHECK: __attribute__((assume("omp_no_openmp"))) __attribute__((assume("ompx_global_assumption"))) void P_with_assumes_call() { void P_with_assumes_call() { P p; p.a = 0; diff --git a/clang/test/OpenMP/declare_simd_ast_print.cpp b/clang/test/OpenMP/declare_simd_ast_print.cpp --- a/clang/test/OpenMP/declare_simd_ast_print.cpp +++ b/clang/test/OpenMP/declare_simd_ast_print.cpp @@ -60,7 +60,7 @@ class VV { // CHECK: #pragma omp declare simd uniform(this, a) linear(val(b): a) - // CHECK-NEXT: int add(int a, int b) __attribute__((cold)) { + // CHECK-NEXT: __attribute__((cold)) int add(int a, int b) { // CHECK-NEXT: return a + b; // CHECK-NEXT: } #pragma omp declare simd uniform(this, a) linear(val(b): a) diff --git a/clang/test/SemaCXX/attr-no-sanitize.cpp b/clang/test/SemaCXX/attr-no-sanitize.cpp --- a/clang/test/SemaCXX/attr-no-sanitize.cpp +++ b/clang/test/SemaCXX/attr-no-sanitize.cpp @@ -16,12 +16,12 @@ // DUMP-LABEL: FunctionDecl {{.*}} f4 // DUMP: NoSanitizeAttr {{.*}} thread -// PRINT: int f4() {{\[\[}}clang::no_sanitize("thread")]] +// PRINT: {{\[\[}}clang::no_sanitize("thread")]] int f4() [[clang::no_sanitize("thread")]] int f4(); // DUMP-LABEL: FunctionDecl {{.*}} f4 // DUMP: NoSanitizeAttr {{.*}} hwaddress -// PRINT: int f4() {{\[\[}}clang::no_sanitize("hwaddress")]] +// PRINT: {{\[\[}}clang::no_sanitize("hwaddress")]] int f4() [[clang::no_sanitize("hwaddress")]] int f4(); // DUMP-LABEL: FunctionDecl {{.*}} f5 @@ -36,5 +36,5 @@ // DUMP-LABEL: FunctionDecl {{.*}} f7 // DUMP: NoSanitizeAttr {{.*}} memtag -// PRINT: int f7() {{\[\[}}clang::no_sanitize("memtag")]] +// PRINT: {{\[\[}}clang::no_sanitize("memtag")]] int f7() [[clang::no_sanitize("memtag")]] int f7(); diff --git a/clang/test/SemaCXX/cxx11-attr-print.cpp b/clang/test/SemaCXX/cxx11-attr-print.cpp --- a/clang/test/SemaCXX/cxx11-attr-print.cpp +++ b/clang/test/SemaCXX/cxx11-attr-print.cpp @@ -42,21 +42,21 @@ // CHECK: int f1() __attribute__((warn_unused_result(""))); int f1() __attribute__((warn_unused_result)); -// CHECK: {{\[}}[clang::warn_unused_result("")]]; +// CHECK: {{\[}}[clang::warn_unused_result("")]] int f2 [[clang::warn_unused_result]] (); -// CHECK: {{\[}}[gnu::warn_unused_result("")]]; +// CHECK: {{\[}}[gnu::warn_unused_result("")]] int f3 [[gnu::warn_unused_result]] (); // FIXME: ast-print need to print C++11 // attribute after function declare-id. -// CHECK: {{\[}}[noreturn]]; +// CHECK: {{\[}}[noreturn]] void f4 [[noreturn]] (); // CHECK: __attribute__((gnu_inline)); inline void f6() __attribute__((gnu_inline)); -// CHECK: {{\[}}[gnu::gnu_inline]]; +// CHECK: {{\[}}[gnu::gnu_inline]] inline void f7 [[gnu::gnu_inline]] (); // arguments printing @@ -66,8 +66,8 @@ // CHECK: int m __attribute__((aligned(4 // CHECK: int n alignas(4 // CHECK: int p alignas(int -// CHECK: static int f() __attribute__((pure)) -// CHECK: static int g() {{\[}}[gnu::pure]] +// CHECK: __attribute__((pure)) static int f() +// CHECK: {{\[}}[gnu::pure]] static int g() template struct S { __attribute__((aligned(4))) int m; alignas(4) int n; @@ -82,8 +82,8 @@ // CHECK: int m __attribute__((aligned(4 // CHECK: int n alignas(4 -// CHECK: static int f() __attribute__((pure)) -// CHECK: static int g() {{\[}}[gnu::pure]] +// CHECK: __attribute__((pure)) static int f() +// CHECK: {{\[}}[gnu::pure]] static int g() template struct S; // CHECK: using Small2 {{\[}}[gnu::mode(byte)]] = int;