c++: permit errors inside uninstantiated templates [PR116064]

Message ID 20240801185239.2818703-1-ppalka@redhat.com
State New
Headers
Series c++: permit errors inside uninstantiated templates [PR116064] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed

Commit Message

Patrick Palka Aug. 1, 2024, 6:52 p.m. UTC
  In recent versions of GCC we've been diagnosing more and more kinds of
errors inside a template ahead of time.  This is a largely good thing
because it catches bugs, typos, dead code etc sooner.

But if the template never gets instantiated then such errors are
harmless, and can be inconvenient to work around if say the code in
question is third party and in maintenence mode.  So it'd be useful to
be able to prevent these template errors from rendering the entire TU
ill-formed.  (Note that such code is "ill-formed no diagnostic required"
according the standard.)

To that end this patch extends -fpermissive to downgrade any errors
issued within a template to warnings.  If the template containing a
downgraded error later needs to be instantiated, we'll issue an error
then.  But if the template never gets instantiated then the downgraded
error won't affect validity of the rest of the TU.

This is implemented via a diagnostic hook that gets called for each
diagnostic, and which downgrades an error diagnostic if it's occurring
in a template context (and -fpermissive is active) and additionally
flags this template context as containing a "relaxed" error.

As an example, permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function ‘void f()’:
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable ‘n’
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of ‘void f() [with T = int]’:
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/cp/ChangeLog:

	* cp-tree.h (relaxed_template_errors_t): Declare.
	(related_template_errors): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template pattern containing
	a permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
	New data member.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
---
 gcc/cp/cp-tree.h                              |  5 ++
 gcc/cp/error.cc                               | 53 +++++++++++++++++++
 gcc/cp/pt.cc                                  | 30 +++++++++++
 gcc/diagnostic.cc                             |  4 ++
 gcc/diagnostic.h                              |  4 ++
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 +++++++
 .../g++.dg/template/permissive-error1a.C      | 31 +++++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 10 files changed, 150 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
  

Comments

Jason Merrill Aug. 2, 2024, 4:51 p.m. UTC | #1
On 8/1/24 2:52 PM, Patrick Palka wrote:
> In recent versions of GCC we've been diagnosing more and more kinds of
> errors inside a template ahead of time.  This is a largely good thing
> because it catches bugs, typos, dead code etc sooner.
> 
> But if the template never gets instantiated then such errors are
> harmless, and can be inconvenient to work around if say the code in
> question is third party and in maintenence mode.  So it'd be useful to

"maintenance"

> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index d80bac822ba..0bb0a482e28 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public format_postprocessor
>     deferred_printed_type m_type_b;
>   };
>   
> +/* A map from TEMPLATE_DECL to the location of the first error (if any)
> +   within the template that we permissivly downgraded to a warning.  */

"permissively"

