diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 3f8b72cdc00..0cf01c6dba4 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1582,6 +1582,10 @@ fdiagnostics-show-template-tree C++ ObjC++ Var(flag_diagnostics_show_template_tree) Init(0) Print hierarchical comparisons when template types are mismatched. +fdiagnostics-use-aliases +C++ Var(flag_diagnostics_use_aliases) Init(1) +Replace identifiers or scope names in diagnostics as defined by the diagnose_as attribute. + fdirectives-only C ObjC C++ ObjC++ Preprocess directives only. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index cb254e0adea..139ef8f1c0d 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -5858,7 +5858,9 @@ enum auto_deduction_context identical to their defaults. TFF_NO_TEMPLATE_BINDINGS: do not print information about the template arguments for a function template specialization. - TFF_POINTER: we are printing a pointer type. */ + TFF_POINTER: we are printing a pointer type. + TFF_AS_PRIMARY: treat as primary template even if printing a specialized + type. */ #define TFF_PLAIN_IDENTIFIER (0) #define TFF_SCOPE (1) @@ -5876,6 +5878,7 @@ enum auto_deduction_context #define TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS (1 << 12) #define TFF_NO_TEMPLATE_BINDINGS (1 << 13) #define TFF_POINTER (1 << 14) +#define TFF_AS_PRIMARY (1 << 15) /* These constants can be used as bit flags to control strip_typedefs. diff --git a/gcc/cp/error.c b/gcc/cp/error.c index b0836d83888..1950ffb8ab6 100644 --- a/gcc/cp/error.c +++ b/gcc/cp/error.c @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #include "internal-fn.h" #include "gcc-rich-location.h" #include "cp-name-hint.h" +#include "attribs.h" #define pp_separate_with_comma(PP) pp_cxx_separate_with (PP, ',') #define pp_separate_with_semicolon(PP) pp_cxx_separate_with (PP, ';') @@ -66,6 +67,7 @@ static void dump_alias_template_specialization (cxx_pretty_printer *, tree, int) static void dump_type (cxx_pretty_printer *, tree, int); static void dump_typename (cxx_pretty_printer *, tree, int); static void dump_simple_decl (cxx_pretty_printer *, tree, tree, int); +static void dump_decl_name_or_diagnose_as (cxx_pretty_printer *, tree, int); static void dump_decl (cxx_pretty_printer *, tree, int); static void dump_template_decl (cxx_pretty_printer *, tree, int); static void dump_function_decl (cxx_pretty_printer *, tree, int); @@ -231,7 +233,15 @@ dump_scope (cxx_pretty_printer *pp, tree scope, int flags) { if (scope != global_namespace) { - dump_decl (pp, scope, f); + tree diagnose_as + = flag_diagnostics_use_aliases + ? lookup_attribute ("diagnose_as", DECL_ATTRIBUTES (scope)) + : NULL_TREE; + if (diagnose_as) + pp_cxx_ws_string ( + pp, TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (diagnose_as)))); + else + dump_decl (pp, scope, f); pp_cxx_colon_colon (pp); } } @@ -760,6 +770,45 @@ class_key_or_enum_as_string (tree t) return "struct"; } +/* Print out an enclosing scope of a template instance by inspecting the tree + SPECIAL of the template instantiation and the tree GENERAL of the most + general template in parallel under the control of FLAGS while adjusting + PARMS as needed. This allows the diagnose_as attribute to override the name + of full specializations, while hiding its template parameters, and to + override the name of partial specializations without falling back to printing + the template parameters of the general template decl. */ + +static void +dump_template_scope (cxx_pretty_printer *pp, tree special, tree general, + tree *parms, int flags) +{ + gcc_assert (parms); + if (special != general && RECORD_OR_UNION_TYPE_P (special) && *parms) + { + gcc_assert (RECORD_OR_UNION_TYPE_P (general)); + const bool tmplate + = TYPE_LANG_SPECIFIC (special) && CLASSTYPE_TEMPLATE_INFO (special) + && (TREE_CODE (CLASSTYPE_TI_TEMPLATE (special)) != TEMPLATE_DECL + || PRIMARY_TEMPLATE_P (CLASSTYPE_TI_TEMPLATE (special))); + dump_template_scope (pp, CP_TYPE_CONTEXT (special), + CP_TYPE_CONTEXT (general), + tmplate ? &TREE_CHAIN(*parms) : parms, flags); + tree attr = lookup_attribute ("diagnose_as", TYPE_ATTRIBUTES (special)); + if (attr) + { + pp_cxx_ws_string (pp, TREE_STRING_POINTER ( + TREE_VALUE (TREE_VALUE (attr)))); + if (tmplate) + TREE_VALUE (*parms) = make_tree_vec (0); + } + else + dump_aggr_type (pp, general, flags | TFF_UNQUALIFIED_NAME); + pp_cxx_colon_colon (pp); + } + else + dump_scope (pp, general, flags); +} + /* Print out a class declaration T under the control of FLAGS, in the form `class foo'. */ @@ -777,6 +826,7 @@ dump_aggr_type (cxx_pretty_printer *pp, tree t, int flags) tree decl = TYPE_NAME (t); + tree diagnose_as = NULL_TREE; if (decl) { typdef = (!DECL_ARTIFICIAL (decl) @@ -803,8 +853,14 @@ dump_aggr_type (cxx_pretty_printer *pp, tree t, int flags) if (! (flags & TFF_UNQUALIFIED_NAME)) dump_scope (pp, CP_DECL_CONTEXT (decl), flags | TFF_SCOPE); flags &= ~TFF_UNQUALIFIED_NAME; + + tree diagnose_as_specialized = NULL_TREE; if (tmplate) { + if (flag_diagnostics_use_aliases) + diagnose_as_specialized + = lookup_attribute ("diagnose_as", TYPE_ATTRIBUTES (t)); + /* Because the template names are mangled, we have to locate the most general template, and use that name. */ tree tpl = TYPE_TI_TEMPLATE (t); @@ -813,9 +869,54 @@ dump_aggr_type (cxx_pretty_printer *pp, tree t, int flags) tpl = DECL_TI_TEMPLATE (tpl); decl = tpl; } + + if (flag_diagnostics_use_aliases) + { + diagnose_as = lookup_attribute ("diagnose_as", + TYPE_ATTRIBUTES (TREE_TYPE (decl))); + if (diagnose_as_specialized + && (!diagnose_as || TREE_VALUE (diagnose_as_specialized) + != TREE_VALUE (diagnose_as))) + { + diagnose_as = diagnose_as_specialized; + /* Skip dump_template_parms if diagnose_as applies to a full + specialization. */ + tree args = CLASSTYPE_TI_ARGS (t); + args = INNERMOST_TEMPLATE_ARGS (args); + gcc_assert (args); + tmplate = false; + for (int i = 0; i < NUM_TMPL_ARGS (args); ++i) + { + tree arg = TREE_VEC_ELT (args, i); + while (INDIRECT_TYPE_P (arg)) + arg = TREE_TYPE (arg); + if (WILDCARD_TYPE_P (arg)) + { + tmplate = true; + break; + } + } + } + /* Given + + template struct [[gnu::diagnose_as("AA")]] A { + struct [[gnu::diagnose_as("BB")]] B {}; + }; + + A::B will reach the following branch and find the diagnose_as + attribute for B. */ + else if (!tmplate && !diagnose_as && TYPE_TEMPLATE_INFO (t)) + diagnose_as + = lookup_attribute ("diagnose_as", + TYPE_ATTRIBUTES ( + TREE_TYPE (TYPE_TI_TEMPLATE (t)))); + } } - if (LAMBDA_TYPE_P (t)) + if (diagnose_as) + pp_cxx_ws_string (pp, TREE_STRING_POINTER ( + TREE_VALUE (TREE_VALUE (diagnose_as)))); + else if (LAMBDA_TYPE_P (t)) { /* A lambda's "type" is essentially its signature. */ pp_string (pp, M_(""); } else - dump_decl (pp, DECL_NAME (t), flags); + dump_decl_name_or_diagnose_as (pp, t, flags); } else if (DECL_DECOMPOSITION_P (t)) pp_string (pp, M_("")); @@ -1181,6 +1282,25 @@ dump_decl_name (cxx_pretty_printer *pp, tree t, int flags) pp_cxx_tree_identifier (pp, t); } +/* Print the DECL_NAME of DECL unless a different string was requested by the + diagnose_as attribute. */ + +static void +dump_decl_name_or_diagnose_as (cxx_pretty_printer *pp, tree decl, int flags) +{ + if (flag_diagnostics_use_aliases) + { + tree attr = lookup_attribute ("diagnose_as", DECL_ATTRIBUTES (decl)); + if (attr) + { + pp_cxx_ws_string ( + pp, TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr)))); + return; + } + } + dump_decl_name (pp, DECL_NAME (decl), flags); +} + /* Dump a human readable string for the decl T under control of FLAGS. */ static void @@ -1226,7 +1346,7 @@ dump_decl (cxx_pretty_printer *pp, tree t, int flags) || flags & TFF_CLASS_KEY_OR_ENUM)) { pp_cxx_ws_string (pp, "using"); - dump_decl (pp, DECL_NAME (t), flags); + dump_decl_name_or_diagnose_as(pp, t, flags); pp_cxx_whitespace (pp); pp_cxx_ws_string (pp, "="); pp_cxx_whitespace (pp); @@ -1408,7 +1528,7 @@ dump_decl (cxx_pretty_printer *pp, tree t, int flags) TREE_CODE (DECL_INITIAL (t)) == TEMPLATE_PARM_INDEX)) dump_simple_decl (pp, t, TREE_TYPE (t), flags); else if (DECL_NAME (t)) - dump_decl (pp, DECL_NAME (t), flags); + dump_decl_name_or_diagnose_as (pp, t, flags); else if (DECL_INITIAL (t)) dump_expr (pp, DECL_INITIAL (t), flags | TFF_EXPR_IN_PARENS); else @@ -1427,7 +1547,7 @@ dump_decl (cxx_pretty_printer *pp, tree t, int flags) } dump_type (pp, scope, flags); pp_cxx_colon_colon (pp); - dump_decl (pp, DECL_NAME (t), flags); + dump_decl_name_or_diagnose_as (pp, t, flags); if (variadic) pp_cxx_ws_string (pp, "..."); } @@ -1667,7 +1787,8 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags) tree template_args = NULL_TREE; tree template_parms = NULL_TREE; int show_return = flags & TFF_RETURN_TYPE || flags & TFF_DECL_SPECIFIERS; - int do_outer_scope = ! (flags & TFF_UNQUALIFIED_NAME); + bool do_outer_scope = ! (flags & TFF_UNQUALIFIED_NAME); + bool do_template_scope = false; tree exceptions; bool constexpr_p; tree ret = NULL_TREE; @@ -1684,6 +1805,11 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags) /* Likewise for the constexpr specifier, in case t is a specialization. */ constexpr_p = DECL_DECLARED_CONSTEXPR_P (t); + /* Keep t before the following branch makes t point to a more general + template. Without the specialized template, the diagnose_as attribute on + the function is lost. */ + tree specialized_t = t; + /* Pretty print template instantiations only. */ if (DECL_USE_TEMPLATE (t) && DECL_TEMPLATE_INFO (t) && !(flags & TFF_NO_TEMPLATE_BINDINGS) @@ -1695,8 +1821,46 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags) tmpl = most_general_template (t); if (tmpl && TREE_CODE (tmpl) == TEMPLATE_DECL) { + /* Inspect whether any record/union type in the context of the + instantiated function template carries a diagnose_as attribute. If + one does, we need to print the scope and template argument list + differently. Consider: + + template struct A { + template struct B; + template struct B { + void f(); + }; + }; + using Achar [[gnu::diagnose_as("Achar")]] = A; + + Now Achar::B::f() needs to print as "Achar::B::f() + [with U = int]". However, DECL_CONTEXT (t) would print as + "Achar::B" and dump_aggr_type has no sensible way to + retrieve A::B. tmpl was generalized to A::B::f + and thus dump_aggr_type (DECL_CONTEXT (tmpl)) would print + "A::B [with T = char, U = int]". I.e. the diagnose_as + attribute on the A instantiation is lost. */ + if (tmpl != t && do_outer_scope && flag_diagnostics_use_aliases) + { + for (tree context = DECL_CONTEXT (t); + context && RECORD_OR_UNION_TYPE_P (context); + context = TYPE_CONTEXT (context)) + { + if (lookup_attribute ("diagnose_as", + TYPE_ATTRIBUTES (context))) + { + do_template_scope = true; + break; + } + } + } + template_parms = DECL_TEMPLATE_PARMS (tmpl); t = tmpl; + /* The "[with ...]" clause is printed, thus dump_template_params must + unconditionally present functions as primary templates. */ + dump_function_name_flags |= TFF_AS_PRIMARY; } } @@ -1743,6 +1907,25 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags) /* Print the function name. */ if (!do_outer_scope) /* Nothing. */; + else if (do_template_scope) + { + template_parms = copy_list (template_parms); + bool func_template = TREE_TYPE (TREE_VALUE (template_parms)) == t; + dump_template_scope(pp, DECL_CONTEXT (specialized_t), DECL_CONTEXT (t), + func_template ? &TREE_CHAIN (template_parms) + : &template_parms, + flags); + if (TREE_VEC_LENGTH (TREE_VALUE (template_parms)) == 0) + { + /* If no template parms are left, make it a NULL_TREE so that no + "[with ]" is printed. */ + tree p = TREE_CHAIN (template_parms); + for (; p && TREE_VEC_LENGTH (TREE_VALUE (p)) == 0; p = TREE_CHAIN (p)) + ; + if (!p) + template_parms = template_args = NULL_TREE; + } + } else if (cname) { dump_type (pp, cname, flags); @@ -1751,7 +1934,7 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags) else dump_scope (pp, CP_DECL_CONTEXT (t), flags); - dump_function_name (pp, t, dump_function_name_flags); + dump_function_name (pp, specialized_t, dump_function_name_flags); if (!(flags & TFF_NO_FUNCTION_ARGUMENTS)) { @@ -1931,6 +2114,14 @@ dump_function_name (cxx_pretty_printer *pp, tree t, int flags) if (TREE_CODE (t) == TEMPLATE_DECL) t = DECL_TEMPLATE_RESULT (t); + if (flag_diagnostics_use_aliases) + { + tree attr = lookup_attribute ("diagnose_as", DECL_ATTRIBUTES (t)); + if (attr) + name = get_identifier ( + TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr)))); + } + /* Don't let the user see __comp_ctor et al. */ if (DECL_CONSTRUCTOR_P (t) || DECL_DESTRUCTOR_P (t)) @@ -1985,6 +2176,8 @@ dump_template_parms (cxx_pretty_printer *pp, tree info, { tree args = info ? TI_ARGS (info) : NULL_TREE; + if (flags & TFF_AS_PRIMARY) + primary = true; if (primary && flags & TFF_TEMPLATE_NAME) return; flags &= ~(TFF_CLASS_KEY_OR_ENUM | TFF_TEMPLATE_NAME); @@ -3164,7 +3357,7 @@ lang_decl_name (tree decl, int v, bool translate) && TREE_CODE (decl) == NAMESPACE_DECL) dump_decl (cxx_pp, decl, TFF_PLAIN_IDENTIFIER | TFF_UNQUALIFIED_NAME); else - dump_decl (cxx_pp, DECL_NAME (decl), TFF_PLAIN_IDENTIFIER); + dump_decl_name_or_diagnose_as (cxx_pp, decl, TFF_PLAIN_IDENTIFIER); return pp_ggc_formatted_text (cxx_pp); } @@ -3949,6 +4142,13 @@ comparable_template_types_p (tree type_a, tree type_b) if (!CLASS_TYPE_P (type_b)) return false; + /* If one of the types has a diagnose_as attribute set it is presented as a + non-template (even if it's a template specialization). */ + if (flag_diagnostics_use_aliases + && (lookup_attribute ("diagnose_as", TYPE_ATTRIBUTES (type_a)) + || lookup_attribute ("diagnose_as", TYPE_ATTRIBUTES (type_b)))) + return false; + tree tinfo_a = TYPE_TEMPLATE_INFO (type_a); tree tinfo_b = TYPE_TEMPLATE_INFO (type_b); if (!tinfo_a || !tinfo_b) @@ -4048,8 +4248,21 @@ print_template_differences (pretty_printer *pp, tree type_a, tree type_b, tree tinfo_a = TYPE_TEMPLATE_INFO (type_a); tree tinfo_b = TYPE_TEMPLATE_INFO (type_b); - pp_printf (pp, "%s<", - IDENTIFIER_POINTER (DECL_NAME (TI_TEMPLATE (tinfo_a)))); + const char* identifier_a + = IDENTIFIER_POINTER (DECL_NAME (TI_TEMPLATE (tinfo_a))); + if (flag_diagnostics_use_aliases) + { + /* Locate the most general template, and see whether it carries the + diagnose_as attribute. If it does, use it as identifier for type_a. */ + tree tpl = TI_TEMPLATE (tinfo_a); + while (DECL_TEMPLATE_INFO (tpl)) + tpl = DECL_TI_TEMPLATE (tpl); + tree attr = lookup_attribute ("diagnose_as", + TYPE_ATTRIBUTES (TREE_TYPE (tpl))); + if (attr) + identifier_a = TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr))); + } + pp_printf (pp, "%s<", identifier_a); tree args_a = TI_ARGS (tinfo_a); tree args_b = TI_ARGS (tinfo_b); diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c index 4e84e2f9987..7782fd09ead 100644 --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -6082,6 +6082,31 @@ handle_namespace_attrs (tree ns, tree attributes) DECL_ATTRIBUTES (ns) = tree_cons (name, args, DECL_ATTRIBUTES (ns)); } + else if (is_attribute_p ("diagnose_as", name)) + { + if (!args || TREE_CHAIN (args) + || TREE_CODE (TREE_VALUE (args)) != STRING_CST) + { + warning (OPT_Wattributes, + "%qD attribute requires a single NTBS argument", + name); + continue; + } + tree existing + = lookup_attribute ("diagnose_as", DECL_ATTRIBUTES (ns)); + if (existing + && !cp_tree_equal (TREE_VALUE (args), + TREE_VALUE (TREE_VALUE (existing)))) + { + auto_diagnostic_group d; + warning (OPT_Wattributes, "%qD redeclared with different %qD " + "attribute value", ns, name); + inform (DECL_SOURCE_LOCATION (ns), "previous declaration here"); + continue; + } + DECL_ATTRIBUTES (ns) = tree_cons (name, args, + DECL_ATTRIBUTES (ns)); + } else { warning (OPT_Wattributes, "%qD attribute directive ignored", diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index a8bfd5fc053..251f6db4055 100644 --- a/gcc/cp/tree.c +++ b/gcc/cp/tree.c @@ -46,6 +46,7 @@ static tree verify_stmt_tree_r (tree *, int *, void *); static tree handle_init_priority_attribute (tree *, tree, tree, int, bool *); static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *); +static tree handle_diagnose_as_attribute (tree *, tree, tree, int, bool *); /* If REF is an lvalue, returns the kind of lvalue that REF is. Otherwise, returns clk_none. */ @@ -4860,6 +4861,8 @@ const struct attribute_spec cxx_attribute_table[] = handle_init_priority_attribute, NULL }, { "abi_tag", 1, -1, false, false, false, true, handle_abi_tag_attribute, NULL }, + { "diagnose_as", 1, 1, false, false, false, false, + handle_diagnose_as_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -5128,6 +5131,119 @@ handle_abi_tag_attribute (tree* node, tree name, tree args, return NULL_TREE; } +static bool +check_diagnose_as_redeclaration (const_tree decl, const_tree name, + const_tree old, const_tree new_) +{ + if (!old) + return true; + if (TREE_CODE (TREE_VALUE (old)) == TREE_LIST) + old = TREE_VALUE (old); + if (TREE_CODE (TREE_VALUE (new_)) == TREE_LIST) + new_ = TREE_VALUE (new_); + tree old_value = TREE_VALUE (old); + tree new_value = TREE_VALUE (new_); + if (cp_tree_equal (old_value, new_value)) + return true; + warning (OPT_Wattributes, "%qD redeclared with %<%D(%E)%> " + "attribute", decl, name, new_value); + inform (DECL_SOURCE_LOCATION (decl), "previous declaration here"); + return false; +} + +static tree +handle_diagnose_as_attribute (tree* node, tree name, tree args, + int flags, bool* no_add_attrs) +{ + tree decl = NULL_TREE; + if (!args || TREE_CHAIN (args) + || TREE_CODE (TREE_VALUE (args)) != STRING_CST) + { + warning (OPT_Wattributes, + "%qD attribute requires a single NTBS argument", + name); + goto fail; + } + if (TREE_CODE (*node) == TYPE_DECL) + { + // Apply the attribute to the type alias itself. + decl = *node; + tree type = TREE_TYPE (*node); + if (CLASS_TYPE_P (type) && CLASSTYPE_TEMPLATE_INSTANTIATION (type)) + { + if (COMPLETE_OR_OPEN_TYPE_P (type)) + warning (OPT_Wattributes, + "ignoring %qE attribute applied to template %qT after " + "instantiation", name, type); + else + { + type = strip_typedefs(type, nullptr, 0); + // And apply the attribute to the specialization on the RHS. + tree attributes = tree_cons (name, args, NULL_TREE); + decl_attributes (&type, attributes, + ATTR_FLAG_TYPE_IN_PLACE, NULL_TREE); + } + } + } + else if (TYPE_P (*node)) + { + if (!OVERLOAD_TYPE_P (*node)) + { + error ("%qE attribute applied to non-class, non-enum type %qT", + name, *node); + goto fail; + } + else if (!(flags & (int)ATTR_FLAG_TYPE_IN_PLACE)) + { + error ("%qE attribute applied to %qT after its definition", + name, *node); + goto fail; + } + decl = TYPE_NAME (*node); + } + else + { + if (!VAR_OR_FUNCTION_DECL_P (*node)) + { + error ("%qE attribute applied to non-function, non-variable %qD", + name, *node); + goto fail; + } + else if (DECL_LANGUAGE (*node) == lang_c) + { + error ("%qE attribute applied to % declaration %qD", + name, *node); + goto fail; + } + decl = *node; + } + + // Make sure all declarations have the same diagnose_as string. + if (DECL_SOURCE_LOCATION (decl) != input_location) + { + tree attributes = TYPE_P (*node) ? TYPE_ATTRIBUTES (*node) + : DECL_ATTRIBUTES (decl); + if (!check_diagnose_as_redeclaration ( + decl, name, lookup_attribute ("diagnose_as", attributes), args)) + goto fail; + } + else if (CLASS_TYPE_P (*node) && CLASSTYPE_IMPLICIT_INSTANTIATION (*node)) + { + // The above branch (different source location) is taken for declarations + // of type aliases that modify an implicit template specialization on the + // RHS. This branch is taken when the template is instantiated via + // instantiate_class_template_1, in which case the attribute either + // already has the value from the general template or from a type alias. + goto fail; + } + + return NULL_TREE; + + fail: + *no_add_attrs = true; + return NULL_TREE; +} + /* Return a new PTRMEM_CST of the indicated TYPE. The MEMBER is the thing pointed to by the constant. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index c8caf36f293..da8928fc667 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -2865,6 +2865,46 @@ types (@pxref{Variable Attributes}, @pxref{Type Attributes}.) The message attached to the attribute is affected by the setting of the @option{-fmessage-length} option. +@item diagnose_as ("@var{string}") +@cindex @code{diagnose_as} function attribute +The @code{diagnose_as} attribute modifies how the entity the attribute +appertains to is diagnosed in compiler messages and @code{__func__}, +@code{__FUNCTION__}, and @code{__PRETTY_FUNCTION__} macros. (The result of +@code{typeid} is not affected.) If the attribute is applied to a +@code{namespace}, the specified string replaces the complete enclosing scope. +The effect of the @code{diagnose_as} attribute can be disabled with the +@option{-fno-diagnostics-use-aliases} command line option. + +@smallexample +namespace Foo @{ + namespace Bar @{ + inline namespace v1 [[gnu::diagnose_as("Foobar")]] @{ + int f() @{ + // __PRETTY_FUNCTION__ == "void Foobar::f()" + @} + @} + @} +@} +// In function 'int Foobar::f()': +// warning: no return statement in function returning non-void [-Wreturn-type] +@end smallexample + +The @code{diagnose_as} attribute can be used with namespaces, functions, +variables, alias declarations (but not alias templates), and user-defined types +(classes, unions, and enums). If the alias declaration aliases an implicit class +template specialization (before instantiation), the attribute is additionally +applied to the class template specialization. Whether the attribute has an +effect on partial template specializations is unspecified and may depend on +several different factors. + +@smallexample +template struct basic_string @{@}; +using string [[gnu::diagnose_as("string")]] = basic_string; +int f(basic_string) @{@} +// In function 'int f(string)': +// warning: no return statement in function returning non-void [-Wreturn-type] +@end smallexample + @item error ("@var{message}") @itemx warning ("@var{message}") @cindex @code{error} function attribute diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index f5a9dc33819..84c19344c89 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -311,7 +311,8 @@ Objective-C and Objective-C++ Dialects}. -fdiagnostics-show-path-depths @gol -fno-show-column @gol -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]} @gol --fdiagnostics-column-origin=@var{origin}} +-fdiagnostics-column-origin=@var{origin} @gol +-fno-diagnostics-aliases} @item Warning Options @xref{Warning Options,,Options to Request or Suppress Warnings}. @@ -5077,6 +5078,12 @@ first column. The default value of 1 corresponds to traditional GCC behavior and to the GNU style guide. Some utilities may perform better with an origin of 0; any non-negative value may be specified. +@item -fno-diagnostics-use-aliases +@opindex fno-diagnostics-use-aliases +@opindex fdiagnostics-use-aliases +Do not replace identifiers or scope names with the aliases defined with the +@code{[[gnu::diagnose_as("alias")]]} attribute. + @item -fdiagnostics-format=@var{FORMAT} @opindex fdiagnostics-format Select a different format for printing diagnostics. diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as1.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as1.C new file mode 100644 index 00000000000..ca9db2b3a5c --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as1.C @@ -0,0 +1,177 @@ +// { dg-options "-fdiagnostics-use-aliases -fpretty-templates" } + +#ifdef __cpp_constexpr +template + constexpr bool is_char() { return false; } + +template <> + constexpr bool is_char() { return true; } + +template + constexpr bool is_int() { return false; } + +template <> + constexpr bool is_int() { return true; } +#endif + +namespace foo0 __attribute__((diagnose_as())) {} // { dg-warning "'diagnose_as' attribute requires a single NTBS argument" } + +namespace foo1 __attribute__((diagnose_as("foo2"))) {} // { dg-line foo1 } +namespace foo1 __attribute__((diagnose_as("XXXX"))) {} // { dg-warning "'foo1' redeclared with different 'diagnose_as' attribute value" } +// { dg-message "previous declaration here" "" { target *-*-* } foo1 } +namespace foo1 +{ + void f() { + f(1); // { dg-error "too many arguments to function 'void foo2::f\\(\\)'" } + } + + class __attribute__((diagnose_as("XX"))) X; // { dg-line XX } + class __attribute__((diagnose_as("--"))) X { // { dg-warning "'class foo2::XX' redeclared with 'diagnose_as\\(\"--\"\\)' attribute" } + // { dg-message "previous declaration here" "" { target *-*-* } XX } + __attribute__((diagnose_as("ff"))) void f(); + }; + class Y : X + { + void g() { + f(); // { dg-error "'void foo2::XX::ff\\(\\)' is private within this context" } + } + }; + + class __attribute__((diagnose_as())) Z0; // { dg-error "wrong number of arguments specified for 'diagnose_as' attribute" } + class __attribute__((diagnose_as(1))) Z1; // { dg-warning "'diagnose_as' attribute requires a single NTBS argument" } + class __attribute__((diagnose_as("a", "b"))) Z2; // { dg-error "wrong number of arguments specified for 'diagnose_as' attribute" } + + template class A0 {}; + typedef A0 Achar __attribute__((diagnose_as("Achar"))); + template class A0; + typedef A0 Aint __attribute__((diagnose_as("Aint"))); // { dg-warning "ignoring 'diagnose_as' attribute applied to template 'foo2::A0' after instantiation" } +} + +namespace A +{ + inline namespace B __attribute__((diagnose_as("@1"))) + { + template + struct __attribute__((diagnose_as("@2"))) C + { + __attribute__((diagnose_as("fun:1"))) + void f() {} // { dg-line ABC_f } + + template + __attribute__((diagnose_as("fun:2"))) + void g(T, U) {} // { dg-line ABC_gT } + + template + __attribute__((diagnose_as("fun:2.2"))) + void g() {} // { dg-line ABC_g } + + __attribute__((diagnose_as("fun:3"))) + void h() + { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:3") == 0, ""); + static_assert(__builtin_strcmp(__func__, "fun:3") == 0, ""); + constexpr const char* ref + = is_int() ? "void @1::@3::fun:3()" + : "void @1::@2::fun:3() [with U = char]"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, ""); +#endif + } + + template + __attribute__((diagnose_as("fun:4"))) + void ht() + { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:4") == 0, ""); + constexpr const char* ref + = is_int() + ? "void @1::@3::fun:4() [with T = float]" + : "void @1::@2::fun:4() [with T = float; U = char]"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, ""); +#endif + } + + template + struct __attribute__((diagnose_as("@5"))) E + { + __attribute__((diagnose_as("fun:5"))) static void f() { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:5") == 0, ""); + constexpr const char* ref = is_int() + ? is_char() + ? "static void @1::@3::@6::fun:5()" + : "static void @1::@3::@5::fun:5() [with T0 = float; T1 = short int]" + : is_char() + ? "static void @1::@2::@6::fun:5() [with U = char]" + : "static void @1::@2::@5::fun:5() [with T0 = float; T1 = short int; U = char]"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, + ""); +#endif + } + }; + typedef E F __attribute__((diagnose_as("@6"))); + + template + struct __attribute__((diagnose_as("@7"))) E + { + __attribute__((diagnose_as("fun:6"))) static void f() { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:6") == 0, ""); + constexpr const char* ref = is_int() + ? "static void @1::@3::@7::fun:6() [with T = float]" + : "static void @1::@2::@7::fun:6() [with T = float; U = char]"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, + ""); +#endif + } + }; + }; + + typedef C D __attribute__((diagnose_as("@3"))); + + template <> + struct __attribute__((diagnose_as("@4"))) C + { + }; + } +} + +void fn_1(int); +void fn_2(A::D); + +int main () +{ + fn_2 (A::D ()); + fn_1 (A::D ()); // { dg-error "cannot convert '@1::@3' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@3' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@2' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@4' to 'int'" } + + A::C().f(0); // { dg-error "no matching function for call to '@1::@3::f\\(int\\)'" } + // { dg-message "candidate: 'void @1::@3::fun:1\\(\\)'" "" { target *-*-* } ABC_f } + + A::C().f(0); // { dg-error "no matching function for call to '@1::@2::f\\(int\\)'" } + // { dg-message "candidate: 'void @1::@2::fun:1\\(\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_f } + + A::C().f(0); // { dg-error "'struct @1::@4' has no member named 'f'" } + + A::C().g(); // { dg-error "no matching function for call to '@1::@3::g\\(\\)'" } + // { dg-message "candidate: 'template void @1::@3::fun:2\\(T, U\\)'" "" { target *-*-* } ABC_gT } + // { dg-message "candidate: 'template void @1::@3::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g } + + A::C().g(); // { dg-error "no matching function for call to '@1::@2::g\\(\\)'" } + // { dg-message "candidate: 'template void @1::@2::fun:2\\(T, U\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_gT } + // { dg-message "candidate: 'template void @1::@2::fun:2.2\\(\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_g } + + A::C().h(); + A::C().h(); + A::C().ht(); + A::C().ht(); + A::C::E::f(); + A::C::E::f(); + A::C::E::f(); + A::C::E::f(); + A::C::F::f(); + A::C::F::f(); +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as2.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as2.C new file mode 100644 index 00000000000..b1d46d12024 --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as2.C @@ -0,0 +1,144 @@ +// { dg-options "-fdiagnostics-use-aliases -fno-pretty-templates" } + +#ifdef __cpp_constexpr +template + constexpr bool is_char() { return false; } + +template <> + constexpr bool is_char() { return true; } + +template + constexpr bool is_int() { return false; } + +template <> + constexpr bool is_int() { return true; } +#endif + +namespace A +{ + inline namespace B __attribute__((diagnose_as("@1"))) + { + template + struct __attribute__((diagnose_as("@2"))) C + { + __attribute__((diagnose_as("fun:1"))) + void f() {} // { dg-line ABC_f } + + template + __attribute__((diagnose_as("fun:2"))) + void g(T, U) {} // { dg-line ABC_gT } + + template + __attribute__((diagnose_as("fun:2.2"))) + void g() {} // { dg-line ABC_g } + + __attribute__((diagnose_as("fun:3"))) + void h() + { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:3") == 0, ""); + constexpr const char* ref + = is_int() ? "void @1::@3::fun:3()" + : "void @1::@2::fun:3()"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, ""); +#endif + } + + template + __attribute__((diagnose_as("fun:4"))) + void ht() + { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:4") == 0, ""); + constexpr const char* ref + = is_int() ? "void @1::@3::fun:4()" + : "void @1::@2::fun:4()"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, ""); +#endif + } + + template + struct __attribute__((diagnose_as("@5"))) E + { + __attribute__((diagnose_as("fun:5"))) static void f() { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:5") == 0, ""); + constexpr const char* ref = is_int() + ? is_char() + ? "static void @1::@3::@6::fun:5()" + : "static void @1::@3::@5::fun:5()" + : is_char() + ? "static void @1::@2::@6::fun:5()" + : "static void @1::@2::@5::fun:5()"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, + ""); +#endif + } + }; + typedef E F __attribute__((diagnose_as("@6"))); + + template + struct __attribute__((diagnose_as("@7"))) E + { + __attribute__((diagnose_as("fun:6"))) static void f() { +#ifdef __cpp_constexpr + static_assert(__builtin_strcmp(__FUNCTION__, "fun:6") == 0, ""); + // Note that @7 is ignored with -fno-pretty-templates. After all + // diagnose_as on partial specializations is not supported. + constexpr const char* ref = is_int() + ? "static void @1::@3::@5::fun:6()" + : "static void @1::@2::@5::fun:6()"; + static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, + ""); +#endif + } + }; + }; + + typedef C D __attribute__((diagnose_as("@3"))); + + template <> + struct __attribute__((diagnose_as("@4"))) C + { + }; + } +} + +void fn_1(int); +void fn_2(A::D); + +int main () +{ + fn_2 (A::D ()); + fn_1 (A::D ()); // { dg-error "cannot convert '@1::@3' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@3' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@2' to 'int'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::@4' to 'int'" } + + A::C().f(0); // { dg-error "no matching function for call to '@1::@3::f\\(int\\)'" } + // { dg-message "candidate: 'void @1::@3::fun:1\\(\\)'" "" { target *-*-* } ABC_f } + + A::C().f(0); // { dg-error "no matching function for call to '@1::@2::f\\(int\\)'" } + // { dg-message "candidate: 'void @1::@2::fun:1\\(\\)'" "" { target *-*-* } ABC_f } + + A::C().f(0); // { dg-error "'struct @1::@4' has no member named 'f'" } + + A::C().g(); // { dg-error "no matching function for call to '@1::@3::g\\(\\)'" } + // { dg-message "candidate: 'template void @1::@3::fun:2\\(T, int\\)'" "" { target *-*-* } ABC_gT } + // { dg-message "candidate: 'template void @1::@3::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g } + + A::C().g(); // { dg-error "no matching function for call to '@1::@2::g\\(\\)'" } + // { dg-message "candidate: 'template void @1::@2::fun:2\\(T, char\\)'" "" { target *-*-* } ABC_gT } + // { dg-message "candidate: 'template void @1::@2::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g } + + A::C().h(); + A::C().h(); + A::C().ht(); + A::C().ht(); + A::C::E::f(); + A::C::E::f(); + A::C::E::f(); + A::C::E::f(); + A::C::F::f(); + A::C::F::f(); +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as3.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as3.C new file mode 100644 index 00000000000..beedb9624a8 --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as3.C @@ -0,0 +1,152 @@ +// { dg-options "-fdiagnostics-use-aliases -fpretty-templates" } +// { dg-do compile { target c++11 } } + +const char* volatile s; + +namespace foo +{ + template + struct [[gnu::diagnose_as("Bar'")]] Bar + { + template + struct [[gnu::diagnose_as("A'")]] A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + template + struct A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + template + struct [[gnu::diagnose_as("Adouble")]] A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + using Special [[gnu::diagnose_as("SpecialA")]] = A; + + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + template + struct + [[gnu::diagnose_as("BarPtr")]] + Bar + { + template + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + template <> + struct [[gnu::diagnose_as("SpecialBar")]] Bar + { + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + using barchar [[gnu::diagnose_as("barchar")]] = Bar; +} + +namespace A +{ + inline namespace B __attribute__((diagnose_as("@1"))) + { + template + struct __attribute__((diagnose_as("@2"))) C + { + template + __attribute__((diagnose_as("fun:2"))) + constexpr const char* g() { return __PRETTY_FUNCTION__; } + + __attribute__((diagnose_as("fun:3"))) + constexpr const char* h() + { return __PRETTY_FUNCTION__; } + }; + + typedef C D __attribute__((diagnose_as("@3"))); + } +} + +template +struct [[gnu::diagnose_as("X.0")]] X0 +{ + template + struct X1 + { + template + struct X2 + { + template + [[gnu::diagnose_as("f-1")]] + static constexpr const char* f() { + return __PRETTY_FUNCTION__; + } + }; + + using XX [[gnu::diagnose_as("X2'")]] = X2; + }; + + template + [[gnu::diagnose_as("f-1")]] + static constexpr const char* f() { + return __PRETTY_FUNCTION__; + } + + struct [[gnu::diagnose_as("X.3")]] X3 + { + template + [[gnu::diagnose_as("f-1")]] + static constexpr const char* f() { + return __PRETTY_FUNCTION__; + } + }; +}; + +using X0int [[gnu::diagnose_as("[int|int]")]] = X0; + +#define TEST(expected, fun) \ + static_assert(__builtin_strcmp(expected, fun) == 0, "") + + +void h() { + TEST("constexpr const char* @1::@3::fun:3()", + A::C().h()); + TEST("constexpr const char* @1::@3::fun:2() [with T = float]", + A::C().g()); + TEST("constexpr const char* @1::@2::fun:3() [with U = char]", + A::C().h()); + TEST("constexpr const char* @1::@2::fun:2() [with T = float; U = char]", + A::C().g()); + TEST("constexpr const char* foo::barchar::A'::f() [with T0 = char; T1 = char]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::A'::f() [with T = char]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::Adouble::f() [with T = int]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::SpecialA::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::A'::f() [with T0 = char; T1 = char; U = float]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::A'::f() [with T = char; U = float]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::Adouble::f() [with T = char; U = float]", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::SpecialA::f() [with U = float]", + (foo::Bar::A().f())); + TEST("static constexpr const char* foo::barchar::f()", + foo::Bar::f()); + TEST("static constexpr const char* foo::BarPtr::f() [with U = int; P = char]", + foo::Bar::f()); + TEST("static constexpr const char* foo::BarPtr::f() [with U = int; P = float]", + foo::Bar::f()); + TEST("static constexpr const char* foo::Bar'::f() [with U = float]", + foo::Bar::f()); + TEST("static constexpr const char* foo::SpecialBar::f()", + foo::Bar::f()); + TEST("static constexpr const char* X.0::X1::X2'::f-1() [with T3 = long int; U3 = long long int; T1 = short int; U1 = int; T0 = char; U0 = short int]", + (X0::X1::X2::f())); + TEST("static constexpr const char* X.0::X1::X2::f-1() [with T3 = long int; U3 = long long int; T2 = long int; U2 = int; T1 = short int; U1 = int; T0 = char; U0 = short int]", + (X0::X1::X2::f())); + TEST("static constexpr const char* X.0::X.3::f-1() [with T3 = long int; U3 = long long int; T0 = char; U0 = short int]", + (X0::X3::f())); + TEST("static constexpr const char* [int|int]::f-1() [with T3 = long int; U3 = long long int]", + (X0::f())); + TEST("static constexpr const char* [int|int]::X.3::f-1() [with T3 = long int; U3 = long long int]", + (X0::X3::f())); +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as4.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as4.C new file mode 100644 index 00000000000..89b800c7b9d --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as4.C @@ -0,0 +1,158 @@ +// { dg-options "-fdiagnostics-use-aliases -fno-pretty-templates" } +// { dg-do compile { target c++11 } } + +const char* volatile s; + +namespace foo +{ + template + struct [[gnu::diagnose_as("Bar'")]] Bar + { + template + struct [[gnu::diagnose_as("A'")]] A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + template + struct A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + template + struct [[gnu::diagnose_as("Adouble")]] A + { constexpr const char* f() { return __PRETTY_FUNCTION__; } }; + + using Special [[gnu::diagnose_as("SpecialA")]] = A; + + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + template + struct + [[gnu::diagnose_as("BarPtr")]] + Bar + { + template + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + template <> + struct [[gnu::diagnose_as("SpecialBar")]] Bar + { + static constexpr const char* f() { return __PRETTY_FUNCTION__; } + }; + + using barchar [[gnu::diagnose_as("barchar")]] = Bar; +} + +namespace A +{ + inline namespace B __attribute__((diagnose_as("@1"))) + { + template + struct __attribute__((diagnose_as("@2"))) C + { + template + __attribute__((diagnose_as("fun:2"))) + constexpr const char* g() { return __PRETTY_FUNCTION__; } + + __attribute__((diagnose_as("fun:3"))) + constexpr const char* h() + { return __PRETTY_FUNCTION__; } + }; + + typedef C D __attribute__((diagnose_as("@3"))); + } +} + +template +struct [[gnu::diagnose_as("XX0")]] X0 +{ + template + struct X1 + { + template + struct X2 + { + template + [[gnu::diagnose_as("f-1")]] + static constexpr const char* f() { + return __PRETTY_FUNCTION__; + } + }; + + using XX [[gnu::diagnose_as("X2'")]] = X2; + }; + + template + [[gnu::diagnose_as("f-2")]] + static constexpr const char* f() { + return __PRETTY_FUNCTION__; + } + + struct [[gnu::diagnose_as("XX3")]] X3 + { + template + [[gnu::diagnose_as("f-3")]] + static constexpr const char* f() { // { dg-line X0_X3_f } + return __PRETTY_FUNCTION__; + } + }; +}; + +using X0int [[gnu::diagnose_as("X0intint")]] = X0; + +#define TEST(expected, fun) \ + static_assert(__builtin_strcmp(expected, fun) == 0, "") + + +void h() { + TEST("constexpr const char* @1::@3::fun:3()", + A::C().h()); + TEST("constexpr const char* @1::@3::fun:2()", + A::C().g()); + TEST("constexpr const char* @1::@2::fun:3()", + A::C().h()); + TEST("constexpr const char* @1::@2::fun:2()", + A::C().g()); + TEST("constexpr const char* foo::barchar::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::barchar::SpecialA::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::A'::f()", + (foo::Bar::A().f())); + TEST("constexpr const char* foo::Bar'::SpecialA::f()", + (foo::Bar::A().f())); + TEST("static constexpr const char* foo::barchar::f()", + foo::Bar::f()); + TEST("static constexpr const char* foo::Bar'::f()", + foo::Bar::f()); + TEST("static constexpr const char* foo::Bar'::f()", + foo::Bar::f()); + TEST("static constexpr const char* foo::Bar'::f()", + foo::Bar::f()); + TEST("static constexpr const char* foo::SpecialBar::f()", + foo::Bar::f()); + TEST("static constexpr const char* XX0::X1::X2'::f-1()", + (X0::X1::X2::f())); + TEST("static constexpr const char* XX0::X1::X2::f-1()", + (X0::X1::X2::f())); + TEST("static constexpr const char* XX0::XX3::f-3()", + (X0::X3::f())); + TEST("static constexpr const char* X0intint::f-2()", + (X0::f())); + TEST("static constexpr const char* X0intint::XX3::f-3()", + (X0::X3::f())); + + X0::X3::f(1); // { dg-error "no matching function for call to 'XX0::XX3::f\\(int\\)" } + // { dg-message "candidate: 'static constexpr const char\\* XX0::XX3::f-3\\(\\)'" "" { target *-*-* } X0_X3_f } + + X0::X3::f(1); // { dg-error "no matching function for call to 'X0intint::XX3::f\\(int\\)" } + // { dg-message "candidate: 'static constexpr const char\\* X0intint::XX3::f-3\\(\\)'" "" { target *-*-* } X0_X3_f } +}