From patchwork Mon Nov 15 00:35:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Matthias Kretz X-Patchwork-Id: 1555071 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=) Received: from sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by bilbo.ozlabs.org (Postfix) with ESMTPS id 4Hsqxs0HDwz9sPf for ; Mon, 15 Nov 2021 11:36:31 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id C046C3858034 for ; Mon, 15 Nov 2021 00:36:28 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from lxmtout2.gsi.de (lxmtout2.gsi.de [140.181.3.112]) by sourceware.org (Postfix) with ESMTPS id 553823858013 for ; Mon, 15 Nov 2021 00:35:37 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 553823858013 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=gsi.de Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gsi.de Received: from localhost (localhost [127.0.0.1]) by lxmtout2.gsi.de (Postfix) with ESMTP id C6E97202AD67; Mon, 15 Nov 2021 01:35:35 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at lxmtout2.gsi.de Received: from lxmtout2.gsi.de ([127.0.0.1]) by localhost (lxmtout2.gsi.de [127.0.0.1]) (amavisd-new, port 10024) with LMTP id BMmyfP4IdTz8; Mon, 15 Nov 2021 01:35:35 +0100 (CET) Received: from srvex3.campus.gsi.de (unknown [10.10.4.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) by lxmtout2.gsi.de (Postfix) with ESMTPS id 5339D202AD65; Mon, 15 Nov 2021 01:35:35 +0100 (CET) Received: from excalibur.localnet (140.181.3.12) by srvex3.campus.gsi.de (10.10.4.16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.17; Mon, 15 Nov 2021 01:35:34 +0100 From: Matthias Kretz To: , Jason Merrill Subject: [PATCH v5] c++: Add gnu::diagnose_as attribute Date: Mon, 15 Nov 2021 01:35:34 +0100 Message-ID: <1845718.Z2AExFMA8N@excalibur> Organization: GSI Helmholtzzentrum =?utf-8?q?f=C3=BCr?= Schwerionenforschung In-Reply-To: References: <4361366.VLH7GnMWUR@minbar> <4019800.7NyaFmOyK0@excalibur> MIME-Version: 1.0 X-Originating-IP: [140.181.3.12] X-ClientProxiedBy: srvex3.Campus.gsi.de (10.10.4.16) To srvex3.campus.gsi.de (10.10.4.16) X-Spam-Status: No, score=-10.4 required=5.0 tests=BAYES_00, BODY_8BITS, GIT_PATCH_0, KAM_DMARC_STATUS, RCVD_IN_MSPIKE_H2, SPF_PASS, TXREP, T_SPF_HELO_PERMERROR autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Sender: "Gcc-patches" Sorry for taking so long. I hope we can still get this done for GCC 12. One open question: If we change std::__cxx11::basic_string to std::string with this feature, should DWARF strings change or not? I.e. should diagnose_as be conditional on (pp->flags & pp_c_flag_gnu_v3)? If these strings are only for user consumption, I think the DWARF strings should be affected by the attribute... Oh, and note that the current patch depends on the "c++: Print function template parms when relevant" patch I sent on Nov 8th. On Wednesday, 8 September 2021 04:21:51 CEST Jason Merrill wrote: > On 7/23/21 4:58 AM, Matthias Kretz wrote: > > gcc/cp/ChangeLog: > > PR c++/89370 > > * cp-tree.h: Add is_alias_template_p declaration. > > * decl2.c (is_alias_template_p): New function. Determines > > whether a given TYPE_DECL is actually an alias template that is > > still missing its template_info. > > I still think you want to share code with get_underlying_template. For > the case where the alias doesn't have DECL_TEMPLATE_INFO yet, you can > compare to current_template_args (). Or you could do some initial > processing that doesn't care about templates in the handler, and then do > more in cp_parser_alias_declaration after the call to grokfield/start_decl. I still don't understand how I could make use of get_underlying_template. I.e. I don't even understand how get_underlying_template answers any of the questions I need answered. I used way too much time trying to make this work... > If you still think you need this function, let's call it > is_renaming_alias_template or renaming_alias_template_p; using both is_ > and _p is redundant. I don't have a strong preference which. OK. > > (is_late_template_attribute): Decls with diagnose_as attribute > > are early attributes only if they are alias templates. > > Is there a reason not to apply it early to other templates as well? Unconditionally returning false for diagnose_as in is_late_template_attribute makes renamed class templates print without template parameter list. E.g. template struct [[diagnose_as("foo")]] A; using bar [[diagnose_as]] = A; template struct A { template struct B {}; using C [[diagnose_as]] = B; }; could query for attributes. So IIUC, member types of class templates require late attributes. > > * error.c (dump_scope): When printing the name of a namespace, > > look for the diagnose_as attribute. If found, print the > > associated string instead of calling dump_decl. > > Did you decide not to handle this in dump_decl, so we use the > diagnose_as when referring to the namespace in non-scope contexts as well? Good question. dump_decl is the more general place for handling the attribute and that's where I moved it to. > > + if (flag_diagnostics_use_aliases) > > + { > > + tree attr = lookup_attribute ("diagnose_as", DECL_ATTRIBUTES > > (decl)); + if (attr && TREE_VALUE (attr)) > > + { > > + pp_cxx_ws_string ( > > + pp, TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr)))); > > This pattern is used several places outside this function; can we factor > it into something like > > if (maybe_print_diagnose_as (special)) > /* OK */; Yes, I added the functions lookup_diagnose_as_attribute and dump_diagnose_as_alias to remove code duplication. > Missing space before ( OK. I think I found and fixed all of them. > > + if (tmplate) > > + TREE_VALUE (*parms) = make_tree_vec (0); > > This could use a comment. Added. > > (dump_aggr_type): If the type has a diagnose_as attribute, print > > the associated string instead of printing the original type > > name. Print template parms only if the attribute was not applied > > to the instantiation / full specialization. Delay call to > > dump_scope until the diagnose_as attribute is found. If the > > attribute has a second argument, use it to override the context > > passed to dump_scope. > > > > + 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; > > + } > > + } > > I think you want any_dependent_template_args_p (args) Yes, except that I need `++processing_template_decl` before calling it (and decrement after it, of course). Is that acceptable? > Checking WILDCARD_TYPE_P is generally not what you want; plenty of > dependent types don't show up specifically as wildcards. T*, for instance. Right, that's why I had `while (INDIRECT_TYPE_P (arg)) arg = TREE_TYPE (arg);` before the wildcard test. But I replaced all of those with calls to any_dependent_template_arguments_p now. > > + if (diagnose_as) > > + pp_cxx_ws_string (pp, TREE_STRING_POINTER ( > > + TREE_VALUE (TREE_VALUE (diagnose_as)))); > > ( needs to go on the next line. I'd format this as > > if (diagnose_as) > pp_cxx_ws_string (pp, (TREE_STRING_POINTER > (TREE_VALUE (TREE_VALUE (diagnose_as))))); > > There's a lot of this formatting pattern in the patch. All fixed. > Missing space before ( fixed. > > (lang_decl_name): Ditto. > > (dump_function_decl): Walk the functions context list to > > determine whether a call to dump_template_scope is required. > > Ensure function templates diagnosed with pretty templates set > > TFF_TEMPLATE_NAME to skip dump_template_parms. > > (dump_function_name): Replace the function's identifier with the > > diagnose_as attribute value, if set. Expand > > DECL_FRIEND_PSEUDO_TEMPLATE_INSTANTIATION to DECL_USE_TEMPLATE > > and consequently call dump_template_parms with primary = false. > > (comparable_template_types_p): Consider the types not a template > > if one carries a diagnose_as attribute. > > I'd think it would be more helpful to suppress diagnose_as if the types > are comparable. So instead of diagnosing e.g. 'std::string' vs. 'std::wstring' it should print 'std::basic_string' vs. 'std::basic_string' here? I believe that would be more confusing than helpful. Either switch all aliases on or all of them off. > > (cp_parser_namespace_alias_definition): Allow optional > > attributes before and after the identifier. Fast exit, restoring > > input_location, if the expected CPP_EQ token is missing. Pass > > attributes to do_namespace_alias. > > > > + if (attributes > > + && !cp_parser_uncommitted_to_tentative_parse_p (parser)) > > + pedwarn (input_location, OPT_Wpedantic, > > + "standard attributes on namespaces aliases must follow " > > + "the namespace alias name"); > > Maybe remember where we saw attributes and warn later, after we've > committed to parsing as an alias. OK. > Or use cp_parser_skip_attributes_opt > to avoid tentative parsing in the first place. I don't see how to do that easily. We need to parse up to the RT_EQ token before we know whether it's a namespace alias or namespace definition. > > + if (nullptr == cp_parser_require (parser, CPP_EQ, RT_EQ)) > > The usual pattern is > > if (!cp_parser_require done > > (handle_diagnose_as_attribute): New function; copied and > > adjusted from handle_abi_tag_attribute. If the given *node is a > > TYPE_DECL: allow no argument to the attribute, using DECL_NAME > > instead; apply the attribute to the type on the RHS in place, > > even if the type is complete. Allow 2 arguments when called from > > handle_diagnose_as_attribute. For type aliases, append > > CP_DECL_CONTEXT as second attribute argument when the RHS type > > has a different context. Warn about alias templates without > > matching template arguments. Apply the attribute to the primary > > template type for alias templates. > > All this description of semantics should be in a comment rather than the > CHangeLog. done > > + /* Reject alias templates without wildcards on the innermost > > template arg s > > + of the RHS type. E.g. template using A = B; */ > > + if (DECL_LANG_SPECIFIC (decl) > > + && DECL_TEMPLATE_INFO (decl) > > + && DECL_ALIAS_TEMPLATE_P (DECL_TI_TEMPLATE (decl)) > > + && PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (decl))) > > + return error_mark_node; > > I don't think this is doing anything useful; if we had already done > push_template_decl by this point, it would reject all alias templates. Without this test e.g. template using foo [[gnu::diagnose_as]] = typename bar::inner; is not rejected. I want to reject cases where the RHS is not a primary template because I cannot apply the attribute to bar::inner until foo instantiates bar. Thus, if bar::inner is used before the alias is used the diagnose_as attribute is not present. > > + // Add the DECL_CONTEXT of the alias to the attribute if it is > > different + // to the context of the type. > > How about using the alias TYPE_DECL itself as the argument to the > attribute on the type? Then we wouldn't need to copy its name into a > string, either. Done. New patch: This attribute overrides the diagnostics output string for the entity it appertains to. The motivation is to improve QoI for library TS implementations, where diagnostics have a very bad signal-to-noise ratio due to the long namespaces involved. With the attribute, it is possible to solve PR89370 and make std::__cxx11::basic_string<_CharT, _Traits, _Alloc> appear as std::string in diagnostic output without extra hacks to recognize the type in the C++ frontend. Signed-off-by: Matthias Kretz gcc/ChangeLog: PR c++/89370 * doc/extend.texi: Document the diagnose_as attribute. * doc/invoke.texi: Document -fno-diagnostics-use-aliases. gcc/c-family/ChangeLog: PR c++/89370 * c.opt (fdiagnostics-use-aliases): New diagnostics flag. gcc/cp/ChangeLog: PR c++/89370 * cp-tree.h: Add is_renaming_alias_template declaration. * decl2.c (is_renaming_alias_template): New function. Determines whether a given TYPE_DECL is actually an alias template that is still missing its template_info. (is_late_template_attribute): Decls with diagnose_as attribute are early attributes only if they are alias templates. * error.c (dump_scope): Don't remove TFF_AS_PRIMARY flag. (lookup_diagnose_as_attribute): New function. (dump_diagnose_as_alias): New function. (dump_decl_name_or_diagnose_as): New function to replace dump_decl (pp, DECL_NAME(t), flags) and inspect the tree for the diagnose_as attribute before printing the DECL_NAME. (dump_template_scope): New function. Prints the scope of a template instance correctly applying diagnose_as attributes and adjusting the list of template parms accordingly. (dump_aggr_type): If the type has a diagnose_as attribute, print the associated string instead of printing the original type name. Print template parms only if the attribute was not applied to the instantiation / full specialization. Delay call to dump_scope until the diagnose_as attribute is found. If the attribute has a second argument, use it to override the context passed to dump_scope. (dump_simple_decl): Call dump_decl_name_or_diagnose_as instead of dump_decl. (lang_decl_name): Ditto. (dump_decl): Ditto. When printing the name of a namespace, look for the diagnose_as attribute. If found, print the associated string instead of calling dump_decl. (dump_function_decl): Walk the functions context list to determine whether a call to dump_template_scope is required. (dump_function_name): Replace the function's identifier with the diagnose_as attribute value, if set. (comparable_template_types_p): Consider the types not a template if one carries a diagnose_as attribute. (print_template_differences): Replace the identifier with the diagnose_as attribute value on the most general template, if it is set. * name-lookup.c (handle_namespace_attrs): Handle the diagnose_as attribute on namespaces. Ensure exactly one string argument. Ensure previous diagnose_as attributes used the same name. 'diagnose_as' on namespace aliases are forwarded to the original namespace. Support no-argument 'diagnose_as' on namespace aliases. (do_namespace_alias): Add attributes parameter and call handle_namespace_attrs. * name-lookup.h (do_namespace_alias): Add attributes tree parameter. * parser.c (cp_parser_declaration): If the next token is RID_NAMESPACE, tentatively parse a namespace alias definition. If this fails expect a namespace definition. (cp_parser_namespace_alias_definition): Allow optional attributes before and after the identifier. Fast exit, restoring input_location, if the expected CPP_EQ token is missing. Pass attributes to do_namespace_alias. * tree.c (cxx_attribute_table): Add diagnose_as attribute to the table. (check_diagnose_as_redeclaration): New function; copied and adjusted from check_abi_tag_redeclaration. (handle_diagnose_as_attribute): New function; copied and adjusted from handle_abi_tag_attribute. If the given *node is a TYPE_DECL: allow no argument to the attribute, using the TYPE_DECL as attribute value instead; apply the attribute to the type on the RHS in place, even if the type is complete. Allow 2 arguments when called from handle_diagnose_as_attribute. For type aliases, append CP_DECL_CONTEXT as second attribute argument when the RHS type has a different context. Warn about alias templates without matching template arguments. Apply the attribute to the primary template type for alias templates. gcc/testsuite/ChangeLog: PR c++/89370 * g++.dg/diagnostic/default-template-args-1.C: Add testcase with partial specialization. * g++.dg/diagnostic/diagnose-as1.C: New test. * g++.dg/diagnostic/diagnose-as2.C: New test. * g++.dg/diagnostic/diagnose-as3.C: New test. * g++.dg/diagnostic/diagnose-as4.C: New test. * g++.dg/diagnostic/diagnose-as5.C: New test. * g++.dg/diagnostic/diagnose-as6.C: New test. * g++.dg/diagnostic/diagnose-as7.C: New test. --- gcc/c-family/c.opt | 4 + gcc/cp/cp-tree.h | 1 + gcc/cp/decl2.c | 37 +++ gcc/cp/error.c | 289 ++++++++++++++++-- gcc/cp/name-lookup.c | 53 +++- gcc/cp/name-lookup.h | 2 +- gcc/cp/parser.c | 40 ++- gcc/cp/tree.c | 166 ++++++++++ gcc/doc/extend.texi | 45 +++ gcc/doc/invoke.texi | 9 +- .../diagnostic/default-template-args-1.C | 8 + .../g++.dg/diagnostic/diagnose-as1.C | 228 ++++++++++++++ .../g++.dg/diagnostic/diagnose-as2.C | 144 +++++++++ .../g++.dg/diagnostic/diagnose-as3.C | 152 +++++++++ .../g++.dg/diagnostic/diagnose-as4.C | 158 ++++++++++ .../g++.dg/diagnostic/diagnose-as5.C | 21 ++ .../g++.dg/diagnostic/diagnose-as6.C | 45 +++ .../g++.dg/diagnostic/diagnose-as7.C | 43 +++ 18 files changed, 1400 insertions(+), 45 deletions(-) create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as1.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as2.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as3.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as4.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as5.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as6.C create mode 100644 gcc/testsuite/g++.dg/diagnostic/diagnose-as7.C -- ────────────────────────────────────────────────────────────────────────── Dr. Matthias Kretz https://mattkretz.github.io GSI Helmholtz Centre for Heavy Ion Research https://gsi.de stdₓ::simd ────────────────────────────────────────────────────────────────────────── diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 06457ac739e..b1e809cfef9 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1619,6 +1619,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 e1aa95afd05..13b2bd2aaae 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -6829,6 +6829,7 @@ extern tree grokfield (const cp_declarator *, cp_decl_specifier_seq *, tree, bool, tree, tree); extern tree grokbitfield (const cp_declarator *, cp_decl_specifier_seq *, tree, tree, tree); +extern bool is_renaming_alias_template (tree); extern tree splice_template_attributes (tree *, tree); extern bool any_dependent_type_attributes_p (tree); extern tree cp_reconstruct_complex_type (tree, tree); diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c index 32d3fe3636d..6f11850f875 100644 --- a/gcc/cp/decl2.c +++ b/gcc/cp/decl2.c @@ -1139,6 +1139,39 @@ grokbitfield (const cp_declarator *declarator, return value; } +/* Return true iff DECL is an alias template of a class template. This predicate + also works before the alias template has its DECL_TEMPLATE_INFO. */ +bool +is_renaming_alias_template (tree decl) +{ + if (TREE_CODE (decl) != TYPE_DECL) + return false; + + tree type = strip_typedefs (TREE_TYPE (decl), nullptr, 0); + if (!TYPE_LANG_SPECIFIC (type) || !TYPE_TEMPLATE_INFO (type)) + return false; + + /* Ensure it's a real alias template not just + template struct A { + struct B {}; + template struct C {}; + using D [[gnu::diagnose_as]] = B; + using E [[gnu::diagnose_as]] = C; + }; + A::D and A::E are not alias templates. + - For A::D, the DECL_PRIMARY_TEMPLATE is A and not B, which would be the + case for a real alias template. + - For A::E, the innermost template params belong to C but its template + args have no wildcard types, which would be the case for a real + alias template. */ + tree tmpl = CLASSTYPE_TI_TEMPLATE (type); + if (!PRIMARY_TEMPLATE_P (tmpl)) + return false; + + tree targs = INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)); + return any_dependent_template_arguments_p (targs); +} + /* Returns true iff ATTR is an attribute which needs to be applied at instantiation time rather than template definition time. */ @@ -1166,6 +1199,10 @@ is_late_template_attribute (tree attr, tree decl) || is_attribute_p ("used", name))) return false; + /* Allow alias templates to set diagnose_as on the RHS template. */ + if (is_attribute_p ("diagnose_as", name)) + return !is_renaming_alias_template (decl); + /* Attribute tls_model wants to modify the symtab. */ if (is_attribute_p ("tls_model", name)) return true; diff --git a/gcc/cp/error.c b/gcc/cp/error.c index 86e9d12103a..9c09a3c57fa 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); @@ -104,6 +106,8 @@ static void cp_print_error_function (diagnostic_context *, diagnostic_info *); static bool cp_printer (pretty_printer *, text_info *, const char *, int, bool, bool, bool, bool *, const char **); +static tree lookup_diagnose_as_attribute (cxx_pretty_printer *, tree); +static void dump_diagnose_as_alias (cxx_pretty_printer *, tree, tree, int); /* Struct for handling %H or %I, which require delaying printing the type until a postprocessing stage. */ @@ -216,7 +220,7 @@ dump_module_suffix (cxx_pretty_printer *pp, tree decl) static void dump_scope (cxx_pretty_printer *pp, tree scope, int flags) { - int f = flags & (TFF_SCOPE | TFF_CHASE_TYPEDEF); + int f = flags & (TFF_SCOPE | TFF_CHASE_TYPEDEF | TFF_AS_PRIMARY); if (scope == NULL_TREE) return; @@ -231,7 +235,7 @@ dump_scope (cxx_pretty_printer *pp, tree scope, int flags) { if (scope != global_namespace) { - dump_decl (pp, scope, f); + dump_decl (pp, scope, f); pp_cxx_colon_colon (pp); } } @@ -770,6 +774,60 @@ 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) +{ + if (special != general && CLASS_TYPE_P (special) && parms) + { + gcc_assert (CLASS_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); + flags |= TFF_UNQUALIFIED_NAME; + tree attr = lookup_diagnose_as_attribute (pp, TYPE_ATTRIBUTES (special)); + if (attr) + { + dump_diagnose_as_alias (pp, attr, NULL_TREE, flags); + if (tmplate) + /* Hide the template parameters of this level from + dump_template_bindings so that the diagnose_as attribute makes + the type appear as a non-template. */ + TREE_VALUE (parms) = make_tree_vec (0); + } + else if (NON_DEFAULT_TEMPLATE_ARGS_COUNT (CLASSTYPE_TI_ARGS (special)) + && NON_DEFAULT_TEMPLATE_ARGS_COUNT (CLASSTYPE_TI_ARGS (general)) + && GET_NON_DEFAULT_TEMPLATE_ARGS_COUNT + (CLASSTYPE_TI_ARGS (special)) + > GET_NON_DEFAULT_TEMPLATE_ARGS_COUNT + (CLASSTYPE_TI_ARGS (general))) + /* If SPECIAL has more non-default template args than GENERAL, print + SPECIAL (with TFF_AS_PRIMARY in FLAGS). Otherwise, template + parameters will be missing in the output. */ + dump_aggr_type (pp, special, flags); + else + /* Printing without TFF_AS_PRIMARY is necessary to print partial + specializations of class templates correctly. If GENERAL is not a + partial specialization, TFF_AS_PRIMARY makes no difference. */ + dump_aggr_type (pp, general, flags & ~TFF_AS_PRIMARY); + pp_cxx_colon_colon (pp); + } + else + dump_scope (pp, special, flags); +} + /* Print out a class declaration T under the control of FLAGS, in the form `class foo'. */ @@ -787,6 +845,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) @@ -810,11 +869,13 @@ dump_aggr_type (cxx_pretty_printer *pp, tree t, int flags) && (TREE_CODE (CLASSTYPE_TI_TEMPLATE (t)) != TEMPLATE_DECL || PRIMARY_TEMPLATE_P (CLASSTYPE_TI_TEMPLATE (t))); - if (! (flags & TFF_UNQUALIFIED_NAME)) - dump_scope (pp, CP_DECL_CONTEXT (decl), flags | TFF_SCOPE); - flags &= ~TFF_UNQUALIFIED_NAME; + const tree context = CP_DECL_CONTEXT (decl); + tree diagnose_as_specialized = NULL_TREE; if (tmplate) { + diagnose_as_specialized = lookup_diagnose_as_attribute + (pp, 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); @@ -823,9 +884,48 @@ 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_diagnose_as_attribute + (pp, TYPE_ATTRIBUTES (TREE_TYPE (decl))); + if (diagnose_as_specialized + && (!diagnose_as || diagnose_as_specialized != 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); + ++processing_template_decl; + tmplate = any_dependent_template_arguments_p (args); + --processing_template_decl; + } + /* 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_diagnose_as_attribute + (pp, TYPE_ATTRIBUTES (TREE_TYPE (TYPE_TI_TEMPLATE (t)))); + } + + if (diagnose_as) + dump_diagnose_as_alias (pp, diagnose_as, context, flags); + else if (! (flags & TFF_UNQUALIFIED_NAME)) + dump_scope (pp, context, flags | TFF_SCOPE); + flags &= ~TFF_UNQUALIFIED_NAME; } - if (LAMBDA_TYPE_P (t)) + if (diagnose_as) + /* identifier printed via dump_diagnose_as_alias above */; + 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_("")); @@ -1191,6 +1291,64 @@ dump_decl_name (cxx_pretty_printer *pp, tree t, int flags) pp_cxx_tree_identifier (pp, t); } +/* Returns a TREE_LIST of diagnose_as attribute arguments if set, NULL_TREE + otherwise. */ + +static tree +lookup_diagnose_as_attribute (cxx_pretty_printer *, tree attributes) +{ + if (!flag_diagnostics_use_aliases) + return NULL_TREE; + tree attr = lookup_attribute ("diagnose_as", attributes); + if (!attr) + return NULL_TREE; + return TREE_VALUE (attr); +} + +/* Print out the diagnose_as attribute argument list ATTR under the control of + FLAGS. If FLAGS does not have TFF_UNQUALIFIED_NAME set, include the CONTEXT + in the output. ATTR can override CONTEXT. */ + +static void +dump_diagnose_as_alias (cxx_pretty_printer *pp, tree diagnose_as, tree context, + int flags) +{ + + tree arg0 = TREE_VALUE (diagnose_as); + if (TREE_CODE (arg0) == STRING_CST) + { + if (! (flags & TFF_UNQUALIFIED_NAME)) + { + if (TREE_CHAIN (diagnose_as)) + context = TREE_VALUE (TREE_CHAIN (diagnose_as)); + if (context) + dump_scope (pp, context, flags | TFF_SCOPE); + } + pp_cxx_ws_string (pp, TREE_STRING_POINTER (arg0)); + } + else + { + gcc_assert (TREE_CODE (arg0) == TYPE_DECL); + dump_simple_decl (pp, arg0, + DECL_ORIGINAL_TYPE (arg0) ? DECL_ORIGINAL_TYPE (arg0) + : TREE_TYPE (arg0), + flags & ~TFF_DECL_SPECIFIERS); + } +} + +/* 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) +{ + tree attr = lookup_diagnose_as_attribute (pp, DECL_ATTRIBUTES (decl)); + if (attr) + dump_diagnose_as_alias (pp, attr, NULL_TREE, flags | TFF_UNQUALIFIED_NAME); + else + dump_decl_name (pp, DECL_NAME (decl), flags); +} + /* Dump a human readable string for the decl T under control of FLAGS. */ static void @@ -1236,7 +1394,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); @@ -1289,18 +1447,28 @@ dump_decl (cxx_pretty_printer *pp, tree t, int flags) pp->declaration (t); else { - if (! (flags & TFF_UNQUALIFIED_NAME)) - dump_scope (pp, CP_DECL_CONTEXT (t), flags); - flags &= ~TFF_UNQUALIFIED_NAME; - if (DECL_NAME (t) == NULL_TREE) - { - if (!(pp->flags & pp_c_flag_gnu_v3)) - pp_cxx_ws_string (pp, M_("{anonymous}")); - else - pp_cxx_ws_string (pp, M_("(anonymous namespace)")); - } + tree diagnose_as + = lookup_diagnose_as_attribute (pp, DECL_ATTRIBUTES (t)); + if (diagnose_as) + /* Do not print DECL_CONTEXT (t), if there's any, since the + diagnose_as attribute on a namespace hides all enclosing scopes. + */ + dump_diagnose_as_alias (pp, diagnose_as, NULL_TREE, flags); else - pp_cxx_tree_identifier (pp, DECL_NAME (t)); + { + if (! (flags & TFF_UNQUALIFIED_NAME)) + dump_scope (pp, CP_DECL_CONTEXT (t), flags); + flags &= ~TFF_UNQUALIFIED_NAME; + if (DECL_NAME (t) == NULL_TREE) + { + if (!(pp->flags & pp_c_flag_gnu_v3)) + pp_cxx_ws_string (pp, M_("{anonymous}")); + else + pp_cxx_ws_string (pp, M_("(anonymous namespace)")); + } + else + pp_cxx_tree_identifier (pp, DECL_NAME (t)); + } } break; @@ -1418,7 +1586,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 @@ -1437,7 +1605,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, "..."); } @@ -1678,7 +1846,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; @@ -1713,6 +1882,43 @@ 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 or + might be an instantiation of a partially specialized class + template. 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) + { + for (tree context = DECL_CONTEXT (t); + context && CLASS_TYPE_P (context); + context = TYPE_CONTEXT (context)) + { + if (lookup_diagnose_as_attribute (pp, + TYPE_ATTRIBUTES (context)) + || CLASSTYPE_USE_TEMPLATE (context)) + { + do_template_scope = true; + break; + } + } + } + template_parms = DECL_TEMPLATE_PARMS (tmpl); t = tmpl; /* The "[with ...]" clause is printed, thus dump functions printing @@ -1764,6 +1970,15 @@ 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 | specialized_flags); + } else if (cname) { dump_type (pp, cname, flags | specialized_flags); @@ -1965,7 +2180,10 @@ dump_function_name (cxx_pretty_printer *pp, tree t, int flags) name = constructor_name (DECL_CONTEXT (t)); } - if (DECL_DESTRUCTOR_P (t)) + tree attr = lookup_diagnose_as_attribute (pp, DECL_ATTRIBUTES (t)); + if (attr) + dump_diagnose_as_alias (pp, attr, NULL_TREE, TFF_PLAIN_IDENTIFIER); + else if (DECL_DESTRUCTOR_P (t)) { pp_cxx_complement (pp); dump_decl (pp, name, TFF_PLAIN_IDENTIFIER); @@ -3213,7 +3431,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); } @@ -3998,6 +4216,12 @@ 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 (lookup_diagnose_as_attribute (cxx_pp, TYPE_ATTRIBUTES (type_a)) + || lookup_diagnose_as_attribute (cxx_pp, 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) @@ -4097,8 +4321,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_diagnose_as_attribute + (cxx_pp, TYPE_ATTRIBUTES (TREE_TYPE (tpl))); + if (attr) + identifier_a = TREE_STRING_POINTER (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 7f5e41ab610..e1a451aff40 100644 --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -6090,6 +6090,53 @@ 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 (DECL_NAMESPACE_ALIAS (ns)) + { // apply attribute to original namespace + if (!args) + { // turn alias identifier into attribute argument + tree ns_name = DECL_NAME (ns); + tree str = fix_string_type ( + build_string(IDENTIFIER_LENGTH (ns_name) + 1, + IDENTIFIER_POINTER (ns_name))); + args = build_tree_list (NULL_TREE, str); + } + else if (TREE_CHAIN (args) + || TREE_CODE (TREE_VALUE (args)) != STRING_CST) + { + warning (OPT_Wattributes, + "%qD attribute requires 0 or 1 NTBS arguments", + name); + continue; + } + tree attributes = tree_cons (name, args, NULL_TREE); + handle_namespace_attrs (ORIGINAL_NAMESPACE (ns), attributes); + continue; + } + 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", @@ -6122,7 +6169,7 @@ pop_decl_namespace (void) /* Process a namespace-alias declaration. */ void -do_namespace_alias (tree alias, tree name_space) +do_namespace_alias (tree alias, tree name_space, tree attributes) { if (name_space == error_mark_node) return; @@ -6138,6 +6185,10 @@ do_namespace_alias (tree alias, tree name_space) DECL_CONTEXT (alias) = FROB_CONTEXT (current_scope ()); set_originating_module (alias); + /* Apply attributes. */ + if (attributes) + handle_namespace_attrs (alias, attributes); + pushdecl (alias); /* Emit debug info for namespace alias. */ diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index f63c4f5b8bb..37f62a81823 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -435,7 +435,7 @@ extern tree cp_namespace_decls (tree); extern void set_decl_namespace (tree, tree, bool); extern void push_decl_namespace (tree); extern void pop_decl_namespace (void); -extern void do_namespace_alias (tree, tree); +extern void do_namespace_alias (tree, tree, tree = NULL_TREE); extern tree do_class_using_decl (tree, tree); extern tree lookup_arg_dependent (tree, tree, vec *); extern tree search_anon_aggr (tree, tree, bool = false); diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 12c598798db..fb10574943c 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -14819,20 +14819,15 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs) || token1->keyword == RID_STATIC || token1->keyword == RID_INLINE)) cp_parser_explicit_instantiation (parser); - /* If the next token is `namespace', check for a named or unnamed - namespace definition. */ - else if (token1->keyword == RID_NAMESPACE - && (/* A named namespace definition. */ - (token2->type == CPP_NAME - && (cp_lexer_peek_nth_token (parser->lexer, 3)->type - != CPP_EQ)) - || (token2->type == CPP_OPEN_SQUARE - && cp_lexer_peek_nth_token (parser->lexer, 3)->type - == CPP_OPEN_SQUARE) - /* An unnamed namespace definition. */ - || token2->type == CPP_OPEN_BRACE - || token2->keyword == RID_ATTRIBUTE)) - cp_parser_namespace_definition (parser); + /* If the next token is `namespace', we have either a namespace alias + definition or a namespace definition. */ + else if (token1->keyword == RID_NAMESPACE) + { + cp_parser_parse_tentatively (parser); + cp_parser_namespace_alias_definition (parser); + if (!cp_parser_parse_definitely (parser)) + cp_parser_namespace_definition (parser); + } /* An inline (associated) namespace definition. */ else if (token2->keyword == RID_NAMESPACE && token1->keyword == RID_INLINE) @@ -21279,15 +21274,20 @@ cp_parser_namespace_alias_definition (cp_parser* parser) { tree identifier; tree namespace_specifier; + const location_t start = input_location; cp_token *token = cp_lexer_peek_token (parser->lexer); /* Look for the `namespace' keyword. */ cp_parser_require_keyword (parser, RID_NAMESPACE, RT_NAMESPACE); + /* Look for attributes (GCC extension). */ + tree left_attributes = cp_parser_attributes_opt (parser); /* Look for the identifier. */ identifier = cp_parser_identifier (parser); if (identifier == error_mark_node) return; + /* Look for more attributes (GCC extension). */ + tree attributes = attr_chainon (left_attributes, cp_parser_attributes_opt (parser)); /* Look for the `=' token. */ if (!cp_parser_uncommitted_to_tentative_parse_p (parser) && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) @@ -21299,7 +21299,15 @@ cp_parser_namespace_alias_definition (cp_parser* parser) cp_lexer_consume_token (parser->lexer); return; } - cp_parser_require (parser, CPP_EQ, RT_EQ); + if (!cp_parser_require (parser, CPP_EQ, RT_EQ)) + { + input_location = start; + return; + } + if (left_attributes) + pedwarn (input_location, OPT_Wpedantic, + "standard attributes on namespaces aliases must follow " + "the namespace alias name"); /* Look for the qualified-namespace-specifier. */ namespace_specifier = cp_parser_qualified_namespace_specifier (parser); @@ -21308,7 +21316,7 @@ cp_parser_namespace_alias_definition (cp_parser* parser) cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON); /* Register the alias in the symbol table. */ - do_namespace_alias (identifier, namespace_specifier); + do_namespace_alias (identifier, namespace_specifier, attributes); } /* Parse a qualified-namespace-specifier. diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index 32ddf835a91..a2ce39925a2 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. */ @@ -4890,6 +4891,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", 0, -1, false, false, false, false, + handle_diagnose_as_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -5158,6 +5161,169 @@ handle_abi_tag_attribute (tree* node, tree name, tree args, return NULL_TREE; } +/* DECL is being redeclared; the old declaration had the diagnose_as attribute + in OLD, and the new one has the attribute in NEW_. Give an error if the + two attributes are not equal. */ + +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; +} + +/* Handle a "diagnose_as" attribute. + + If the given NODE is a TYPE_DECL: Apply the attribute to the TREE_TYPE in + place, even if the type is complete. If no argument was given to the + attribute, use NODE as attribute argument when recursing to the TREE_TYPE. + Append CP_DECL_CONTEXT as second attribute argument if the TREE_TYPE type has + a different context. Alias templates without matching template arguments are + not supported. Apply the attribute to the primary template type for alias + templates. */ + +static tree +handle_diagnose_as_attribute (tree* node, tree name, tree args, + int flags, bool* no_add_attrs) +{ + tree decl = NULL_TREE; + const bool is_alias = (TREE_CODE (*node) == TYPE_DECL); + if (args && TREE_PURPOSE (args) == integer_zero_node) + /* We're recursing from handle_diagnose_as_attribute from a TYPE_DECL. */; + else if (is_alias) + { + if (args && (TREE_CHAIN (args) + || TREE_CODE (TREE_VALUE (args)) != STRING_CST)) + { + warning (OPT_Wattributes, + "%qD attribute requires 0 or 1 NTBS arguments", name); + goto fail; + } + } + else 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 (is_alias && CLASS_TYPE_P (TREE_TYPE (*node))) + { + decl = *node; + + /* Reject alias templates without primary template on the RHS. E.g. + template using A = B; */ + if (DECL_LANG_SPECIFIC (decl) + && DECL_TEMPLATE_INFO (decl) + && DECL_ALIAS_TEMPLATE_P (DECL_TI_TEMPLATE (decl)) + && PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (decl))) + return error_mark_node; + + /* Apply the attribute to the specialization on the RHS. */ + tree type = strip_typedefs (TREE_TYPE (*node), nullptr, 0); + + if (is_renaming_alias_template (decl)) + { + type = CLASSTYPE_PRIMARY_TEMPLATE_TYPE (type); + /* Warn and skip if the template arguments don't match up like in + template using A = B; */ + if (!same_type_p (TREE_TYPE (decl), type)) + return error_mark_node; + } + + if (!args) + /* Pass the TYPE_DECL as attribute argument, i.e. tell TYPE to be + diagnosed as DECL. INTEGER_ZERO_NODE is to skip the argument checks + on recursion. */ + args = build_tree_list (integer_zero_node, decl); + else if (!TREE_CHAIN (args) + && CP_DECL_CONTEXT (decl) != CP_TYPE_CONTEXT (type)) + { + /* If the context of DECL and TYPE differ, diagnostics of TYPE would + be printed within the wrong scope. Therefore, pass the context of + DECL as second argument to the attribute, so that it can be printed + correctly. INTEGER_ZERO_NODE is used skip the argument checks on + recursion. */ + TREE_PURPOSE (args) = integer_zero_node; + TREE_CHAIN (args) = build_tree_list (NULL_TREE, + CP_DECL_CONTEXT (decl)); + } + + if (COMPLETE_TYPE_P (type)) + { + /* If TYPE is already complete, cplus_decl_attributes cannot be used + anymore. Recurse to handle_diagnose_as_attribute with the type + given on the RHS of the alias. On success, append the diagnose_as + attribute to TYPE's attributes. */ + tree rhs = handle_diagnose_as_attribute + (&type, name, args, flags, no_add_attrs); + if (rhs == error_mark_node || *no_add_attrs) + return rhs; + else + TYPE_ATTRIBUTES (type) + = tree_cons (name, args, TYPE_ATTRIBUTES (type)); + } + else + { + tree attributes = tree_cons (name, args, NULL_TREE); + cplus_decl_attributes (&type, attributes, ATTR_FLAG_TYPE_IN_PLACE); + } + } + else if (TYPE_P (*node)) + { + if (!OVERLOAD_TYPE_P (*node)) + return error_mark_node; + decl = TYPE_NAME (*node); + } + else + { + if (!VAR_OR_FUNCTION_DECL_P (*node) + || DECL_LANGUAGE (*node) != lang_cplusplus) + return error_mark_node; + 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 ef654d7b878..a97f617f16d 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -2915,6 +2915,51 @@ interface. Other than emitting an error rather than a warning, the The @code{unavailable} attribute can also be used for variables and types (@pxref{Variable Attributes}, @pxref{Type Attributes}.) +@item diagnose_as ("@var{string}") +@itemx diagnose_as +@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. + +The argument @var{string} is optional for type and namespace aliases. If it is +omitted, the identifier of the type or namespace alias is unconditionally used +in all places where the type / namespace on the right-hand side is diagnosed. + +@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, namespace aliases, +functions, variables, alias declarations (but not alias templates), and +user-defined types (classes, unions, and enums). If the alias declaration +aliases a class type (including template specializations), the attribute is +additionally applied to the class type. 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]] = 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 a7c4d24a762..c3af0c5d4bf 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -313,7 +313,8 @@ Objective-C and Objective-C++ Dialects}. -fno-show-column @gol -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]} @gol -fdiagnostics-column-origin=@var{origin} @gol --fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}} +-fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]} +-fno-diagnostics-aliases} @item Warning Options @xref{Warning Options,,Options to Request or Suppress Warnings}. @@ -5159,6 +5160,12 @@ Unicode characters. For the example above, the following will be printed: before<80>after @end smallexample +@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/default-template-args-1.C b/gcc/testsuite/g++.dg/diagnostic/default-template-args-1.C index 7a535515740..8086ea16fcd 100644 --- a/gcc/testsuite/g++.dg/diagnostic/default-template-args-1.C +++ b/gcc/testsuite/g++.dg/diagnostic/default-template-args-1.C @@ -34,6 +34,13 @@ template [[deprecated]] static void f(d); }; +template + struct c + { + template + [[deprecated]] static void g(d); + }; + template struct B { typedef T X; }; @@ -68,6 +75,7 @@ int main() c::f(1.); // { dg-warning "'static void c::f\\(d\\) .with d = double; a = float; b = int.'" } c::f(1); // { dg-warning "'static void c::f\\(d\\) .with d = int; a = float; b = int.'" } c::f(1.f); // { dg-warning "'static void c::f\\(d\\) .with d = float; e = int; a = float; b = int.'" } + c::g(1.); // { dg-warning "'static void c::g\\(d\\) .with d = double; T = int.'" } D::foo(1); // { dg-warning "'static void D::foo\\(typename B::X\\) .with U = int; typename B::X = int.'" } } 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..c62e5a7424d --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as1.C @@ -0,0 +1,228 @@ +// { 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 __attribute__((diagnose_as())) foo0 {} // { dg-warning "'diagnose_as' attribute requires a single NTBS argument" } + +namespace __attribute__((diagnose_as("foo2"))) foo1 {} // { dg-line foo1 } +namespace __attribute__((diagnose_as("XXXX"))) foo1 {} // { dg-warning "'foo2' 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-warning "'diagnose_as' attribute requires a single NTBS argument" } + class __attribute__((diagnose_as(1))) Z1; // { dg-warning "'diagnose_as' attribute requires a single NTBS argument" } + class __attribute__((diagnose_as("a", "b"))) Z2; // { dg-warning "'diagnose_as' attribute requires a single NTBS argument" } + + template class A0 {}; + typedef A0 Achar __attribute__((diagnose_as("Achar"))); + template class A0; + typedef A0 Aint __attribute__((diagnose_as("Aint"))); // OK + typedef A0 Aint2 __attribute__((diagnose_as("Aint2"))); // { dg-warning "'class foo2::Aint' redeclared with 'diagnose_as\\(\"Aint2\"\\)' attribute" } + typedef A0 Aint3 __attribute__((diagnose_as(1))); // { dg-warning "'diagnose_as' attribute requires 0 or 1 NTBS arguments" } + +#if __cplusplus >= 201103L + template using foo [[gnu::diagnose_as]] = A0; + template using aRef [[gnu::diagnose_as]] = A0; // { dg-warning "'diagnose_as' attribute ignored" "" { target c++11 } } +#endif +} + +namespace A +{ + inline namespace __attribute__((diagnose_as("@1"))) B + { + 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 + { + }; + + typedef C E __attribute__((diagnose_as)); + } +} + +namespace frob +{ + namespace ni + { + struct kate {}; + namespace cate + { + struct me {}; + } + } +} + +namespace frobni __attribute__((diagnose_as("twiggle"))) = frob::ni; +namespace frobnicate __attribute__((diagnose_as)) = frob::ni::cate; +namespace wrong __attribute__((diagnose_as(1))) = frob::ni; // { dg-warning "'diagnose_as' attribute requires 0 or 1 NTBS arguments" } +namespace wrong2 __attribute__((diagnose_as("wrong", 1))) = frob::ni; // { dg-warning "'diagnose_as' attribute requires 0 or 1 NTBS arguments" } + +#if __cplusplus >= 201103L +template + struct outer + { + struct inner {}; + }; +template + using inandout [[gnu::diagnose_as]] = typename outer::inner; // { dg-warning "'diagnose_as' attribute ignored" "" { target c++11 } } +#endif + + +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'" } + fn_1 (A::C ()); // { dg-error "cannot convert '@1::E' to 'int'" } + fn_1 (A::E ()); // { dg-error "cannot convert '@1::E' to 'int'" } + fn_1 (foo1::A0 ()); // { dg-error "cannot convert 'foo2::Aint' to 'int'" } + fn_1 (foo1::Aint2 ()); // { dg-error "cannot convert 'foo2::Aint2' {aka 'foo2::Aint'} to 'int'" } +#if __cplusplus >= 201103L + fn_1 (foo1::foo ()); // { dg-error "cannot convert 'foo2::foo' to 'int'" "" { target c++11 } } + fn_1 (foo1::foo ()); // { dg-error "cannot convert 'foo2::foo' to 'int'" "" { target c++11 } } + fn_1 (foo1::foo ()); // { dg-error "cannot convert 'foo2::foo' to 'int'" "" { target c++11 } } + fn_1 (foo1::aRef ()); // { dg-error "cannot convert 'foo2::aRef' {aka 'foo2::foo'} to 'int'" "" { target c++11 } } + fn_1 (frob::ni::kate ()); // { dg-error "cannot convert 'twiggle::kate' to 'int'" "" { target c++11 } } + fn_1 (frob::ni::cate::me ()); // { dg-error "cannot convert 'frobnicate::me' to 'int'" "" { target c++11 } } + fn_1 (inandout ()); // { dg-error "cannot convert 'inandout' {aka 'outer::inner'} to 'int'" "" { target c++11 } } + fn_1 (outer::inner ()); // { dg-error "cannot convert 'outer::inner' to 'int'" "" { target c++11 } } +#endif + + 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..2020443bcef --- /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 P = char]", + foo::Bar::f()); + TEST("static constexpr const char* foo::BarPtr::f() [with 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..a36f38aeac0 --- /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: 'template 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: 'template static constexpr const char\\* X0intint::XX3::f-3\\(\\)'" "" { target *-*-* } X0_X3_f } +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as5.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as5.C new file mode 100644 index 00000000000..2a46d64964f --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as5.C @@ -0,0 +1,21 @@ +// { dg-options "-fdiagnostics-use-aliases -fpretty-templates" } +// { dg-do compile { target c++11 } } + +namespace std +{ + template + class basic_string {}; + + namespace pmr + { + struct P {}; + using string [[gnu::diagnose_as]] = basic_string; + } +} + +void f(int); + +int main() +{ + f(std::pmr::string()); // { dg-error "cannot convert 'std::pmr::string' to 'int'" } +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as6.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as6.C new file mode 100644 index 00000000000..94747a7aa6e --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as6.C @@ -0,0 +1,45 @@ +// { dg-options "-fdiagnostics-use-aliases -fpretty-templates" } +// { dg-do compile { target c++11 } } + +template + struct A + { + struct X {}; + using Y [[gnu::diagnose_as]] = X; + }; + +template <> + struct [[gnu::diagnose_as("A!")]] A + { + struct X {}; + using Y [[gnu::diagnose_as]] = X; + }; + +void f(int); + +template + using B [[gnu::diagnose_as]] = A; + +// this is not a real alias template +template + using C [[gnu::diagnose_as]] = A; // { dg-warning "'diagnose_as' attribute ignored" } + +// Would the following request A to be diagnosed as D? +// What about `= A', etc? This is not worth the implementation +// complexity. +template + using D [[gnu::diagnose_as]] = A; // { dg-warning "'diagnose_as' attribute ignored" } + +template + struct E {}; +template + using F [[gnu::diagnose_as("EF")]] = E; // { dg-warning "'diagnose_as' attribute ignored" } + +int main() +{ + f(A()); // { dg-error "cannot convert 'B' to 'int'" } + f(A::X()); // { dg-error "cannot convert 'B::Y' to 'int'" } + f(B::X()); // { dg-error "cannot convert 'A!::Y' to 'int'" } + f(C::X()); // { dg-error "cannot convert 'A!::Y' to 'int'" } + f(F()); // { dg-error "cannot convert 'F' {aka 'E'} to 'int'" } +} diff --git a/gcc/testsuite/g++.dg/diagnostic/diagnose-as7.C b/gcc/testsuite/g++.dg/diagnostic/diagnose-as7.C new file mode 100644 index 00000000000..3005c787615 --- /dev/null +++ b/gcc/testsuite/g++.dg/diagnostic/diagnose-as7.C @@ -0,0 +1,43 @@ +// { dg-options "-fdiagnostics-use-aliases -fpretty-templates" } +// { dg-do compile { target c++11 } } + +namespace A +{ + inline namespace B + { + template + struct __attribute__((diagnose_as("@2"))) C + { + template + [[gnu::diagnose_as("fun:1")]] void f() {} // { dg-line f } + + template + [[gnu::diagnose_as("fun:2")]] void g(T) {} // { dg-line g } + + template + [[deprecated, gnu::diagnose_as("fun:3")]] void h(T) {} + + template + [[deprecated, gnu::diagnose_as("fun:4")]] R f0(T) { return R(); } + }; + + using D [[gnu::diagnose_as]] = C; + } +} + +namespace AB [[gnu::diagnose_as]] = A::B; + +int main() +{ + A::C().f(1); // { dg-error "no matching function for call to 'AB::D::f\\(int\\)'" } + // { dg-message "candidate: 'template void AB::D::fun:1\\(\\)'" "" { target *-*-* } f } + + A::C().g(1, 1); // { dg-error "no matching function for call to 'AB::D::g\\(int, int\\)'" } + // { dg-message "candidate: 'template void AB::D::fun:2\\(T\\)'" "" { target *-*-* } g } + + A::C().h(1); // { dg-warning "'void AB::D::fun:3\\(T\\) \\\[with T = int\\\]' is deprecated" } + + A::C().f0(1); // { dg-warning "'R AB::D::fun:4\\(T\\) \\\[with T = int\\\]' is deprecated" } + A::C().f0(1); // { dg-warning "'R AB::D::fun:4\\(T\\) \\\[with T = float\\\]' is deprecated" } + A::C().f0(1); // { dg-warning "'R AB::D::fun:4\\(T\\) \\\[with T = float; R = int\\\]' is deprecated" } +}