diff mbox series

[v5] c++: Add gnu::diagnose_as attribute

Message ID 1845718.Z2AExFMA8N@excalibur
State New
Headers show
Series [v5] c++: Add gnu::diagnose_as attribute | expand

Commit Message

Matthias Kretz Nov. 15, 2021, 12:35 a.m. UTC
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<char> 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 <class T> struct [[diagnose_as("foo")]] A;
  using bar [[diagnose_as]] = A<int>;

  template <class T> struct A {
    template <class U> struct B {};
    using C [[diagnose_as]] = B<int>;
  };

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<char, ...>' vs. 'std::basic_string<wchar_t, ...>' 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 <class> 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 <class T>
    using foo [[gnu::diagnose_as]] = typename bar<T>::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<T>::inner until foo<U> 
instantiates bar<U>. Thus, if bar<U>::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 <m.kretz@gsi.de>

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 mbox series

Patch

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 <class T> struct A {
+	 struct B {};
+	 template <class U> struct C {};
+	 using D [[gnu::diagnose_as]] = B;
+	 using E [[gnu::diagnose_as]] = C<int>;
+       };
+     A<T>::D and A<T>::E are not alias templates.
+     - For A<T>::D, the DECL_PRIMARY_TEMPLATE is A and not B, which would be the
+       case for a real alias template.
+     - For A<T>::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 <class T> struct [[gnu::diagnose_as("AA")]] A {
+	         struct [[gnu::diagnose_as("BB")]] B {};
+	       };
+
+	     A<int>::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_("<lambda"));
@@ -1147,7 +1247,7 @@  dump_simple_decl (cxx_pretty_printer *pp, tree t, tree type, int flags)
 	  pp_string (pp, " capture>");
 	}
       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_("<structured bindings>"));
@@ -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 <class T> struct A {
+		 template <class U0, class U1> struct B;
+		 template <class U> struct B<U, int> {
+		   void f();
+		 };
+	       };
+	       using Achar [[gnu::diagnose_as("Achar")]] = A<char>;
+
+	     Now Achar::B<int, int>::f() needs to print as "Achar::B<U, int>::f()
+	     [with U = int]". However, DECL_CONTEXT (t) would print as
+	     "Achar::B<int, int>" and dump_aggr_type has no sensible way to
+	     retrieve A<T>::B<U, int>. tmpl was generalized to A<T>::B<U, int>::f
+	     and thus dump_aggr_type (DECL_CONTEXT (tmpl)) would print
+	     "A<T>::B<U, int> [with T = char, U = int]". I.e. the diagnose_as
+	     attribute on the A<char> 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<tree, va_gc> *);
 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 <class> 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 <class T> using A = B<int, T>;  */
+	  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 <class T> struct basic_string @{@};
+using string [[gnu::diagnose_as]] = basic_string<char>;
+int f(basic_string<char>) @{@}
+// 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<CF><80><BF>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 <class a, class b = int>
       [[deprecated]] static void f(d);
   };
 
+template <class T>
+  struct c<T, char>
+  {
+    template <class d, class e = int>
+      [[deprecated]] static void g(d);
+  };
+
 template <class T>
 struct B { typedef T X; };
 
@@ -68,6 +75,7 @@  int main()
   c<float, int>::f(1.);    // { dg-warning "'static void c<a, b>::f\\(d\\) .with d = double; a = float; b = int.'" }
   c<float, int>::f<int>(1);    // { dg-warning "'static void c<a, b>::f<d>\\(d\\) .with d = int; a = float; b = int.'" }
   c<float, int>::f<float, int>(1.f);    // { dg-warning "'static void c<a, b>::f<d, e>\\(d\\) .with d = float; e = int; a = float; b = int.'" }
+  c<int, char>::g(1.); // { dg-warning "'static void c<T, char>::g\\(d\\) .with d = double; T = int.'" }
 
   D<int>::foo(1); // { dg-warning "'static void D<U>::foo\\(typename B<V>::X\\) .with U = int; typename B<V>::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 <typename T>