> +relaxed_template_errors_t *relaxed_template_errors;
> +
> +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> +
> +   In -fpermissive mode we downgrade errors within a template to
> +   warnings, and only issue an error if we later need to instantiate
> +   the template.  */
> +
> +static void
> +cp_adjust_diagnostic_info (diagnostic_context *context,
> +			   diagnostic_info *diagnostic)
> +{
> +  tree ti;
> +  if (diagnostic->kind == DK_ERROR
> +      && context->m_permissive
> +      && !current_instantiation ()
> +      && in_template_context
> +      && (ti = get_template_info (current_scope ())))
> +    {
> +      if (!relaxed_template_errors)
> +	relaxed_template_errors = new relaxed_template_errors_t;
> +
> +      tree tmpl = TI_TEMPLATE (ti);
> +      if (!relaxed_template_errors->get (tmpl))
> +	relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
> +      diagnostic->kind = DK_WARNING;

Rather than check m_permissive directly and downgrade to DK_WARNING, how 
about downgrading to DK_PERMERROR?  That way people will get the 
[-fpermissive] clue.

...though I suppose DK_PERMERROR doesn't work where you call this hook 
in report_diagnostic, at which point we've already reassigned it into 
DK_WARNING or DK_ERROR in diagnostic_impl.

But we could still set diagnostic->option_index even for DK_ERROR, 
whether to context->m_opt_permissive or to its own warning flag, perhaps 
-Wno-template-body?

> +/* A generalization of seen_error which also returns true if we've
> +   permissively downgraded an error to a warning inside a template.  */
> +
> +bool
> +cp_seen_error ()
> +{
> +#undef seen_error
> +  if (seen_error ())
> +    return true;
> +
> +  tree ti;
> +  if (relaxed_template_errors
> +      && in_template_context
> +      && (ti = get_template_info (current_scope ()))

Let's factor the "in template body" checks in this function and the 
previous one into a separate function; I expect we want the 
!current_instantiation test in this case as well?

> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 77fa5907c3d..b58ccb318d5 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
>     if (! push_tinst_level (type))
>       return type;
>   
> +  if (relaxed_template_errors)
> +    if (location_t *error_loc = relaxed_template_errors->get (templ))
> +      {
> +	/* We're trying to instantiate a template pattern containing
> +	   an error that we've permissively downgraded to a warning.
> +	   Issue a hard error now.  */
> +	location_t decl_loc = location_of (templ);
> +	error_at (decl_loc, "instantiating erroneous template pattern");

I wouldn't use the internal term "pattern" in diagnostics, I think just 
"erroneous template" is enough.

> @@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
>   	}
>       }
>   
> +  if (relaxed_template_errors)
> +    if (location_t *error_loc = relaxed_template_errors->get (td))
> +      {
> +	/* We're trying to instantiate a template pattern containing
> +	   an error that we've permissively downgraded to a warning.
> +	   Issue a hard error now.  */
> +	location_t decl_loc = location_of (td);
> +	error_at (decl_loc, "instantiating erroneous template pattern");

Likewise.

> diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
> index 71d2f44e40c..cbd2c82f19d 100644
> --- a/gcc/diagnostic.cc
> +++ b/gcc/diagnostic.cc
> @@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
>     m_warn_system_headers = false;
>     m_max_errors = 0;
>     m_internal_error = nullptr;
> +  m_adjust_diagnostic_info = nullptr;
>     m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
>     m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
>     m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
> @@ -1409,6 +1410,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
>        flush diagnostics with on_end_group when the topmost group is ended.  */
>     gcc_assert (m_diagnostic_groups.m_nesting_depth > 0);
>   
> +  if (m_adjust_diagnostic_info)
> +    m_adjust_diagnostic_info (this, diagnostic);

I wonder about the ordering of calling the hook vs setting was_warning. 
I suppose it doesn't make a practical difference in this case; if it 
ends up as a warning it'll be suppressed by -w or -Wno-system-header 
whether or not was_warning is set.

It would make a difference if a hook wanted to upgrade warning to error; 
in this position -w would not effect such an upgraded diagnostic.

I lean toward moving the call after the was_warning bits, so was_warning 
reflects whether the diagnostic was a warning before this adjustment 
just as it does before other adjustments.

Jason
  
Patrick Palka Aug. 2, 2024, 7:04 p.m. UTC | #2
On Fri, 2 Aug 2024, Jason Merrill wrote:

> On 8/1/24 2:52 PM, Patrick Palka wrote:
> > In recent versions of GCC we've been diagnosing more and more kinds of
> > errors inside a template ahead of time.  This is a largely good thing
> > because it catches bugs, typos, dead code etc sooner.
> > 
> > But if the template never gets instantiated then such errors are
> > harmless, and can be inconvenient to work around if say the code in
> > question is third party and in maintenence mode.  So it'd be useful to
> 
> "maintenance"

Fixed

> 
> > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > index d80bac822ba..0bb0a482e28 100644
> > --- a/gcc/cp/error.cc
> > +++ b/gcc/cp/error.cc
> > @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
> > format_postprocessor
> >     deferred_printed_type m_type_b;
> >   };
> >   +/* A map from TEMPLATE_DECL to the location of the first error (if any)
> > +   within the template that we permissivly downgraded to a warning.  */
> 
> "permissively"

Fixed

> 
> > +relaxed_template_errors_t *relaxed_template_errors;
> > +
> > +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> > +
> > +   In -fpermissive mode we downgrade errors within a template to
> > +   warnings, and only issue an error if we later need to instantiate
> > +   the template.  */
> > +
> > +static void
> > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > +			   diagnostic_info *diagnostic)
> > +{
> > +  tree ti;
> > +  if (diagnostic->kind == DK_ERROR
> > +      && context->m_permissive
> > +      && !current_instantiation ()
> > +      && in_template_context
> > +      && (ti = get_template_info (current_scope ())))
> > +    {
> > +      if (!relaxed_template_errors)
> > +	relaxed_template_errors = new relaxed_template_errors_t;
> > +
> > +      tree tmpl = TI_TEMPLATE (ti);
> > +      if (!relaxed_template_errors->get (tmpl))
> > +	relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
> > +      diagnostic->kind = DK_WARNING;
> 
> Rather than check m_permissive directly and downgrade to DK_WARNING, how about
> downgrading to DK_PERMERROR?  That way people will get the [-fpermissive]
> clue.
> 
> ...though I suppose DK_PERMERROR doesn't work where you call this hook in
> report_diagnostic, at which point we've already reassigned it into DK_WARNING
> or DK_ERROR in diagnostic_impl.
> 
> But we could still set diagnostic->option_index even for DK_ERROR, whether to
> context->m_opt_permissive or to its own warning flag, perhaps
> -Wno-template-body?

Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
option_index to it for each downgraded error.  Thus -permissive
-Wno-template-body would suppress the downgraded warnings entirely, and
only issue a generic error upon instantiation of the erroneous template.

> 
> > +/* A generalization of seen_error which also returns true if we've
> > +   permissively downgraded an error to a warning inside a template.  */
> > +
> > +bool
> > +cp_seen_error ()
> > +{
> > +#undef seen_error
> > +  if (seen_error ())
> > +    return true;
> > +
> > +  tree ti;
> > +  if (relaxed_template_errors
> > +      && in_template_context
> > +      && (ti = get_template_info (current_scope ()))
> 
> Let's factor the "in template body" checks in this function and the previous
> one into a separate function; I expect we want the !current_instantiation test
> in this case as well?

Done.
> 
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 77fa5907c3d..b58ccb318d5 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
> >     if (! push_tinst_level (type))
> >       return type;
> >   +  if (relaxed_template_errors)
> > +    if (location_t *error_loc = relaxed_template_errors->get (templ))
> > +      {
> > +	/* We're trying to instantiate a template pattern containing
> > +	   an error that we've permissively downgraded to a warning.
> > +	   Issue a hard error now.  */
> > +	location_t decl_loc = location_of (templ);
> > +	error_at (decl_loc, "instantiating erroneous template pattern");
> 
> I wouldn't use the internal term "pattern" in diagnostics, I think just
> "erroneous template" is enough.

Fixed

> 
> > @@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool
> > expl_inst_class_mem_p)
> >   	}
> >       }
> >   +  if (relaxed_template_errors)
> > +    if (location_t *error_loc = relaxed_template_errors->get (td))
> > +      {
> > +	/* We're trying to instantiate a template pattern containing
> > +	   an error that we've permissively downgraded to a warning.
> > +	   Issue a hard error now.  */
> > +	location_t decl_loc = location_of (td);
> > +	error_at (decl_loc, "instantiating erroneous template pattern");
> 
> Likewise.

Fixed.

> 
> > diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
> > index 71d2f44e40c..cbd2c82f19d 100644
> > --- a/gcc/diagnostic.cc
> > +++ b/gcc/diagnostic.cc
> > @@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
> >     m_warn_system_headers = false;
> >     m_max_errors = 0;
> >     m_internal_error = nullptr;
> > +  m_adjust_diagnostic_info = nullptr;
> >     m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
> >     m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
> >     m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
> > @@ -1409,6 +1410,9 @@ diagnostic_context::report_diagnostic (diagnostic_info
> > *diagnostic)
> >        flush diagnostics with on_end_group when the topmost group is ended.
> > */
> >     gcc_assert (m_diagnostic_groups.m_nesting_depth > 0);
> >   +  if (m_adjust_diagnostic_info)
> > +    m_adjust_diagnostic_info (this, diagnostic);
> 
> I wonder about the ordering of calling the hook vs setting was_warning. I
> suppose it doesn't make a practical difference in this case; if it ends up as
> a warning it'll be suppressed by -w or -Wno-system-header whether or not
> was_warning is set.
> 
> It would make a difference if a hook wanted to upgrade warning to error; in
> this position -w would not effect such an upgraded diagnostic.
> 
> I lean toward moving the call after the was_warning bits, so was_warning
> reflects whether the diagnostic was a warning before this adjustment just as
> it does before other adjustments.

Makes sense, fixed.

Here's v2, bootstrap and regtest in progress, like so?

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

In recent versions of GCC we've been diagnosing more and more kinds of
errors inside a template ahead of time.  This is a largely good thing
because it catches bugs, typos, dead code etc sooner.

But if the template never gets instantiated then such errors are harmless
and can be inconvenient to work around if say the code in question is
third party and in maintenance mode.  So it'd be handy to be able to
prevent these template errors from rendering the entire TU uncompilable.
(Note that such code is "ill-formed no diagnostic required" according
the standard.)

To that end this patch extends -fpermissive to downgrade any errors
issued within a template to warnings.  If the template containing a
downgraded error later needs to be instantiated, we'll issue an error
then.  But if the template never gets instantiated then the downgraded
error won't affect validity of the rest of the TU.

This is implemented via a diagnostic hook that gets called for each
diagnostic, and which downgrades an error diagnostic if we detect it's
occurring from a template context (and -fpermissive is active) and
additionally flags this template context as containing a "relaxed"
error.

As an example, permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function ‘void f()’:
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable ‘n’ [-Wtemplate-body]
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of ‘void f() [with T = int]’:
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (relaxed_template_errors_t): Declare.
	(related_template_errors): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template pattern containing
	a permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
	New data member.
	* doc/invoke.texi (-Wno-template-body): Document.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
---
 gcc/c-family/c.opt                            |  4 ++
 gcc/cp/cp-tree.h                              |  5 ++
 gcc/cp/error.cc                               | 61 +++++++++++++++++++
 gcc/cp/pt.cc                                  | 30 +++++++++
 gcc/diagnostic.cc                             |  4 ++
 gcc/diagnostic.h                              |  4 ++
 gcc/doc/invoke.texi                           | 10 ++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 ++++++
 .../g++.dg/template/permissive-error1a.C      | 33 ++++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 13 files changed, 203 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a52682d835c..78f299dd4a6 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Downgrade errors within templates into warnings in -fpermissive mode.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..ffdc0ba38ae 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7189,6 +7189,11 @@ extern bool pedwarn_cxx98                       (location_t, int, const char *,
 extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
+using relaxed_template_errors_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern relaxed_template_errors_t *relaxed_template_errors;
+extern bool cp_seen_error			(void);
+#define seen_error cp_seen_error
 
 /* in except.cc */
 extern void init_terminate_fn			(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..92ab30efb03 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,66 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECL to the location of the first error (if any)
+   within the template that we permissively downgraded to a warning.  */
+
+relaxed_template_errors_t *relaxed_template_errors;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   In -fpermissive mode we downgrade errors within a template to
+   warnings, and only issue an error if we later need to instantiate
+   the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR && context->m_permissive)
+    if (tree tmpl = get_current_template ())
+      {
+	if (!relaxed_template_errors)
+	  relaxed_template_errors = new relaxed_template_errors_t;
+
+	if (!relaxed_template_errors->get (tmpl))
+	  relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
+	diagnostic->kind = DK_WARNING;
+	diagnostic->option_index = OPT_Wtemplate_body;
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  /* cp-tree.h #defines seen_error to cp_seen_error.  */
+#undef seen_error
+  if (seen_error ())
+    return true;
+
+  if (relaxed_template_errors)
+    if (tree tmpl = get_current_template ())
+      if (relaxed_template_errors->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +247,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..3aa64b3d41c 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	TYPE_BEING_DEFINED (type) = false;
+	CLASSTYPE_ERRONEOUS (type) = true;
+	return type;
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	return d;
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..e90c121cb95 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..d3d77c85e9a 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -699,6 +699,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..598016cc58a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,13 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+In @option{-fpermissive} mode, errors inside templates are downgraded
+into warnings, and a proper error is issued only upon instantiation of
+the erroneous template.  This option controls emission of these warnings.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..c4b5f760ff5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,33 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..3d3462c956e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options "-fpermissive -Wno-template-body" }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// the downgraded warnings.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-message "first error appeared here" "" { target *-*-* } }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-message "first error appeared here" "" { target *-*-* } }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  
Patrick Palka Aug. 2, 2024, 8:18 p.m. UTC | #3
On Fri, 2 Aug 2024, Patrick Palka wrote:

> On Fri, 2 Aug 2024, Jason Merrill wrote:
> 
> > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > In recent versions of GCC we've been diagnosing more and more kinds of
> > > errors inside a template ahead of time.  This is a largely good thing
> > > because it catches bugs, typos, dead code etc sooner.
> > > 
> > > But if the template never gets instantiated then such errors are
> > > harmless, and can be inconvenient to work around if say the code in
> > > question is third party and in maintenence mode.  So it'd be useful to
> > 
> > "maintenance"
> 
> Fixed
> 
> > 
> > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > index d80bac822ba..0bb0a482e28 100644
> > > --- a/gcc/cp/error.cc
> > > +++ b/gcc/cp/error.cc
> > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
> > > format_postprocessor
> > >     deferred_printed_type m_type_b;
> > >   };
> > >   +/* A map from TEMPLATE_DECL to the location of the first error (if any)
> > > +   within the template that we permissivly downgraded to a warning.  */
> > 
> > "permissively"
> 
> Fixed
> 
> > 
> > > +relaxed_template_errors_t *relaxed_template_errors;
> > > +
> > > +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> > > +
> > > +   In -fpermissive mode we downgrade errors within a template to
> > > +   warnings, and only issue an error if we later need to instantiate
> > > +   the template.  */
> > > +
> > > +static void
> > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > +			   diagnostic_info *diagnostic)
> > > +{
> > > +  tree ti;
> > > +  if (diagnostic->kind == DK_ERROR
> > > +      && context->m_permissive
> > > +      && !current_instantiation ()
> > > +      && in_template_context
> > > +      && (ti = get_template_info (current_scope ())))
> > > +    {
> > > +      if (!relaxed_template_errors)
> > > +	relaxed_template_errors = new relaxed_template_errors_t;
> > > +
> > > +      tree tmpl = TI_TEMPLATE (ti);
> > > +      if (!relaxed_template_errors->get (tmpl))
> > > +	relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
> > > +      diagnostic->kind = DK_WARNING;
> > 
> > Rather than check m_permissive directly and downgrade to DK_WARNING, how about
> > downgrading to DK_PERMERROR?  That way people will get the [-fpermissive]
> > clue.
> > 
> > ...though I suppose DK_PERMERROR doesn't work where you call this hook in
> > report_diagnostic, at which point we've already reassigned it into DK_WARNING
> > or DK_ERROR in diagnostic_impl.
> > 
> > But we could still set diagnostic->option_index even for DK_ERROR, whether to
> > context->m_opt_permissive or to its own warning flag, perhaps
> > -Wno-template-body?
> 
> Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
> option_index to it for each downgraded error.  Thus -permissive
> -Wno-template-body would suppress the downgraded warnings entirely, and
> only issue a generic error upon instantiation of the erroneous template.

... or did you have in mind to set option_index even when not using
-fpermissive so that eligible non-downgraded errors get the
[-fpermissive] or [-Wtemplate-body] hint as well?

IMHO I'm not sure that'd be worth the extra noise since the vast
majority of users appreciate and expect errors to get diagnosed inside
templates.

And on second thought I'm not sure what extra value a new warning flag
adds either.  I can't think of a good reason why one would use
-fpermissive -Wno-template-body?

> 
> > 
> > > +/* A generalization of seen_error which also returns true if we've
> > > +   permissively downgraded an error to a warning inside a template.  */
> > > +
> > > +bool
> > > +cp_seen_error ()
> > > +{
> > > +#undef seen_error
> > > +  if (seen_error ())
> > > +    return true;
> > > +
> > > +  tree ti;
> > > +  if (relaxed_template_errors
> > > +      && in_template_context
> > > +      && (ti = get_template_info (current_scope ()))
> > 
> > Let's factor the "in template body" checks in this function and the previous
> > one into a separate function; I expect we want the !current_instantiation test
> > in this case as well?
> 
> Done.
> > 
> > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > > index 77fa5907c3d..b58ccb318d5 100644
> > > --- a/gcc/cp/pt.cc
> > > +++ b/gcc/cp/pt.cc
> > > @@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
> > >     if (! push_tinst_level (type))
> > >       return type;
> > >   +  if (relaxed_template_errors)
> > > +    if (location_t *error_loc = relaxed_template_errors->get (templ))
> > > +      {
> > > +	/* We're trying to instantiate a template pattern containing
> > > +	   an error that we've permissively downgraded to a warning.
> > > +	   Issue a hard error now.  */
> > > +	location_t decl_loc = location_of (templ);
> > > +	error_at (decl_loc, "instantiating erroneous template pattern");
> > 
> > I wouldn't use the internal term "pattern" in diagnostics, I think just
> > "erroneous template" is enough.
> 
> Fixed
> 
> > 
> > > @@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool
> > > expl_inst_class_mem_p)
> > >   	}
> > >       }
> > >   +  if (relaxed_template_errors)
> > > +    if (location_t *error_loc = relaxed_template_errors->get (td))
> > > +      {
> > > +	/* We're trying to instantiate a template pattern containing
> > > +	   an error that we've permissively downgraded to a warning.
> > > +	   Issue a hard error now.  */
> > > +	location_t decl_loc = location_of (td);
> > > +	error_at (decl_loc, "instantiating erroneous template pattern");
> > 
> > Likewise.
> 
> Fixed.
> 
> > 
> > > diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
> > > index 71d2f44e40c..cbd2c82f19d 100644
> > > --- a/gcc/diagnostic.cc
> > > +++ b/gcc/diagnostic.cc
> > > @@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
> > >     m_warn_system_headers = false;
> > >     m_max_errors = 0;
> > >     m_internal_error = nullptr;
> > > +  m_adjust_diagnostic_info = nullptr;
> > >     m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
> > >     m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
> > >     m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
> > > @@ -1409,6 +1410,9 @@ diagnostic_context::report_diagnostic (diagnostic_info
> > > *diagnostic)
> > >        flush diagnostics with on_end_group when the topmost group is ended.
> > > */
> > >     gcc_assert (m_diagnostic_groups.m_nesting_depth > 0);
> > >   +  if (m_adjust_diagnostic_info)
> > > +    m_adjust_diagnostic_info (this, diagnostic);
> > 
> > I wonder about the ordering of calling the hook vs setting was_warning. I
> > suppose it doesn't make a practical difference in this case; if it ends up as
> > a warning it'll be suppressed by -w or -Wno-system-header whether or not
> > was_warning is set.
> > 
> > It would make a difference if a hook wanted to upgrade warning to error; in
> > this position -w would not effect such an upgraded diagnostic.
> > 
> > I lean toward moving the call after the was_warning bits, so was_warning
> > reflects whether the diagnostic was a warning before this adjustment just as
> > it does before other adjustments.
> 
> Makes sense, fixed.
> 
> Here's v2, bootstrap and regtest in progress, like so?
> 
> -- >8 --
> 
> Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]
> 
> In recent versions of GCC we've been diagnosing more and more kinds of
> errors inside a template ahead of time.  This is a largely good thing
> because it catches bugs, typos, dead code etc sooner.
> 
> But if the template never gets instantiated then such errors are harmless
> and can be inconvenient to work around if say the code in question is
> third party and in maintenance mode.  So it'd be handy to be able to
> prevent these template errors from rendering the entire TU uncompilable.
> (Note that such code is "ill-formed no diagnostic required" according
> the standard.)
> 
> To that end this patch extends -fpermissive to downgrade any errors
> issued within a template to warnings.  If the template containing a
> downgraded error later needs to be instantiated, we'll issue an error
> then.  But if the template never gets instantiated then the downgraded
> error won't affect validity of the rest of the TU.
> 
> This is implemented via a diagnostic hook that gets called for each
> diagnostic, and which downgrades an error diagnostic if we detect it's
> occurring from a template context (and -fpermissive is active) and
> additionally flags this template context as containing a "relaxed"
> error.
> 
> As an example, permissive-error1a.C gives:
> 
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In function ‘void f()’:
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable ‘n’ [-Wtemplate-body]
>     7 |   ++n;
>       |     ^
> ...
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of ‘void f() [with T = int]’:
> gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
>    26 |   f<int>();
>       |   ~~~~~~^~
> gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern
>     5 | void f() {
>       |      ^
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
>     7 |   ++n; // {
>       |     ^
> ...
> 
> 	PR c++/116064
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt (Wtemplate-body): New warning.
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (relaxed_template_errors_t): Declare.
> 	(related_template_errors): Declare.
> 	(cp_seen_error): Declare.
> 	(seen_error): #define to cp_seen_error.
> 	* error.cc (get_current_template): Define.
> 	(relaxed_template_errors): Define.
> 	(cp_adjust_diagnostic_info): Define.
> 	(cp_seen_error): Define.
> 	(cxx_initialize_diagnostics): Set
> 	diagnostic_context::m_adjust_diagnostic_info.
> 	* pt.cc (instantiate_class_template): Issue a hard error
> 	when trying to instantiate a template pattern containing
> 	a permissively downgraded error.
> 	(instantiate_decl): Likewise.
> 
> gcc/ChangeLog:
> 
> 	* diagnostic.cc (diagnostic_context::initialize): Set
> 	m_adjust_diagnostic_info.
> 	(diagnostic_context::report_diagnostic): Call
> 	m_adjust_diagnostic_info.
> 	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
> 	New data member.
> 	* doc/invoke.texi (-Wno-template-body): Document.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/typedef-init.C: Downgrade error inside template
> 	to warning due to -fpermissive.
> 	* g++.dg/pr84492.C: Likewise.
> 	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
> 	* g++.dg/template/permissive-error1.C: New test.
> 	* g++.dg/template/permissive-error1a.C: New test.
> 	* g++.dg/template/permissive-error1b.C: New test.
> ---
>  gcc/c-family/c.opt                            |  4 ++
>  gcc/cp/cp-tree.h                              |  5 ++
>  gcc/cp/error.cc                               | 61 +++++++++++++++++++
>  gcc/cp/pt.cc                                  | 30 +++++++++
>  gcc/diagnostic.cc                             |  4 ++
>  gcc/diagnostic.h                              |  4 ++
>  gcc/doc/invoke.texi                           | 10 ++-
>  gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
>  gcc/testsuite/g++.dg/pr84492.C                |  4 +-
>  .../g++.dg/template/permissive-error1.C       | 20 ++++++
>  .../g++.dg/template/permissive-error1a.C      | 33 ++++++++++
>  .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
>  gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
>  13 files changed, 203 insertions(+), 5 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
>  create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
>  create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index a52682d835c..78f299dd4a6 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1420,6 +1420,10 @@ Wtautological-compare
>  C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
>  Warn if a comparison always evaluates to true or false.
>  
> +Wtemplate-body
> +C++ ObjC++ Var(warn_template_body) Warning Init(1)
> +Downgrade errors within templates into warnings in -fpermissive mode.
> +
>  Wtemplate-id-cdtor
>  C++ ObjC++ Var(warn_template_id_cdtor) Warning
>  Warn about simple-template-id in a constructor or destructor.
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 238d786b067..ffdc0ba38ae 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7189,6 +7189,11 @@ extern bool pedwarn_cxx98                       (location_t, int, const char *,
>  extern location_t location_of                   (tree);
>  extern void qualified_name_lookup_error		(tree, tree, tree,
>  						 location_t);
> +using relaxed_template_errors_t
> +  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
> +extern relaxed_template_errors_t *relaxed_template_errors;
> +extern bool cp_seen_error			(void);
> +#define seen_error cp_seen_error
>  
>  /* in except.cc */
>  extern void init_terminate_fn			(void);
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index d80bac822ba..92ab30efb03 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -165,6 +165,66 @@ class cxx_format_postprocessor : public format_postprocessor
>    deferred_printed_type m_type_b;
>  };
>  
> +/* Return the in-scope template that's currently being parsed, or
> +   NULL_TREE otherwise.  */
> +
> +static tree
> +get_current_template ()
> +{
> +  if (in_template_context && !current_instantiation ())
> +    if (tree ti = get_template_info (current_scope ()))
> +      return TI_TEMPLATE (ti);
> +
> +  return NULL_TREE;
> +}
> +
> +/* A map from TEMPLATE_DECL to the location of the first error (if any)
> +   within the template that we permissively downgraded to a warning.  */
> +
> +relaxed_template_errors_t *relaxed_template_errors;
> +
> +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> +
> +   In -fpermissive mode we downgrade errors within a template to
> +   warnings, and only issue an error if we later need to instantiate
> +   the template.  */
> +
> +static void
> +cp_adjust_diagnostic_info (diagnostic_context *context,
> +			   diagnostic_info *diagnostic)
> +{
> +  if (diagnostic->kind == DK_ERROR && context->m_permissive)
> +    if (tree tmpl = get_current_template ())
> +      {
> +	if (!relaxed_template_errors)
> +	  relaxed_template_errors = new relaxed_template_errors_t;
> +
> +	if (!relaxed_template_errors->get (tmpl))
> +	  relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
> +	diagnostic->kind = DK_WARNING;
> +	diagnostic->option_index = OPT_Wtemplate_body;
> +      }
> +}
> +
> +/* A generalization of seen_error which also returns true if we've
> +   permissively downgraded an error to a warning inside a template.  */
> +
> +bool
> +cp_seen_error ()
> +{
> +  /* cp-tree.h #defines seen_error to cp_seen_error.  */
> +#undef seen_error
> +  if (seen_error ())
> +    return true;
> +
> +  if (relaxed_template_errors)
> +    if (tree tmpl = get_current_template ())
> +      if (relaxed_template_errors->get (tmpl))
> +	return true;
> +
> +  return false;
> +}
> +
>  /* CONTEXT->printer is a basic pretty printer that was constructed
>     presumably by diagnostic_initialize(), called early in the
>     compiler's initialization process (in general_init) Before the FE
> @@ -187,6 +247,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
>    diagnostic_starter (context) = cp_diagnostic_starter;
>    /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
>    diagnostic_format_decoder (context) = cp_printer;
> +  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
>    pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
>  }
>  
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 77fa5907c3d..3aa64b3d41c 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
>    if (! push_tinst_level (type))
>      return type;
>  
> +  if (relaxed_template_errors)
> +    if (location_t *error_loc = relaxed_template_errors->get (templ))
> +      {
> +	/* We're trying to instantiate a template pattern containing
> +	   an error that we've permissively downgraded to a warning.
> +	   Issue a hard error now.  */
> +	location_t decl_loc = location_of (templ);
> +	error_at (decl_loc, "instantiating erroneous template");
> +	inform (*error_loc, "first error appeared here");
> +
> +	pop_tinst_level ();
> +	TYPE_BEING_DEFINED (type) = false;
> +	CLASSTYPE_ERRONEOUS (type) = true;
> +	return type;
> +      }
> +
>    int saved_unevaluated_operand = cp_unevaluated_operand;
>    int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
>  
> @@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
>  	}
>      }
>  
> +  if (relaxed_template_errors)
> +    if (location_t *error_loc = relaxed_template_errors->get (td))
> +      {
> +	/* We're trying to instantiate a template pattern containing
> +	   an error that we've permissively downgraded to a warning.
> +	   Issue a hard error now.  */
> +	location_t decl_loc = location_of (td);
> +	error_at (decl_loc, "instantiating erroneous template");
> +	inform (*error_loc, "first error appeared here");
> +
> +	pop_tinst_level ();
> +	return d;
> +      }
> +
>    code_pattern = DECL_TEMPLATE_RESULT (td);
>  
>    /* We should never be trying to instantiate a member of a class
> diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
> index 71d2f44e40c..e90c121cb95 100644
> --- a/gcc/diagnostic.cc
> +++ b/gcc/diagnostic.cc
> @@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
>    m_warn_system_headers = false;
>    m_max_errors = 0;
>    m_internal_error = nullptr;
> +  m_adjust_diagnostic_info = nullptr;
>    m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
>    m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
>    m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
> @@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
>    if (was_warning && m_inhibit_warnings)
>      return false;
>  
> +  if (m_adjust_diagnostic_info)
> +    m_adjust_diagnostic_info (this, diagnostic);
> +
>    if (diagnostic->kind == DK_PEDWARN)
>      {
>        diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 79386ccbf85..d3d77c85e9a 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -699,6 +699,10 @@ public:
>    /* Client hook to report an internal error.  */
>    void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
>  
> +  /* Client hook to adjust properties of the given diagnostic that we're
> +     about to issue, such as its kind.  */
> +  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
> +
>  private:
>    /* Client-supplied callbacks for working with options.  */
>    struct {
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index ef2213b4e84..598016cc58a 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -270,7 +270,8 @@ in the following sections.
>  -Wno-non-template-friend  -Wold-style-cast
>  -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
>  -Wsized-deallocation  -Wsuggest-final-methods
> --Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
> +-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
> +-Wno-template-id-cdtor
>  -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
>  -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
>  
> @@ -4634,6 +4635,13 @@ namespaces, and this may be used to enforce that rule.  The warning is
>  inactive inside a system header file, such as the STL, so one can still
>  use the STL.  One may also use using directives and qualified names.
>  
> +@opindex Wtemplate-body
> +@opindex Wno-template-body
> +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
> +In @option{-fpermissive} mode, errors inside templates are downgraded
> +into warnings, and a proper error is issued only upon instantiation of
> +the erroneous template.  This option controls emission of these warnings.
> +
>  @opindex Wtemplate-id-cdtor
>  @opindex Wno-template-id-cdtor
>  @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
> diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
> index 153303d217b..47a6642de51 100644
> --- a/gcc/testsuite/g++.dg/ext/typedef-init.C
> +++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
> @@ -32,5 +32,5 @@ struct S {
>  
>  template<int> void foo()
>  {
> -    typedef int i = 0; /* { dg-error "is initialized" } */
> +    typedef int i = 0; /* { dg-warning "is initialized" } */
>  }
> diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
> index 1a2922096d1..08f368ff29b 100644
> --- a/gcc/testsuite/g++.dg/pr84492.C
> +++ b/gcc/testsuite/g++.dg/pr84492.C
> @@ -3,7 +3,7 @@
>  
>  template<int> int foo()
>  {
> -  return ({ foo; }); // { dg-error "insufficient context" }
> +  return ({ foo; }); // { dg-warning "insufficient context" }
>  }
>  
>  int bar()
> @@ -35,6 +35,6 @@ class C
>    }
>    bool g(int)
>    {
> -    return ({ g; }); // { dg-error "insufficient context" }
> +    return ({ g; }); // { dg-warning "insufficient context" }
>    }
>  };
> diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
> new file mode 100644
> index 00000000000..e4536a8b90e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
> @@ -0,0 +1,20 @@
> +// PR c++/116064
> +// { dg-additional-options -fpermissive }
> +
> +template<class T>
> +void f() {
> +  const int n = 42;
> +  ++n; // { dg-warning "read-only" }
> +}
> +
> +template<class T>
> +struct A {
> +  void f(typename A::type); // { dg-warning "does not name a type" }
> +};
> +
> +template<class T>
> +struct B {
> +  void f() {
> +    this->g(); // { dg-warning "no member" }
> +  }
> +};
> diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
> new file mode 100644
> index 00000000000..c4b5f760ff5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
> @@ -0,0 +1,33 @@
> +// PR c++/116064
> +// { dg-additional-options -fpermissive }
> +// Like permissive-error1.C but verify instantiating the errorneous
> +// templates gives an error after all.
> +
> +template<class T>
> +void f() {  // { dg-error "instantiating erroneous template" }
> +  const int n = 42;
> +  ++n; // { dg-warning "read-only" }
> +       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
> +}
> +
> +template<class T>
> +struct A { // { dg-error "instantiating erroneous template" }
> +  void f(typename A::type); // { dg-warning "does not name a type" }
> +		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
> +};
> +
> +template<class T>
> +struct B {
> +  void f() {   // { dg-error "instantiating erroneous template" }
> +    this->g(); // { dg-warning "no member" }
> +	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
> +  }
> +};
> +
> +int main() {
> +  f<int>(); // { dg-message "required from here" }
> +  A<int> a; // { dg-message "required from here" }
> +	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
> +  B<int> b; // { dg-bogus "" }
> +  b.f();    // { dg-message "required from here" }
> +}
> diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
> new file mode 100644
> index 00000000000..3d3462c956e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
> @@ -0,0 +1,30 @@
> +// PR c++/116064
> +// { dg-additional-options "-fpermissive -Wno-template-body" }
> +// Like permissive-error1a.C but verify -Wno-template-body suppresses
> +// the downgraded warnings.
> +
> +template<class T>
> +void f() {  // { dg-error "instantiating erroneous template" }
> +  const int n = 42;
> +  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
> +}
> +
> +template<class T>
> +struct A { // { dg-error "instantiating erroneous template" }
> +  void f(typename A::type); // { dg-message "first error appeared here" "" { target *-*-* } }
> +};
> +
> +template<class T>
> +struct B {
> +  void f() {   // { dg-error "instantiating erroneous template" }
> +    this->g(); // { dg-message "first error appeared here" "" { target *-*-* } }
> +  }
> +};
> +
> +int main() {
> +  f<int>(); // { dg-message "required from here" }
> +  A<int> a; // { dg-message "required from here" }
> +	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
> +  B<int> b; // { dg-bogus "" }
> +  b.f();    // { dg-message "required from here" }
> +}
> diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
> index a3fbc17f163..c5cbde521ad 100644
> --- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
> +++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
> @@ -1,5 +1,4 @@
>  // { dg-do assemble  }
> -// { dg-options "-fpermissive -w" }
>  // Origin: Mark Mitchell <mark@codesourcery.com>
>  
>  char foo[26];
> -- 
> 2.46.0.39.g891ee3b9db
  
Jason Merrill Aug. 5, 2024, 3:35 p.m. UTC | #4
On 8/2/24 4:18 PM, Patrick Palka wrote:
> On Fri, 2 Aug 2024, Patrick Palka wrote:
> 
>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>
>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>> In recent versions of GCC we've been diagnosing more and more kinds of
>>>> errors inside a template ahead of time.  This is a largely good thing
>>>> because it catches bugs, typos, dead code etc sooner.
>>>>
>>>> But if the template never gets instantiated then such errors are
>>>> harmless, and can be inconvenient to work around if say the code in
>>>> question is third party and in maintenence mode.  So it'd be useful to
>>>
>>> "maintenance"
>>
>> Fixed
>>
>>>
>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>> index d80bac822ba..0bb0a482e28 100644
>>>> --- a/gcc/cp/error.cc
>>>> +++ b/gcc/cp/error.cc
>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
>>>> format_postprocessor
>>>>      deferred_printed_type m_type_b;
>>>>    };
>>>>    +/* A map from TEMPLATE_DECL to the location of the first error (if any)
>>>> +   within the template that we permissivly downgraded to a warning.  */
>>>
>>> "permissively"
>>
>> Fixed
>>
>>>
>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>> +
>>>> +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
>>>> +
>>>> +   In -fpermissive mode we downgrade errors within a template to
>>>> +   warnings, and only issue an error if we later need to instantiate
>>>> +   the template.  */
>>>> +
>>>> +static void
>>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>>> +			   diagnostic_info *diagnostic)
>>>> +{
>>>> +  tree ti;
>>>> +  if (diagnostic->kind == DK_ERROR
>>>> +      && context->m_permissive
>>>> +      && !current_instantiation ()
>>>> +      && in_template_context
>>>> +      && (ti = get_template_info (current_scope ())))
>>>> +    {
>>>> +      if (!relaxed_template_errors)
>>>> +	relaxed_template_errors = new relaxed_template_errors_t;
>>>> +
>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>> +	relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
>>>> +      diagnostic->kind = DK_WARNING;
>>>
>>> Rather than check m_permissive directly and downgrade to DK_WARNING, how about
>>> downgrading to DK_PERMERROR?  That way people will get the [-fpermissive]
>>> clue.
>>>
>>> ...though I suppose DK_PERMERROR doesn't work where you call this hook in
>>> report_diagnostic, at which point we've already reassigned it into DK_WARNING
>>> or DK_ERROR in diagnostic_impl.
>>>
>>> But we could still set diagnostic->option_index even for DK_ERROR, whether to
>>> context->m_opt_permissive or to its own warning flag, perhaps
>>> -Wno-template-body?
>>
>> Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
>> option_index to it for each downgraded error.  Thus -permissive
>> -Wno-template-body would suppress the downgraded warnings entirely, and
>> only issue a generic error upon instantiation of the erroneous template.
> 
> ... or did you have in mind to set option_index even when not using
> -fpermissive so that eligible non-downgraded errors get the
> [-fpermissive] or [-Wtemplate-body] hint as well?

Yes.

> IMHO I'm not sure that'd be worth the extra noise since the vast
> majority of users appreciate and expect errors to get diagnosed inside
> templates.

But people trying to build legacy code should appreciate the pointer for 
how to make it compile, as with other permerrors.

> And on second thought I'm not sure what extra value a new warning flag
> adds either.  I can't think of a good reason why one would use
> -fpermissive -Wno-template-body?

One would use -Wno-template-body (or -Wno-error=template-body) without 
-fpermissive, like with the various permerror_opt cases.

Jason
  
Patrick Palka Aug. 5, 2024, 5:14 p.m. UTC | #5
On Mon, 5 Aug 2024, Jason Merrill wrote:

> On 8/2/24 4:18 PM, Patrick Palka wrote:
> > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > 
> > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > 
> > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > In recent versions of GCC we've been diagnosing more and more kinds of
> > > > > errors inside a template ahead of time.  This is a largely good thing
> > > > > because it catches bugs, typos, dead code etc sooner.
> > > > > 
> > > > > But if the template never gets instantiated then such errors are
> > > > > harmless, and can be inconvenient to work around if say the code in
> > > > > question is third party and in maintenence mode.  So it'd be useful to
> > > > 
> > > > "maintenance"
> > > 
> > > Fixed
> > > 
> > > > 
> > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > --- a/gcc/cp/error.cc
> > > > > +++ b/gcc/cp/error.cc
> > > > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
> > > > > format_postprocessor
> > > > >      deferred_printed_type m_type_b;
> > > > >    };
> > > > >    +/* A map from TEMPLATE_DECL to the location of the first error (if
> > > > > any)
> > > > > +   within the template that we permissivly downgraded to a warning.
> > > > > */
> > > > 
> > > > "permissively"
> > > 
> > > Fixed
> > > 
> > > > 
> > > > > +relaxed_template_errors_t *relaxed_template_errors;
> > > > > +
> > > > > +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> > > > > +
> > > > > +   In -fpermissive mode we downgrade errors within a template to
> > > > > +   warnings, and only issue an error if we later need to instantiate
> > > > > +   the template.  */
> > > > > +
> > > > > +static void
> > > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > > +			   diagnostic_info *diagnostic)
> > > > > +{
> > > > > +  tree ti;
> > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > +      && context->m_permissive
> > > > > +      && !current_instantiation ()
> > > > > +      && in_template_context
> > > > > +      && (ti = get_template_info (current_scope ())))
> > > > > +    {
> > > > > +      if (!relaxed_template_errors)
> > > > > +	relaxed_template_errors = new relaxed_template_errors_t;
> > > > > +
> > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > +	relaxed_template_errors->put (tmpl,
> > > > > diagnostic->richloc->get_loc ());
> > > > > +      diagnostic->kind = DK_WARNING;
> > > > 
> > > > Rather than check m_permissive directly and downgrade to DK_WARNING, how
> > > > about
> > > > downgrading to DK_PERMERROR?  That way people will get the
> > > > [-fpermissive]
> > > > clue.
> > > > 
> > > > ...though I suppose DK_PERMERROR doesn't work where you call this hook
> > > > in
> > > > report_diagnostic, at which point we've already reassigned it into
> > > > DK_WARNING
> > > > or DK_ERROR in diagnostic_impl.
> > > > 
> > > > But we could still set diagnostic->option_index even for DK_ERROR,
> > > > whether to
> > > > context->m_opt_permissive or to its own warning flag, perhaps
> > > > -Wno-template-body?
> > > 
> > > Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
> > > option_index to it for each downgraded error.  Thus -permissive
> > > -Wno-template-body would suppress the downgraded warnings entirely, and
> > > only issue a generic error upon instantiation of the erroneous template.
> > 
> > ... or did you have in mind to set option_index even when not using
> > -fpermissive so that eligible non-downgraded errors get the
> > [-fpermissive] or [-Wtemplate-body] hint as well?
> 
> Yes.
> 
> > IMHO I'm not sure that'd be worth the extra noise since the vast
> > majority of users appreciate and expect errors to get diagnosed inside
> > templates.
> 
> But people trying to build legacy code should appreciate the pointer for how
> to make it compile, as with other permerrors.
> 
> > And on second thought I'm not sure what extra value a new warning flag
> > adds either.  I can't think of a good reason why one would use
> > -fpermissive -Wno-template-body?
> 
> One would use -Wno-template-body (or -Wno-error=template-body) without
> -fpermissive, like with the various permerror_opt cases.

Since compiling legacy/unmaintained code is the only plausible use case,
why have a dedicated warning flag instead of just recommending -fpermissive
when compiling legacy code?  I don't quite understand the motivation for
adding a new permerror_opt flag for this class of errors.

-Wnarrowing is an existing permerror_opt flag, but I can imagine it's
useful to pass -Wno-error=narrowing etc when incrementally migrating
C / C++98 code to modern C++ where you don't want any conformance errors
allowed by -fpermissive to sneak in.  So being able to narrowly control
this class of errors seems useful, so a dedicated flag makes sense.

But there's no parallel for -Wtemplate-body here, since by assumption
the code base is unmaintained / immutable.  Otherwise the more proper
fix would be to just fix and/or delete the uninstantiated erroneous
template.  If say you're #including a legacy header that has such
errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
the #include should be totally fine too.

I just don't see the use case for being able to narrowly control this
class of errors that justifies the extra implementation complexity
(specifically for properly detecting -Wno-error=template-body in the
callback hook)?

> 
> Jason
> 
>
  
Jason Merrill Aug. 5, 2024, 5:42 p.m. UTC | #6
On 8/5/24 1:14 PM, Patrick Palka wrote:
> On Mon, 5 Aug 2024, Jason Merrill wrote:
> 
>> On 8/2/24 4:18 PM, Patrick Palka wrote:
>>> On Fri, 2 Aug 2024, Patrick Palka wrote:
>>>
>>>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>>>
>>>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>>>> In recent versions of GCC we've been diagnosing more and more kinds of
>>>>>> errors inside a template ahead of time.  This is a largely good thing
>>>>>> because it catches bugs, typos, dead code etc sooner.
>>>>>>
>>>>>> But if the template never gets instantiated then such errors are
>>>>>> harmless, and can be inconvenient to work around if say the code in
>>>>>> question is third party and in maintenence mode.  So it'd be useful to
>>>>>
>>>>> "maintenance"
>>>>
>>>> Fixed
>>>>
>>>>>
>>>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>>>> index d80bac822ba..0bb0a482e28 100644
>>>>>> --- a/gcc/cp/error.cc
>>>>>> +++ b/gcc/cp/error.cc
>>>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
>>>>>> format_postprocessor
>>>>>>       deferred_printed_type m_type_b;
>>>>>>     };
>>>>>>     +/* A map from TEMPLATE_DECL to the location of the first error (if
>>>>>> any)
>>>>>> +   within the template that we permissivly downgraded to a warning.
>>>>>> */
>>>>>
>>>>> "permissively"
>>>>
>>>> Fixed
>>>>
>>>>>
>>>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>>>> +
>>>>>> +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
>>>>>> +
>>>>>> +   In -fpermissive mode we downgrade errors within a template to
>>>>>> +   warnings, and only issue an error if we later need to instantiate
>>>>>> +   the template.  */
>>>>>> +
>>>>>> +static void
>>>>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>>>>> +			   diagnostic_info *diagnostic)
>>>>>> +{
>>>>>> +  tree ti;
>>>>>> +  if (diagnostic->kind == DK_ERROR
>>>>>> +      && context->m_permissive
>>>>>> +      && !current_instantiation ()
>>>>>> +      && in_template_context
>>>>>> +      && (ti = get_template_info (current_scope ())))
>>>>>> +    {
>>>>>> +      if (!relaxed_template_errors)
>>>>>> +	relaxed_template_errors = new relaxed_template_errors_t;
>>>>>> +
>>>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>>>> +	relaxed_template_errors->put (tmpl,
>>>>>> diagnostic->richloc->get_loc ());
>>>>>> +      diagnostic->kind = DK_WARNING;
>>>>>
>>>>> Rather than check m_permissive directly and downgrade to DK_WARNING, how
>>>>> about
>>>>> downgrading to DK_PERMERROR?  That way people will get the
>>>>> [-fpermissive]
>>>>> clue.
>>>>>
>>>>> ...though I suppose DK_PERMERROR doesn't work where you call this hook
>>>>> in
>>>>> report_diagnostic, at which point we've already reassigned it into
>>>>> DK_WARNING
>>>>> or DK_ERROR in diagnostic_impl.
>>>>>
>>>>> But we could still set diagnostic->option_index even for DK_ERROR,
>>>>> whether to
>>>>> context->m_opt_permissive or to its own warning flag, perhaps
>>>>> -Wno-template-body?
>>>>
>>>> Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
>>>> option_index to it for each downgraded error.  Thus -permissive
>>>> -Wno-template-body would suppress the downgraded warnings entirely, and
>>>> only issue a generic error upon instantiation of the erroneous template.
>>>
>>> ... or did you have in mind to set option_index even when not using
>>> -fpermissive so that eligible non-downgraded errors get the
>>> [-fpermissive] or [-Wtemplate-body] hint as well?
>>
>> Yes.
>>
>>> IMHO I'm not sure that'd be worth the extra noise since the vast
>>> majority of users appreciate and expect errors to get diagnosed inside
>>> templates.
>>
>> But people trying to build legacy code should appreciate the pointer for how
>> to make it compile, as with other permerrors.
>>
>>> And on second thought I'm not sure what extra value a new warning flag
>>> adds either.  I can't think of a good reason why one would use
>>> -fpermissive -Wno-template-body?
>>
>> One would use -Wno-template-body (or -Wno-error=template-body) without
>> -fpermissive, like with the various permerror_opt cases.
> 
> Since compiling legacy/unmaintained code is the only plausible use case,
> why have a dedicated warning flag instead of just recommending -fpermissive
> when compiling legacy code?  I don't quite understand the motivation for
> adding a new permerror_opt flag for this class of errors.

It seems to me an interesting class of errors, but I don't mind leaving 
it under just -fpermissive if you prefer.

> -Wnarrowing is an existing permerror_opt flag, but I can imagine it's
> useful to pass -Wno-error=narrowing etc when incrementally migrating
> C / C++98 code to modern C++ where you don't want any conformance errors
> allowed by -fpermissive to sneak in.  So being able to narrowly control
> this class of errors seems useful, so a dedicated flag makes sense.
> 
> But there's no parallel for -Wtemplate-body here, since by assumption
> the code base is unmaintained / immutable.  Otherwise the more proper
> fix would be to just fix and/or delete the uninstantiated erroneous
> template.  If say you're #including a legacy header that has such
> errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
> the #include should be totally fine too.
> 
> I just don't see the use case for being able to narrowly control this
> class of errors that justifies the extra implementation complexity
> (specifically for properly detecting -Wno-error=template-body in the
> callback hook)?

The hook shouldn't need to do anything special; report_diagnostic 
handles -Wno-error=whatever.

Jason
  
Patrick Palka Aug. 5, 2024, 7:47 p.m. UTC | #7
On Mon, 5 Aug 2024, Jason Merrill wrote:

> On 8/5/24 1:14 PM, Patrick Palka wrote:
> > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > 
> > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > 
> > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > In recent versions of GCC we've been diagnosing more and more
> > > > > > > kinds of
> > > > > > > errors inside a template ahead of time.  This is a largely good
> > > > > > > thing
> > > > > > > because it catches bugs, typos, dead code etc sooner.
> > > > > > > 
> > > > > > > But if the template never gets instantiated then such errors are
> > > > > > > harmless, and can be inconvenient to work around if say the code
> > > > > > > in
> > > > > > > question is third party and in maintenence mode.  So it'd be
> > > > > > > useful to
> > > > > > 
> > > > > > "maintenance"
> > > > > 
> > > > > Fixed
> > > > > 
> > > > > > 
> > > > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > --- a/gcc/cp/error.cc
> > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
> > > > > > > format_postprocessor
> > > > > > >       deferred_printed_type m_type_b;
> > > > > > >     };
> > > > > > >     +/* A map from TEMPLATE_DECL to the location of the first
> > > > > > > error (if
> > > > > > > any)
> > > > > > > +   within the template that we permissivly downgraded to a
> > > > > > > warning.
> > > > > > > */
> > > > > > 
> > > > > > "permissively"
> > > > > 
> > > > > Fixed
> > > > > 
> > > > > > 
> > > > > > > +relaxed_template_errors_t *relaxed_template_errors;
> > > > > > > +
> > > > > > > +/* Callback function
> > > > > > > diagnostic_context::m_adjust_diagnostic_info.
> > > > > > > +
> > > > > > > +   In -fpermissive mode we downgrade errors within a template to
> > > > > > > +   warnings, and only issue an error if we later need to
> > > > > > > instantiate
> > > > > > > +   the template.  */
> > > > > > > +
> > > > > > > +static void
> > > > > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > > > > +			   diagnostic_info *diagnostic)
> > > > > > > +{
> > > > > > > +  tree ti;
> > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > +      && context->m_permissive
> > > > > > > +      && !current_instantiation ()
> > > > > > > +      && in_template_context
> > > > > > > +      && (ti = get_template_info (current_scope ())))
> > > > > > > +    {
> > > > > > > +      if (!relaxed_template_errors)
> > > > > > > +	relaxed_template_errors = new relaxed_template_errors_t;
> > > > > > > +
> > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > > > +	relaxed_template_errors->put (tmpl,
> > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > 
> > > > > > Rather than check m_permissive directly and downgrade to DK_WARNING,
> > > > > > how
> > > > > > about
> > > > > > downgrading to DK_PERMERROR?  That way people will get the
> > > > > > [-fpermissive]
> > > > > > clue.
> > > > > > 
> > > > > > ...though I suppose DK_PERMERROR doesn't work where you call this
> > > > > > hook
> > > > > > in
> > > > > > report_diagnostic, at which point we've already reassigned it into
> > > > > > DK_WARNING
> > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > 
> > > > > > But we could still set diagnostic->option_index even for DK_ERROR,
> > > > > > whether to
> > > > > > context->m_opt_permissive or to its own warning flag, perhaps
> > > > > > -Wno-template-body?
> > > > > 
> > > > > Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
> > > > > option_index to it for each downgraded error.  Thus -permissive
> > > > > -Wno-template-body would suppress the downgraded warnings entirely,
> > > > > and
> > > > > only issue a generic error upon instantiation of the erroneous
> > > > > template.
> > > > 
> > > > ... or did you have in mind to set option_index even when not using
> > > > -fpermissive so that eligible non-downgraded errors get the
> > > > [-fpermissive] or [-Wtemplate-body] hint as well?
> > > 
> > > Yes.
> > > 
> > > > IMHO I'm not sure that'd be worth the extra noise since the vast
> > > > majority of users appreciate and expect errors to get diagnosed inside
> > > > templates.
> > > 
> > > But people trying to build legacy code should appreciate the pointer for
> > > how
> > > to make it compile, as with other permerrors.
> > > 
> > > > And on second thought I'm not sure what extra value a new warning flag
> > > > adds either.  I can't think of a good reason why one would use
> > > > -fpermissive -Wno-template-body?
> > > 
> > > One would use -Wno-template-body (or -Wno-error=template-body) without
> > > -fpermissive, like with the various permerror_opt cases.
> > 
> > Since compiling legacy/unmaintained code is the only plausible use case,
> > why have a dedicated warning flag instead of just recommending -fpermissive
> > when compiling legacy code?  I don't quite understand the motivation for
> > adding a new permerror_opt flag for this class of errors.
> 
> It seems to me an interesting class of errors, but I don't mind leaving it
> under just -fpermissive if you prefer.
> 
> > -Wnarrowing is an existing permerror_opt flag, but I can imagine it's
> > useful to pass -Wno-error=narrowing etc when incrementally migrating
> > C / C++98 code to modern C++ where you don't want any conformance errors
> > allowed by -fpermissive to sneak in.  So being able to narrowly control
> > this class of errors seems useful, so a dedicated flag makes sense.
> > 
> > But there's no parallel for -Wtemplate-body here, since by assumption
> > the code base is unmaintained / immutable.  Otherwise the more proper
> > fix would be to just fix and/or delete the uninstantiated erroneous
> > template.  If say you're #including a legacy header that has such
> > errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
> > the #include should be totally fine too.

I just realized #pragma GCC diagnostic warning "-fpermissive" etc
doesn't actually work since -fpermissive isn't a warning flag.  So
having a dedicated flag for the class of errors has at least one clear
benefit -- we can use #pragmas to selectively disable the errors now.

> > 
> > I just don't see the use case for being able to narrowly control this
> > class of errors that justifies the extra implementation complexity
> > (specifically for properly detecting -Wno-error=template-body in the
> > callback hook)?
> 
> The hook shouldn't need to do anything special; report_diagnostic handles
> -Wno-error=whatever.

The issue was that the callback has to know in advance whether
-Wno-error=template-body is active so that it can flag the template as
having a relaxed error.  Checking !warning_enabled_at wasn't enough
because it will return true even for -Wno-error=template-body.  I
think we just need to check diagnostic_enabled directly, like so?

Bootstrap and regtest on x86_64-pc-linux-gnu in progress.

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

As an example, permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (relaxed_template_errors_t): Declare.
	(related_template_errors): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template pattern containing
	a permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
	public.
	(diagnostic_context::m_adjust_diagnostic_info): New data member.
	* doc/invoke.texi (-Wno-template-body): Document.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
	* g++.dg/template/permissive-error1c.C: New test.
---
 gcc/c-family/c.opt                            |  4 +
 gcc/cp/cp-tree.h                              |  5 ++
 gcc/cp/error.cc                               | 81 +++++++++++++++++++
 gcc/cp/pt.cc                                  | 30 +++++++
 gcc/diagnostic.cc                             |  4 +
 gcc/diagnostic.h                              |  8 +-
 gcc/doc/invoke.texi                           | 11 ++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 +++++
 .../g++.dg/template/permissive-error1a.C      | 33 ++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 +++++++
 .../g++.dg/template/permissive-error1c.C      | 33 ++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 14 files changed, 259 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a52682d835c..44117ba713c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..ffdc0ba38ae 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7189,6 +7189,11 @@ extern bool pedwarn_cxx98                       (location_t, int, const char *,
 extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
+using relaxed_template_errors_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern relaxed_template_errors_t *relaxed_template_errors;
+extern bool cp_seen_error			(void);
+#define seen_error cp_seen_error
 
 /* in except.cc */
 extern void init_terminate_fn			(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..56587170609 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,86 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECL to the location of the first error (if any)
+   within the template that we permissively downgraded to a warning.  */
+
+relaxed_template_errors_t *relaxed_template_errors;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	/* Determine if this error should/will be downgraded to a warning,
+	   or suppressed.  */
+	diagnostic_info diagnostic_copy = *diagnostic;
+	if (/* -fpermissive  */
+	    context->m_permissive
+	    /* -Wno-template-body  */
+	    || !context->diagnostic_enabled (&diagnostic_copy)
+	    /* -Wno-error=template-body  */
+	    || diagnostic_copy.kind == DK_WARNING)
+	  {
+	    diagnostic->kind = DK_WARNING;
+	    if (!relaxed_template_errors)
+	      relaxed_template_errors = new relaxed_template_errors_t;
+	    if (!relaxed_template_errors->get (tmpl))
+	      {
+		/* Remember that this template had a relaxed error
+		   so that we'll issue a hard error upon instantiation.  */
+		location_t error_loc = diagnostic->richloc->get_loc ();
+		relaxed_template_errors->put (tmpl, error_loc);
+	      }
+	  }
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  /* cp-tree.h #defines seen_error to cp_seen_error.  */
+#undef seen_error
+  if (seen_error ())
+    return true;
+
+  if (relaxed_template_errors)
+    if (tree tmpl = get_current_template ())
+      if (relaxed_template_errors->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +267,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..3aa64b3d41c 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,22 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	TYPE_BEING_DEFINED (type) = false;
+	CLASSTYPE_ERRONEOUS (type) = true;
+	return type;
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27307,20 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	return d;
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..e90c121cb95 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..426cdfb3a46 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -581,6 +581,8 @@ public:
 			  const char *, const char *, va_list *,
 			  diagnostic_t) ATTRIBUTE_GCC_DIAG(7,0);
 
+  bool diagnostic_enabled (diagnostic_info *diagnostic);
+
 private:
   bool includes_seen_p (const line_map_ordinary *map);
 
@@ -593,8 +595,6 @@ private:
 
   void error_recursion () ATTRIBUTE_NORETURN;
 
-  bool diagnostic_enabled (diagnostic_info *diagnostic);
-
   void get_any_inlining_info (diagnostic_info *diagnostic);
 
   void show_locus (const rich_location &richloc,
@@ -699,6 +699,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..348da934cfc 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..c4b5f760ff5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,33 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..7fb74300ddc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// the diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-message "first error appeared here" "" { target *-*-* } }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-message "first error appeared here" "" { target *-*-* } }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 00000000000..c3f79e0198c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,33 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  
Jason Merrill Aug. 5, 2024, 9:26 p.m. UTC | #8
On 8/5/24 3:47 PM, Patrick Palka wrote:
> On Mon, 5 Aug 2024, Jason Merrill wrote:
> 
>> On 8/5/24 1:14 PM, Patrick Palka wrote:
>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>
>>>> On 8/2/24 4:18 PM, Patrick Palka wrote:
>>>>> On Fri, 2 Aug 2024, Patrick Palka wrote:
>>>>>
>>>>>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>>>>>
>>>>>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>>>>>> In recent versions of GCC we've been diagnosing more and more
>>>>>>>> kinds of
>>>>>>>> errors inside a template ahead of time.  This is a largely good
>>>>>>>> thing
>>>>>>>> because it catches bugs, typos, dead code etc sooner.
>>>>>>>>
>>>>>>>> But if the template never gets instantiated then such errors are
>>>>>>>> harmless, and can be inconvenient to work around if say the code
>>>>>>>> in
>>>>>>>> question is third party and in maintenence mode.  So it'd be
>>>>>>>> useful to
>>>>>>>
>>>>>>> "maintenance"
>>>>>>
>>>>>> Fixed
>>>>>>
>>>>>>>
>>>>>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>>>>>> index d80bac822ba..0bb0a482e28 100644
>>>>>>>> --- a/gcc/cp/error.cc
>>>>>>>> +++ b/gcc/cp/error.cc
>>>>>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
>>>>>>>> format_postprocessor
>>>>>>>>        deferred_printed_type m_type_b;
>>>>>>>>      };
>>>>>>>>      +/* A map from TEMPLATE_DECL to the location of the first
>>>>>>>> error (if
>>>>>>>> any)
>>>>>>>> +   within the template that we permissivly downgraded to a
>>>>>>>> warning.
>>>>>>>> */
>>>>>>>
>>>>>>> "permissively"
>>>>>>
>>>>>> Fixed
>>>>>>
>>>>>>>
>>>>>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>>>>>> +
>>>>>>>> +/* Callback function
>>>>>>>> diagnostic_context::m_adjust_diagnostic_info.
>>>>>>>> +
>>>>>>>> +   In -fpermissive mode we downgrade errors within a template to
>>>>>>>> +   warnings, and only issue an error if we later need to
>>>>>>>> instantiate
>>>>>>>> +   the template.  */
>>>>>>>> +
>>>>>>>> +static void
>>>>>>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>>>>>>> +			   diagnostic_info *diagnostic)
>>>>>>>> +{
>>>>>>>> +  tree ti;
>>>>>>>> +  if (diagnostic->kind == DK_ERROR
>>>>>>>> +      && context->m_permissive
>>>>>>>> +      && !current_instantiation ()
>>>>>>>> +      && in_template_context
>>>>>>>> +      && (ti = get_template_info (current_scope ())))
>>>>>>>> +    {
>>>>>>>> +      if (!relaxed_template_errors)
>>>>>>>> +	relaxed_template_errors = new relaxed_template_errors_t;
>>>>>>>> +
>>>>>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>>>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>>>>>> +	relaxed_template_errors->put (tmpl,
>>>>>>>> diagnostic->richloc->get_loc ());
>>>>>>>> +      diagnostic->kind = DK_WARNING;
>>>>>>>
>>>>>>> Rather than check m_permissive directly and downgrade to DK_WARNING,
>>>>>>> how
>>>>>>> about
>>>>>>> downgrading to DK_PERMERROR?  That way people will get the
>>>>>>> [-fpermissive]
>>>>>>> clue.
>>>>>>>
>>>>>>> ...though I suppose DK_PERMERROR doesn't work where you call this
>>>>>>> hook
>>>>>>> in
>>>>>>> report_diagnostic, at which point we've already reassigned it into
>>>>>>> DK_WARNING
>>>>>>> or DK_ERROR in diagnostic_impl.
>>>>>>>
>>>>>>> But we could still set diagnostic->option_index even for DK_ERROR,
>>>>>>> whether to
>>>>>>> context->m_opt_permissive or to its own warning flag, perhaps
>>>>>>> -Wno-template-body?
>>>>>>
>>>>>> Fixed by adding an enabled-by-default -Wtemplate-body flag and setting
>>>>>> option_index to it for each downgraded error.  Thus -permissive
>>>>>> -Wno-template-body would suppress the downgraded warnings entirely,
>>>>>> and
>>>>>> only issue a generic error upon instantiation of the erroneous
>>>>>> template.
>>>>>
>>>>> ... or did you have in mind to set option_index even when not using
>>>>> -fpermissive so that eligible non-downgraded errors get the
>>>>> [-fpermissive] or [-Wtemplate-body] hint as well?
>>>>
>>>> Yes.
>>>>
>>>>> IMHO I'm not sure that'd be worth the extra noise since the vast
>>>>> majority of users appreciate and expect errors to get diagnosed inside
>>>>> templates.
>>>>
>>>> But people trying to build legacy code should appreciate the pointer for
>>>> how
>>>> to make it compile, as with other permerrors.
>>>>
>>>>> And on second thought I'm not sure what extra value a new warning flag
>>>>> adds either.  I can't think of a good reason why one would use
>>>>> -fpermissive -Wno-template-body?
>>>>
>>>> One would use -Wno-template-body (or -Wno-error=template-body) without
>>>> -fpermissive, like with the various permerror_opt cases.
>>>
>>> Since compiling legacy/unmaintained code is the only plausible use case,
>>> why have a dedicated warning flag instead of just recommending -fpermissive
>>> when compiling legacy code?  I don't quite understand the motivation for
>>> adding a new permerror_opt flag for this class of errors.
>>
>> It seems to me an interesting class of errors, but I don't mind leaving it
>> under just -fpermissive if you prefer.
>>
>>> -Wnarrowing is an existing permerror_opt flag, but I can imagine it's
>>> useful to pass -Wno-error=narrowing etc when incrementally migrating
>>> C / C++98 code to modern C++ where you don't want any conformance errors
>>> allowed by -fpermissive to sneak in.  So being able to narrowly control
>>> this class of errors seems useful, so a dedicated flag makes sense.
>>>
>>> But there's no parallel for -Wtemplate-body here, since by assumption
>>> the code base is unmaintained / immutable.  Otherwise the more proper
>>> fix would be to just fix and/or delete the uninstantiated erroneous
>>> template.  If say you're #including a legacy header that has such
>>> errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
>>> the #include should be totally fine too.
> 
> I just realized #pragma GCC diagnostic warning "-fpermissive" etc
> doesn't actually work since -fpermissive isn't a warning flag.  So
> having a dedicated flag for the class of errors has at least one clear
> benefit -- we can use #pragmas to selectively disable the errors now.
> 
>>>
>>> I just don't see the use case for being able to narrowly control this
>>> class of errors that justifies the extra implementation complexity
>>> (specifically for properly detecting -Wno-error=template-body in the
>>> callback hook)?
>>
>> The hook shouldn't need to do anything special; report_diagnostic handles
>> -Wno-error=whatever.
> 
> The issue was that the callback has to know in advance whether
> -Wno-error=template-body is active so that it can flag the template as
> having a relaxed error.  Checking !warning_enabled_at wasn't enough
> because it will return true even for -Wno-error=template-body.  I
> think we just need to check diagnostic_enabled directly, like so?

Why not always flag it?  It doesn't seem harmful to give the 
"instatiating erroneous template" error later even if we gave a hard 
error during parsing.

Jason
  
Patrick Palka Aug. 5, 2024, 10:09 p.m. UTC | #9
On Mon, 5 Aug 2024, Jason Merrill wrote:

> On 8/5/24 3:47 PM, Patrick Palka wrote:
> > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/5/24 1:14 PM, Patrick Palka wrote:
> > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > 
> > > > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > > > 
> > > > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > > > 
> > > > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > > > In recent versions of GCC we've been diagnosing more and more
> > > > > > > > > kinds of
> > > > > > > > > errors inside a template ahead of time.  This is a largely
> > > > > > > > > good
> > > > > > > > > thing
> > > > > > > > > because it catches bugs, typos, dead code etc sooner.
> > > > > > > > > 
> > > > > > > > > But if the template never gets instantiated then such errors
> > > > > > > > > are
> > > > > > > > > harmless, and can be inconvenient to work around if say the
> > > > > > > > > code
> > > > > > > > > in
> > > > > > > > > question is third party and in maintenence mode.  So it'd be
> > > > > > > > > useful to
> > > > > > > > 
> > > > > > > > "maintenance"
> > > > > > > 
> > > > > > > Fixed
> > > > > > > 
> > > > > > > > 
> > > > > > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > > > --- a/gcc/cp/error.cc
> > > > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
> > > > > > > > > format_postprocessor
> > > > > > > > >        deferred_printed_type m_type_b;
> > > > > > > > >      };
> > > > > > > > >      +/* A map from TEMPLATE_DECL to the location of the first
> > > > > > > > > error (if
> > > > > > > > > any)
> > > > > > > > > +   within the template that we permissivly downgraded to a
> > > > > > > > > warning.
> > > > > > > > > */
> > > > > > > > 
> > > > > > > > "permissively"
> > > > > > > 
> > > > > > > Fixed
> > > > > > > 
> > > > > > > > 
> > > > > > > > > +relaxed_template_errors_t *relaxed_template_errors;
> > > > > > > > > +
> > > > > > > > > +/* Callback function
> > > > > > > > > diagnostic_context::m_adjust_diagnostic_info.
> > > > > > > > > +
> > > > > > > > > +   In -fpermissive mode we downgrade errors within a template
> > > > > > > > > to
> > > > > > > > > +   warnings, and only issue an error if we later need to
> > > > > > > > > instantiate
> > > > > > > > > +   the template.  */
> > > > > > > > > +
> > > > > > > > > +static void
> > > > > > > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > > > > > > +			   diagnostic_info *diagnostic)
> > > > > > > > > +{
> > > > > > > > > +  tree ti;
> > > > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > > > +      && context->m_permissive
> > > > > > > > > +      && !current_instantiation ()
> > > > > > > > > +      && in_template_context
> > > > > > > > > +      && (ti = get_template_info (current_scope ())))
> > > > > > > > > +    {
> > > > > > > > > +      if (!relaxed_template_errors)
> > > > > > > > > +	relaxed_template_errors = new
> > > > > > > > > relaxed_template_errors_t;
> > > > > > > > > +
> > > > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > > > > > +	relaxed_template_errors->put (tmpl,
> > > > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > > > 
> > > > > > > > Rather than check m_permissive directly and downgrade to
> > > > > > > > DK_WARNING,
> > > > > > > > how
> > > > > > > > about
> > > > > > > > downgrading to DK_PERMERROR?  That way people will get the
> > > > > > > > [-fpermissive]
> > > > > > > > clue.
> > > > > > > > 
> > > > > > > > ...though I suppose DK_PERMERROR doesn't work where you call
> > > > > > > > this
> > > > > > > > hook
> > > > > > > > in
> > > > > > > > report_diagnostic, at which point we've already reassigned it
> > > > > > > > into
> > > > > > > > DK_WARNING
> > > > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > > > 
> > > > > > > > But we could still set diagnostic->option_index even for
> > > > > > > > DK_ERROR,
> > > > > > > > whether to
> > > > > > > > context->m_opt_permissive or to its own warning flag, perhaps
> > > > > > > > -Wno-template-body?
> > > > > > > 
> > > > > > > Fixed by adding an enabled-by-default -Wtemplate-body flag and
> > > > > > > setting
> > > > > > > option_index to it for each downgraded error.  Thus -permissive
> > > > > > > -Wno-template-body would suppress the downgraded warnings
> > > > > > > entirely,
> > > > > > > and
> > > > > > > only issue a generic error upon instantiation of the erroneous
> > > > > > > template.
> > > > > > 
> > > > > > ... or did you have in mind to set option_index even when not using
> > > > > > -fpermissive so that eligible non-downgraded errors get the
> > > > > > [-fpermissive] or [-Wtemplate-body] hint as well?
> > > > > 
> > > > > Yes.
> > > > > 
> > > > > > IMHO I'm not sure that'd be worth the extra noise since the vast
> > > > > > majority of users appreciate and expect errors to get diagnosed
> > > > > > inside
> > > > > > templates.
> > > > > 
> > > > > But people trying to build legacy code should appreciate the pointer
> > > > > for
> > > > > how
> > > > > to make it compile, as with other permerrors.
> > > > > 
> > > > > > And on second thought I'm not sure what extra value a new warning
> > > > > > flag
> > > > > > adds either.  I can't think of a good reason why one would use
> > > > > > -fpermissive -Wno-template-body?
> > > > > 
> > > > > One would use -Wno-template-body (or -Wno-error=template-body) without
> > > > > -fpermissive, like with the various permerror_opt cases.
> > > > 
> > > > Since compiling legacy/unmaintained code is the only plausible use case,
> > > > why have a dedicated warning flag instead of just recommending
> > > > -fpermissive
> > > > when compiling legacy code?  I don't quite understand the motivation for
> > > > adding a new permerror_opt flag for this class of errors.
> > > 
> > > It seems to me an interesting class of errors, but I don't mind leaving it
> > > under just -fpermissive if you prefer.
> > > 
> > > > -Wnarrowing is an existing permerror_opt flag, but I can imagine it's
> > > > useful to pass -Wno-error=narrowing etc when incrementally migrating
> > > > C / C++98 code to modern C++ where you don't want any conformance errors
> > > > allowed by -fpermissive to sneak in.  So being able to narrowly control
> > > > this class of errors seems useful, so a dedicated flag makes sense.
> > > > 
> > > > But there's no parallel for -Wtemplate-body here, since by assumption
> > > > the code base is unmaintained / immutable.  Otherwise the more proper
> > > > fix would be to just fix and/or delete the uninstantiated erroneous
> > > > template.  If say you're #including a legacy header that has such
> > > > errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
> > > > the #include should be totally fine too.
> > 
> > I just realized #pragma GCC diagnostic warning "-fpermissive" etc
> > doesn't actually work since -fpermissive isn't a warning flag.  So
> > having a dedicated flag for the class of errors has at least one clear
> > benefit -- we can use #pragmas to selectively disable the errors now.
> > 
> > > > 
> > > > I just don't see the use case for being able to narrowly control this
> > > > class of errors that justifies the extra implementation complexity
> > > > (specifically for properly detecting -Wno-error=template-body in the
> > > > callback hook)?
> > > 
> > > The hook shouldn't need to do anything special; report_diagnostic handles
> > > -Wno-error=whatever.
> > 
> > The issue was that the callback has to know in advance whether
> > -Wno-error=template-body is active so that it can flag the template as
> > having a relaxed error.  Checking !warning_enabled_at wasn't enough
> > because it will return true even for -Wno-error=template-body.  I
> > think we just need to check diagnostic_enabled directly, like so?
> 
> Why not always flag it?  It doesn't seem harmful to give the "instatiating
> erroneous template" error later even if we gave a hard error during parsing.

Hmm, it just seems redundant to me I guess?  The reason we need the
"instantiating erroneous template" error is to ensure that _some_ error
was issued rendering the TU ill-formed when an erroneous template gets
instantiated.  If parse-time errors are hard errors then this is
guaranteed, but it's not if we're downgrading such errors.

On that note it just occurred to me that we don't need to abort
instantiation after the "instantiating erroneous template" error --
for sake of error recovery we can proceed to instantiate as if we
issued a hard error at parse time.

This version removes the early returns from instantiate_decl and
instantiate_class_template.

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (relaxed_template_errors_t): Declare.
	(related_template_errors): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template pattern containing
	a permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
	public.
	(diagnostic_context::m_adjust_diagnostic_info): New data member.
	* doc/invoke.texi (-Wno-template-body): Document.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
	* g++.dg/template/permissive-error1c.C: New test.
---
 gcc/c-family/c.opt                            |  4 +
 gcc/cp/cp-tree.h                              |  5 ++
 gcc/cp/error.cc                               | 81 +++++++++++++++++++
 gcc/cp/pt.cc                                  | 24 ++++++
 gcc/diagnostic.cc                             |  4 +
 gcc/diagnostic.h                              |  8 +-
 gcc/doc/invoke.texi                           | 11 ++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 +++++
 .../g++.dg/template/permissive-error1a.C      | 33 ++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 +++++++
 .../g++.dg/template/permissive-error1c.C      | 33 ++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 14 files changed, 253 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a52682d835c..44117ba713c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..ffdc0ba38ae 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7189,6 +7189,11 @@ extern bool pedwarn_cxx98                       (location_t, int, const char *,
 extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
+using relaxed_template_errors_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern relaxed_template_errors_t *relaxed_template_errors;
+extern bool cp_seen_error			(void);
+#define seen_error cp_seen_error
 
 /* in except.cc */
 extern void init_terminate_fn			(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..f02f7d52e48 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,86 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECL to the location of the first error (if any)
+   within the template that we permissively downgraded to a warning.  */
+
+relaxed_template_errors_t *relaxed_template_errors;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error if we later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	/* Determine if this error should/will be downgraded to a warning
+	   or get suppressed.  */
+	diagnostic_info diagnostic_copy = *diagnostic;
+	if (/* -fpermissive  */
+	    context->m_permissive
+	    /* -Wno-template-body  */
+	    || !context->diagnostic_enabled (&diagnostic_copy)
+	    /* -Wno-error=template-body  */
+	    || diagnostic_copy.kind == DK_WARNING)
+	  {
+	    diagnostic->kind = DK_WARNING;
+	    if (!relaxed_template_errors)
+	      relaxed_template_errors = new relaxed_template_errors_t;
+	    if (!relaxed_template_errors->get (tmpl))
+	      {
+		/* Remember that this template had a relaxed error so
+		   that we'll issue a hard error upon its instantiation.  */
+		location_t error_loc = diagnostic->richloc->get_loc ();
+		relaxed_template_errors->put (tmpl, error_loc);
+	      }
+	  }
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  /* cp-tree.h #defines seen_error to cp_seen_error.  */
+#undef seen_error
+  if (seen_error ())
+    return true;
+
+  if (relaxed_template_errors)
+    if (tree tmpl = get_current_template ())
+      if (relaxed_template_errors->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +267,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..5c66e464d86 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+	relaxed_template_errors->remove (templ);
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27303,18 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+	relaxed_template_errors->remove (td);
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..e90c121cb95 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..426cdfb3a46 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -581,6 +581,8 @@ public:
 			  const char *, const char *, va_list *,
 			  diagnostic_t) ATTRIBUTE_GCC_DIAG(7,0);
 
+  bool diagnostic_enabled (diagnostic_info *diagnostic);
+
 private:
   bool includes_seen_p (const line_map_ordinary *map);
 
@@ -593,8 +595,6 @@ private:
 
   void error_recursion () ATTRIBUTE_NORETURN;
 
-  bool diagnostic_enabled (diagnostic_info *diagnostic);
-
   void get_any_inlining_info (diagnostic_info *diagnostic);
 
   void show_locus (const rich_location &richloc,
@@ -699,6 +699,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..348da934cfc 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..19910f105c2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,33 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..1ac49ce8be5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-message "first error appeared here" "" { target *-*-* } }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-message "first error appeared here" "" { target *-*-* } }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 00000000000..e1e904b0828
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,33 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  
Jason Merrill Aug. 6, 2024, 5:17 p.m. UTC | #10
On 8/5/24 6:09 PM, Patrick Palka wrote:
> On Mon, 5 Aug 2024, Jason Merrill wrote:
> 
>> On 8/5/24 3:47 PM, Patrick Palka wrote:
>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>
>>>> On 8/5/24 1:14 PM, Patrick Palka wrote:
>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>
>>>>>> On 8/2/24 4:18 PM, Patrick Palka wrote:
>>>>>>> On Fri, 2 Aug 2024, Patrick Palka wrote:
>>>>>>>
>>>>>>>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>>>>>>>
>>>>>>>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>>>>>>>> In recent versions of GCC we've been diagnosing more and more
>>>>>>>>>> kinds of
>>>>>>>>>> errors inside a template ahead of time.  This is a largely
>>>>>>>>>> good
>>>>>>>>>> thing
>>>>>>>>>> because it catches bugs, typos, dead code etc sooner.
>>>>>>>>>>
>>>>>>>>>> But if the template never gets instantiated then such errors
>>>>>>>>>> are
>>>>>>>>>> harmless, and can be inconvenient to work around if say the
>>>>>>>>>> code
>>>>>>>>>> in
>>>>>>>>>> question is third party and in maintenence mode.  So it'd be
>>>>>>>>>> useful to
>>>>>>>>>
>>>>>>>>> "maintenance"
>>>>>>>>
>>>>>>>> Fixed
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>>>>>>>> index d80bac822ba..0bb0a482e28 100644
>>>>>>>>>> --- a/gcc/cp/error.cc
>>>>>>>>>> +++ b/gcc/cp/error.cc
>>>>>>>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor : public
>>>>>>>>>> format_postprocessor
>>>>>>>>>>         deferred_printed_type m_type_b;
>>>>>>>>>>       };
>>>>>>>>>>       +/* A map from TEMPLATE_DECL to the location of the first
>>>>>>>>>> error (if
>>>>>>>>>> any)
>>>>>>>>>> +   within the template that we permissivly downgraded to a
>>>>>>>>>> warning.
>>>>>>>>>> */
>>>>>>>>>
>>>>>>>>> "permissively"
>>>>>>>>
>>>>>>>> Fixed
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>>>>>>>> +
>>>>>>>>>> +/* Callback function
>>>>>>>>>> diagnostic_context::m_adjust_diagnostic_info.
>>>>>>>>>> +
>>>>>>>>>> +   In -fpermissive mode we downgrade errors within a template
>>>>>>>>>> to
>>>>>>>>>> +   warnings, and only issue an error if we later need to
>>>>>>>>>> instantiate
>>>>>>>>>> +   the template.  */
>>>>>>>>>> +
>>>>>>>>>> +static void
>>>>>>>>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>>>>>>>>> +			   diagnostic_info *diagnostic)
>>>>>>>>>> +{
>>>>>>>>>> +  tree ti;
>>>>>>>>>> +  if (diagnostic->kind == DK_ERROR
>>>>>>>>>> +      && context->m_permissive
>>>>>>>>>> +      && !current_instantiation ()
>>>>>>>>>> +      && in_template_context
>>>>>>>>>> +      && (ti = get_template_info (current_scope ())))
>>>>>>>>>> +    {
>>>>>>>>>> +      if (!relaxed_template_errors)
>>>>>>>>>> +	relaxed_template_errors = new
>>>>>>>>>> relaxed_template_errors_t;
>>>>>>>>>> +
>>>>>>>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>>>>>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>>>>>>>> +	relaxed_template_errors->put (tmpl,
>>>>>>>>>> diagnostic->richloc->get_loc ());
>>>>>>>>>> +      diagnostic->kind = DK_WARNING;
>>>>>>>>>
>>>>>>>>> Rather than check m_permissive directly and downgrade to
>>>>>>>>> DK_WARNING,
>>>>>>>>> how
>>>>>>>>> about
>>>>>>>>> downgrading to DK_PERMERROR?  That way people will get the
>>>>>>>>> [-fpermissive]
>>>>>>>>> clue.
>>>>>>>>>
>>>>>>>>> ...though I suppose DK_PERMERROR doesn't work where you call
>>>>>>>>> this
>>>>>>>>> hook
>>>>>>>>> in
>>>>>>>>> report_diagnostic, at which point we've already reassigned it
>>>>>>>>> into
>>>>>>>>> DK_WARNING
>>>>>>>>> or DK_ERROR in diagnostic_impl.
>>>>>>>>>
>>>>>>>>> But we could still set diagnostic->option_index even for
>>>>>>>>> DK_ERROR,
>>>>>>>>> whether to
>>>>>>>>> context->m_opt_permissive or to its own warning flag, perhaps
>>>>>>>>> -Wno-template-body?
>>>>>>>>
>>>>>>>> Fixed by adding an enabled-by-default -Wtemplate-body flag and
>>>>>>>> setting
>>>>>>>> option_index to it for each downgraded error.  Thus -permissive
>>>>>>>> -Wno-template-body would suppress the downgraded warnings
>>>>>>>> entirely,
>>>>>>>> and
>>>>>>>> only issue a generic error upon instantiation of the erroneous
>>>>>>>> template.
>>>>>>>
>>>>>>> ... or did you have in mind to set option_index even when not using
>>>>>>> -fpermissive so that eligible non-downgraded errors get the
>>>>>>> [-fpermissive] or [-Wtemplate-body] hint as well?
>>>>>>
>>>>>> Yes.
>>>>>>
>>>>>>> IMHO I'm not sure that'd be worth the extra noise since the vast
>>>>>>> majority of users appreciate and expect errors to get diagnosed
>>>>>>> inside
>>>>>>> templates.
>>>>>>
>>>>>> But people trying to build legacy code should appreciate the pointer
>>>>>> for
>>>>>> how
>>>>>> to make it compile, as with other permerrors.
>>>>>>
>>>>>>> And on second thought I'm not sure what extra value a new warning
>>>>>>> flag
>>>>>>> adds either.  I can't think of a good reason why one would use
>>>>>>> -fpermissive -Wno-template-body?
>>>>>>
>>>>>> One would use -Wno-template-body (or -Wno-error=template-body) without
>>>>>> -fpermissive, like with the various permerror_opt cases.
>>>>>
>>>>> Since compiling legacy/unmaintained code is the only plausible use case,
>>>>> why have a dedicated warning flag instead of just recommending
>>>>> -fpermissive
>>>>> when compiling legacy code?  I don't quite understand the motivation for
>>>>> adding a new permerror_opt flag for this class of errors.
>>>>
>>>> It seems to me an interesting class of errors, but I don't mind leaving it
>>>> under just -fpermissive if you prefer.
>>>>
>>>>> -Wnarrowing is an existing permerror_opt flag, but I can imagine it's
>>>>> useful to pass -Wno-error=narrowing etc when incrementally migrating
>>>>> C / C++98 code to modern C++ where you don't want any conformance errors
>>>>> allowed by -fpermissive to sneak in.  So being able to narrowly control
>>>>> this class of errors seems useful, so a dedicated flag makes sense.
>>>>>
>>>>> But there's no parallel for -Wtemplate-body here, since by assumption
>>>>> the code base is unmaintained / immutable.  Otherwise the more proper
>>>>> fix would be to just fix and/or delete the uninstantiated erroneous
>>>>> template.  If say you're #including a legacy header that has such
>>>>> errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
>>>>> the #include should be totally fine too.
>>>
>>> I just realized #pragma GCC diagnostic warning "-fpermissive" etc
>>> doesn't actually work since -fpermissive isn't a warning flag.  So
>>> having a dedicated flag for the class of errors has at least one clear
>>> benefit -- we can use #pragmas to selectively disable the errors now.
>>>
>>>>>
>>>>> I just don't see the use case for being able to narrowly control this
>>>>> class of errors that justifies the extra implementation complexity
>>>>> (specifically for properly detecting -Wno-error=template-body in the
>>>>> callback hook)?
>>>>
>>>> The hook shouldn't need to do anything special; report_diagnostic handles
>>>> -Wno-error=whatever.
>>>
>>> The issue was that the callback has to know in advance whether
>>> -Wno-error=template-body is active so that it can flag the template as
>>> having a relaxed error.  Checking !warning_enabled_at wasn't enough
>>> because it will return true even for -Wno-error=template-body.  I
>>> think we just need to check diagnostic_enabled directly, like so?
>>
>> Why not always flag it?  It doesn't seem harmful to give the "instatiating
>> erroneous template" error later even if we gave a hard error during parsing.
> 
> Hmm, it just seems redundant to me I guess?  The reason we need the
> "instantiating erroneous template" error is to ensure that _some_ error
> was issued rendering the TU ill-formed when an erroneous template gets
> instantiated.  If parse-time errors are hard errors then this is
> guaranteed, but it's not if we're downgrading such errors.

Yes, it's redundant, but avoiding it doesn't seem worth duplicating all 
the logic to determine whether it will be an error or not.

Simpler might be to skip the "instantiating" error if seen_error()?

> On that note it just occurred to me that we don't need to abort
> instantiation after the "instantiating erroneous template" error --
> for sake of error recovery we can proceed to instantiate as if we
> issued a hard error at parse time.

Agreed.

Jason
  
Patrick Palka Aug. 6, 2024, 6 p.m. UTC | #11
On Tue, 6 Aug 2024, Jason Merrill wrote:

> On 8/5/24 6:09 PM, Patrick Palka wrote:
> > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/5/24 3:47 PM, Patrick Palka wrote:
> > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > 
> > > > > On 8/5/24 1:14 PM, Patrick Palka wrote:
> > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > 
> > > > > > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > > > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > > > > > 
> > > > > > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > > > > > 
> > > > > > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > > > > > In recent versions of GCC we've been diagnosing more and
> > > > > > > > > > > more
> > > > > > > > > > > kinds of
> > > > > > > > > > > errors inside a template ahead of time.  This is a largely
> > > > > > > > > > > good
> > > > > > > > > > > thing
> > > > > > > > > > > because it catches bugs, typos, dead code etc sooner.
> > > > > > > > > > > 
> > > > > > > > > > > But if the template never gets instantiated then such
> > > > > > > > > > > errors
> > > > > > > > > > > are
> > > > > > > > > > > harmless, and can be inconvenient to work around if say
> > > > > > > > > > > the
> > > > > > > > > > > code
> > > > > > > > > > > in
> > > > > > > > > > > question is third party and in maintenence mode.  So it'd
> > > > > > > > > > > be
> > > > > > > > > > > useful to
> > > > > > > > > > 
> > > > > > > > > > "maintenance"
> > > > > > > > > 
> > > > > > > > > Fixed
> > > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > > > > > --- a/gcc/cp/error.cc
> > > > > > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > > > > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor :
> > > > > > > > > > > public
> > > > > > > > > > > format_postprocessor
> > > > > > > > > > >         deferred_printed_type m_type_b;
> > > > > > > > > > >       };
> > > > > > > > > > >       +/* A map from TEMPLATE_DECL to the location of the
> > > > > > > > > > > first
> > > > > > > > > > > error (if
> > > > > > > > > > > any)
> > > > > > > > > > > +   within the template that we permissivly downgraded to
> > > > > > > > > > > a
> > > > > > > > > > > warning.
> > > > > > > > > > > */
> > > > > > > > > > 
> > > > > > > > > > "permissively"
> > > > > > > > > 
> > > > > > > > > Fixed
> > > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > > +relaxed_template_errors_t *relaxed_template_errors;
> > > > > > > > > > > +
> > > > > > > > > > > +/* Callback function
> > > > > > > > > > > diagnostic_context::m_adjust_diagnostic_info.
> > > > > > > > > > > +
> > > > > > > > > > > +   In -fpermissive mode we downgrade errors within a
> > > > > > > > > > > template
> > > > > > > > > > > to
> > > > > > > > > > > +   warnings, and only issue an error if we later need to
> > > > > > > > > > > instantiate
> > > > > > > > > > > +   the template.  */
> > > > > > > > > > > +
> > > > > > > > > > > +static void
> > > > > > > > > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > > > > > > > > +			   diagnostic_info *diagnostic)
> > > > > > > > > > > +{
> > > > > > > > > > > +  tree ti;
> > > > > > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > > > > > +      && context->m_permissive
> > > > > > > > > > > +      && !current_instantiation ()
> > > > > > > > > > > +      && in_template_context
> > > > > > > > > > > +      && (ti = get_template_info (current_scope ())))
> > > > > > > > > > > +    {
> > > > > > > > > > > +      if (!relaxed_template_errors)
> > > > > > > > > > > +	relaxed_template_errors = new
> > > > > > > > > > > relaxed_template_errors_t;
> > > > > > > > > > > +
> > > > > > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > > > > > > > +	relaxed_template_errors->put (tmpl,
> > > > > > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > > > > > 
> > > > > > > > > > Rather than check m_permissive directly and downgrade to
> > > > > > > > > > DK_WARNING,
> > > > > > > > > > how
> > > > > > > > > > about
> > > > > > > > > > downgrading to DK_PERMERROR?  That way people will get the
> > > > > > > > > > [-fpermissive]
> > > > > > > > > > clue.
> > > > > > > > > > 
> > > > > > > > > > ...though I suppose DK_PERMERROR doesn't work where you call
> > > > > > > > > > this
> > > > > > > > > > hook
> > > > > > > > > > in
> > > > > > > > > > report_diagnostic, at which point we've already reassigned
> > > > > > > > > > it
> > > > > > > > > > into
> > > > > > > > > > DK_WARNING
> > > > > > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > > > > > 
> > > > > > > > > > But we could still set diagnostic->option_index even for
> > > > > > > > > > DK_ERROR,
> > > > > > > > > > whether to
> > > > > > > > > > context->m_opt_permissive or to its own warning flag,
> > > > > > > > > > perhaps
> > > > > > > > > > -Wno-template-body?
> > > > > > > > > 
> > > > > > > > > Fixed by adding an enabled-by-default -Wtemplate-body flag and
> > > > > > > > > setting
> > > > > > > > > option_index to it for each downgraded error.  Thus
> > > > > > > > > -permissive
> > > > > > > > > -Wno-template-body would suppress the downgraded warnings
> > > > > > > > > entirely,
> > > > > > > > > and
> > > > > > > > > only issue a generic error upon instantiation of the erroneous
> > > > > > > > > template.
> > > > > > > > 
> > > > > > > > ... or did you have in mind to set option_index even when not
> > > > > > > > using
> > > > > > > > -fpermissive so that eligible non-downgraded errors get the
> > > > > > > > [-fpermissive] or [-Wtemplate-body] hint as well?
> > > > > > > 
> > > > > > > Yes.
> > > > > > > 
> > > > > > > > IMHO I'm not sure that'd be worth the extra noise since the vast
> > > > > > > > majority of users appreciate and expect errors to get diagnosed
> > > > > > > > inside
> > > > > > > > templates.
> > > > > > > 
> > > > > > > But people trying to build legacy code should appreciate the
> > > > > > > pointer
> > > > > > > for
> > > > > > > how
> > > > > > > to make it compile, as with other permerrors.
> > > > > > > 
> > > > > > > > And on second thought I'm not sure what extra value a new
> > > > > > > > warning
> > > > > > > > flag
> > > > > > > > adds either.  I can't think of a good reason why one would use
> > > > > > > > -fpermissive -Wno-template-body?
> > > > > > > 
> > > > > > > One would use -Wno-template-body (or -Wno-error=template-body)
> > > > > > > without
> > > > > > > -fpermissive, like with the various permerror_opt cases.
> > > > > > 
> > > > > > Since compiling legacy/unmaintained code is the only plausible use
> > > > > > case,
> > > > > > why have a dedicated warning flag instead of just recommending
> > > > > > -fpermissive
> > > > > > when compiling legacy code?  I don't quite understand the motivation
> > > > > > for
> > > > > > adding a new permerror_opt flag for this class of errors.
> > > > > 
> > > > > It seems to me an interesting class of errors, but I don't mind
> > > > > leaving it
> > > > > under just -fpermissive if you prefer.
> > > > > 
> > > > > > -Wnarrowing is an existing permerror_opt flag, but I can imagine
> > > > > > it's
> > > > > > useful to pass -Wno-error=narrowing etc when incrementally migrating
> > > > > > C / C++98 code to modern C++ where you don't want any conformance
> > > > > > errors
> > > > > > allowed by -fpermissive to sneak in.  So being able to narrowly
> > > > > > control
> > > > > > this class of errors seems useful, so a dedicated flag makes sense.
> > > > > > 
> > > > > > But there's no parallel for -Wtemplate-body here, since by
> > > > > > assumption
> > > > > > the code base is unmaintained / immutable.  Otherwise the more
> > > > > > proper
> > > > > > fix would be to just fix and/or delete the uninstantiated erroneous
> > > > > > template.  If say you're #including a legacy header that has such
> > > > > > errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
> > > > > > the #include should be totally fine too.
> > > > 
> > > > I just realized #pragma GCC diagnostic warning "-fpermissive" etc
> > > > doesn't actually work since -fpermissive isn't a warning flag.  So
> > > > having a dedicated flag for the class of errors has at least one clear
> > > > benefit -- we can use #pragmas to selectively disable the errors now.
> > > > 
> > > > > > 
> > > > > > I just don't see the use case for being able to narrowly control
> > > > > > this
> > > > > > class of errors that justifies the extra implementation complexity
> > > > > > (specifically for properly detecting -Wno-error=template-body in the
> > > > > > callback hook)?
> > > > > 
> > > > > The hook shouldn't need to do anything special; report_diagnostic
> > > > > handles
> > > > > -Wno-error=whatever.
> > > > 
> > > > The issue was that the callback has to know in advance whether
> > > > -Wno-error=template-body is active so that it can flag the template as
> > > > having a relaxed error.  Checking !warning_enabled_at wasn't enough
> > > > because it will return true even for -Wno-error=template-body.  I
> > > > think we just need to check diagnostic_enabled directly, like so?
> > > 
> > > Why not always flag it?  It doesn't seem harmful to give the "instatiating
> > > erroneous template" error later even if we gave a hard error during
> > > parsing.
> > 
> > Hmm, it just seems redundant to me I guess?  The reason we need the
> > "instantiating erroneous template" error is to ensure that _some_ error
> > was issued rendering the TU ill-formed when an erroneous template gets
> > instantiated.  If parse-time errors are hard errors then this is
> > guaranteed, but it's not if we're downgrading such errors.
> 
> Yes, it's redundant, but avoiding it doesn't seem worth duplicating all the
> logic to determine whether it will be an error or not.
> 
> Simpler might be to skip the "instantiating" error if seen_error()?

Sure.  This makes the expected diagnostics in the -Wno-template-body
testcase a bit lacking since there's three instantiated templates with
one logical error each, but only the first error is diagnosed.  But I
don't particularly mind that.

> 
> > On that note it just occurred to me that we don't need to abort
> > instantiation after the "instantiating erroneous template" error --
> > for sake of error recovery we can proceed to instantiate as if we
> > issued a hard error at parse time.
> 
> Agreed.
> 
> Jason
> 
> 

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

In recent versions of GCC we've been diagnosing more and more kinds of
errors inside a template ahead of time.  This is a largely good thing
because it catches bugs, typos, dead code etc sooner.

But if the template never gets instantiated then such errors are harmless
and can be inconvenient to work around if say the code in question is
third party and in maintenance mode.  So it'd be handy to be able to
prevent these template errors from rendering the entire TU uncompilable.
(Note that such code is "ill-formed no diagnostic required" according
the standard.)

To that end this patch turns any errors issued within a template into
premerrors associated with a new -Wtemplate-body flag so that they can
be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
the template containing a downgraded error later needs to be instantiated,
we'll issue an error then.  But if the template never gets instantiated
then the downgraded error won't affect validity of the rest of the TU.

This is implemented via a diagnostic hook that gets called for each
diagnostic, and which adjusts an error diagnostic if we detect it's
occurring from a template context and additionally flags this template
context as erroneous.

As an example, permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (erroneous_templates_t): Declare.
	(erroneous_templates): Declare.
	(cp_seen_error): Declare.
	(basic_seen_error): Define as alias to existing seen_error.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template pattern containing
	a permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
	public.
	(diagnostic_context::m_adjust_diagnostic_info): New data member.
	* doc/invoke.texi (-Wno-template-body): Document.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
	* g++.dg/template/permissive-error1c.C: New test.
---
 gcc/c-family/c.opt                            |  4 ++
 gcc/cp/cp-tree.h                              |  8 +++
 gcc/cp/error.cc                               | 68 +++++++++++++++++++
 gcc/cp/pt.cc                                  | 24 +++++++
 gcc/diagnostic.cc                             |  4 ++
 gcc/diagnostic.h                              |  8 ++-
 gcc/doc/invoke.texi                           | 11 ++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 ++++++
 .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 ++++++++
 .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 14 files changed, 239 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a52682d835c..44117ba713c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..cb69535a5af 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7190,6 +7190,14 @@ extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
 
+using erroneous_templates_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern erroneous_templates_t *erroneous_templates;
+
+inline bool basic_seen_error () { return seen_error (); }
+extern bool cp_seen_error ();
+#define seen_error cp_seen_error
+
 /* in except.cc */
 extern void init_terminate_fn			(void);
 extern void init_exception_processing		(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..94c863b311e 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,73 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (scope_chain && in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECLs that we've determined to be erroneous
+   at parse time to the location of the first error within.  */
+
+erroneous_templates_t *erroneous_templates;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error if we later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	if (!erroneous_templates)
+	  erroneous_templates = new erroneous_templates_t;
+	if (!erroneous_templates->get (tmpl))
+	  {
+	    /* Remember that this template had a parse-time error so
+	       that we'll ensure a hard error has been issued upon
+	       its instantiation.  */
+	    location_t error_loc = diagnostic->richloc->get_loc ();
+	    erroneous_templates->put (tmpl, error_loc);
+	  }
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  if (basic_seen_error ())
+    return true;
+
+  if (erroneous_templates)
+    if (tree tmpl = get_current_template ())
+      if (erroneous_templates->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +254,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..c9de25c9744 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (erroneous_templates && !basic_seen_error ())
+    if (location_t *error_loc = erroneous_templates->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now to ensure the TU is considered
+	   ill-formed.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27303,18 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (erroneous_templates && !basic_seen_error ())
+    if (location_t *error_loc = erroneous_templates->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now to ensure the TU is considered
+	   ill-formed.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..e90c121cb95 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..426cdfb3a46 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -581,6 +581,8 @@ public:
 			  const char *, const char *, va_list *,
 			  diagnostic_t) ATTRIBUTE_GCC_DIAG(7,0);
 
+  bool diagnostic_enabled (diagnostic_info *diagnostic);
+
 private:
   bool includes_seen_p (const line_map_ordinary *map);
 
@@ -593,8 +595,6 @@ private:
 
   void error_recursion () ATTRIBUTE_NORETURN;
 
-  bool diagnostic_enabled (diagnostic_info *diagnostic);
-
   void get_any_inlining_info (diagnostic_info *diagnostic);
 
   void show_locus (const rich_location &richloc,
@@ -699,6 +699,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..348da934cfc 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..ac47151f0db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..a67b7374625
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type);
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g();
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 00000000000..fd5f26655e5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  
Jason Merrill Aug. 6, 2024, 8:47 p.m. UTC | #12
On 8/6/24 2:00 PM, Patrick Palka wrote:
> On Tue, 6 Aug 2024, Jason Merrill wrote:
> 
>> On 8/5/24 6:09 PM, Patrick Palka wrote:
>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>
>>>> On 8/5/24 3:47 PM, Patrick Palka wrote:
>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>
>>>>>> On 8/5/24 1:14 PM, Patrick Palka wrote:
>>>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>>>
>>>>>>>> On 8/2/24 4:18 PM, Patrick Palka wrote:
>>>>>>>>> On Fri, 2 Aug 2024, Patrick Palka wrote:
>>>>>>>>>
>>>>>>>>>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>>>>>>>>>
>>>>>>>>>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>>>>>>>>>> In recent versions of GCC we've been diagnosing more and
>>>>>>>>>>>> more
>>>>>>>>>>>> kinds of
>>>>>>>>>>>> errors inside a template ahead of time.  This is a largely
>>>>>>>>>>>> good
>>>>>>>>>>>> thing
>>>>>>>>>>>> because it catches bugs, typos, dead code etc sooner.
>>>>>>>>>>>>
>>>>>>>>>>>> But if the template never gets instantiated then such
>>>>>>>>>>>> errors
>>>>>>>>>>>> are
>>>>>>>>>>>> harmless, and can be inconvenient to work around if say
>>>>>>>>>>>> the
>>>>>>>>>>>> code
>>>>>>>>>>>> in
>>>>>>>>>>>> question is third party and in maintenence mode.  So it'd
>>>>>>>>>>>> be
>>>>>>>>>>>> useful to
>>>>>>>>>>>
>>>>>>>>>>> "maintenance"
>>>>>>>>>>
>>>>>>>>>> Fixed
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>>>>>>>>>> index d80bac822ba..0bb0a482e28 100644
>>>>>>>>>>>> --- a/gcc/cp/error.cc
>>>>>>>>>>>> +++ b/gcc/cp/error.cc
>>>>>>>>>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor :
>>>>>>>>>>>> public
>>>>>>>>>>>> format_postprocessor
>>>>>>>>>>>>          deferred_printed_type m_type_b;
>>>>>>>>>>>>        };
>>>>>>>>>>>>        +/* A map from TEMPLATE_DECL to the location of the
>>>>>>>>>>>> first
>>>>>>>>>>>> error (if
>>>>>>>>>>>> any)
>>>>>>>>>>>> +   within the template that we permissivly downgraded to
>>>>>>>>>>>> a
>>>>>>>>>>>> warning.
>>>>>>>>>>>> */
>>>>>>>>>>>
>>>>>>>>>>> "permissively"
>>>>>>>>>>
>>>>>>>>>> Fixed
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>>>>>>>>>> +
>>>>>>>>>>>> +/* Callback function
>>>>>>>>>>>> diagnostic_context::m_adjust_diagnostic_info.
>>>>>>>>>>>> +
>>>>>>>>>>>> +   In -fpermissive mode we downgrade errors within a
>>>>>>>>>>>> template
>>>>>>>>>>>> to
>>>>>>>>>>>> +   warnings, and only issue an error if we later need to
>>>>>>>>>>>> instantiate
>>>>>>>>>>>> +   the template.  */
>>>>>>>>>>>> +
>>>>>>>>>>>> +static void
>>>>>>>>>>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>>>>>>>>>>> +			   diagnostic_info *diagnostic)
>>>>>>>>>>>> +{
>>>>>>>>>>>> +  tree ti;
>>>>>>>>>>>> +  if (diagnostic->kind == DK_ERROR
>>>>>>>>>>>> +      && context->m_permissive
>>>>>>>>>>>> +      && !current_instantiation ()
>>>>>>>>>>>> +      && in_template_context
>>>>>>>>>>>> +      && (ti = get_template_info (current_scope ())))
>>>>>>>>>>>> +    {
>>>>>>>>>>>> +      if (!relaxed_template_errors)
>>>>>>>>>>>> +	relaxed_template_errors = new
>>>>>>>>>>>> relaxed_template_errors_t;
>>>>>>>>>>>> +
>>>>>>>>>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>>>>>>>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>>>>>>>>>> +	relaxed_template_errors->put (tmpl,
>>>>>>>>>>>> diagnostic->richloc->get_loc ());
>>>>>>>>>>>> +      diagnostic->kind = DK_WARNING;
>>>>>>>>>>>
>>>>>>>>>>> Rather than check m_permissive directly and downgrade to
>>>>>>>>>>> DK_WARNING,
>>>>>>>>>>> how
>>>>>>>>>>> about
>>>>>>>>>>> downgrading to DK_PERMERROR?  That way people will get the
>>>>>>>>>>> [-fpermissive]
>>>>>>>>>>> clue.
>>>>>>>>>>>
>>>>>>>>>>> ...though I suppose DK_PERMERROR doesn't work where you call
>>>>>>>>>>> this
>>>>>>>>>>> hook
>>>>>>>>>>> in
>>>>>>>>>>> report_diagnostic, at which point we've already reassigned
>>>>>>>>>>> it
>>>>>>>>>>> into
>>>>>>>>>>> DK_WARNING
>>>>>>>>>>> or DK_ERROR in diagnostic_impl.
>>>>>>>>>>>
>>>>>>>>>>> But we could still set diagnostic->option_index even for
>>>>>>>>>>> DK_ERROR,
>>>>>>>>>>> whether to
>>>>>>>>>>> context->m_opt_permissive or to its own warning flag,
>>>>>>>>>>> perhaps
>>>>>>>>>>> -Wno-template-body?
>>>>>>>>>>
>>>>>>>>>> Fixed by adding an enabled-by-default -Wtemplate-body flag and
>>>>>>>>>> setting
>>>>>>>>>> option_index to it for each downgraded error.  Thus
>>>>>>>>>> -permissive
>>>>>>>>>> -Wno-template-body would suppress the downgraded warnings
>>>>>>>>>> entirely,
>>>>>>>>>> and
>>>>>>>>>> only issue a generic error upon instantiation of the erroneous
>>>>>>>>>> template.
>>>>>>>>>
>>>>>>>>> ... or did you have in mind to set option_index even when not
>>>>>>>>> using
>>>>>>>>> -fpermissive so that eligible non-downgraded errors get the
>>>>>>>>> [-fpermissive] or [-Wtemplate-body] hint as well?
>>>>>>>>
>>>>>>>> Yes.
>>>>>>>>
>>>>>>>>> IMHO I'm not sure that'd be worth the extra noise since the vast
>>>>>>>>> majority of users appreciate and expect errors to get diagnosed
>>>>>>>>> inside
>>>>>>>>> templates.
>>>>>>>>
>>>>>>>> But people trying to build legacy code should appreciate the
>>>>>>>> pointer
>>>>>>>> for
>>>>>>>> how
>>>>>>>> to make it compile, as with other permerrors.
>>>>>>>>
>>>>>>>>> And on second thought I'm not sure what extra value a new
>>>>>>>>> warning
>>>>>>>>> flag
>>>>>>>>> adds either.  I can't think of a good reason why one would use
>>>>>>>>> -fpermissive -Wno-template-body?
>>>>>>>>
>>>>>>>> One would use -Wno-template-body (or -Wno-error=template-body)
>>>>>>>> without
>>>>>>>> -fpermissive, like with the various permerror_opt cases.
>>>>>>>
>>>>>>> Since compiling legacy/unmaintained code is the only plausible use
>>>>>>> case,
>>>>>>> why have a dedicated warning flag instead of just recommending
>>>>>>> -fpermissive
>>>>>>> when compiling legacy code?  I don't quite understand the motivation
>>>>>>> for
>>>>>>> adding a new permerror_opt flag for this class of errors.
>>>>>>
>>>>>> It seems to me an interesting class of errors, but I don't mind
>>>>>> leaving it
>>>>>> under just -fpermissive if you prefer.
>>>>>>
>>>>>>> -Wnarrowing is an existing permerror_opt flag, but I can imagine
>>>>>>> it's
>>>>>>> useful to pass -Wno-error=narrowing etc when incrementally migrating
>>>>>>> C / C++98 code to modern C++ where you don't want any conformance
>>>>>>> errors
>>>>>>> allowed by -fpermissive to sneak in.  So being able to narrowly
>>>>>>> control
>>>>>>> this class of errors seems useful, so a dedicated flag makes sense.
>>>>>>>
>>>>>>> But there's no parallel for -Wtemplate-body here, since by
>>>>>>> assumption
>>>>>>> the code base is unmaintained / immutable.  Otherwise the more
>>>>>>> proper
>>>>>>> fix would be to just fix and/or delete the uninstantiated erroneous
>>>>>>> template.  If say you're #including a legacy header that has such
>>>>>>> errors, then doing #pragma GCC diagnostic "-fpermissive -w" around
>>>>>>> the #include should be totally fine too.
>>>>>
>>>>> I just realized #pragma GCC diagnostic warning "-fpermissive" etc
>>>>> doesn't actually work since -fpermissive isn't a warning flag.  So
>>>>> having a dedicated flag for the class of errors has at least one clear
>>>>> benefit -- we can use #pragmas to selectively disable the errors now.
>>>>>
>>>>>>>
>>>>>>> I just don't see the use case for being able to narrowly control
>>>>>>> this
>>>>>>> class of errors that justifies the extra implementation complexity
>>>>>>> (specifically for properly detecting -Wno-error=template-body in the
>>>>>>> callback hook)?
>>>>>>
>>>>>> The hook shouldn't need to do anything special; report_diagnostic
>>>>>> handles
>>>>>> -Wno-error=whatever.
>>>>>
>>>>> The issue was that the callback has to know in advance whether
>>>>> -Wno-error=template-body is active so that it can flag the template as
>>>>> having a relaxed error.  Checking !warning_enabled_at wasn't enough
>>>>> because it will return true even for -Wno-error=template-body.  I
>>>>> think we just need to check diagnostic_enabled directly, like so?
>>>>
>>>> Why not always flag it?  It doesn't seem harmful to give the "instatiating
>>>> erroneous template" error later even if we gave a hard error during
>>>> parsing.
>>>
>>> Hmm, it just seems redundant to me I guess?  The reason we need the
>>> "instantiating erroneous template" error is to ensure that _some_ error
>>> was issued rendering the TU ill-formed when an erroneous template gets
>>> instantiated.  If parse-time errors are hard errors then this is
>>> guaranteed, but it's not if we're downgrading such errors.
>>
>> Yes, it's redundant, but avoiding it doesn't seem worth duplicating all the
>> logic to determine whether it will be an error or not.
>>
>> Simpler might be to skip the "instantiating" error if seen_error()?
> 
> Sure.  This makes the expected diagnostics in the -Wno-template-body
> testcase a bit lacking since there's three instantiated templates with
> one logical error each, but only the first error is diagnosed.  But I
> don't particularly mind that.
> 
>>
>>> On that note it just occurred to me that we don't need to abort
>>> instantiation after the "instantiating erroneous template" error --
>>> for sake of error recovery we can proceed to instantiate as if we
>>> issued a hard error at parse time.
>>
>> Agreed.
>>
>> Jason
>>
>>
> 
> -- >8 --
> 
> Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]
> 
> In recent versions of GCC we've been diagnosing more and more kinds of
> errors inside a template ahead of time.  This is a largely good thing
> because it catches bugs, typos, dead code etc sooner.
> 
> But if the template never gets instantiated then such errors are harmless
> and can be inconvenient to work around if say the code in question is
> third party and in maintenance mode.  So it'd be handy to be able to
> prevent these template errors from rendering the entire TU uncompilable.
> (Note that such code is "ill-formed no diagnostic required" according
> the standard.)
> 
> To that end this patch turns any errors issued within a template into
> premerrors associated with a new -Wtemplate-body flag so that they can

perm

> be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
> the template containing a downgraded error later needs to be instantiated,
> we'll issue an error then.  But if the template never gets instantiated
> then the downgraded error won't affect validity of the rest of the TU.
> 
> This is implemented via a diagnostic hook that gets called for each
> diagnostic, and which adjusts an error diagnostic if we detect it's
> occurring from a template context and additionally flags this template
> context as erroneous.
> 
> As an example, permissive-error1a.C gives:
> 
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
>      7 |   ++n;
>        |     ^
> ...
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
> gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
>     26 |   f<int>();
>        |   ~~~~~~^~
> gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template pattern

Drop "pattern" here too.

>      5 | void f() {
>        |      ^
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
>      7 |   ++n; // {
>        |     ^
> ...

What happens if we compile an interface/header unit with an erroneous 
uninstantiated template?  I'd probably reject such a module rather than 
write out the erroneity.  Possibly just if the template isn't discarded, 
not sure if that distinction is too much trouble to bother with.

> 	PR c++/116064
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt (Wtemplate-body): New warning.
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (erroneous_templates_t): Declare.
> 	(erroneous_templates): Declare.
> 	(cp_seen_error): Declare.
> 	(basic_seen_error): Define as alias to existing seen_error.

Or we could use (seen_error)() where we mean the basic one?

> 	(seen_error): #define to cp_seen_error.
> 	* error.cc (get_current_template): Define.
> 	(relaxed_template_errors): Define.
> 	(cp_adjust_diagnostic_info): Define.
> 	(cp_seen_error): Define.
> 	(cxx_initialize_diagnostics): Set
> 	diagnostic_context::m_adjust_diagnostic_info.
> 	* pt.cc (instantiate_class_template): Issue a hard error
> 	when trying to instantiate a template pattern containing
> 	a permissively downgraded error.
> 	(instantiate_decl): Likewise.
> 
> gcc/ChangeLog:
> 
> 	* diagnostic.cc (diagnostic_context::initialize): Set
> 	m_adjust_diagnostic_info.
> 	(diagnostic_context::report_diagnostic): Call
> 	m_adjust_diagnostic_info.
> 	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
> 	public.

You shouldn't need this change now?

> +static void
> +cp_adjust_diagnostic_info (diagnostic_context *context,
> +			   diagnostic_info *diagnostic)
> +{
> +  if (diagnostic->kind == DK_ERROR)
> +    if (tree tmpl = get_current_template ())
> +      {
> +	diagnostic->option_index = OPT_Wtemplate_body;
> +
> +	if (context->m_permissive)
> +	  diagnostic->kind = DK_WARNING;
> +
> +	if (!erroneous_templates)
> +	  erroneous_templates = new erroneous_templates_t;
> +	if (!erroneous_templates->get (tmpl))
> +	  {
> +	    /* Remember that this template had a parse-time error so
> +	       that we'll ensure a hard error has been issued upon
> +	       its instantiation.  */
> +	    location_t error_loc = diagnostic->richloc->get_loc ();
> +	    erroneous_templates->put (tmpl, error_loc);

Instead of get+put you could use the get return value to store error_loc?

> +@opindex Wtemplate-body
> +@opindex Wno-template-body
> +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
> +Disable diagnosing errors when parsing a template, and instead issue an
> +error only upon instantiation of the template.  This flag can also be
> +used to downgrade such errors into warnings with @option{Wno-error=} or
> +@option{-fpermissive}.

Please also refer to this flag in the -fpermissive documentation.

Jason
  
Patrick Palka Aug. 6, 2024, 9:47 p.m. UTC | #13
On Tue, 6 Aug 2024, Jason Merrill wrote:

> On 8/6/24 2:00 PM, Patrick Palka wrote:
> > On Tue, 6 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/5/24 6:09 PM, Patrick Palka wrote:
> > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > 
> > > > > On 8/5/24 3:47 PM, Patrick Palka wrote:
> > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > 
> > > > > > > On 8/5/24 1:14 PM, Patrick Palka wrote:
> > > > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > > > 
> > > > > > > > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > > > > > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > > > > > > > 
> > > > > > > > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > > > > > > > 
> > > > > > > > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > > > > > > > In recent versions of GCC we've been diagnosing more
> > > > > > > > > > > > > and
> > > > > > > > > > > > > more
> > > > > > > > > > > > > kinds of
> > > > > > > > > > > > > errors inside a template ahead of time.  This is a
> > > > > > > > > > > > > largely
> > > > > > > > > > > > > good
> > > > > > > > > > > > > thing
> > > > > > > > > > > > > because it catches bugs, typos, dead code etc sooner.
> > > > > > > > > > > > > 
> > > > > > > > > > > > > But if the template never gets instantiated then such
> > > > > > > > > > > > > errors
> > > > > > > > > > > > > are
> > > > > > > > > > > > > harmless, and can be inconvenient to work around if
> > > > > > > > > > > > > say
> > > > > > > > > > > > > the
> > > > > > > > > > > > > code
> > > > > > > > > > > > > in
> > > > > > > > > > > > > question is third party and in maintenence mode.  So
> > > > > > > > > > > > > it'd
> > > > > > > > > > > > > be
> > > > > > > > > > > > > useful to
> > > > > > > > > > > > 
> > > > > > > > > > > > "maintenance"
> > > > > > > > > > > 
> > > > > > > > > > > Fixed
> > > > > > > > > > > 
> > > > > > > > > > > > 
> > > > > > > > > > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > > > > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > > > > > > > --- a/gcc/cp/error.cc
> > > > > > > > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > > > > > > > @@ -165,6 +165,58 @@ class cxx_format_postprocessor :
> > > > > > > > > > > > > public
> > > > > > > > > > > > > format_postprocessor
> > > > > > > > > > > > >          deferred_printed_type m_type_b;
> > > > > > > > > > > > >        };
> > > > > > > > > > > > >        +/* A map from TEMPLATE_DECL to the location of
> > > > > > > > > > > > > the
> > > > > > > > > > > > > first
> > > > > > > > > > > > > error (if
> > > > > > > > > > > > > any)
> > > > > > > > > > > > > +   within the template that we permissivly downgraded
> > > > > > > > > > > > > to
> > > > > > > > > > > > > a
> > > > > > > > > > > > > warning.
> > > > > > > > > > > > > */
> > > > > > > > > > > > 
> > > > > > > > > > > > "permissively"
> > > > > > > > > > > 
> > > > > > > > > > > Fixed
> > > > > > > > > > > 
> > > > > > > > > > > > 
> > > > > > > > > > > > > +relaxed_template_errors_t *relaxed_template_errors;
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +/* Callback function
> > > > > > > > > > > > > diagnostic_context::m_adjust_diagnostic_info.
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +   In -fpermissive mode we downgrade errors within a
> > > > > > > > > > > > > template
> > > > > > > > > > > > > to
> > > > > > > > > > > > > +   warnings, and only issue an error if we later need
> > > > > > > > > > > > > to
> > > > > > > > > > > > > instantiate
> > > > > > > > > > > > > +   the template.  */
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +static void
> > > > > > > > > > > > > +cp_adjust_diagnostic_info (diagnostic_context
> > > > > > > > > > > > > *context,
> > > > > > > > > > > > > +			   diagnostic_info
> > > > > > > > > > > > > *diagnostic)
> > > > > > > > > > > > > +{
> > > > > > > > > > > > > +  tree ti;
> > > > > > > > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > > > > > > > +      && context->m_permissive
> > > > > > > > > > > > > +      && !current_instantiation ()
> > > > > > > > > > > > > +      && in_template_context
> > > > > > > > > > > > > +      && (ti = get_template_info (current_scope ())))
> > > > > > > > > > > > > +    {
> > > > > > > > > > > > > +      if (!relaxed_template_errors)
> > > > > > > > > > > > > +	relaxed_template_errors = new
> > > > > > > > > > > > > relaxed_template_errors_t;
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > > > > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > > > > > > > > > +	relaxed_template_errors->put (tmpl,
> > > > > > > > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > > > > > > > 
> > > > > > > > > > > > Rather than check m_permissive directly and downgrade to
> > > > > > > > > > > > DK_WARNING,
> > > > > > > > > > > > how
> > > > > > > > > > > > about
> > > > > > > > > > > > downgrading to DK_PERMERROR?  That way people will get
> > > > > > > > > > > > the
> > > > > > > > > > > > [-fpermissive]
> > > > > > > > > > > > clue.
> > > > > > > > > > > > 
> > > > > > > > > > > > ...though I suppose DK_PERMERROR doesn't work where you
> > > > > > > > > > > > call
> > > > > > > > > > > > this
> > > > > > > > > > > > hook
> > > > > > > > > > > > in
> > > > > > > > > > > > report_diagnostic, at which point we've already
> > > > > > > > > > > > reassigned
> > > > > > > > > > > > it
> > > > > > > > > > > > into
> > > > > > > > > > > > DK_WARNING
> > > > > > > > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > > > > > > > 
> > > > > > > > > > > > But we could still set diagnostic->option_index even for
> > > > > > > > > > > > DK_ERROR,
> > > > > > > > > > > > whether to
> > > > > > > > > > > > context->m_opt_permissive or to its own warning flag,
> > > > > > > > > > > > perhaps
> > > > > > > > > > > > -Wno-template-body?
> > > > > > > > > > > 
> > > > > > > > > > > Fixed by adding an enabled-by-default -Wtemplate-body flag
> > > > > > > > > > > and
> > > > > > > > > > > setting
> > > > > > > > > > > option_index to it for each downgraded error.  Thus
> > > > > > > > > > > -permissive
> > > > > > > > > > > -Wno-template-body would suppress the downgraded warnings
> > > > > > > > > > > entirely,
> > > > > > > > > > > and
> > > > > > > > > > > only issue a generic error upon instantiation of the
> > > > > > > > > > > erroneous
> > > > > > > > > > > template.
> > > > > > > > > > 
> > > > > > > > > > ... or did you have in mind to set option_index even when
> > > > > > > > > > not
> > > > > > > > > > using
> > > > > > > > > > -fpermissive so that eligible non-downgraded errors get the
> > > > > > > > > > [-fpermissive] or [-Wtemplate-body] hint as well?
> > > > > > > > > 
> > > > > > > > > Yes.
> > > > > > > > > 
> > > > > > > > > > IMHO I'm not sure that'd be worth the extra noise since the
> > > > > > > > > > vast
> > > > > > > > > > majority of users appreciate and expect errors to get
> > > > > > > > > > diagnosed
> > > > > > > > > > inside
> > > > > > > > > > templates.
> > > > > > > > > 
> > > > > > > > > But people trying to build legacy code should appreciate the
> > > > > > > > > pointer
> > > > > > > > > for
> > > > > > > > > how
> > > > > > > > > to make it compile, as with other permerrors.
> > > > > > > > > 
> > > > > > > > > > And on second thought I'm not sure what extra value a new
> > > > > > > > > > warning
> > > > > > > > > > flag
> > > > > > > > > > adds either.  I can't think of a good reason why one would
> > > > > > > > > > use
> > > > > > > > > > -fpermissive -Wno-template-body?
> > > > > > > > > 
> > > > > > > > > One would use -Wno-template-body (or -Wno-error=template-body)
> > > > > > > > > without
> > > > > > > > > -fpermissive, like with the various permerror_opt cases.
> > > > > > > > 
> > > > > > > > Since compiling legacy/unmaintained code is the only plausible
> > > > > > > > use
> > > > > > > > case,
> > > > > > > > why have a dedicated warning flag instead of just recommending
> > > > > > > > -fpermissive
> > > > > > > > when compiling legacy code?  I don't quite understand the
> > > > > > > > motivation
> > > > > > > > for
> > > > > > > > adding a new permerror_opt flag for this class of errors.
> > > > > > > 
> > > > > > > It seems to me an interesting class of errors, but I don't mind
> > > > > > > leaving it
> > > > > > > under just -fpermissive if you prefer.
> > > > > > > 
> > > > > > > > -Wnarrowing is an existing permerror_opt flag, but I can imagine
> > > > > > > > it's
> > > > > > > > useful to pass -Wno-error=narrowing etc when incrementally
> > > > > > > > migrating
> > > > > > > > C / C++98 code to modern C++ where you don't want any
> > > > > > > > conformance
> > > > > > > > errors
> > > > > > > > allowed by -fpermissive to sneak in.  So being able to narrowly
> > > > > > > > control
> > > > > > > > this class of errors seems useful, so a dedicated flag makes
> > > > > > > > sense.
> > > > > > > > 
> > > > > > > > But there's no parallel for -Wtemplate-body here, since by
> > > > > > > > assumption
> > > > > > > > the code base is unmaintained / immutable.  Otherwise the more
> > > > > > > > proper
> > > > > > > > fix would be to just fix and/or delete the uninstantiated
> > > > > > > > erroneous
> > > > > > > > template.  If say you're #including a legacy header that has
> > > > > > > > such
> > > > > > > > errors, then doing #pragma GCC diagnostic "-fpermissive -w"
> > > > > > > > around
> > > > > > > > the #include should be totally fine too.
> > > > > > 
> > > > > > I just realized #pragma GCC diagnostic warning "-fpermissive" etc
> > > > > > doesn't actually work since -fpermissive isn't a warning flag.  So
> > > > > > having a dedicated flag for the class of errors has at least one
> > > > > > clear
> > > > > > benefit -- we can use #pragmas to selectively disable the errors
> > > > > > now.
> > > > > > 
> > > > > > > > 
> > > > > > > > I just don't see the use case for being able to narrowly control
> > > > > > > > this
> > > > > > > > class of errors that justifies the extra implementation
> > > > > > > > complexity
> > > > > > > > (specifically for properly detecting -Wno-error=template-body in
> > > > > > > > the
> > > > > > > > callback hook)?
> > > > > > > 
> > > > > > > The hook shouldn't need to do anything special; report_diagnostic
> > > > > > > handles
> > > > > > > -Wno-error=whatever.
> > > > > > 
> > > > > > The issue was that the callback has to know in advance whether
> > > > > > -Wno-error=template-body is active so that it can flag the template
> > > > > > as
> > > > > > having a relaxed error.  Checking !warning_enabled_at wasn't enough
> > > > > > because it will return true even for -Wno-error=template-body.  I
> > > > > > think we just need to check diagnostic_enabled directly, like so?
> > > > > 
> > > > > Why not always flag it?  It doesn't seem harmful to give the
> > > > > "instatiating
> > > > > erroneous template" error later even if we gave a hard error during
> > > > > parsing.
> > > > 
> > > > Hmm, it just seems redundant to me I guess?  The reason we need the
> > > > "instantiating erroneous template" error is to ensure that _some_ error
> > > > was issued rendering the TU ill-formed when an erroneous template gets
> > > > instantiated.  If parse-time errors are hard errors then this is
> > > > guaranteed, but it's not if we're downgrading such errors.
> > > 
> > > Yes, it's redundant, but avoiding it doesn't seem worth duplicating all
> > > the
> > > logic to determine whether it will be an error or not.
> > > 
> > > Simpler might be to skip the "instantiating" error if seen_error()?
> > 
> > Sure.  This makes the expected diagnostics in the -Wno-template-body
> > testcase a bit lacking since there's three instantiated templates with
> > one logical error each, but only the first error is diagnosed.  But I
> > don't particularly mind that.
> > 
> > > 
> > > > On that note it just occurred to me that we don't need to abort
> > > > instantiation after the "instantiating erroneous template" error --
> > > > for sake of error recovery we can proceed to instantiate as if we
> > > > issued a hard error at parse time.
> > > 
> > > Agreed.
> > > 
> > > Jason
> > > 
> > > 
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: permit errors inside uninstantiated templates
> > [PR116064]
> > 
> > In recent versions of GCC we've been diagnosing more and more kinds of
> > errors inside a template ahead of time.  This is a largely good thing
> > because it catches bugs, typos, dead code etc sooner.
> > 
> > But if the template never gets instantiated then such errors are harmless
> > and can be inconvenient to work around if say the code in question is
> > third party and in maintenance mode.  So it'd be handy to be able to
> > prevent these template errors from rendering the entire TU uncompilable.
> > (Note that such code is "ill-formed no diagnostic required" according
> > the standard.)
> > 
> > To that end this patch turns any errors issued within a template into
> > premerrors associated with a new -Wtemplate-body flag so that they can
> 
> perm
> 
> > be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
> > the template containing a downgraded error later needs to be instantiated,
> > we'll issue an error then.  But if the template never gets instantiated
> > then the downgraded error won't affect validity of the rest of the TU.
> > 
> > This is implemented via a diagnostic hook that gets called for each
> > diagnostic, and which adjusts an error diagnostic if we detect it's
> > occurring from a template context and additionally flags this template
> > context as erroneous.
> > 
> > As an example, permissive-error1a.C gives:
> > 
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment
> > of read-only variable 'n' [-Wtemplate-body]
> >      7 |   ++n;
> >        |     ^
> > ...
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of
> > 'void f() [with T = int]':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from
> > here
> >     26 |   f<int>();
> >        |   ~~~~~~^~
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating
> > erroneous template pattern
> 
> Drop "pattern" here too.

Fixed 

> 
> >      5 | void f() {
> >        |      ^
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error
> > appeared here
> >      7 |   ++n; // {
> >        |     ^
> > ...
> 
> What happens if we compile an interface/header unit with an erroneous
> uninstantiated template?  I'd probably reject such a module rather than write
> out the erroneity.  Possibly just if the template isn't discarded, not sure if
> that distinction is too much trouble to bother with.

I think it's good enough to uniformly reject the module given how small
the intersection -fpermissive and modules users is, I imagine.

> 
> > 	PR c++/116064
> > 
> > gcc/c-family/ChangeLog:
> > 
> > 	* c.opt (Wtemplate-body): New warning.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-tree.h (erroneous_templates_t): Declare.
> > 	(erroneous_templates): Declare.
> > 	(cp_seen_error): Declare.
> > 	(basic_seen_error): Define as alias to existing seen_error.
> 
> Or we could use (seen_error)() where we mean the basic one?

Sure, changed.

> 
> > 	(seen_error): #define to cp_seen_error.
> > 	* error.cc (get_current_template): Define.
> > 	(relaxed_template_errors): Define.
> > 	(cp_adjust_diagnostic_info): Define.
> > 	(cp_seen_error): Define.
> > 	(cxx_initialize_diagnostics): Set
> > 	diagnostic_context::m_adjust_diagnostic_info.
> > 	* pt.cc (instantiate_class_template): Issue a hard error
> > 	when trying to instantiate a template pattern containing
> > 	a permissively downgraded error.
> > 	(instantiate_decl): Likewise.
> > 
> > gcc/ChangeLog:
> > 
> > 	* diagnostic.cc (diagnostic_context::initialize): Set
> > 	m_adjust_diagnostic_info.
> > 	(diagnostic_context::report_diagnostic): Call
> > 	m_adjust_diagnostic_info.
> > 	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
> > 	public.
> 
> You shouldn't need this change now?

Oops, removed.

> 
> > +static void
> > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > +			   diagnostic_info *diagnostic)
> > +{
> > +  if (diagnostic->kind == DK_ERROR)
> > +    if (tree tmpl = get_current_template ())
> > +      {
> > +	diagnostic->option_index = OPT_Wtemplate_body;
> > +
> > +	if (context->m_permissive)
> > +	  diagnostic->kind = DK_WARNING;
> > +
> > +	if (!erroneous_templates)
> > +	  erroneous_templates = new erroneous_templates_t;
> > +	if (!erroneous_templates->get (tmpl))
> > +	  {
> > +	    /* Remember that this template had a parse-time error so
> > +	       that we'll ensure a hard error has been issued upon
> > +	       its instantiation.  */
> > +	    location_t error_loc = diagnostic->richloc->get_loc ();
> > +	    erroneous_templates->put (tmpl, error_loc);
> 
> Instead of get+put you could use the get return value to store error_loc?

We need to use get_or_insert if we want to combine the two lookups.
I went ahead and used hash_map_safe_get_or_insert here to get rid
of the construction boilerplate as well.

> 
> > +@opindex Wtemplate-body
> > +@opindex Wno-template-body
> > +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
> > +Disable diagnosing errors when parsing a template, and instead issue an
> > +error only upon instantiation of the template.  This flag can also be
> > +used to downgrade such errors into warnings with @option{Wno-error=} or
> > +@option{-fpermissive}.
> 
> Please also refer to this flag in the -fpermissive documentation.

Done.

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

In recent versions of GCC we've been diagnosing more and more kinds of
errors inside a template ahead of time.  This is a largely good thing
because it catches bugs, typos, dead code etc sooner.

But if the template never gets instantiated then such errors are harmless
and can be inconvenient to work around if say the code in question is
third party and in maintenance mode.  So it'd be handy to be able to
prevent these template errors from rendering the entire TU uncompilable.
(Note that such code is "ill-formed no diagnostic required" according
the standard.)

To that end this patch turns any errors issued within a template into
permerrors associated with a new -Wtemplate-body flag so that they can
be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
the template containing a downgraded error later needs to be instantiated,
we'll issue an error then.  But if the template never gets instantiated
then the downgraded error won't affect validity of the rest of the TU.

This is implemented via a diagnostic hook that gets called for each
diagnostic, and which adjusts an error diagnostic if we detect it's
occurring from a template context and additionally flags this template
context as erroneous.

As an example, permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (erroneous_templates_t): Declare.
	(erroneous_templates): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* module.cc (finish_module_processing): Don't write the
	module if it contains an erroneous template.
	* pt.cc (instantiate_class_template): Issue a hard error
	when trying to instantiate a template containing a
	permissively downgraded error.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
	New data member.
	* doc/invoke.texi (-Wno-template-body): Document.
	(-fpermissive): Mention -Wtemplate-body.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
	* g++.dg/template/permissive-error1c.C: New test.
---
 gcc/c-family/c.opt                            |  4 ++
 gcc/cp/cp-tree.h                              |  7 ++
 gcc/cp/error.cc                               | 67 +++++++++++++++++++
 gcc/cp/module.cc                              |  5 +-
 gcc/cp/pt.cc                                  | 24 +++++++
 gcc/diagnostic.cc                             |  4 ++
 gcc/diagnostic.h                              |  4 ++
 gcc/doc/invoke.texi                           | 12 +++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 ++++++
 .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
 .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 15 files changed, 240 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a52682d835c..44117ba713c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..08ee5895c6a 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7190,6 +7190,13 @@ extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
 
+using erroneous_templates_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern erroneous_templates_t *erroneous_templates;
+
+extern bool cp_seen_error ();
+#define seen_error() cp_seen_error()
+
 /* in except.cc */
 extern void init_terminate_fn			(void);
 extern void init_exception_processing		(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..7eab0bf76b4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,72 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (scope_chain && in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECLs that we've determined to be erroneous
+   at parse time to the location of the first error within.  */
+
+erroneous_templates_t *erroneous_templates;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error if we later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	bool existed;
+	location_t &error_loc
+	  = hash_map_safe_get_or_insert<false> (erroneous_templates,
+						tmpl, &existed);
+	if (!existed)
+	  /* Remember that this template had a parse-time error so
+	     that we'll ensure a hard error has been issued upon
+	     its instantiation.  */
+	  error_loc = diagnostic->richloc->get_loc ();
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  if ((seen_error) ())
+    return true;
+
+  if (erroneous_templates)
+    if (tree tmpl = get_current_template ())
+      if (erroneous_templates->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index d1607a06757..7130faf26f5 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader *reader)
 
       cookie = new module_processing_cookie (cmi_name, tmp_name, fd, e);
 
-      if (errorcount)
+      if (errorcount
+	  /* Don't write the module if it contains an erroneous template.  */
+	  || (erroneous_templates
+	      && !erroneous_templates->is_empty ()))
 	warning_at (state->loc, 0, "not writing module %qs due to errors",
 		    state->get_flatname ());
       else if (cookie->out.begin ())
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..b83922a530d 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (erroneous_templates && !(seen_error) ())
+    if (location_t *error_loc = erroneous_templates->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now to ensure the TU is considered
+	   ill-formed.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27303,18 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (erroneous_templates && !(seen_error) ())
+    if (location_t *error_loc = erroneous_templates->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now to ensure the TU is considered
+	   ill-formed.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..e90c121cb95 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1416,6 +1417,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..d3d77c85e9a 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -699,6 +699,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..643016a9e93 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
@@ -6361,6 +6370,7 @@ that have their own flag:
 -Wint-conversion @r{(C and Objective-C only)}
 -Wnarrowing @r{(C++ and Objective-C++ only)}
 -Wreturn-mismatch @r{(C and Objective-C only)}
+-Wtemplate-body @r{(C++ and Objective-C++ only)}
 }
 
 The @option{-fpermissive} option is the default for historic C language
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..ac47151f0db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..a67b7374625
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type);
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g();
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 00000000000..fd5f26655e5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  
Jason Merrill Aug. 6, 2024, 9:52 p.m. UTC | #14
On 8/6/24 5:47 PM, Patrick Palka wrote:
> On Tue, 6 Aug 2024, Jason Merrill wrote:
> 
>> On 8/6/24 2:00 PM, Patrick Palka wrote:
>>> On Tue, 6 Aug 2024, Jason Merrill wrote:
>>>
>>>> On 8/5/24 6:09 PM, Patrick Palka wrote:
>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>
>>>>>> On 8/5/24 3:47 PM, Patrick Palka wrote:
>>>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>>>
>>>>>>>> On 8/5/24 1:14 PM, Patrick Palka wrote:
>>>>>>>>> On Mon, 5 Aug 2024, Jason Merrill wrote:
>>>>>>>>>
>>>>>>>>>> On 8/2/24 4:18 PM, Patrick Palka wrote:
>>>>>>>>>>> On Fri, 2 Aug 2024, Patrick Palka wrote:
>>>>>>>>>>>
>>>>>>>>>>>> On Fri, 2 Aug 2024, Jason Merrill wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> On 8/1/24 2:52 PM, Patrick Palka wrote:
>>>>>>>>>>>>>> In recent versions of GCC we've been diagnosing more
>>>>>>>>>>>>>> and
>>>>>>>>>>>>>> more
>>>>>>>>>>>>>> kinds of
>>>>>>>>>>>>>> errors inside a template ahead of time.  This is a
>>>>>>>>>>>>>> largely
>>>>>>>>>>>>>> good
>>>>>>>>>>>>>> thing
>>>>>>>>>>>>>> because it catches bugs, typos, dead code etc sooner.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> But if the template never gets instantiated then such
>>>>>>>>>>>>>> errors
>>>>>>>>>>>>>> are
>>>>>>>>>>>>>> harmless, and can be inconvenient to work around if
>>>>>>>>>>>>>> say
>>>>>>>>>>>>>> the
>>>>>>>>>>>>>> code
>>>>>>>>>>>>>> in
>>>>>>>>>>>>>> question is third party and in maintenence mode.  So
>>>>>>>>>>>>>> it'd
>>>>>>>>>>>>>> be
>>>>>>>>>>>>>> useful to
>>>>>>>>>>>>>
>>>>>>>>>>>>> "maintenance"
>>>>>>>>>>>>
>>>>>>>>>>>> Fixed
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
>>>>>>>>>>>>>> index d80bac822ba..0bb0a482e28 100644
>>>>>>>>>>>>>> --- a/gcc/cp/error.cc
>>>>>>>>>>>>>> +++ b/gcc/cp/error.cc
>>>>>>>>>>>>>> @@ -165,6 +165,58 @@ class cxx_format_postprocessor :
>>>>>>>>>>>>>> public
>>>>>>>>>>>>>> format_postprocessor
>>>>>>>>>>>>>>           deferred_printed_type m_type_b;
>>>>>>>>>>>>>>         };
>>>>>>>>>>>>>>         +/* A map from TEMPLATE_DECL to the location of
>>>>>>>>>>>>>> the
>>>>>>>>>>>>>> first
>>>>>>>>>>>>>> error (if
>>>>>>>>>>>>>> any)
>>>>>>>>>>>>>> +   within the template that we permissivly downgraded
>>>>>>>>>>>>>> to
>>>>>>>>>>>>>> a
>>>>>>>>>>>>>> warning.
>>>>>>>>>>>>>> */
>>>>>>>>>>>>>
>>>>>>>>>>>>> "permissively"
>>>>>>>>>>>>
>>>>>>>>>>>> Fixed
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> +relaxed_template_errors_t *relaxed_template_errors;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +/* Callback function
>>>>>>>>>>>>>> diagnostic_context::m_adjust_diagnostic_info.
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +   In -fpermissive mode we downgrade errors within a
>>>>>>>>>>>>>> template
>>>>>>>>>>>>>> to
>>>>>>>>>>>>>> +   warnings, and only issue an error if we later need
>>>>>>>>>>>>>> to
>>>>>>>>>>>>>> instantiate
>>>>>>>>>>>>>> +   the template.  */
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +static void
>>>>>>>>>>>>>> +cp_adjust_diagnostic_info (diagnostic_context
>>>>>>>>>>>>>> *context,
>>>>>>>>>>>>>> +			   diagnostic_info
>>>>>>>>>>>>>> *diagnostic)
>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>> +  tree ti;
>>>>>>>>>>>>>> +  if (diagnostic->kind == DK_ERROR
>>>>>>>>>>>>>> +      && context->m_permissive
>>>>>>>>>>>>>> +      && !current_instantiation ()
>>>>>>>>>>>>>> +      && in_template_context
>>>>>>>>>>>>>> +      && (ti = get_template_info (current_scope ())))
>>>>>>>>>>>>>> +    {
>>>>>>>>>>>>>> +      if (!relaxed_template_errors)
>>>>>>>>>>>>>> +	relaxed_template_errors = new
>>>>>>>>>>>>>> relaxed_template_errors_t;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +      tree tmpl = TI_TEMPLATE (ti);
>>>>>>>>>>>>>> +      if (!relaxed_template_errors->get (tmpl))
>>>>>>>>>>>>>> +	relaxed_template_errors->put (tmpl,
>>>>>>>>>>>>>> diagnostic->richloc->get_loc ());
>>>>>>>>>>>>>> +      diagnostic->kind = DK_WARNING;
>>>>>>>>>>>>>
>>>>>>>>>>>>> Rather than check m_permissive directly and downgrade to
>>>>>>>>>>>>> DK_WARNING,
>>>>>>>>>>>>> how
>>>>>>>>>>>>> about
>>>>>>>>>>>>> downgrading to DK_PERMERROR?  That way people will get
>>>>>>>>>>>>> the
>>>>>>>>>>>>> [-fpermissive]
>>>>>>>>>>>>> clue.
>>>>>>>>>>>>>
>>>>>>>>>>>>> ...though I suppose DK_PERMERROR doesn't work where you
>>>>>>>>>>>>> call
>>>>>>>>>>>>> this
>>>>>>>>>>>>> hook
>>>>>>>>>>>>> in
>>>>>>>>>>>>> report_diagnostic, at which point we've already
>>>>>>>>>>>>> reassigned
>>>>>>>>>>>>> it
>>>>>>>>>>>>> into
>>>>>>>>>>>>> DK_WARNING
>>>>>>>>>>>>> or DK_ERROR in diagnostic_impl.
>>>>>>>>>>>>>
>>>>>>>>>>>>> But we could still set diagnostic->option_index even for
>>>>>>>>>>>>> DK_ERROR,
>>>>>>>>>>>>> whether to
>>>>>>>>>>>>> context->m_opt_permissive or to its own warning flag,
>>>>>>>>>>>>> perhaps
>>>>>>>>>>>>> -Wno-template-body?
>>>>>>>>>>>>
>>>>>>>>>>>> Fixed by adding an enabled-by-default -Wtemplate-body flag
>>>>>>>>>>>> and
>>>>>>>>>>>> setting
>>>>>>>>>>>> option_index to it for each downgraded error.  Thus
>>>>>>>>>>>> -permissive
>>>>>>>>>>>> -Wno-template-body would suppress the downgraded warnings
>>>>>>>>>>>> entirely,
>>>>>>>>>>>> and
>>>>>>>>>>>> only issue a generic error upon instantiation of the
>>>>>>>>>>>> erroneous
>>>>>>>>>>>> template.
>>>>>>>>>>>
>>>>>>>>>>> ... or did you have in mind to set option_index even when
>>>>>>>>>>> not
>>>>>>>>>>> using
>>>>>>>>>>> -fpermissive so that eligible non-downgraded errors get the
>>>>>>>>>>> [-fpermissive] or [-Wtemplate-body] hint as well?
>>>>>>>>>>
>>>>>>>>>> Yes.
>>>>>>>>>>
>>>>>>>>>>> IMHO I'm not sure that'd be worth the extra noise since the
>>>>>>>>>>> vast
>>>>>>>>>>> majority of users appreciate and expect errors to get
>>>>>>>>>>> diagnosed
>>>>>>>>>>> inside
>>>>>>>>>>> templates.
>>>>>>>>>>
>>>>>>>>>> But people trying to build legacy code should appreciate the
>>>>>>>>>> pointer
>>>>>>>>>> for
>>>>>>>>>> how
>>>>>>>>>> to make it compile, as with other permerrors.
>>>>>>>>>>
>>>>>>>>>>> And on second thought I'm not sure what extra value a new
>>>>>>>>>>> warning
>>>>>>>>>>> flag
>>>>>>>>>>> adds either.  I can't think of a good reason why one would
>>>>>>>>>>> use
>>>>>>>>>>> -fpermissive -Wno-template-body?
>>>>>>>>>>
>>>>>>>>>> One would use -Wno-template-body (or -Wno-error=template-body)
>>>>>>>>>> without
>>>>>>>>>> -fpermissive, like with the various permerror_opt cases.
>>>>>>>>>
>>>>>>>>> Since compiling legacy/unmaintained code is the only plausible
>>>>>>>>> use
>>>>>>>>> case,
>>>>>>>>> why have a dedicated warning flag instead of just recommending
>>>>>>>>> -fpermissive
>>>>>>>>> when compiling legacy code?  I don't quite understand the
>>>>>>>>> motivation
>>>>>>>>> for
>>>>>>>>> adding a new permerror_opt flag for this class of errors.
>>>>>>>>
>>>>>>>> It seems to me an interesting class of errors, but I don't mind
>>>>>>>> leaving it
>>>>>>>> under just -fpermissive if you prefer.
>>>>>>>>
>>>>>>>>> -Wnarrowing is an existing permerror_opt flag, but I can imagine
>>>>>>>>> it's
>>>>>>>>> useful to pass -Wno-error=narrowing etc when incrementally
>>>>>>>>> migrating
>>>>>>>>> C / C++98 code to modern C++ where you don't want any
>>>>>>>>> conformance
>>>>>>>>> errors
>>>>>>>>> allowed by -fpermissive to sneak in.  So being able to narrowly
>>>>>>>>> control
>>>>>>>>> this class of errors seems useful, so a dedicated flag makes
>>>>>>>>> sense.
>>>>>>>>>
>>>>>>>>> But there's no parallel for -Wtemplate-body here, since by
>>>>>>>>> assumption
>>>>>>>>> the code base is unmaintained / immutable.  Otherwise the more
>>>>>>>>> proper
>>>>>>>>> fix would be to just fix and/or delete the uninstantiated
>>>>>>>>> erroneous
>>>>>>>>> template.  If say you're #including a legacy header that has
>>>>>>>>> such
>>>>>>>>> errors, then doing #pragma GCC diagnostic "-fpermissive -w"
>>>>>>>>> around
>>>>>>>>> the #include should be totally fine too.
>>>>>>>
>>>>>>> I just realized #pragma GCC diagnostic warning "-fpermissive" etc
>>>>>>> doesn't actually work since -fpermissive isn't a warning flag.  So
>>>>>>> having a dedicated flag for the class of errors has at least one
>>>>>>> clear
>>>>>>> benefit -- we can use #pragmas to selectively disable the errors
>>>>>>> now.
>>>>>>>
>>>>>>>>>
>>>>>>>>> I just don't see the use case for being able to narrowly control
>>>>>>>>> this
>>>>>>>>> class of errors that justifies the extra implementation
>>>>>>>>> complexity
>>>>>>>>> (specifically for properly detecting -Wno-error=template-body in
>>>>>>>>> the
>>>>>>>>> callback hook)?
>>>>>>>>
>>>>>>>> The hook shouldn't need to do anything special; report_diagnostic
>>>>>>>> handles
>>>>>>>> -Wno-error=whatever.
>>>>>>>
>>>>>>> The issue was that the callback has to know in advance whether
>>>>>>> -Wno-error=template-body is active so that it can flag the template
>>>>>>> as
>>>>>>> having a relaxed error.  Checking !warning_enabled_at wasn't enough
>>>>>>> because it will return true even for -Wno-error=template-body.  I
>>>>>>> think we just need to check diagnostic_enabled directly, like so?
>>>>>>
>>>>>> Why not always flag it?  It doesn't seem harmful to give the
>>>>>> "instatiating
>>>>>> erroneous template" error later even if we gave a hard error during
>>>>>> parsing.
>>>>>
>>>>> Hmm, it just seems redundant to me I guess?  The reason we need the
>>>>> "instantiating erroneous template" error is to ensure that _some_ error
>>>>> was issued rendering the TU ill-formed when an erroneous template gets
>>>>> instantiated.  If parse-time errors are hard errors then this is
>>>>> guaranteed, but it's not if we're downgrading such errors.
>>>>
>>>> Yes, it's redundant, but avoiding it doesn't seem worth duplicating all
>>>> the
>>>> logic to determine whether it will be an error or not.
>>>>
>>>> Simpler might be to skip the "instantiating" error if seen_error()?
>>>
>>> Sure.  This makes the expected diagnostics in the -Wno-template-body
>>> testcase a bit lacking since there's three instantiated templates with
>>> one logical error each, but only the first error is diagnosed.  But I
>>> don't particularly mind that.
>>>
>>>>
>>>>> On that note it just occurred to me that we don't need to abort
>>>>> instantiation after the "instantiating erroneous template" error --
>>>>> for sake of error recovery we can proceed to instantiate as if we
>>>>> issued a hard error at parse time.
>>>>
>>>> Agreed.
>>>>
>>>> Jason
>>>>
>>>>
>>>
>>> -- >8 --
>>>
>>> Subject: [PATCH] c++: permit errors inside uninstantiated templates
>>> [PR116064]
>>>
>>> In recent versions of GCC we've been diagnosing more and more kinds of
>>> errors inside a template ahead of time.  This is a largely good thing
>>> because it catches bugs, typos, dead code etc sooner.
>>>
>>> But if the template never gets instantiated then such errors are harmless
>>> and can be inconvenient to work around if say the code in question is
>>> third party and in maintenance mode.  So it'd be handy to be able to
>>> prevent these template errors from rendering the entire TU uncompilable.
>>> (Note that such code is "ill-formed no diagnostic required" according
>>> the standard.)
>>>
>>> To that end this patch turns any errors issued within a template into
>>> premerrors associated with a new -Wtemplate-body flag so that they can
>>
>> perm
>>
>>> be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
>>> the template containing a downgraded error later needs to be instantiated,
>>> we'll issue an error then.  But if the template never gets instantiated
>>> then the downgraded error won't affect validity of the rest of the TU.
>>>
>>> This is implemented via a diagnostic hook that gets called for each
>>> diagnostic, and which adjusts an error diagnostic if we detect it's
>>> occurring from a template context and additionally flags this template
>>> context as erroneous.
>>>
>>> As an example, permissive-error1a.C gives:
>>>
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment
>>> of read-only variable 'n' [-Wtemplate-body]
>>>       7 |   ++n;
>>>         |     ^
>>> ...
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of
>>> 'void f() [with T = int]':
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from
>>> here
>>>      26 |   f<int>();
>>>         |   ~~~~~~^~
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating
>>> erroneous template pattern
>>
>> Drop "pattern" here too.
> 
> Fixed
> 
>>
>>>       5 | void f() {
>>>         |      ^
>>> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error
>>> appeared here
>>>       7 |   ++n; // {
>>>         |     ^
>>> ...
>>
>> What happens if we compile an interface/header unit with an erroneous
>> uninstantiated template?  I'd probably reject such a module rather than write
>> out the erroneity.  Possibly just if the template isn't discarded, not sure if
>> that distinction is too much trouble to bother with.
> 
> I think it's good enough to uniformly reject the module given how small
> the intersection -fpermissive and modules users is, I imagine.
> 
>>
>>> 	PR c++/116064
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>> 	* c.opt (Wtemplate-body): New warning.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* cp-tree.h (erroneous_templates_t): Declare.
>>> 	(erroneous_templates): Declare.
>>> 	(cp_seen_error): Declare.
>>> 	(basic_seen_error): Define as alias to existing seen_error.
>>
>> Or we could use (seen_error)() where we mean the basic one?
> 
> Sure, changed.
> 
>>
>>> 	(seen_error): #define to cp_seen_error.
>>> 	* error.cc (get_current_template): Define.
>>> 	(relaxed_template_errors): Define.
>>> 	(cp_adjust_diagnostic_info): Define.
>>> 	(cp_seen_error): Define.
>>> 	(cxx_initialize_diagnostics): Set
>>> 	diagnostic_context::m_adjust_diagnostic_info.
>>> 	* pt.cc (instantiate_class_template): Issue a hard error
>>> 	when trying to instantiate a template pattern containing
>>> 	a permissively downgraded error.
>>> 	(instantiate_decl): Likewise.
>>>
>>> gcc/ChangeLog:
>>>
>>> 	* diagnostic.cc (diagnostic_context::initialize): Set
>>> 	m_adjust_diagnostic_info.
>>> 	(diagnostic_context::report_diagnostic): Call
>>> 	m_adjust_diagnostic_info.
>>> 	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
>>> 	public.
>>
>> You shouldn't need this change now?
> 
> Oops, removed.
> 
>>
>>> +static void
>>> +cp_adjust_diagnostic_info (diagnostic_context *context,
>>> +			   diagnostic_info *diagnostic)
>>> +{
>>> +  if (diagnostic->kind == DK_ERROR)
>>> +    if (tree tmpl = get_current_template ())
>>> +      {
>>> +	diagnostic->option_index = OPT_Wtemplate_body;
>>> +
>>> +	if (context->m_permissive)
>>> +	  diagnostic->kind = DK_WARNING;
>>> +
>>> +	if (!erroneous_templates)
>>> +	  erroneous_templates = new erroneous_templates_t;
>>> +	if (!erroneous_templates->get (tmpl))
>>> +	  {
>>> +	    /* Remember that this template had a parse-time error so
>>> +	       that we'll ensure a hard error has been issued upon
>>> +	       its instantiation.  */
>>> +	    location_t error_loc = diagnostic->richloc->get_loc ();
>>> +	    erroneous_templates->put (tmpl, error_loc);
>>
>> Instead of get+put you could use the get return value to store error_loc?
> 
> We need to use get_or_insert if we want to combine the two lookups.
> I went ahead and used hash_map_safe_get_or_insert here to get rid
> of the construction boilerplate as well.
> 
>>
>>> +@opindex Wtemplate-body
>>> +@opindex Wno-template-body
>>> +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
>>> +Disable diagnosing errors when parsing a template, and instead issue an
>>> +error only upon instantiation of the template.  This flag can also be
>>> +used to downgrade such errors into warnings with @option{Wno-error=} or
>>> +@option{-fpermissive}.
>>
>> Please also refer to this flag in the -fpermissive documentation.
> 
> Done.
> 
> -- >8 --
> 
> Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]
> 
> In recent versions of GCC we've been diagnosing more and more kinds of
> errors inside a template ahead of time.  This is a largely good thing
> because it catches bugs, typos, dead code etc sooner.
> 
> But if the template never gets instantiated then such errors are harmless
> and can be inconvenient to work around if say the code in question is
> third party and in maintenance mode.  So it'd be handy to be able to
> prevent these template errors from rendering the entire TU uncompilable.
> (Note that such code is "ill-formed no diagnostic required" according
> the standard.)
> 
> To that end this patch turns any errors issued within a template into
> permerrors associated with a new -Wtemplate-body flag so that they can
> be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
> the template containing a downgraded error later needs to be instantiated,
> we'll issue an error then.  But if the template never gets instantiated
> then the downgraded error won't affect validity of the rest of the TU.
> 
> This is implemented via a diagnostic hook that gets called for each
> diagnostic, and which adjusts an error diagnostic if we detect it's
> occurring from a template context and additionally flags this template
> context as erroneous.
> 
> As an example, permissive-error1a.C gives:
> 
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
>      7 |   ++n;
>        |     ^
> ...
> gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
> gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
>     26 |   f<int>();
>        |   ~~~~~~^~
> gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template
>      5 | void f() {
>        |      ^
> gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
>      7 |   ++n; // {
>        |     ^
> ...
> 
> 	PR c++/116064
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt (Wtemplate-body): New warning.
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (erroneous_templates_t): Declare.
> 	(erroneous_templates): Declare.
> 	(cp_seen_error): Declare.
> 	(seen_error): #define to cp_seen_error.
> 	* error.cc (get_current_template): Define.
> 	(relaxed_template_errors): Define.
> 	(cp_adjust_diagnostic_info): Define.
> 	(cp_seen_error): Define.
> 	(cxx_initialize_diagnostics): Set
> 	diagnostic_context::m_adjust_diagnostic_info.
> 	* module.cc (finish_module_processing): Don't write the
> 	module if it contains an erroneous template.
> 	* pt.cc (instantiate_class_template): Issue a hard error
> 	when trying to instantiate a template containing a
> 	permissively downgraded error.
> 	(instantiate_decl): Likewise.
> 
> gcc/ChangeLog:
> 
> 	* diagnostic.cc (diagnostic_context::initialize): Set
> 	m_adjust_diagnostic_info.
> 	(diagnostic_context::report_diagnostic): Call
> 	m_adjust_diagnostic_info.
> 	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
> 	New data member.
> 	* doc/invoke.texi (-Wno-template-body): Document.
> 	(-fpermissive): Mention -Wtemplate-body.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/typedef-init.C: Downgrade error inside template
> 	to warning due to -fpermissive.
> 	* g++.dg/pr84492.C: Likewise.
> 	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
> 	* g++.dg/template/permissive-error1.C: New test.
> 	* g++.dg/template/permissive-error1a.C: New test.
> 	* g++.dg/template/permissive-error1b.C: New test.
> 	* g++.dg/template/permissive-error1c.C: New test.
> ---
>   gcc/c-family/c.opt                            |  4 ++
>   gcc/cp/cp-tree.h                              |  7 ++
>   gcc/cp/error.cc                               | 67 +++++++++++++++++++
>   gcc/cp/module.cc                              |  5 +-
>   gcc/cp/pt.cc                                  | 24 +++++++
>   gcc/diagnostic.cc                             |  4 ++
>   gcc/diagnostic.h                              |  4 ++
>   gcc/doc/invoke.texi                           | 12 +++-
>   gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
>   gcc/testsuite/g++.dg/pr84492.C                |  4 +-
>   .../g++.dg/template/permissive-error1.C       | 20 ++++++
>   .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
>   .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
>   .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
>   gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
>   15 files changed, 240 insertions(+), 6 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
>   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
>   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
>   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index a52682d835c..44117ba713c 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1420,6 +1420,10 @@ Wtautological-compare
>   C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
>   Warn if a comparison always evaluates to true or false.
>   
> +Wtemplate-body
> +C++ ObjC++ Var(warn_template_body) Warning Init(1)
> +Diagnose errors when parsing a template.
> +
>   Wtemplate-id-cdtor
>   C++ ObjC++ Var(warn_template_id_cdtor) Warning
>   Warn about simple-template-id in a constructor or destructor.
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 238d786b067..08ee5895c6a 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7190,6 +7190,13 @@ extern location_t location_of                   (tree);
>   extern void qualified_name_lookup_error		(tree, tree, tree,
>   						 location_t);
>   
> +using erroneous_templates_t
> +  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
> +extern erroneous_templates_t *erroneous_templates;
> +
> +extern bool cp_seen_error ();
> +#define seen_error() cp_seen_error()
> +
>   /* in except.cc */
>   extern void init_terminate_fn			(void);
>   extern void init_exception_processing		(void);
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index d80bac822ba..7eab0bf76b4 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -165,6 +165,72 @@ class cxx_format_postprocessor : public format_postprocessor
>     deferred_printed_type m_type_b;
>   };
>   
> +/* Return the in-scope template that's currently being parsed, or
> +   NULL_TREE otherwise.  */
> +
> +static tree
> +get_current_template ()
> +{
> +  if (scope_chain && in_template_context && !current_instantiation ())
> +    if (tree ti = get_template_info (current_scope ()))
> +      return TI_TEMPLATE (ti);
> +
> +  return NULL_TREE;
> +}
> +
> +/* A map from TEMPLATE_DECLs that we've determined to be erroneous
> +   at parse time to the location of the first error within.  */
> +
> +erroneous_templates_t *erroneous_templates;
> +
> +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> +
> +   Errors issued when parsing a template are automatically treated like
> +   permerrors associated with the -Wtemplate-body flag and can be
> +   downgraded into warnings accordingly, in which case we'll still
> +   issue an error if we later need to instantiate the template.  */
> +
> +static void
> +cp_adjust_diagnostic_info (diagnostic_context *context,
> +			   diagnostic_info *diagnostic)
> +{
> +  if (diagnostic->kind == DK_ERROR)
> +    if (tree tmpl = get_current_template ())
> +      {
> +	diagnostic->option_index = OPT_Wtemplate_body;
> +
> +	if (context->m_permissive)
> +	  diagnostic->kind = DK_WARNING;
> +
> +	bool existed;
> +	location_t &error_loc
> +	  = hash_map_safe_get_or_insert<false> (erroneous_templates,
> +						tmpl, &existed);
> +	if (!existed)
> +	  /* Remember that this template had a parse-time error so
> +	     that we'll ensure a hard error has been issued upon
> +	     its instantiation.  */
> +	  error_loc = diagnostic->richloc->get_loc ();
> +      }
> +}
> +
> +/* A generalization of seen_error which also returns true if we've
> +   permissively downgraded an error to a warning inside a template.  */
> +
> +bool
> +cp_seen_error ()
> +{
> +  if ((seen_error) ())
> +    return true;
> +
> +  if (erroneous_templates)
> +    if (tree tmpl = get_current_template ())
> +      if (erroneous_templates->get (tmpl))
> +	return true;
> +
> +  return false;
> +}
> +
>   /* CONTEXT->printer is a basic pretty printer that was constructed
>      presumably by diagnostic_initialize(), called early in the
>      compiler's initialization process (in general_init) Before the FE
> @@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
>     diagnostic_starter (context) = cp_diagnostic_starter;
>     /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
>     diagnostic_format_decoder (context) = cp_printer;
> +  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
>     pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
>   }
>   
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index d1607a06757..7130faf26f5 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader *reader)
>   
>         cookie = new module_processing_cookie (cmi_name, tmp_name, fd, e);
>   
> -      if (errorcount)
> +      if (errorcount
> +	  /* Don't write the module if it contains an erroneous template.  */
> +	  || (erroneous_templates
> +	      && !erroneous_templates->is_empty ()))
>   	warning_at (state->loc, 0, "not writing module %qs due to errors",
>   		    state->get_flatname ());
>         else if (cookie->out.begin ())
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 77fa5907c3d..b83922a530d 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
>     if (! push_tinst_level (type))
>       return type;
>   
> +  if (erroneous_templates && !(seen_error) ())
> +    if (location_t *error_loc = erroneous_templates->get (templ))
> +      {
> +	/* We're trying to instantiate a template pattern containing
> +	   an error that we've permissively downgraded to a warning.
> +	   Issue a hard error now to ensure the TU is considered
> +	   ill-formed.  */
> +	location_t decl_loc = location_of (templ);
> +	error_at (decl_loc, "instantiating erroneous template");
> +	inform (*error_loc, "first error appeared here");
> +      }

Let's factor this out instead of repeating it.  OK with that change.

Jason
  
David Malcolm Aug. 6, 2024, 11:05 p.m. UTC | #15
On Tue, 2024-08-06 at 17:52 -0400, Jason Merrill wrote:
> On 8/6/24 5:47 PM, Patrick Palka wrote:
> > On Tue, 6 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/6/24 2:00 PM, Patrick Palka wrote:
> > > > On Tue, 6 Aug 2024, Jason Merrill wrote:
> > > > 
> > > > > On 8/5/24 6:09 PM, Patrick Palka wrote:
> > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > 
> > > > > > > On 8/5/24 3:47 PM, Patrick Palka wrote:
> > > > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > > > 
> > > > > > > > > On 8/5/24 1:14 PM, Patrick Palka wrote:
> > > > > > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > > > > > 
> > > > > > > > > > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > > > > > > > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > > > > > > > > > 
> > > > > > > > > > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > > > > > > > > > In recent versions of GCC we've been
> > > > > > > > > > > > > > > diagnosing more
> > > > > > > > > > > > > > > and
> > > > > > > > > > > > > > > more
> > > > > > > > > > > > > > > kinds of
> > > > > > > > > > > > > > > errors inside a template ahead of time. 
> > > > > > > > > > > > > > > This is a
> > > > > > > > > > > > > > > largely
> > > > > > > > > > > > > > > good
> > > > > > > > > > > > > > > thing
> > > > > > > > > > > > > > > because it catches bugs, typos, dead code
> > > > > > > > > > > > > > > etc sooner.
> > > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > But if the template never gets
> > > > > > > > > > > > > > > instantiated then such
> > > > > > > > > > > > > > > errors
> > > > > > > > > > > > > > > are
> > > > > > > > > > > > > > > harmless, and can be inconvenient to work
> > > > > > > > > > > > > > > around if
> > > > > > > > > > > > > > > say
> > > > > > > > > > > > > > > the
> > > > > > > > > > > > > > > code
> > > > > > > > > > > > > > > in
> > > > > > > > > > > > > > > question is third party and in
> > > > > > > > > > > > > > > maintenence mode.  So
> > > > > > > > > > > > > > > it'd
> > > > > > > > > > > > > > > be
> > > > > > > > > > > > > > > useful to
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > "maintenance"
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > diff --git a/gcc/cp/error.cc
> > > > > > > > > > > > > > > b/gcc/cp/error.cc
> > > > > > > > > > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > > > > > > > > > --- a/gcc/cp/error.cc
> > > > > > > > > > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > > > > > > > > > @@ -165,6 +165,58 @@ class
> > > > > > > > > > > > > > > cxx_format_postprocessor :
> > > > > > > > > > > > > > > public
> > > > > > > > > > > > > > > format_postprocessor
> > > > > > > > > > > > > > >           deferred_printed_type m_type_b;
> > > > > > > > > > > > > > >         };
> > > > > > > > > > > > > > >         +/* A map from TEMPLATE_DECL to
> > > > > > > > > > > > > > > the location of
> > > > > > > > > > > > > > > the
> > > > > > > > > > > > > > > first
> > > > > > > > > > > > > > > error (if
> > > > > > > > > > > > > > > any)
> > > > > > > > > > > > > > > +   within the template that we
> > > > > > > > > > > > > > > permissivly downgraded
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > a
> > > > > > > > > > > > > > > warning.
> > > > > > > > > > > > > > > */
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > "permissively"
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > +relaxed_template_errors_t
> > > > > > > > > > > > > > > *relaxed_template_errors;
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +/* Callback function
> > > > > > > > > > > > > > > diagnostic_context::m_adjust_diagnostic_i
> > > > > > > > > > > > > > > nfo.
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +   In -fpermissive mode we downgrade
> > > > > > > > > > > > > > > errors within a
> > > > > > > > > > > > > > > template
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > +   warnings, and only issue an error if
> > > > > > > > > > > > > > > we later need
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > instantiate
> > > > > > > > > > > > > > > +   the template.  */
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +static void
> > > > > > > > > > > > > > > +cp_adjust_diagnostic_info
> > > > > > > > > > > > > > > (diagnostic_context
> > > > > > > > > > > > > > > *context,
> > > > > > > > > > > > > > > +                         
> > > > > > > > > > > > > > > diagnostic_info
> > > > > > > > > > > > > > > *diagnostic)
> > > > > > > > > > > > > > > +{
> > > > > > > > > > > > > > > +  tree ti;
> > > > > > > > > > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > > > > > > > > > +      && context->m_permissive
> > > > > > > > > > > > > > > +      && !current_instantiation ()
> > > > > > > > > > > > > > > +      && in_template_context
> > > > > > > > > > > > > > > +      && (ti = get_template_info
> > > > > > > > > > > > > > > (current_scope ())))
> > > > > > > > > > > > > > > +    {
> > > > > > > > > > > > > > > +      if (!relaxed_template_errors)
> > > > > > > > > > > > > > > +       relaxed_template_errors = new
> > > > > > > > > > > > > > > relaxed_template_errors_t;
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > > > > > > > > > +      if (!relaxed_template_errors->get
> > > > > > > > > > > > > > > (tmpl))
> > > > > > > > > > > > > > > +       relaxed_template_errors->put
> > > > > > > > > > > > > > > (tmpl,
> > > > > > > > > > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > > > > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > Rather than check m_permissive directly and
> > > > > > > > > > > > > > downgrade to
> > > > > > > > > > > > > > DK_WARNING,
> > > > > > > > > > > > > > how
> > > > > > > > > > > > > > about
> > > > > > > > > > > > > > downgrading to DK_PERMERROR?  That way
> > > > > > > > > > > > > > people will get
> > > > > > > > > > > > > > the
> > > > > > > > > > > > > > [-fpermissive]
> > > > > > > > > > > > > > clue.
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > ...though I suppose DK_PERMERROR doesn't
> > > > > > > > > > > > > > work where you
> > > > > > > > > > > > > > call
> > > > > > > > > > > > > > this
> > > > > > > > > > > > > > hook
> > > > > > > > > > > > > > in
> > > > > > > > > > > > > > report_diagnostic, at which point we've
> > > > > > > > > > > > > > already
> > > > > > > > > > > > > > reassigned
> > > > > > > > > > > > > > it
> > > > > > > > > > > > > > into
> > > > > > > > > > > > > > DK_WARNING
> > > > > > > > > > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > But we could still set diagnostic-
> > > > > > > > > > > > > > >option_index even for
> > > > > > > > > > > > > > DK_ERROR,
> > > > > > > > > > > > > > whether to
> > > > > > > > > > > > > > context->m_opt_permissive or to its own
> > > > > > > > > > > > > > warning flag,
> > > > > > > > > > > > > > perhaps
> > > > > > > > > > > > > > -Wno-template-body?
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed by adding an enabled-by-default -
> > > > > > > > > > > > > Wtemplate-body flag
> > > > > > > > > > > > > and
> > > > > > > > > > > > > setting
> > > > > > > > > > > > > option_index to it for each downgraded
> > > > > > > > > > > > > error.  Thus
> > > > > > > > > > > > > -permissive
> > > > > > > > > > > > > -Wno-template-body would suppress the
> > > > > > > > > > > > > downgraded warnings
> > > > > > > > > > > > > entirely,
> > > > > > > > > > > > > and
> > > > > > > > > > > > > only issue a generic error upon instantiation
> > > > > > > > > > > > > of the
> > > > > > > > > > > > > erroneous
> > > > > > > > > > > > > template.
> > > > > > > > > > > > 
> > > > > > > > > > > > ... or did you have in mind to set option_index
> > > > > > > > > > > > even when
> > > > > > > > > > > > not
> > > > > > > > > > > > using
> > > > > > > > > > > > -fpermissive so that eligible non-downgraded
> > > > > > > > > > > > errors get the
> > > > > > > > > > > > [-fpermissive] or [-Wtemplate-body] hint as
> > > > > > > > > > > > well?
> > > > > > > > > > > 
> > > > > > > > > > > Yes.
> > > > > > > > > > > 
> > > > > > > > > > > > IMHO I'm not sure that'd be worth the extra
> > > > > > > > > > > > noise since the
> > > > > > > > > > > > vast
> > > > > > > > > > > > majority of users appreciate and expect errors
> > > > > > > > > > > > to get
> > > > > > > > > > > > diagnosed
> > > > > > > > > > > > inside
> > > > > > > > > > > > templates.
> > > > > > > > > > > 
> > > > > > > > > > > But people trying to build legacy code should
> > > > > > > > > > > appreciate the
> > > > > > > > > > > pointer
> > > > > > > > > > > for
> > > > > > > > > > > how
> > > > > > > > > > > to make it compile, as with other permerrors.
> > > > > > > > > > > 
> > > > > > > > > > > > And on second thought I'm not sure what extra
> > > > > > > > > > > > value a new
> > > > > > > > > > > > warning
> > > > > > > > > > > > flag
> > > > > > > > > > > > adds either.  I can't think of a good reason
> > > > > > > > > > > > why one would
> > > > > > > > > > > > use
> > > > > > > > > > > > -fpermissive -Wno-template-body?
> > > > > > > > > > > 
> > > > > > > > > > > One would use -Wno-template-body (or -Wno-
> > > > > > > > > > > error=template-body)
> > > > > > > > > > > without
> > > > > > > > > > > -fpermissive, like with the various permerror_opt
> > > > > > > > > > > cases.
> > > > > > > > > > 
> > > > > > > > > > Since compiling legacy/unmaintained code is the
> > > > > > > > > > only plausible
> > > > > > > > > > use
> > > > > > > > > > case,
> > > > > > > > > > why have a dedicated warning flag instead of just
> > > > > > > > > > recommending
> > > > > > > > > > -fpermissive
> > > > > > > > > > when compiling legacy code?  I don't quite
> > > > > > > > > > understand the
> > > > > > > > > > motivation
> > > > > > > > > > for
> > > > > > > > > > adding a new permerror_opt flag for this class of
> > > > > > > > > > errors.
> > > > > > > > > 
> > > > > > > > > It seems to me an interesting class of errors, but I
> > > > > > > > > don't mind
> > > > > > > > > leaving it
> > > > > > > > > under just -fpermissive if you prefer.
> > > > > > > > > 
> > > > > > > > > > -Wnarrowing is an existing permerror_opt flag, but
> > > > > > > > > > I can imagine
> > > > > > > > > > it's
> > > > > > > > > > useful to pass -Wno-error=narrowing etc when
> > > > > > > > > > incrementally
> > > > > > > > > > migrating
> > > > > > > > > > C / C++98 code to modern C++ where you don't want
> > > > > > > > > > any
> > > > > > > > > > conformance
> > > > > > > > > > errors
> > > > > > > > > > allowed by -fpermissive to sneak in.  So being able
> > > > > > > > > > to narrowly
> > > > > > > > > > control
> > > > > > > > > > this class of errors seems useful, so a dedicated
> > > > > > > > > > flag makes
> > > > > > > > > > sense.
> > > > > > > > > > 
> > > > > > > > > > But there's no parallel for -Wtemplate-body here,
> > > > > > > > > > since by
> > > > > > > > > > assumption
> > > > > > > > > > the code base is unmaintained / immutable. 
> > > > > > > > > > Otherwise the more
> > > > > > > > > > proper
> > > > > > > > > > fix would be to just fix and/or delete the
> > > > > > > > > > uninstantiated
> > > > > > > > > > erroneous
> > > > > > > > > > template.  If say you're #including a legacy header
> > > > > > > > > > that has
> > > > > > > > > > such
> > > > > > > > > > errors, then doing #pragma GCC diagnostic "-
> > > > > > > > > > fpermissive -w"
> > > > > > > > > > around
> > > > > > > > > > the #include should be totally fine too.
> > > > > > > > 
> > > > > > > > I just realized #pragma GCC diagnostic warning "-
> > > > > > > > fpermissive" etc
> > > > > > > > doesn't actually work since -fpermissive isn't a
> > > > > > > > warning flag.  So
> > > > > > > > having a dedicated flag for the class of errors has at
> > > > > > > > least one
> > > > > > > > clear
> > > > > > > > benefit -- we can use #pragmas to selectively disable
> > > > > > > > the errors
> > > > > > > > now.
> > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > I just don't see the use case for being able to
> > > > > > > > > > narrowly control
> > > > > > > > > > this
> > > > > > > > > > class of errors that justifies the extra
> > > > > > > > > > implementation
> > > > > > > > > > complexity
> > > > > > > > > > (specifically for properly detecting -Wno-
> > > > > > > > > > error=template-body in
> > > > > > > > > > the
> > > > > > > > > > callback hook)?
> > > > > > > > > 
> > > > > > > > > The hook shouldn't need to do anything special;
> > > > > > > > > report_diagnostic
> > > > > > > > > handles
> > > > > > > > > -Wno-error=whatever.
> > > > > > > > 
> > > > > > > > The issue was that the callback has to know in advance
> > > > > > > > whether
> > > > > > > > -Wno-error=template-body is active so that it can flag
> > > > > > > > the template
> > > > > > > > as
> > > > > > > > having a relaxed error.  Checking !warning_enabled_at
> > > > > > > > wasn't enough
> > > > > > > > because it will return true even for -Wno-
> > > > > > > > error=template-body.  I
> > > > > > > > think we just need to check diagnostic_enabled
> > > > > > > > directly, like so?
> > > > > > > 
> > > > > > > Why not always flag it?  It doesn't seem harmful to give
> > > > > > > the
> > > > > > > "instatiating
> > > > > > > erroneous template" error later even if we gave a hard
> > > > > > > error during
> > > > > > > parsing.
> > > > > > 
> > > > > > Hmm, it just seems redundant to me I guess?  The reason we
> > > > > > need the
> > > > > > "instantiating erroneous template" error is to ensure that
> > > > > > _some_ error
> > > > > > was issued rendering the TU ill-formed when an erroneous
> > > > > > template gets
> > > > > > instantiated.  If parse-time errors are hard errors then
> > > > > > this is
> > > > > > guaranteed, but it's not if we're downgrading such errors.
> > > > > 
> > > > > Yes, it's redundant, but avoiding it doesn't seem worth
> > > > > duplicating all
> > > > > the
> > > > > logic to determine whether it will be an error or not.
> > > > > 
> > > > > Simpler might be to skip the "instantiating" error if
> > > > > seen_error()?
> > > > 
> > > > Sure.  This makes the expected diagnostics in the -Wno-
> > > > template-body
> > > > testcase a bit lacking since there's three instantiated
> > > > templates with
> > > > one logical error each, but only the first error is diagnosed. 
> > > > But I
> > > > don't particularly mind that.
> > > > 
> > > > > 
> > > > > > On that note it just occurred to me that we don't need to
> > > > > > abort
> > > > > > instantiation after the "instantiating erroneous template"
> > > > > > error --
> > > > > > for sake of error recovery we can proceed to instantiate as
> > > > > > if we
> > > > > > issued a hard error at parse time.
> > > > > 
> > > > > Agreed.
> > > > > 
> > > > > Jason
> > > > > 
> > > > > 
> > > > 
> > > > -- >8 --
> > > > 
> > > > Subject: [PATCH] c++: permit errors inside uninstantiated
> > > > templates
> > > > [PR116064]
> > > > 
> > > > In recent versions of GCC we've been diagnosing more and more
> > > > kinds of
> > > > errors inside a template ahead of time.  This is a largely good
> > > > thing
> > > > because it catches bugs, typos, dead code etc sooner.
> > > > 
> > > > But if the template never gets instantiated then such errors
> > > > are harmless
> > > > and can be inconvenient to work around if say the code in
> > > > question is
> > > > third party and in maintenance mode.  So it'd be handy to be
> > > > able to
> > > > prevent these template errors from rendering the entire TU
> > > > uncompilable.
> > > > (Note that such code is "ill-formed no diagnostic required"
> > > > according
> > > > the standard.)
> > > > 
> > > > To that end this patch turns any errors issued within a
> > > > template into
> > > > premerrors associated with a new -Wtemplate-body flag so that
> > > > they can
> > > 
> > > perm
> > > 
> > > > be downgraded via e.g. -fpermissive or -Wno-template=template-
> > > > body.  If
> > > > the template containing a downgraded error later needs to be
> > > > instantiated,
> > > > we'll issue an error then.  But if the template never gets
> > > > instantiated
> > > > then the downgraded error won't affect validity of the rest of
> > > > the TU.
> > > > 
> > > > This is implemented via a diagnostic hook that gets called for
> > > > each
> > > > diagnostic, and which adjusts an error diagnostic if we detect
> > > > it's
> > > > occurring from a template context and additionally flags this
> > > > template
> > > > context as erroneous.
> > > > 
> > > > As an example, permissive-error1a.C gives:
> > > > 
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C: In function
> > > > 'void f()':
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5:
> > > > warning: increment
> > > > of read-only variable 'n' [-Wtemplate-body]
> > > >       7 |   ++n;
> > > >         |     ^
> > > > ...
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C: In
> > > > instantiation of
> > > > 'void f() [with T = int]':
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:  
> > > > required from
> > > > here
> > > >      26 |   f<int>();
> > > >         |   ~~~~~~^~
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error:
> > > > instantiating
> > > > erroneous template pattern
> > > 
> > > Drop "pattern" here too.
> > 
> > Fixed
> > 
> > > 
> > > >       5 | void f() {
> > > >         |      ^
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note:
> > > > first error
> > > > appeared here
> > > >       7 |   ++n; // {
> > > >         |     ^
> > > > ...
> > > 
> > > What happens if we compile an interface/header unit with an
> > > erroneous
> > > uninstantiated template?  I'd probably reject such a module
> > > rather than write
> > > out the erroneity.  Possibly just if the template isn't
> > > discarded, not sure if
> > > that distinction is too much trouble to bother with.
> > 
> > I think it's good enough to uniformly reject the module given how
> > small
> > the intersection -fpermissive and modules users is, I imagine.
> > 
> > > 
> > > >         PR c++/116064
> > > > 
> > > > gcc/c-family/ChangeLog:
> > > > 
> > > >         * c.opt (Wtemplate-body): New warning.
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > >         * cp-tree.h (erroneous_templates_t): Declare.
> > > >         (erroneous_templates): Declare.
> > > >         (cp_seen_error): Declare.
> > > >         (basic_seen_error): Define as alias to existing
> > > > seen_error.
> > > 
> > > Or we could use (seen_error)() where we mean the basic one?
> > 
> > Sure, changed.
> > 
> > > 
> > > >         (seen_error): #define to cp_seen_error.
> > > >         * error.cc (get_current_template): Define.
> > > >         (relaxed_template_errors): Define.
> > > >         (cp_adjust_diagnostic_info): Define.
> > > >         (cp_seen_error): Define.
> > > >         (cxx_initialize_diagnostics): Set
> > > >         diagnostic_context::m_adjust_diagnostic_info.
> > > >         * pt.cc (instantiate_class_template): Issue a hard
> > > > error
> > > >         when trying to instantiate a template pattern
> > > > containing
> > > >         a permissively downgraded error.
> > > >         (instantiate_decl): Likewise.
> > > > 
> > > > gcc/ChangeLog:
> > > > 
> > > >         * diagnostic.cc (diagnostic_context::initialize): Set
> > > >         m_adjust_diagnostic_info.
> > > >         (diagnostic_context::report_diagnostic): Call
> > > >         m_adjust_diagnostic_info.
> > > >         * diagnostic.h
> > > > (diagnostic_context::diagnostic_enabled): Make
> > > >         public.
> > > 
> > > You shouldn't need this change now?
> > 
> > Oops, removed.
> > 
> > > 
> > > > +static void
> > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > +                          diagnostic_info *diagnostic)
> > > > +{
> > > > +  if (diagnostic->kind == DK_ERROR)
> > > > +    if (tree tmpl = get_current_template ())
> > > > +      {
> > > > +       diagnostic->option_index = OPT_Wtemplate_body;
> > > > +
> > > > +       if (context->m_permissive)
> > > > +         diagnostic->kind = DK_WARNING;
> > > > +
> > > > +       if (!erroneous_templates)
> > > > +         erroneous_templates = new erroneous_templates_t;
> > > > +       if (!erroneous_templates->get (tmpl))
> > > > +         {
> > > > +           /* Remember that this template had a parse-time
> > > > error so
> > > > +              that we'll ensure a hard error has been issued
> > > > upon
> > > > +              its instantiation.  */
> > > > +           location_t error_loc = diagnostic->richloc->get_loc
> > > > ();
> > > > +           erroneous_templates->put (tmpl, error_loc);
> > > 
> > > Instead of get+put you could use the get return value to store
> > > error_loc?
> > 
> > We need to use get_or_insert if we want to combine the two lookups.
> > I went ahead and used hash_map_safe_get_or_insert here to get rid
> > of the construction boilerplate as well.
> > 
> > > 
> > > > +@opindex Wtemplate-body
> > > > +@opindex Wno-template-body
> > > > +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
> > > > +Disable diagnosing errors when parsing a template, and instead
> > > > issue an
> > > > +error only upon instantiation of the template.  This flag can
> > > > also be
> > > > +used to downgrade such errors into warnings with @option{Wno-
> > > > error=} or
> > > > +@option{-fpermissive}.
> > > 
> > > Please also refer to this flag in the -fpermissive documentation.
> > 
> > Done.
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: permit errors inside uninstantiated templates
> > [PR116064]
> > 
> > In recent versions of GCC we've been diagnosing more and more kinds
> > of
> > errors inside a template ahead of time.  This is a largely good
> > thing
> > because it catches bugs, typos, dead code etc sooner.
> > 
> > But if the template never gets instantiated then such errors are
> > harmless
> > and can be inconvenient to work around if say the code in question
> > is
> > third party and in maintenance mode.  So it'd be handy to be able
> > to
> > prevent these template errors from rendering the entire TU
> > uncompilable.
> > (Note that such code is "ill-formed no diagnostic required"
> > according
> > the standard.)
> > 
> > To that end this patch turns any errors issued within a template
> > into
> > permerrors associated with a new -Wtemplate-body flag so that they
> > can
> > be downgraded via e.g. -fpermissive or -Wno-template=template-
> > body.  If
> > the template containing a downgraded error later needs to be
> > instantiated,
> > we'll issue an error then.  But if the template never gets
> > instantiated
> > then the downgraded error won't affect validity of the rest of the
> > TU.
> > 
> > This is implemented via a diagnostic hook that gets called for each
> > diagnostic, and which adjusts an error diagnostic if we detect it's
> > occurring from a template context and additionally flags this
> > template
> > context as erroneous.
> > 
> > As an example, permissive-error1a.C gives:
> > 
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In function
> > 'void f()':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning:
> > increment of read-only variable 'n' [-Wtemplate-body]
> >      7 |   ++n;
> >        |     ^
> > ...
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In
> > instantiation of 'void f() [with T = int]':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required
> > from here
> >     26 |   f<int>();
> >        |   ~~~~~~^~
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error:
> > instantiating erroneous template
> >      5 | void f() {
> >        |      ^
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first
> > error appeared here
> >      7 |   ++n; // {
> >        |     ^
> > ...
> > 
> >         PR c++/116064
> > 
> > gcc/c-family/ChangeLog:
> > 
> >         * c.opt (Wtemplate-body): New warning.
> > 
> > gcc/cp/ChangeLog:
> > 
> >         * cp-tree.h (erroneous_templates_t): Declare.
> >         (erroneous_templates): Declare.
> >         (cp_seen_error): Declare.
> >         (seen_error): #define to cp_seen_error.
> >         * error.cc (get_current_template): Define.
> >         (relaxed_template_errors): Define.
> >         (cp_adjust_diagnostic_info): Define.
> >         (cp_seen_error): Define.
> >         (cxx_initialize_diagnostics): Set
> >         diagnostic_context::m_adjust_diagnostic_info.
> >         * module.cc (finish_module_processing): Don't write the
> >         module if it contains an erroneous template.
> >         * pt.cc (instantiate_class_template): Issue a hard error
> >         when trying to instantiate a template containing a
> >         permissively downgraded error.
> >         (instantiate_decl): Likewise.
> > 
> > gcc/ChangeLog:
> > 
> >         * diagnostic.cc (diagnostic_context::initialize): Set
> >         m_adjust_diagnostic_info.
> >         (diagnostic_context::report_diagnostic): Call
> >         m_adjust_diagnostic_info.
> >         * diagnostic.h
> > (diagnostic_context::m_adjust_diagnostic_info):
> >         New data member.
> >         * doc/invoke.texi (-Wno-template-body): Document.
> >         (-fpermissive): Mention -Wtemplate-body.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >         * g++.dg/ext/typedef-init.C: Downgrade error inside
> > template
> >         to warning due to -fpermissive.
> >         * g++.dg/pr84492.C: Likewise.
> >         * g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-
> > options.
> >         * g++.dg/template/permissive-error1.C: New test.
> >         * g++.dg/template/permissive-error1a.C: New test.
> >         * g++.dg/template/permissive-error1b.C: New test.
> >         * g++.dg/template/permissive-error1c.C: New test.
> > ---
> >   gcc/c-family/c.opt                            |  4 ++
> >   gcc/cp/cp-tree.h                              |  7 ++
> >   gcc/cp/error.cc                               | 67
> > +++++++++++++++++++
> >   gcc/cp/module.cc                              |  5 +-
> >   gcc/cp/pt.cc                                  | 24 +++++++
> >   gcc/diagnostic.cc                             |  4 ++
> >   gcc/diagnostic.h                              |  4 ++
> >   gcc/doc/invoke.texi                           | 12 +++-
> >   gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
> >   gcc/testsuite/g++.dg/pr84492.C                |  4 +-
> >   .../g++.dg/template/permissive-error1.C       | 20 ++++++
> >   .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
> >   .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
> >   .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
> >   gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
> >   15 files changed, 240 insertions(+), 6 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-
> > error1.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-
> > error1a.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-
> > error1b.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-
> > error1c.C
> > 
> > diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> > index a52682d835c..44117ba713c 100644
> > --- a/gcc/c-family/c.opt
> > +++ b/gcc/c-family/c.opt
> > @@ -1420,6 +1420,10 @@ Wtautological-compare
> >   C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning
> > LangEnabledBy(C ObjC C++ ObjC++,Wall)
> >   Warn if a comparison always evaluates to true or false.
> >   
> > +Wtemplate-body
> > +C++ ObjC++ Var(warn_template_body) Warning Init(1)
> > +Diagnose errors when parsing a template.
> > +
> >   Wtemplate-id-cdtor
> >   C++ ObjC++ Var(warn_template_id_cdtor) Warning
> >   Warn about simple-template-id in a constructor or destructor.
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 238d786b067..08ee5895c6a 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7190,6 +7190,13 @@ extern location_t
> > location_of                   (tree);
> >   extern void qualified_name_lookup_error               (tree,
> > tree, tree,
> >                                                  location_t);
> >   
> > +using erroneous_templates_t
> > +  = hash_map<tree, location_t,
> > simple_hashmap_traits<tree_decl_hash, location_t>>;
> > +extern erroneous_templates_t *erroneous_templates;
> > +
> > +extern bool cp_seen_error ();
> > +#define seen_error() cp_seen_error()
> > +
> >   /* in except.cc */
> >   extern void init_terminate_fn                 (void);
> >   extern void init_exception_processing         (void);
> > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > index d80bac822ba..7eab0bf76b4 100644
> > --- a/gcc/cp/error.cc
> > +++ b/gcc/cp/error.cc
> > @@ -165,6 +165,72 @@ class cxx_format_postprocessor : public
> > format_postprocessor
> >     deferred_printed_type m_type_b;
> >   };
> >   
> > +/* Return the in-scope template that's currently being parsed, or
> > +   NULL_TREE otherwise.  */
> > +
> > +static tree
> > +get_current_template ()
> > +{
> > +  if (scope_chain && in_template_context && !current_instantiation
> > ())
> > +    if (tree ti = get_template_info (current_scope ()))
> > +      return TI_TEMPLATE (ti);
> > +
> > +  return NULL_TREE;
> > +}
> > +
> > +/* A map from TEMPLATE_DECLs that we've determined to be erroneous
> > +   at parse time to the location of the first error within.  */
> > +
> > +erroneous_templates_t *erroneous_templates;
> > +
> > +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> > +
> > +   Errors issued when parsing a template are automatically treated
> > like
> > +   permerrors associated with the -Wtemplate-body flag and can be
> > +   downgraded into warnings accordingly, in which case we'll still
> > +   issue an error if we later need to instantiate the template. 
> > */
> > +
> > +static void
> > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > +                          diagnostic_info *diagnostic)
> > +{
> > +  if (diagnostic->kind == DK_ERROR)
> > +    if (tree tmpl = get_current_template ())
> > +      {
> > +       diagnostic->option_index = OPT_Wtemplate_body;
> > +
> > +       if (context->m_permissive)
> > +         diagnostic->kind = DK_WARNING;
> > +
> > +       bool existed;
> > +       location_t &error_loc
> > +         = hash_map_safe_get_or_insert<false>
> > (erroneous_templates,
> > +                                               tmpl, &existed);
> > +       if (!existed)
> > +         /* Remember that this template had a parse-time error so
> > +            that we'll ensure a hard error has been issued upon
> > +            its instantiation.  */
> > +         error_loc = diagnostic->richloc->get_loc ();
> > +      }
> > +}
> > +
> > +/* A generalization of seen_error which also returns true if we've
> > +   permissively downgraded an error to a warning inside a
> > template.  */
> > +
> > +bool
> > +cp_seen_error ()
> > +{
> > +  if ((seen_error) ())
> > +    return true;
> > +
> > +  if (erroneous_templates)
> > +    if (tree tmpl = get_current_template ())
> > +      if (erroneous_templates->get (tmpl))
> > +       return true;
> > +
> > +  return false;
> > +}
> > +
> >   /* CONTEXT->printer is a basic pretty printer that was
> > constructed
> >      presumably by diagnostic_initialize(), called early in the
> >      compiler's initialization process (in general_init) Before the
> > FE
> > @@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context
> > *context)
> >     diagnostic_starter (context) = cp_diagnostic_starter;
> >     /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
> >     diagnostic_format_decoder (context) = cp_printer;
> > +  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
> >     pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
> >   }
> >   
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index d1607a06757..7130faf26f5 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader
> > *reader)
> >   
> >         cookie = new module_processing_cookie (cmi_name, tmp_name,
> > fd, e);
> >   
> > -      if (errorcount)
> > +      if (errorcount
> > +         /* Don't write the module if it contains an erroneous
> > template.  */
> > +         || (erroneous_templates
> > +             && !erroneous_templates->is_empty ()))
> >         warning_at (state->loc, 0, "not writing module %qs due to
> > errors",
> >                     state->get_flatname ());
> >         else if (cookie->out.begin ())
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 77fa5907c3d..b83922a530d 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
> >     if (! push_tinst_level (type))
> >       return type;
> >   
> > +  if (erroneous_templates && !(seen_error) ())
> > +    if (location_t *error_loc = erroneous_templates->get (templ))
> > +      {
> > +       /* We're trying to instantiate a template pattern
> > containing
> > +          an error that we've permissively downgraded to a
> > warning.
> > +          Issue a hard error now to ensure the TU is considered
> > +          ill-formed.  */
> > +       location_t decl_loc = location_of (templ);
> > +       error_at (decl_loc, "instantiating erroneous template");
> > +       inform (*error_loc, "first error appeared here");
> > +      }

I confess I haven't been paying proper attention to this thread -
sorry, but please can you add an
   auto_diagnostic_group d;
before the "error_at", so that the "inform" is assocated with it.

Thanks
Dave


> 
> Let's factor this out instead of repeating it.  OK with that change.
> 
> Jason
>
  
Patrick Palka Aug. 7, 2024, 12:58 a.m. UTC | #16
On Tue, 6 Aug 2024, Jason Merrill wrote:

> On 8/6/24 5:47 PM, Patrick Palka wrote:
> > On Tue, 6 Aug 2024, Jason Merrill wrote:
> > 
> > > On 8/6/24 2:00 PM, Patrick Palka wrote:
> > > > On Tue, 6 Aug 2024, Jason Merrill wrote:
> > > > 
> > > > > On 8/5/24 6:09 PM, Patrick Palka wrote:
> > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > 
> > > > > > > On 8/5/24 3:47 PM, Patrick Palka wrote:
> > > > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > > > 
> > > > > > > > > On 8/5/24 1:14 PM, Patrick Palka wrote:
> > > > > > > > > > On Mon, 5 Aug 2024, Jason Merrill wrote:
> > > > > > > > > > 
> > > > > > > > > > > On 8/2/24 4:18 PM, Patrick Palka wrote:
> > > > > > > > > > > > On Fri, 2 Aug 2024, Patrick Palka wrote:
> > > > > > > > > > > > 
> > > > > > > > > > > > > On Fri, 2 Aug 2024, Jason Merrill wrote:
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > On 8/1/24 2:52 PM, Patrick Palka wrote:
> > > > > > > > > > > > > > > In recent versions of GCC we've been diagnosing
> > > > > > > > > > > > > > > more
> > > > > > > > > > > > > > > and
> > > > > > > > > > > > > > > more
> > > > > > > > > > > > > > > kinds of
> > > > > > > > > > > > > > > errors inside a template ahead of time.  This is a
> > > > > > > > > > > > > > > largely
> > > > > > > > > > > > > > > good
> > > > > > > > > > > > > > > thing
> > > > > > > > > > > > > > > because it catches bugs, typos, dead code etc
> > > > > > > > > > > > > > > sooner.
> > > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > But if the template never gets instantiated then
> > > > > > > > > > > > > > > such
> > > > > > > > > > > > > > > errors
> > > > > > > > > > > > > > > are
> > > > > > > > > > > > > > > harmless, and can be inconvenient to work around
> > > > > > > > > > > > > > > if
> > > > > > > > > > > > > > > say
> > > > > > > > > > > > > > > the
> > > > > > > > > > > > > > > code
> > > > > > > > > > > > > > > in
> > > > > > > > > > > > > > > question is third party and in maintenence mode.
> > > > > > > > > > > > > > > So
> > > > > > > > > > > > > > > it'd
> > > > > > > > > > > > > > > be
> > > > > > > > > > > > > > > useful to
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > "maintenance"
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > > > > > > > > > > > index d80bac822ba..0bb0a482e28 100644
> > > > > > > > > > > > > > > --- a/gcc/cp/error.cc
> > > > > > > > > > > > > > > +++ b/gcc/cp/error.cc
> > > > > > > > > > > > > > > @@ -165,6 +165,58 @@ class
> > > > > > > > > > > > > > > cxx_format_postprocessor :
> > > > > > > > > > > > > > > public
> > > > > > > > > > > > > > > format_postprocessor
> > > > > > > > > > > > > > >           deferred_printed_type m_type_b;
> > > > > > > > > > > > > > >         };
> > > > > > > > > > > > > > >         +/* A map from TEMPLATE_DECL to the
> > > > > > > > > > > > > > > location of
> > > > > > > > > > > > > > > the
> > > > > > > > > > > > > > > first
> > > > > > > > > > > > > > > error (if
> > > > > > > > > > > > > > > any)
> > > > > > > > > > > > > > > +   within the template that we permissivly
> > > > > > > > > > > > > > > downgraded
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > a
> > > > > > > > > > > > > > > warning.
> > > > > > > > > > > > > > > */
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > "permissively"
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed
> > > > > > > > > > > > > 
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > > +relaxed_template_errors_t
> > > > > > > > > > > > > > > *relaxed_template_errors;
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +/* Callback function
> > > > > > > > > > > > > > > diagnostic_context::m_adjust_diagnostic_info.
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +   In -fpermissive mode we downgrade errors
> > > > > > > > > > > > > > > within a
> > > > > > > > > > > > > > > template
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > +   warnings, and only issue an error if we later
> > > > > > > > > > > > > > > need
> > > > > > > > > > > > > > > to
> > > > > > > > > > > > > > > instantiate
> > > > > > > > > > > > > > > +   the template.  */
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +static void
> > > > > > > > > > > > > > > +cp_adjust_diagnostic_info (diagnostic_context
> > > > > > > > > > > > > > > *context,
> > > > > > > > > > > > > > > +			   diagnostic_info
> > > > > > > > > > > > > > > *diagnostic)
> > > > > > > > > > > > > > > +{
> > > > > > > > > > > > > > > +  tree ti;
> > > > > > > > > > > > > > > +  if (diagnostic->kind == DK_ERROR
> > > > > > > > > > > > > > > +      && context->m_permissive
> > > > > > > > > > > > > > > +      && !current_instantiation ()
> > > > > > > > > > > > > > > +      && in_template_context
> > > > > > > > > > > > > > > +      && (ti = get_template_info (current_scope
> > > > > > > > > > > > > > > ())))
> > > > > > > > > > > > > > > +    {
> > > > > > > > > > > > > > > +      if (!relaxed_template_errors)
> > > > > > > > > > > > > > > +	relaxed_template_errors = new
> > > > > > > > > > > > > > > relaxed_template_errors_t;
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +      tree tmpl = TI_TEMPLATE (ti);
> > > > > > > > > > > > > > > +      if (!relaxed_template_errors->get (tmpl))
> > > > > > > > > > > > > > > +	relaxed_template_errors->put (tmpl,
> > > > > > > > > > > > > > > diagnostic->richloc->get_loc ());
> > > > > > > > > > > > > > > +      diagnostic->kind = DK_WARNING;
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > Rather than check m_permissive directly and
> > > > > > > > > > > > > > downgrade to
> > > > > > > > > > > > > > DK_WARNING,
> > > > > > > > > > > > > > how
> > > > > > > > > > > > > > about
> > > > > > > > > > > > > > downgrading to DK_PERMERROR?  That way people will
> > > > > > > > > > > > > > get
> > > > > > > > > > > > > > the
> > > > > > > > > > > > > > [-fpermissive]
> > > > > > > > > > > > > > clue.
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > ...though I suppose DK_PERMERROR doesn't work where
> > > > > > > > > > > > > > you
> > > > > > > > > > > > > > call
> > > > > > > > > > > > > > this
> > > > > > > > > > > > > > hook
> > > > > > > > > > > > > > in
> > > > > > > > > > > > > > report_diagnostic, at which point we've already
> > > > > > > > > > > > > > reassigned
> > > > > > > > > > > > > > it
> > > > > > > > > > > > > > into
> > > > > > > > > > > > > > DK_WARNING
> > > > > > > > > > > > > > or DK_ERROR in diagnostic_impl.
> > > > > > > > > > > > > > 
> > > > > > > > > > > > > > But we could still set diagnostic->option_index even
> > > > > > > > > > > > > > for
> > > > > > > > > > > > > > DK_ERROR,
> > > > > > > > > > > > > > whether to
> > > > > > > > > > > > > > context->m_opt_permissive or to its own warning
> > > > > > > > > > > > > > flag,
> > > > > > > > > > > > > > perhaps
> > > > > > > > > > > > > > -Wno-template-body?
> > > > > > > > > > > > > 
> > > > > > > > > > > > > Fixed by adding an enabled-by-default -Wtemplate-body
> > > > > > > > > > > > > flag
> > > > > > > > > > > > > and
> > > > > > > > > > > > > setting
> > > > > > > > > > > > > option_index to it for each downgraded error.  Thus
> > > > > > > > > > > > > -permissive
> > > > > > > > > > > > > -Wno-template-body would suppress the downgraded
> > > > > > > > > > > > > warnings
> > > > > > > > > > > > > entirely,
> > > > > > > > > > > > > and
> > > > > > > > > > > > > only issue a generic error upon instantiation of the
> > > > > > > > > > > > > erroneous
> > > > > > > > > > > > > template.
> > > > > > > > > > > > 
> > > > > > > > > > > > ... or did you have in mind to set option_index even
> > > > > > > > > > > > when
> > > > > > > > > > > > not
> > > > > > > > > > > > using
> > > > > > > > > > > > -fpermissive so that eligible non-downgraded errors get
> > > > > > > > > > > > the
> > > > > > > > > > > > [-fpermissive] or [-Wtemplate-body] hint as well?
> > > > > > > > > > > 
> > > > > > > > > > > Yes.
> > > > > > > > > > > 
> > > > > > > > > > > > IMHO I'm not sure that'd be worth the extra noise since
> > > > > > > > > > > > the
> > > > > > > > > > > > vast
> > > > > > > > > > > > majority of users appreciate and expect errors to get
> > > > > > > > > > > > diagnosed
> > > > > > > > > > > > inside
> > > > > > > > > > > > templates.
> > > > > > > > > > > 
> > > > > > > > > > > But people trying to build legacy code should appreciate
> > > > > > > > > > > the
> > > > > > > > > > > pointer
> > > > > > > > > > > for
> > > > > > > > > > > how
> > > > > > > > > > > to make it compile, as with other permerrors.
> > > > > > > > > > > 
> > > > > > > > > > > > And on second thought I'm not sure what extra value a
> > > > > > > > > > > > new
> > > > > > > > > > > > warning
> > > > > > > > > > > > flag
> > > > > > > > > > > > adds either.  I can't think of a good reason why one
> > > > > > > > > > > > would
> > > > > > > > > > > > use
> > > > > > > > > > > > -fpermissive -Wno-template-body?
> > > > > > > > > > > 
> > > > > > > > > > > One would use -Wno-template-body (or
> > > > > > > > > > > -Wno-error=template-body)
> > > > > > > > > > > without
> > > > > > > > > > > -fpermissive, like with the various permerror_opt cases.
> > > > > > > > > > 
> > > > > > > > > > Since compiling legacy/unmaintained code is the only
> > > > > > > > > > plausible
> > > > > > > > > > use
> > > > > > > > > > case,
> > > > > > > > > > why have a dedicated warning flag instead of just
> > > > > > > > > > recommending
> > > > > > > > > > -fpermissive
> > > > > > > > > > when compiling legacy code?  I don't quite understand the
> > > > > > > > > > motivation
> > > > > > > > > > for
> > > > > > > > > > adding a new permerror_opt flag for this class of errors.
> > > > > > > > > 
> > > > > > > > > It seems to me an interesting class of errors, but I don't
> > > > > > > > > mind
> > > > > > > > > leaving it
> > > > > > > > > under just -fpermissive if you prefer.
> > > > > > > > > 
> > > > > > > > > > -Wnarrowing is an existing permerror_opt flag, but I can
> > > > > > > > > > imagine
> > > > > > > > > > it's
> > > > > > > > > > useful to pass -Wno-error=narrowing etc when incrementally
> > > > > > > > > > migrating
> > > > > > > > > > C / C++98 code to modern C++ where you don't want any
> > > > > > > > > > conformance
> > > > > > > > > > errors
> > > > > > > > > > allowed by -fpermissive to sneak in.  So being able to
> > > > > > > > > > narrowly
> > > > > > > > > > control
> > > > > > > > > > this class of errors seems useful, so a dedicated flag makes
> > > > > > > > > > sense.
> > > > > > > > > > 
> > > > > > > > > > But there's no parallel for -Wtemplate-body here, since by
> > > > > > > > > > assumption
> > > > > > > > > > the code base is unmaintained / immutable.  Otherwise the
> > > > > > > > > > more
> > > > > > > > > > proper
> > > > > > > > > > fix would be to just fix and/or delete the uninstantiated
> > > > > > > > > > erroneous
> > > > > > > > > > template.  If say you're #including a legacy header that has
> > > > > > > > > > such
> > > > > > > > > > errors, then doing #pragma GCC diagnostic "-fpermissive -w"
> > > > > > > > > > around
> > > > > > > > > > the #include should be totally fine too.
> > > > > > > > 
> > > > > > > > I just realized #pragma GCC diagnostic warning "-fpermissive"
> > > > > > > > etc
> > > > > > > > doesn't actually work since -fpermissive isn't a warning flag.
> > > > > > > > So
> > > > > > > > having a dedicated flag for the class of errors has at least one
> > > > > > > > clear
> > > > > > > > benefit -- we can use #pragmas to selectively disable the errors
> > > > > > > > now.
> > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > I just don't see the use case for being able to narrowly
> > > > > > > > > > control
> > > > > > > > > > this
> > > > > > > > > > class of errors that justifies the extra implementation
> > > > > > > > > > complexity
> > > > > > > > > > (specifically for properly detecting
> > > > > > > > > > -Wno-error=template-body in
> > > > > > > > > > the
> > > > > > > > > > callback hook)?
> > > > > > > > > 
> > > > > > > > > The hook shouldn't need to do anything special;
> > > > > > > > > report_diagnostic
> > > > > > > > > handles
> > > > > > > > > -Wno-error=whatever.
> > > > > > > > 
> > > > > > > > The issue was that the callback has to know in advance whether
> > > > > > > > -Wno-error=template-body is active so that it can flag the
> > > > > > > > template
> > > > > > > > as
> > > > > > > > having a relaxed error.  Checking !warning_enabled_at wasn't
> > > > > > > > enough
> > > > > > > > because it will return true even for -Wno-error=template-body.
> > > > > > > > I
> > > > > > > > think we just need to check diagnostic_enabled directly, like
> > > > > > > > so?
> > > > > > > 
> > > > > > > Why not always flag it?  It doesn't seem harmful to give the
> > > > > > > "instatiating
> > > > > > > erroneous template" error later even if we gave a hard error
> > > > > > > during
> > > > > > > parsing.
> > > > > > 
> > > > > > Hmm, it just seems redundant to me I guess?  The reason we need the
> > > > > > "instantiating erroneous template" error is to ensure that _some_
> > > > > > error
> > > > > > was issued rendering the TU ill-formed when an erroneous template
> > > > > > gets
> > > > > > instantiated.  If parse-time errors are hard errors then this is
> > > > > > guaranteed, but it's not if we're downgrading such errors.
> > > > > 
> > > > > Yes, it's redundant, but avoiding it doesn't seem worth duplicating
> > > > > all
> > > > > the
> > > > > logic to determine whether it will be an error or not.
> > > > > 
> > > > > Simpler might be to skip the "instantiating" error if seen_error()?
> > > > 
> > > > Sure.  This makes the expected diagnostics in the -Wno-template-body
> > > > testcase a bit lacking since there's three instantiated templates with
> > > > one logical error each, but only the first error is diagnosed.  But I
> > > > don't particularly mind that.
> > > > 
> > > > > 
> > > > > > On that note it just occurred to me that we don't need to abort
> > > > > > instantiation after the "instantiating erroneous template" error --
> > > > > > for sake of error recovery we can proceed to instantiate as if we
> > > > > > issued a hard error at parse time.
> > > > > 
> > > > > Agreed.
> > > > > 
> > > > > Jason
> > > > > 
> > > > > 
> > > > 
> > > > -- >8 --
> > > > 
> > > > Subject: [PATCH] c++: permit errors inside uninstantiated templates
> > > > [PR116064]
> > > > 
> > > > In recent versions of GCC we've been diagnosing more and more kinds of
> > > > errors inside a template ahead of time.  This is a largely good thing
> > > > because it catches bugs, typos, dead code etc sooner.
> > > > 
> > > > But if the template never gets instantiated then such errors are
> > > > harmless
> > > > and can be inconvenient to work around if say the code in question is
> > > > third party and in maintenance mode.  So it'd be handy to be able to
> > > > prevent these template errors from rendering the entire TU uncompilable.
> > > > (Note that such code is "ill-formed no diagnostic required" according
> > > > the standard.)
> > > > 
> > > > To that end this patch turns any errors issued within a template into
> > > > premerrors associated with a new -Wtemplate-body flag so that they can
> > > 
> > > perm
> > > 
> > > > be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
> > > > the template containing a downgraded error later needs to be
> > > > instantiated,
> > > > we'll issue an error then.  But if the template never gets instantiated
> > > > then the downgraded error won't affect validity of the rest of the TU.
> > > > 
> > > > This is implemented via a diagnostic hook that gets called for each
> > > > diagnostic, and which adjusts an error diagnostic if we detect it's
> > > > occurring from a template context and additionally flags this template
> > > > context as erroneous.
> > > > 
> > > > As an example, permissive-error1a.C gives:
> > > > 
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void
> > > > f()':
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning:
> > > > increment
> > > > of read-only variable 'n' [-Wtemplate-body]
> > > >       7 |   ++n;
> > > >         |     ^
> > > > ...
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of
> > > > 'void f() [with T = int]':
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from
> > > > here
> > > >      26 |   f<int>();
> > > >         |   ~~~~~~^~
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error:
> > > > instantiating
> > > > erroneous template pattern
> > > 
> > > Drop "pattern" here too.
> > 
> > Fixed
> > 
> > > 
> > > >       5 | void f() {
> > > >         |      ^
> > > > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first
> > > > error
> > > > appeared here
> > > >       7 |   ++n; // {
> > > >         |     ^
> > > > ...
> > > 
> > > What happens if we compile an interface/header unit with an erroneous
> > > uninstantiated template?  I'd probably reject such a module rather than
> > > write
> > > out the erroneity.  Possibly just if the template isn't discarded, not
> > > sure if
> > > that distinction is too much trouble to bother with.
> > 
> > I think it's good enough to uniformly reject the module given how small
> > the intersection -fpermissive and modules users is, I imagine.
> > 
> > > 
> > > > 	PR c++/116064
> > > > 
> > > > gcc/c-family/ChangeLog:
> > > > 
> > > > 	* c.opt (Wtemplate-body): New warning.
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* cp-tree.h (erroneous_templates_t): Declare.
> > > > 	(erroneous_templates): Declare.
> > > > 	(cp_seen_error): Declare.
> > > > 	(basic_seen_error): Define as alias to existing seen_error.
> > > 
> > > Or we could use (seen_error)() where we mean the basic one?
> > 
> > Sure, changed.
> > 
> > > 
> > > > 	(seen_error): #define to cp_seen_error.
> > > > 	* error.cc (get_current_template): Define.
> > > > 	(relaxed_template_errors): Define.
> > > > 	(cp_adjust_diagnostic_info): Define.
> > > > 	(cp_seen_error): Define.
> > > > 	(cxx_initialize_diagnostics): Set
> > > > 	diagnostic_context::m_adjust_diagnostic_info.
> > > > 	* pt.cc (instantiate_class_template): Issue a hard error
> > > > 	when trying to instantiate a template pattern containing
> > > > 	a permissively downgraded error.
> > > > 	(instantiate_decl): Likewise.
> > > > 
> > > > gcc/ChangeLog:
> > > > 
> > > > 	* diagnostic.cc (diagnostic_context::initialize): Set
> > > > 	m_adjust_diagnostic_info.
> > > > 	(diagnostic_context::report_diagnostic): Call
> > > > 	m_adjust_diagnostic_info.
> > > > 	* diagnostic.h (diagnostic_context::diagnostic_enabled): Make
> > > > 	public.
> > > 
> > > You shouldn't need this change now?
> > 
> > Oops, removed.
> > 
> > > 
> > > > +static void
> > > > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > > > +			   diagnostic_info *diagnostic)
> > > > +{
> > > > +  if (diagnostic->kind == DK_ERROR)
> > > > +    if (tree tmpl = get_current_template ())
> > > > +      {
> > > > +	diagnostic->option_index = OPT_Wtemplate_body;
> > > > +
> > > > +	if (context->m_permissive)
> > > > +	  diagnostic->kind = DK_WARNING;
> > > > +
> > > > +	if (!erroneous_templates)
> > > > +	  erroneous_templates = new erroneous_templates_t;
> > > > +	if (!erroneous_templates->get (tmpl))
> > > > +	  {
> > > > +	    /* Remember that this template had a parse-time error so
> > > > +	       that we'll ensure a hard error has been issued upon
> > > > +	       its instantiation.  */
> > > > +	    location_t error_loc = diagnostic->richloc->get_loc ();
> > > > +	    erroneous_templates->put (tmpl, error_loc);
> > > 
> > > Instead of get+put you could use the get return value to store error_loc?
> > 
> > We need to use get_or_insert if we want to combine the two lookups.
> > I went ahead and used hash_map_safe_get_or_insert here to get rid
> > of the construction boilerplate as well.
> > 
> > > 
> > > > +@opindex Wtemplate-body
> > > > +@opindex Wno-template-body
> > > > +@item -Wno-template-body @r{(C++ and Objective-C++ only)}
> > > > +Disable diagnosing errors when parsing a template, and instead issue an
> > > > +error only upon instantiation of the template.  This flag can also be
> > > > +used to downgrade such errors into warnings with @option{Wno-error=} or
> > > > +@option{-fpermissive}.
> > > 
> > > Please also refer to this flag in the -fpermissive documentation.
> > 
> > Done.
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: permit errors inside uninstantiated templates
> > [PR116064]
> > 
> > In recent versions of GCC we've been diagnosing more and more kinds of
> > errors inside a template ahead of time.  This is a largely good thing
> > because it catches bugs, typos, dead code etc sooner.
> > 
> > But if the template never gets instantiated then such errors are harmless
> > and can be inconvenient to work around if say the code in question is
> > third party and in maintenance mode.  So it'd be handy to be able to
> > prevent these template errors from rendering the entire TU uncompilable.
> > (Note that such code is "ill-formed no diagnostic required" according
> > the standard.)
> > 
> > To that end this patch turns any errors issued within a template into
> > permerrors associated with a new -Wtemplate-body flag so that they can
> > be downgraded via e.g. -fpermissive or -Wno-template=template-body.  If
> > the template containing a downgraded error later needs to be instantiated,
> > we'll issue an error then.  But if the template never gets instantiated
> > then the downgraded error won't affect validity of the rest of the TU.
> > 
> > This is implemented via a diagnostic hook that gets called for each
> > diagnostic, and which adjusts an error diagnostic if we detect it's
> > occurring from a template context and additionally flags this template
> > context as erroneous.
> > 
> > As an example, permissive-error1a.C gives:
> > 
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment
> > of read-only variable 'n' [-Wtemplate-body]
> >      7 |   ++n;
> >        |     ^
> > ...
> > gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of
> > 'void f() [with T = int]':
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from
> > here
> >     26 |   f<int>();
> >        |   ~~~~~~^~
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating
> > erroneous template
> >      5 | void f() {
> >        |      ^
> > gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error
> > appeared here
> >      7 |   ++n; // {
> >        |     ^
> > ...
> > 
> > 	PR c++/116064
> > 
> > gcc/c-family/ChangeLog:
> > 
> > 	* c.opt (Wtemplate-body): New warning.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-tree.h (erroneous_templates_t): Declare.
> > 	(erroneous_templates): Declare.
> > 	(cp_seen_error): Declare.
> > 	(seen_error): #define to cp_seen_error.
> > 	* error.cc (get_current_template): Define.
> > 	(relaxed_template_errors): Define.
> > 	(cp_adjust_diagnostic_info): Define.
> > 	(cp_seen_error): Define.
> > 	(cxx_initialize_diagnostics): Set
> > 	diagnostic_context::m_adjust_diagnostic_info.
> > 	* module.cc (finish_module_processing): Don't write the
> > 	module if it contains an erroneous template.
> > 	* pt.cc (instantiate_class_template): Issue a hard error
> > 	when trying to instantiate a template containing a
> > 	permissively downgraded error.
> > 	(instantiate_decl): Likewise.
> > 
> > gcc/ChangeLog:
> > 
> > 	* diagnostic.cc (diagnostic_context::initialize): Set
> > 	m_adjust_diagnostic_info.
> > 	(diagnostic_context::report_diagnostic): Call
> > 	m_adjust_diagnostic_info.
> > 	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
> > 	New data member.
> > 	* doc/invoke.texi (-Wno-template-body): Document.
> > 	(-fpermissive): Mention -Wtemplate-body.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/ext/typedef-init.C: Downgrade error inside template
> > 	to warning due to -fpermissive.
> > 	* g++.dg/pr84492.C: Likewise.
> > 	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
> > 	* g++.dg/template/permissive-error1.C: New test.
> > 	* g++.dg/template/permissive-error1a.C: New test.
> > 	* g++.dg/template/permissive-error1b.C: New test.
> > 	* g++.dg/template/permissive-error1c.C: New test.
> > ---
> >   gcc/c-family/c.opt                            |  4 ++
> >   gcc/cp/cp-tree.h                              |  7 ++
> >   gcc/cp/error.cc                               | 67 +++++++++++++++++++
> >   gcc/cp/module.cc                              |  5 +-
> >   gcc/cp/pt.cc                                  | 24 +++++++
> >   gcc/diagnostic.cc                             |  4 ++
> >   gcc/diagnostic.h                              |  4 ++
> >   gcc/doc/invoke.texi                           | 12 +++-
> >   gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
> >   gcc/testsuite/g++.dg/pr84492.C                |  4 +-
> >   .../g++.dg/template/permissive-error1.C       | 20 ++++++
> >   .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
> >   .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
> >   .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
> >   gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
> >   15 files changed, 240 insertions(+), 6 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
> >   create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C
> > 
> > diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> > index a52682d835c..44117ba713c 100644
> > --- a/gcc/c-family/c.opt
> > +++ b/gcc/c-family/c.opt
> > @@ -1420,6 +1420,10 @@ Wtautological-compare
> >   C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C
> > ObjC C++ ObjC++,Wall)
> >   Warn if a comparison always evaluates to true or false.
> >   +Wtemplate-body
> > +C++ ObjC++ Var(warn_template_body) Warning Init(1)
> > +Diagnose errors when parsing a template.
> > +
> >   Wtemplate-id-cdtor
> >   C++ ObjC++ Var(warn_template_id_cdtor) Warning
> >   Warn about simple-template-id in a constructor or destructor.
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 238d786b067..08ee5895c6a 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7190,6 +7190,13 @@ extern location_t location_of
> > (tree);
> >   extern void qualified_name_lookup_error		(tree, tree, tree,
> >   						 location_t);
> >   +using erroneous_templates_t
> > +  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash,
> > location_t>>;
> > +extern erroneous_templates_t *erroneous_templates;
> > +
> > +extern bool cp_seen_error ();
> > +#define seen_error() cp_seen_error()
> > +
> >   /* in except.cc */
> >   extern void init_terminate_fn			(void);
> >   extern void init_exception_processing		(void);
> > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > index d80bac822ba..7eab0bf76b4 100644
> > --- a/gcc/cp/error.cc
> > +++ b/gcc/cp/error.cc
> > @@ -165,6 +165,72 @@ class cxx_format_postprocessor : public
> > format_postprocessor
> >     deferred_printed_type m_type_b;
> >   };
> >   +/* Return the in-scope template that's currently being parsed, or
> > +   NULL_TREE otherwise.  */
> > +
> > +static tree
> > +get_current_template ()
> > +{
> > +  if (scope_chain && in_template_context && !current_instantiation ())
> > +    if (tree ti = get_template_info (current_scope ()))
> > +      return TI_TEMPLATE (ti);
> > +
> > +  return NULL_TREE;
> > +}
> > +
> > +/* A map from TEMPLATE_DECLs that we've determined to be erroneous
> > +   at parse time to the location of the first error within.  */
> > +
> > +erroneous_templates_t *erroneous_templates;
> > +
> > +/* Callback function diagnostic_context::m_adjust_diagnostic_info.
> > +
> > +   Errors issued when parsing a template are automatically treated like
> > +   permerrors associated with the -Wtemplate-body flag and can be
> > +   downgraded into warnings accordingly, in which case we'll still
> > +   issue an error if we later need to instantiate the template.  */
> > +
> > +static void
> > +cp_adjust_diagnostic_info (diagnostic_context *context,
> > +			   diagnostic_info *diagnostic)
> > +{
> > +  if (diagnostic->kind == DK_ERROR)
> > +    if (tree tmpl = get_current_template ())
> > +      {
> > +	diagnostic->option_index = OPT_Wtemplate_body;
> > +
> > +	if (context->m_permissive)
> > +	  diagnostic->kind = DK_WARNING;
> > +
> > +	bool existed;
> > +	location_t &error_loc
> > +	  = hash_map_safe_get_or_insert<false> (erroneous_templates,
> > +						tmpl, &existed);
> > +	if (!existed)
> > +	  /* Remember that this template had a parse-time error so
> > +	     that we'll ensure a hard error has been issued upon
> > +	     its instantiation.  */
> > +	  error_loc = diagnostic->richloc->get_loc ();
> > +      }
> > +}
> > +
> > +/* A generalization of seen_error which also returns true if we've
> > +   permissively downgraded an error to a warning inside a template.  */
> > +
> > +bool
> > +cp_seen_error ()
> > +{
> > +  if ((seen_error) ())
> > +    return true;
> > +
> > +  if (erroneous_templates)
> > +    if (tree tmpl = get_current_template ())
> > +      if (erroneous_templates->get (tmpl))
> > +	return true;
> > +
> > +  return false;
> > +}
> > +
> >   /* CONTEXT->printer is a basic pretty printer that was constructed
> >      presumably by diagnostic_initialize(), called early in the
> >      compiler's initialization process (in general_init) Before the FE
> > @@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
> >     diagnostic_starter (context) = cp_diagnostic_starter;
> >     /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
> >     diagnostic_format_decoder (context) = cp_printer;
> > +  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
> >     pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
> >   }
> >   diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index d1607a06757..7130faf26f5 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader *reader)
> >           cookie = new module_processing_cookie (cmi_name, tmp_name, fd, e);
> >   -      if (errorcount)
> > +      if (errorcount
> > +	  /* Don't write the module if it contains an erroneous template.  */
> > +	  || (erroneous_templates
> > +	      && !erroneous_templates->is_empty ()))
> >   	warning_at (state->loc, 0, "not writing module %qs due to errors",
> >   		    state->get_flatname ());
> >         else if (cookie->out.begin ())
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 77fa5907c3d..b83922a530d 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -12376,6 +12376,18 @@ instantiate_class_template (tree type)
> >     if (! push_tinst_level (type))
> >       return type;
> >   +  if (erroneous_templates && !(seen_error) ())
> > +    if (location_t *error_loc = erroneous_templates->get (templ))
> > +      {
> > +	/* We're trying to instantiate a template pattern containing
> > +	   an error that we've permissively downgraded to a warning.
> > +	   Issue a hard error now to ensure the TU is considered
> > +	   ill-formed.  */
> > +	location_t decl_loc = location_of (templ);
> > +	error_at (decl_loc, "instantiating erroneous template");
> > +	inform (*error_loc, "first error appeared here");
> > +      }
> 
> Let's factor this out instead of repeating it.  OK with that change.

Thanks a lot, this is what I ended up pushing (incorporating David's
auto_diagnostic_group feedback as well):

-- >8 --

Subject: [PATCH] c++: permit errors inside uninstantiated templates [PR116064]

In recent versions of GCC we've been diagnosing more and more kinds of
errors inside a template ahead of time.  This is a largely good thing
because it catches bugs, typos, dead code etc sooner.

But if the template never gets instantiated then such errors are harmless
and can be inconvenient to work around if say the code in question is
third party and in maintenance mode.  So it'd be handy to be able to
prevent these template errors from rendering the entire TU uncompilable.
(Note that such code is "ill-formed no diagnostic required" according
the standard.)

To that end this patch turns any errors issued within a template into
permerrors associated with a new -Wtemplate-body flag so that they can
be downgraded via e.g. -fpermissive or -Wno-error=template-body.  If
the template containing a downgraded error later needs to be instantiated,
we'll issue an error then.  But if the template never gets instantiated
then the downgraded error won't affect validity of the rest of the TU.

This is implemented via a diagnostic hook that gets called for each
diagnostic, and which adjusts an error diagnostic appropriately if we
detect it's occurring from a template context, and additionally flags
the template as erroneous.

For example the new testcase permissive-error1a.C gives:

gcc/testsuite/g++.dg/template/permissive-error1a.C: In function 'void f()':
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: warning: increment of read-only variable 'n' [-Wtemplate-body]
    7 |   ++n;
      |     ^
...
gcc/testsuite/g++.dg/template/permissive-error1a.C: In instantiation of 'void f() [with T = int]':
gcc/testsuite/g++.dg/template/permissive-error1a.C:26:9:   required from here
   26 |   f<int>();
      |   ~~~~~~^~
gcc/testsuite/g++.dg/template/permissive-error1a.C:5:6: error: instantiating erroneous template
    5 | void f() {
      |      ^
gcc/testsuite/g++.dg/template/permissive-error1a.C:7:5: note: first error appeared here
    7 |   ++n; // {
      |     ^
...

	PR c++/116064

gcc/c-family/ChangeLog:

	* c.opt (Wtemplate-body): New warning.

gcc/cp/ChangeLog:

	* cp-tree.h (erroneous_templates_t): Declare.
	(erroneous_templates): Declare.
	(cp_seen_error): Declare.
	(seen_error): #define to cp_seen_error.
	* error.cc (get_current_template): Define.
	(relaxed_template_errors): Define.
	(cp_adjust_diagnostic_info): Define.
	(cp_seen_error): Define.
	(cxx_initialize_diagnostics): Set
	diagnostic_context::m_adjust_diagnostic_info.
	* module.cc (finish_module_processing): Don't write the
	module if it contains an erroneous template.
	* pt.cc (maybe_diagnose_erroneous_template): Define.
	(instantiate_class_template): Call it.
	(instantiate_decl): Likewise.

gcc/ChangeLog:

	* diagnostic.cc (diagnostic_context::initialize): Set
	m_adjust_diagnostic_info.
	(diagnostic_context::report_diagnostic): Call
	m_adjust_diagnostic_info.
	* diagnostic.h (diagnostic_context::m_adjust_diagnostic_info):
	New data member.
	* doc/invoke.texi (-Wno-template-body): Document.
	(-fpermissive): Mention -Wtemplate-body.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/typedef-init.C: Downgrade error inside template
	to warning due to -fpermissive.
	* g++.dg/pr84492.C: Likewise.
	* g++.old-deja/g++.pt/crash51.C: Remove unneeded dg-options.
	* g++.dg/template/permissive-error1.C: New test.
	* g++.dg/template/permissive-error1a.C: New test.
	* g++.dg/template/permissive-error1b.C: New test.
	* g++.dg/template/permissive-error1c.C: New test.

Reviewed-by: Jason Merrill <jason@redhat.com>
---
 gcc/c-family/c.opt                            |  4 ++
 gcc/cp/cp-tree.h                              |  7 ++
 gcc/cp/error.cc                               | 67 +++++++++++++++++++
 gcc/cp/module.cc                              |  5 +-
 gcc/cp/pt.cc                                  | 22 ++++++
 gcc/diagnostic.cc                             |  4 ++
 gcc/diagnostic.h                              |  4 ++
 gcc/doc/invoke.texi                           | 12 +++-
 gcc/testsuite/g++.dg/ext/typedef-init.C       |  2 +-
 gcc/testsuite/g++.dg/pr84492.C                |  4 +-
 .../g++.dg/template/permissive-error1.C       | 20 ++++++
 .../g++.dg/template/permissive-error1a.C      | 31 +++++++++
 .../g++.dg/template/permissive-error1b.C      | 30 +++++++++
 .../g++.dg/template/permissive-error1c.C      | 31 +++++++++
 gcc/testsuite/g++.old-deja/g++.pt/crash51.C   |  1 -
 15 files changed, 238 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1a.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1b.C
 create mode 100644 gcc/testsuite/g++.dg/template/permissive-error1c.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 979f17a7e32..491aa02e1a3 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index b81bc91208f..b1693051231 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7185,6 +7185,13 @@ extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
 
+using erroneous_templates_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern erroneous_templates_t *erroneous_templates;
+
+extern bool cp_seen_error ();
+#define seen_error() cp_seen_error ()
+
 /* in except.cc */
 extern void init_terminate_fn			(void);
 extern void init_exception_processing		(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index da853e203db..ee3868efaed 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,72 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (scope_chain && in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECLs that we've determined to be erroneous
+   at parse time to the location of the first error within.  */
+
+erroneous_templates_t *erroneous_templates;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error if we later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	bool existed;
+	location_t &error_loc
+	  = hash_map_safe_get_or_insert<false> (erroneous_templates,
+						tmpl, &existed);
+	if (!existed)
+	  /* Remember that this template had a parse-time error so
+	     that we'll ensure a hard error has been issued upon
+	     its instantiation.  */
+	  error_loc = diagnostic->richloc->get_loc ();
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  if ((seen_error) ())
+    return true;
+
+  if (erroneous_templates)
+    if (tree tmpl = get_current_template ())
+      if (erroneous_templates->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index d1607a06757..7130faf26f5 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader *reader)
 
       cookie = new module_processing_cookie (cmi_name, tmp_name, fd, e);
 
-      if (errorcount)
+      if (errorcount
+	  /* Don't write the module if it contains an erroneous template.  */
+	  || (erroneous_templates
+	      && !erroneous_templates->is_empty ()))
 	warning_at (state->loc, 0, "not writing module %qs due to errors",
 		    state->get_flatname ());
       else if (cookie->out.begin ())
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 677ed7d1289..9a4ff553b2f 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12289,6 +12289,24 @@ perform_instantiation_time_access_checks (tree tmpl, tree targs)
       }
 }
 
+/* If the template T that we're about to instantiate contained errors at
+   parse time that we downgraded into warnings or suppressed, diagnose the
+   error now to render the TU ill-formed (if the TU has not already been
+   deemed ill-formed by an earlier error).  */
+
+static void
+maybe_diagnose_erroneous_template (tree t)
+{
+  if (erroneous_templates && !(seen_error) ())
+    if (location_t *error_loc = erroneous_templates->get (t))
+      {
+	auto_diagnostic_group d;
+	location_t decl_loc = location_of (t);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+}
+
 tree
 instantiate_class_template (tree type)
 {
@@ -12358,6 +12376,8 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  maybe_diagnose_erroneous_template (templ);
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27251,6 +27271,8 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  maybe_diagnose_erroneous_template (td);
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 3fc81ad47f5..92bd4f80845 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1429,6 +1430,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 83180ded414..2a9f2751dca 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -701,6 +701,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 0fe99ca8ef6..27539a01785 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
@@ -6361,6 +6370,7 @@ that have their own flag:
 -Wint-conversion @r{(C and Objective-C only)}
 -Wnarrowing @r{(C++ and Objective-C++ only)}
 -Wreturn-mismatch @r{(C and Objective-C only)}
+-Wtemplate-body @r{(C++ and Objective-C++ only)}
 }
 
 The @option{-fpermissive} option is the default for historic C language
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..ac47151f0db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 00000000000..a67b7374625
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type);
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g();
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 00000000000..fd5f26655e5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];
  

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d786b067..ffdc0ba38ae 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7189,6 +7189,11 @@  extern bool pedwarn_cxx98                       (location_t, int, const char *,
 extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
+using relaxed_template_errors_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern relaxed_template_errors_t *relaxed_template_errors;
+extern bool cp_seen_error			(void);
+#define seen_error cp_seen_error
 
 /* in except.cc */
 extern void init_terminate_fn			(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index d80bac822ba..0bb0a482e28 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,58 @@  class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* A map from TEMPLATE_DECL to the location of the first error (if any)
+   within the template that we permissivly downgraded to a warning.  */
+
+relaxed_template_errors_t *relaxed_template_errors;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   In -fpermissive mode we downgrade errors within a template to
+   warnings, and only issue an error if we later need to instantiate
+   the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  tree ti;
+  if (diagnostic->kind == DK_ERROR
+      && context->m_permissive
+      && !current_instantiation ()
+      && in_template_context
+      && (ti = get_template_info (current_scope ())))
+    {
+      if (!relaxed_template_errors)
+	relaxed_template_errors = new relaxed_template_errors_t;
+
+      tree tmpl = TI_TEMPLATE (ti);
+      if (!relaxed_template_errors->get (tmpl))
+	relaxed_template_errors->put (tmpl, diagnostic->richloc->get_loc ());
+      diagnostic->kind = DK_WARNING;
+    }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+#undef seen_error
+  if (seen_error ())
+    return true;
+
+  tree ti;
+  if (relaxed_template_errors
+      && in_template_context
+      && (ti = get_template_info (current_scope ()))
+      && relaxed_template_errors->get (TI_TEMPLATE (ti)))
+    return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +239,7 @@  cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 77fa5907c3d..b58ccb318d5 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12376,6 +12376,22 @@  instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (templ))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (templ);
+	error_at (decl_loc, "instantiating erroneous template pattern");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	TYPE_BEING_DEFINED (type) = false;
+	CLASSTYPE_ERRONEOUS (type) = true;
+	return type;
+      }
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27291,6 +27307,20 @@  instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  if (relaxed_template_errors)
+    if (location_t *error_loc = relaxed_template_errors->get (td))
+      {
+	/* We're trying to instantiate a template pattern containing
+	   an error that we've permissively downgraded to a warning.
+	   Issue a hard error now.  */
+	location_t decl_loc = location_of (td);
+	error_at (decl_loc, "instantiating erroneous template pattern");
+	inform (*error_loc, "first error appeared here");
+
+	pop_tinst_level ();
+	return d;
+      }
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 71d2f44e40c..cbd2c82f19d 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@  diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1409,6 +1410,9 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
      flush diagnostics with on_end_group when the topmost group is ended.  */
   gcc_assert (m_diagnostic_groups.m_nesting_depth > 0);
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   /* Give preference to being able to inhibit warnings, before they
      get reclassified to something else.  */
   bool was_warning = (diagnostic->kind == DK_WARNING
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 79386ccbf85..d3d77c85e9a 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -699,6 +699,10 @@  public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b..47a6642de51 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@  struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d1..08f368ff29b 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@ 
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@  class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 00000000000..e4536a8b90e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@ 
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 00000000000..747a483b99f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,31 @@ 
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template pattern" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A { // { dg-error "instantiating erroneous template pattern" }
+  void f(typename A::type); // { dg-warning "does not name a type" }
+		            // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+};
+
+template<class T>
+struct B {
+  void f() {   // { dg-error "instantiating erroneous template pattern" }
+    this->g(); // { dg-warning "no member" }
+	       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a; // { dg-message "required from here" }
+	    // { dg-error "incomplete type" "" { target *-*-* } .-1 }
+  B<int> b; // { dg-bogus "" }
+  b.f();    // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163..c5cbde521ad 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@ 
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];