+  constexpr bool is_char() { return false; }
+
+template <>
+  constexpr bool is_char<char>() { return true; }
+
+template <typename T>
+  constexpr bool is_int() { return false; }
+
+template <>
+  constexpr bool is_int<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 <typename T> class A0 {};
+  typedef A0<char> Achar __attribute__((diagnose_as("Achar")));
+  template class A0<int>;
+  typedef A0<int> Aint __attribute__((diagnose_as("Aint"))); // OK
+  typedef A0<int> Aint2 __attribute__((diagnose_as("Aint2"))); // { dg-warning "'class foo2::Aint' redeclared with 'diagnose_as\\(\"Aint2\"\\)' attribute" }
+  typedef A0<int> Aint3 __attribute__((diagnose_as(1))); // { dg-warning "'diagnose_as' attribute requires 0 or 1 NTBS arguments" }
+
+#if __cplusplus >= 201103L
+  template <typename T> using foo [[gnu::diagnose_as]] = A0<T>;
+  template <typename T> using aRef [[gnu::diagnose_as]] = A0<const T&>; // { dg-warning "'diagnose_as' attribute ignored" "" { target c++11 } }
+#endif
+}
+
+namespace A
+{
+  inline namespace __attribute__((diagnose_as("@1"))) B
+  {
+    template <typename U, typename UU = void>
+      struct __attribute__((diagnose_as("@2"))) C
+      {
+	__attribute__((diagnose_as("fun:1")))
+	void f() {} // { dg-line ABC_f }
+
+	template <typename T>
+	  __attribute__((diagnose_as("fun:2")))
+	  void g(T, U) {} // { dg-line ABC_gT }
+
+	template <typename T>
+	  __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<U>() ? "void @1::@3::fun:3()"
+			  : "void @1::@2<U>::fun:3() [with U = char]";
+	  static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, "");
+#endif
+	}
+
+	template <typename T>
+	  __attribute__((diagnose_as("fun:4")))
+	  void ht()
+	  {
+#ifdef __cpp_constexpr
+	    static_assert(__builtin_strcmp(__FUNCTION__, "fun:4") == 0, "");
+	    constexpr const char* ref
+	      = is_int<U>()
+		  ? "void @1::@3::fun:4<T>() [with T = float]"
+		  : "void @1::@2<U>::fun:4<T>() [with T = float; U = char]";
+	    static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, "");
+#endif
+	  }
+
+	template <typename T0, typename T1>
+	  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<U>()
+		? is_char<T0>()
+		  ? "static void @1::@3::@6::fun:5()"
+		  : "static void @1::@3::@5<T0, T1>::fun:5() [with T0 = float; T1 = short int]"
+		: is_char<T0>()
+		  ? "static void @1::@2<U>::@6::fun:5() [with U = char]"
+		  : "static void @1::@2<U>::@5<T0, T1>::fun:5() [with T0 = float; T1 = short int; U = char]";
+	      static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0,
+			    "");
+#endif
+	    }
+	  };
+	typedef E<char, char> F __attribute__((diagnose_as("@6")));
+
+	template <typename T>
+	  struct __attribute__((diagnose_as("@7"))) E<T, long>
+	  {
+	    __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<U>()
+		  ? "static void @1::@3::@7<T, long int>::fun:6() [with T = float]"
+		  : "static void @1::@2<U>::@7<T, long int>::fun:6() [with T = float; U = char]";
+	      static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0,
+			    "");
+#endif
+	    }
+	  };
+      };
+
+    typedef C<int> D __attribute__((diagnose_as("@3")));
+
+    template <>
+      struct __attribute__((diagnose_as("@4"))) C<float>
+      {
+      };
+
+    typedef C<short> 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 <class T>
+  struct outer
+  {
+    struct inner {};
+  };
+template <class T>
+  using inandout [[gnu::diagnose_as]] = typename outer<T>::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<int> ()); // { dg-error "cannot convert '@1::@3' to 'int'" }
+  fn_1 (A::C<char> ()); // { dg-error "cannot convert '@1::@2<char>' to 'int'" }
+  fn_1 (A::C<float> ()); // { dg-error "cannot convert '@1::@4' to 'int'" }
+  fn_1 (A::C<short> ()); // { dg-error "cannot convert '@1::E' to 'int'" }
+  fn_1 (A::E ()); // { dg-error "cannot convert '@1::E' to 'int'" }
+  fn_1 (foo1::A0<int> ()); // { 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<float> ()); // { dg-error "cannot convert 'foo2::foo<float>' to 'int'" "" { target c++11 } }
+  fn_1 (foo1::foo<const float&> ()); // { dg-error "cannot convert 'foo2::foo<const float&>' to 'int'" "" { target c++11 } }
+  fn_1 (foo1::foo<float const&> ()); // { dg-error "cannot convert 'foo2::foo<const float&>' to 'int'" "" { target c++11 } }
+  fn_1 (foo1::aRef<float> ()); // { dg-error "cannot convert 'foo2::aRef<float>' {aka 'foo2::foo<const float&>'} 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<double> ()); // { dg-error "cannot convert 'inandout<double>' {aka 'outer<double>::inner'} to 'int'" "" { target c++11 } }
+  fn_1 (outer<char>::inner ()); // { dg-error "cannot convert 'outer<char>::inner' to 'int'" "" { target c++11 } }
+#endif
+
+  A::C<int>().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<char>().f(0); // { dg-error "no matching function for call to '@1::@2<char>::f\\(int\\)'" }
+  // { dg-message "candidate: 'void @1::@2<U>::fun:1\\(\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_f }
+
+  A::C<float>().f(0); // { dg-error "'struct @1::@4' has no member named 'f'" }
+
+  A::C<int>().g(); // { dg-error "no matching function for call to '@1::@3::g\\(\\)'" }
+  // { dg-message "candidate: 'template<class T> void @1::@3::fun:2\\(T, U\\)'" "" { target *-*-* } ABC_gT }
+  // { dg-message "candidate: 'template<class T> void @1::@3::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g }
+
+  A::C<char>().g(); // { dg-error "no matching function for call to '@1::@2<char>::g\\(\\)'" }
+  // { dg-message "candidate: 'template<class T> void @1::@2<U>::fun:2\\(T, U\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_gT }
+  // { dg-message "candidate: 'template<class T> void @1::@2<U>::fun:2.2\\(\\) \\\[with U = char\\\]'" "" { target *-*-* } ABC_g }
+
+  A::C<int>().h();
+  A::C<char>().h();
+  A::C<int>().ht<float>();
+  A::C<char>().ht<float>();
+  A::C<int>::E<float, short>::f();
+  A::C<char>::E<float, short>::f();
+  A::C<int>::E<float, long>::f();
+  A::C<char>::E<float, long>::f();
+  A::C<int>::F::f();
+  A::C<char>::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 <typename T>
+  constexpr bool is_char() { return false; }
+
+template <>
+  constexpr bool is_char<char>() { return true; }
+
+template <typename T>
+  constexpr bool is_int() { return false; }
+
+template <>
+  constexpr bool is_int<int>() { return true; }
+#endif
+
+namespace A
+{
+  inline namespace B __attribute__((diagnose_as("@1")))
+  {
+    template <typename U>
+      struct __attribute__((diagnose_as("@2"))) C
+      {
+	__attribute__((diagnose_as("fun:1")))
+	void f() {} // { dg-line ABC_f }
+
+	template <typename T>
+	  __attribute__((diagnose_as("fun:2")))
+	  void g(T, U) {} // { dg-line ABC_gT }
+
+	template <typename T>
+	  __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<U>() ? "void @1::@3::fun:3()"
+			  : "void @1::@2<char>::fun:3()";
+	  static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, "");
+#endif
+	}
+
+	template <typename T>
+	  __attribute__((diagnose_as("fun:4")))
+	  void ht()
+	{
+#ifdef __cpp_constexpr
+	  static_assert(__builtin_strcmp(__FUNCTION__, "fun:4") == 0, "");
+	  constexpr const char* ref
+	    = is_int<U>() ? "void @1::@3::fun:4<float>()"
+			  : "void @1::@2<char>::fun:4<float>()";
+	  static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0, "");
+#endif
+	}
+
+	template <typename T0, typename T1>
+	  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<U>()
+		? is_char<T0>()
+		  ? "static void @1::@3::@6::fun:5()"
+		  : "static void @1::@3::@5<float, short int>::fun:5()"
+		: is_char<T0>()
+		  ? "static void @1::@2<char>::@6::fun:5()"
+		  : "static void @1::@2<char>::@5<float, short int>::fun:5()";
+	      static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0,
+			    "");
+#endif
+	    }
+	  };
+	typedef E<char, char> F __attribute__((diagnose_as("@6")));
+
+	template <typename T>
+	  struct __attribute__((diagnose_as("@7"))) E<T, long>
+	  {
+	    __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<U>()
+		  ? "static void @1::@3::@5<float, long int>::fun:6()"
+		  : "static void @1::@2<char>::@5<float, long int>::fun:6()";
+	      static_assert(__builtin_strcmp(__PRETTY_FUNCTION__, ref) == 0,
+			    "");
+#endif
+	    }
+	  };
+      };
+
+    typedef C<int> D __attribute__((diagnose_as("@3")));
+
+    template <>
+      struct __attribute__((diagnose_as("@4"))) C<float>
+      {
+      };
+  }
+}
+
+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<int> ()); // { dg-error "cannot convert '@1::@3' to 'int'" }
+  fn_1 (A::C<char> ()); // { dg-error "cannot convert '@1::@2<char>' to 'int'" }
+  fn_1 (A::C<float> ()); // { dg-error "cannot convert '@1::@4' to 'int'" }
+
+  A::C<int>().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<char>().f(0); // { dg-error "no matching function for call to '@1::@2<char>::f\\(int\\)'" }
+  // { dg-message "candidate: 'void @1::@2<char>::fun:1\\(\\)'" "" { target *-*-* } ABC_f }
+
+  A::C<float>().f(0); // { dg-error "'struct @1::@4' has no member named 'f'" }
+
+  A::C<int>().g(); // { dg-error "no matching function for call to '@1::@3::g\\(\\)'" }
+  // { dg-message "candidate: 'template<class T> void @1::@3::fun:2\\(T, int\\)'" "" { target *-*-* } ABC_gT }
+  // { dg-message "candidate: 'template<class T> void @1::@3::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g }
+
+  A::C<char>().g(); // { dg-error "no matching function for call to '@1::@2<char>::g\\(\\)'" }
+  // { dg-message "candidate: 'template<class T> void @1::@2<char>::fun:2\\(T, char\\)'" "" { target *-*-* } ABC_gT }
+  // { dg-message "candidate: 'template<class T> void @1::@2<char>::fun:2.2\\(\\)'" "" { target *-*-* } ABC_g }
+
+  A::C<int>().h();
+  A::C<char>().h();
+  A::C<int>().ht<float>();
+  A::C<char>().ht<float>();
+  A::C<int>::E<float, short>::f();
+  A::C<char>::E<float, short>::f();
+  A::C<int>::E<float, long>::f();
+  A::C<char>::E<float, long>::f();
+  A::C<int>::F::f();
+  A::C<char>::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 <class U>
+    struct [[gnu::diagnose_as("Bar'")]] Bar
+    {
+      template <class T0, class T1>
+        struct [[gnu::diagnose_as("A'")]] A
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      template <class T>
+        struct A<T, float>
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      template <class T>
+        struct [[gnu::diagnose_as("Adouble")]] A<T, double>
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      using Special [[gnu::diagnose_as("SpecialA")]] = A<int, int>;
+
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  template <class P>
+    struct
+    [[gnu::diagnose_as("BarPtr")]]
+    Bar<P*>
+    {
+      template <class U = int>
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  template <>
+    struct [[gnu::diagnose_as("SpecialBar")]] Bar<void>
+    {
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  using barchar [[gnu::diagnose_as("barchar")]] = Bar<char>;
+}
+
+namespace A
+{
+  inline namespace B __attribute__((diagnose_as("@1")))
+  {
+    template  <typename U>
+      struct __attribute__((diagnose_as("@2"))) C
+      {
+        template <class T>
+        __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<int> D __attribute__((diagnose_as("@3")));
+  }
+}
+
+template <class T0, class U0>
+struct [[gnu::diagnose_as("X.0")]] X0
+{
+  template <class T1, class U1>
+    struct X1
+    {
+      template <class T2, class U2>
+        struct X2
+        {
+          template <class T3, class U3>
+            [[gnu::diagnose_as("f-1")]]
+            static constexpr const char* f() {
+              return __PRETTY_FUNCTION__;
+            }
+        };
+
+      using XX [[gnu::diagnose_as("X2'")]] = X2<int, long>;
+    };
+
+  template <class T3, class U3>
+    [[gnu::diagnose_as("f-1")]]
+    static constexpr const char* f() {
+      return __PRETTY_FUNCTION__;
+    }
+
+  struct [[gnu::diagnose_as("X.3")]] X3
+  {
+    template <class T3, class U3>
+      [[gnu::diagnose_as("f-1")]]
+      static constexpr const char* f() {
+        return __PRETTY_FUNCTION__;
+      }
+  };
+};
+
+using X0int [[gnu::diagnose_as("[int|int]")]] = X0<int, int>;
+
+#define TEST(expected, fun)                                                    \
+  static_assert(__builtin_strcmp(expected, fun) == 0, "")
+
+
+void h() {
+  TEST("constexpr const char* @1::@3::fun:3()",
+      A::C<int>().h());
+  TEST("constexpr const char* @1::@3::fun:2<T>() [with T = float]",
+      A::C<int>().g<float>());
+  TEST("constexpr const char* @1::@2<U>::fun:3() [with U = char]",
+      A::C<char>().h());
+  TEST("constexpr const char* @1::@2<U>::fun:2<T>() [with T = float; U = char]",
+      A::C<char>().g<float>());
+  TEST("constexpr const char* foo::barchar::A'<T0, T1>::f() [with T0 = char; T1 = char]",
+      (foo::Bar<char>::A<char, char>().f()));
+  TEST("constexpr const char* foo::barchar::A'<T, float>::f() [with T = char]",
+      (foo::Bar<char>::A<char, float>().f()));
+  TEST("constexpr const char* foo::barchar::Adouble<T, double>::f() [with T = int]",
+      (foo::Bar<char>::A<int, double>().f()));
+  TEST("constexpr const char* foo::barchar::SpecialA::f()",
+      (foo::Bar<char>::A<int, int>().f()));
+  TEST("constexpr const char* foo::Bar'<U>::A'<T0, T1>::f() [with T0 = char; T1 = char; U = float]",
+      (foo::Bar<float>::A<char, char>().f()));
+  TEST("constexpr const char* foo::Bar'<U>::A'<T, float>::f() [with T = char; U = float]",
+      (foo::Bar<float>::A<char, float>().f()));
+  TEST("constexpr const char* foo::Bar'<U>::Adouble<T, double>::f() [with T = char; U = float]",
+      (foo::Bar<float>::A<char, double>().f()));
+  TEST("constexpr const char* foo::Bar'<U>::SpecialA::f() [with U = float]",
+      (foo::Bar<float>::A<int, int>().f()));
+  TEST("static constexpr const char* foo::barchar::f()",
+      foo::Bar<char>::f());
+  TEST("static constexpr const char* foo::BarPtr<P*>::f() [with P = char]",
+      foo::Bar<char*>::f());
+  TEST("static constexpr const char* foo::BarPtr<P*>::f() [with P = float]",
+      foo::Bar<float*>::f());
+  TEST("static constexpr const char* foo::Bar'<U>::f() [with U = float]",
+      foo::Bar<float>::f());
+  TEST("static constexpr const char* foo::SpecialBar::f()",
+      foo::Bar<void>::f());
+  TEST("static constexpr const char* X.0<T0, U0>::X1<T1, U1>::X2'::f-1<T3, U3>() [with T3 = long int; U3 = long long int; T1 = short int; U1 = int; T0 = char; U0 = short int]",
+      (X0<char, short>::X1<short, int>::X2<int, long>::f<long, long long>()));
+  TEST("static constexpr const char* X.0<T0, U0>::X1<T1, U1>::X2<T2, U2>::f-1<T3, U3>() [with T3 = long int; U3 = long long int; T2 = long int; U2 = int; T1 = short int; U1 = int; T0 = char; U0 = short int]",
+      (X0<char, short>::X1<short, int>::X2<long, int>::f<long, long long>()));
+  TEST("static constexpr const char* X.0<T0, U0>::X.3::f-1<T3, U3>() [with T3 = long int; U3 = long long int; T0 = char; U0 = short int]",
+      (X0<char, short>::X3::f<long, long long>()));
+  TEST("static constexpr const char* [int|int]::f-1<T3, U3>() [with T3 = long int; U3 = long long int]",
+      (X0<int, int>::f<long, long long>()));
+  TEST("static constexpr const char* [int|int]::X.3::f-1<T3, U3>() [with T3 = long int; U3 = long long int]",
+      (X0<int, int>::X3::f<long, long long>()));
+}
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 <class U>
+    struct [[gnu::diagnose_as("Bar'")]] Bar
+    {
+      template <class T0, class T1>
+        struct [[gnu::diagnose_as("A'")]] A
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      template <class T>
+        struct A<T, float>
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      template <class T>
+        struct [[gnu::diagnose_as("Adouble")]] A<T, double>
+        { constexpr const char* f() { return __PRETTY_FUNCTION__; } };
+
+      using Special [[gnu::diagnose_as("SpecialA")]] = A<int, int>;
+
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  template <class P>
+    struct
+    [[gnu::diagnose_as("BarPtr")]]
+    Bar<P*>
+    {
+      template <class U = int>
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  template <>
+    struct [[gnu::diagnose_as("SpecialBar")]] Bar<void>
+    {
+      static constexpr const char* f() { return __PRETTY_FUNCTION__; }
+    };
+
+  using barchar [[gnu::diagnose_as("barchar")]] = Bar<char>;
+}
+
+namespace A
+{
+  inline namespace B __attribute__((diagnose_as("@1")))
+  {
+    template  <typename U>
+      struct __attribute__((diagnose_as("@2"))) C
+      {
+        template <class T>
+        __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<int> D __attribute__((diagnose_as("@3")));
+  }
+}
+
+template <class T0, class U0>
+struct [[gnu::diagnose_as("XX0")]] X0
+{
+  template <class T1, class U1>
+    struct X1
+    {
+      template <class T2, class U2>
+        struct X2
+        {
+          template <class T3, class U3>
+            [[gnu::diagnose_as("f-1")]]
+            static constexpr const char* f() {
+              return __PRETTY_FUNCTION__;
+            }
+        };
+
+      using XX [[gnu::diagnose_as("X2'")]] = X2<int, long>;
+    };
+
+  template <class T3, class U3>
+    [[gnu::diagnose_as("f-2")]]
+    static constexpr const char* f() {
+      return __PRETTY_FUNCTION__;
+    }
+
+  struct [[gnu::diagnose_as("XX3")]] X3
+  {
+    template <class T3, class U3>
+      [[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<int, int>;
+
+#define TEST(expected, fun)                                                    \
+  static_assert(__builtin_strcmp(expected, fun) == 0, "")
+
+
+void h() {
+  TEST("constexpr const char* @1::@3::fun:3()",
+      A::C<int>().h());
+  TEST("constexpr const char* @1::@3::fun:2<float>()",
+      A::C<int>().g<float>());
+  TEST("constexpr const char* @1::@2<char>::fun:3()",
+      A::C<char>().h());
+  TEST("constexpr const char* @1::@2<char>::fun:2<float>()",
+      A::C<char>().g<float>());
+  TEST("constexpr const char* foo::barchar::A'<char, char>::f()",
+      (foo::Bar<char>::A<char, char>().f()));
+  TEST("constexpr const char* foo::barchar::A'<char, float>::f()",
+      (foo::Bar<char>::A<char, float>().f()));
+  TEST("constexpr const char* foo::barchar::A'<int, double>::f()",
+      (foo::Bar<char>::A<int, double>().f()));
+  TEST("constexpr const char* foo::barchar::SpecialA::f()",
+      (foo::Bar<char>::A<int, int>().f()));
+  TEST("constexpr const char* foo::Bar'<float>::A'<char, char>::f()",
+      (foo::Bar<float>::A<char, char>().f()));
+  TEST("constexpr const char* foo::Bar'<float>::A'<char, float>::f()",
+      (foo::Bar<float>::A<char, float>().f()));
+  TEST("constexpr const char* foo::Bar'<float>::A'<char, double>::f()",
+      (foo::Bar<float>::A<char, double>().f()));
+  TEST("constexpr const char* foo::Bar'<float>::SpecialA::f()",
+      (foo::Bar<float>::A<int, int>().f()));
+  TEST("static constexpr const char* foo::barchar::f()",
+      foo::Bar<char>::f());
+  TEST("static constexpr const char* foo::Bar'<char*>::f()",
+      foo::Bar<char*>::f());
+  TEST("static constexpr const char* foo::Bar'<float*>::f()",
+      foo::Bar<float*>::f());
+  TEST("static constexpr const char* foo::Bar'<float>::f()",
+      foo::Bar<float>::f());
+  TEST("static constexpr const char* foo::SpecialBar::f()",
+      foo::Bar<void>::f());
+  TEST("static constexpr const char* XX0<char, short int>::X1<short int, int>::X2'::f-1<long int, long long int>()",
+      (X0<char, short>::X1<short, int>::X2<int, long>::f<long, long long>()));
+  TEST("static constexpr const char* XX0<char, short int>::X1<short int, int>::X2<long int, int>::f-1<long int, long long int>()",
+      (X0<char, short>::X1<short, int>::X2<long, int>::f<long, long long>()));
+  TEST("static constexpr const char* XX0<char, short int>::XX3::f-3<long int, long long int>()",
+      (X0<char, short>::X3::f<long, long long>()));
+  TEST("static constexpr const char* X0intint::f-2<long int, long long int>()",
+      (X0<int, int>::f<long, long long>()));
+  TEST("static constexpr const char* X0intint::XX3::f-3<long int, long long int>()",
+      (X0<int, int>::X3::f<long, long long>()));
+
+  X0<char, short>::X3::f<long, long long>(1); // { dg-error "no matching function for call to 'XX0<char, short int>::XX3::f<long int, long long int>\\(int\\)" }
+  // { dg-message "candidate: 'template<class T3, class U3> static constexpr const char\\* XX0<char, short int>::XX3::f-3\\(\\)'" "" { target *-*-* } X0_X3_f }
+
+  X0<int, int>::X3::f<long, long long>(1); // { dg-error "no matching function for call to 'X0intint::XX3::f<long int, long long int>\\(int\\)" }
+  // { dg-message "candidate: 'template<class T3, class U3> 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 T, class A>
+    class basic_string {};
+
+  namespace pmr
+  {
+    struct P {};
+    using string [[gnu::diagnose_as]] = basic_string<char, P>;
+  }
+}
+
+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 <class T, class U>
+  struct A
+  {
+    struct X {};
+    using Y [[gnu::diagnose_as]] = X;
+  };
+
+template <>
+  struct [[gnu::diagnose_as("A!")]] A<int, int>
+  {
+    struct X {};
+    using Y [[gnu::diagnose_as]] = X;
+  };
+
+void f(int);
+
+template <class T, class U>
+  using B [[gnu::diagnose_as]] = A<T, U>;
+
+// this is not a real alias template
+template <class T>
+  using C [[gnu::diagnose_as]] = A<int, int>; // { dg-warning "'diagnose_as' attribute ignored" }
+
+// Would the following request A<float, int> to be diagnosed as D<int>?
+// What about `= A<float, const T>', etc? This is not worth the implementation
+// complexity.
+template <class T>
+  using D [[gnu::diagnose_as]] = A<float, T>; // { dg-warning "'diagnose_as' attribute ignored" }
+
+template <class T, class U>
+  struct E {};
+template <class T, class U>
+  using F [[gnu::diagnose_as("EF")]] = E<U, T>; // { dg-warning "'diagnose_as' attribute ignored" }
+
+int main()
+{
+  f(A<char, int>()); // { dg-error "cannot convert 'B<char, int>' to 'int'" }
+  f(A<char, int>::X()); // { dg-error "cannot convert 'B<char, int>::Y' to 'int'" }
+  f(B<int, int>::X()); // { dg-error "cannot convert 'A!::Y' to 'int'" }
+  f(C<float>::X()); // { dg-error "cannot convert 'A!::Y' to 'int'" }
+  f(F<char, int>()); // { dg-error "cannot convert 'F<char, int>' {aka 'E<int, char>'} 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 <typename U>
+      struct __attribute__((diagnose_as("@2"))) C
+      {
+	template <typename T>
+	  [[gnu::diagnose_as("fun:1")]] void f() {} // { dg-line f }
+
+	template <typename T>
+	  [[gnu::diagnose_as("fun:2")]] void g(T) {} // { dg-line g }
+
+	template <typename T>
+	  [[deprecated, gnu::diagnose_as("fun:3")]] void h(T) {}
+
+	template <typename T, typename R = void>
+	  [[deprecated, gnu::diagnose_as("fun:4")]] R f0(T) { return R(); }
+      };
+
+    using D [[gnu::diagnose_as]] = C<int>;
+  }
+}
+
+namespace AB [[gnu::diagnose_as]] = A::B;
+
+int main()
+{
+  A::C<int>().f<float>(1); // { dg-error "no matching function for call to 'AB::D::f<float>\\(int\\)'" }
+  // { dg-message "candidate: 'template<class T> void AB::D::fun:1\\(\\)'" "" { target *-*-* } f }
+
+  A::C<int>().g<int>(1, 1); // { dg-error "no matching function for call to 'AB::D::g<int>\\(int, int\\)'" }
+  // { dg-message "candidate: 'template<class T> void AB::D::fun:2\\(T\\)'" "" { target *-*-* } g }
+
+  A::C<int>().h(1); // { dg-warning "'void AB::D::fun:3\\(T\\) \\\[with T = int\\\]' is deprecated" }
+
+  A::C<int>().f0(1); // { dg-warning "'R AB::D::fun:4\\(T\\) \\\[with T = int\\\]' is deprecated" }
+  A::C<int>().f0<float>(1); // { dg-warning "'R AB::D::fun:4<T>\\(T\\) \\\[with T = float\\\]' is deprecated" }
+  A::C<int>().f0<float, int>(1); // { dg-warning "'R AB::D::fun:4<T, R>\\(T\\) \\\[with T = float; R = int\\\]' is deprecated" }
+}