Hi,
On 9 Jan 2025, at 20:08, Simon Martin wrote:
> On 9 Jan 2025, at 20:00, Marek Polacek wrote:
>
>> On Thu, Jan 09, 2025 at 12:05:43PM -0500, Patrick Palka wrote:
>>> On Wed, 8 Jan 2025, Jason Merrill wrote:
>>>
>>>> On 12/21/24 11:35 AM, Simon Martin wrote:
>>>>> When erroring out due to an incomplete type, we add a contextual
>>>>> note
>>>>> about the type. However, when the error is suppressed by
>>>>> -Wno-template-body, the note remains, making the compiler output
>>>>> quite
>>>>> puzzling.
>>>>>
>>>>> This patch makes sure the note is suppressed if we're processing a
>>>>> template declaration body with -Wno-template-body.
>>>>>
>>>>> Successfully tested on x86_64-pc-linux-gnu.
>>>>>
>>>>> PR c++/118163
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> * cp-tree.h (get_current_template): Declare.
>>>>> * error.cc (get_current_template): Make non static.
>>>>> * typeck2.cc (cxx_incomplete_type_inform): Suppress note when
>>>>> parsing a template declaration with -Wno-template-body.
>>>>
>>>> I think rather than adding this sort of thing in lots of places
>>>> where an error
>>>> is followed by an inform, we should change error to return bool
>>>> like
>>>> other
>>>> diagnostic functions, and check its return value before calling
>>>> cxx_incomplete_type_inform or plain inform. This likely involves
>>>> the same
>>>> number of changes, but they should be smaller.
>>>>
>>>> Patrick, what do you think?
>>>
>>> That makes sense to me, it's consistent with the 'warning' API and
>>> how
>>> we handle issuing a warning followed by a note. But since the
>>> -Wtemplate-body mechanism is really only useful for compiling legacy
>>> code where you don't really care about any diagnostics anyway, and
>>> the intended way to use it is -fpermissive /
>>> -Wno-error=template-body
>>> rather than -Wno-template-body, I'd prefer a less invasive solution
>>> that
>>> doesn't change the API of 'error' if possible.
>>>
>>> I wonder if we can work around this by taking advantage of the fact
>>> that
>>> notes that follow an error are expected to be linked via an active
>>> auto_diagnostic_group? Roughly, if we issued a -Wtemplate-body
>>> diagnostic from an active auto_diagnostic_group then all other
>>> diagnostics from that auto_diagnostic_group should also be
>>> associated
>>> with -Wtemplate-body, including notes. That way -Wno-template-body
>>> will
>>> effectively suppress subsequent notes followed by an eligible error,
>>> and
>>> no 'error' callers need to be changed (unless to use
>>> auto_diagnostic_group).
>>
>> FWIW, I love this auto_diagnostic_group idea.
> Thanks folks, I’ll explore the auto_diagnostic_group idea (and maybe
> *also* the error returning bool one because I am not a fan of
> functions
> that “lie” to their callers :-))
>
> I’ll send a follow-up patch in the coming days.
Please find attached an updated version of the patch, that implements
Patrick’s idea and fixes both PR118163 and PR118392. It tracks the
depth at which a warning is inhibited, and suppresses all the notes from
that depth on until an error/warning is emitted or that depth is left.
Successfully tested on x86_64-pc-linux-gnu. OK for GCC 16?
Thanks, Simon
From 865472e427aba34ba07a71e85c8a52619fe381ce Mon Sep 17 00:00:00 2001
From: Simon Martin <si...@nasilyan.com>
Date: Sun, 12 Jan 2025 10:53:00 +0100
Subject: [PATCH] c++: Inhibit subsequent warnings/notes in diagnostic_groups
with an inhibited warning [PR118163,PR118392]
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Those 2 PRs show that even when using a *single* diagnostic_group, it's
possible to end up with a warning being inhibited and its associated
note still emitted, which leads to puzzling user experience. Example
from PR118392:
===
$ gcc/cc1plus inhibit-warn-3.C -w
inhibit-warn-3.C:10:17: note: only here as a âfriendâ
10 | friend void bar();
| ^~~
===
Following a suggestion from ppalka@, this patch keeps track of the
"diagnostic depth" at which a warning in inhibited, and makes sure that
all subsequent notes at that depth or deeper are inhibited as well,
until a subsequent warning or error is accepted at that depth, or the
diagnostic_group or nesting level is popped.
I briefly checked the selftests mechanism in diagnostic.cc and I don't
think this is something that we can add a test for there. Let me know if
I'm wrong.
Successfully tested on x86_64-pc-linux-gnu. OK for GCC 16?
PR c++/118163
PR c++/118392
gcc/ChangeLog:
* diagnostic.cc (diagnostic_context::initialize): Initialize
m_diagnostic_groups.m_inhibiting_notes_from.
(diagnostic_context::inhibit_notes_in_group): New.
(diagnostic_context::notes_inhibited_in_group): New
(diagnostic_context::report_diagnostic): Call
inhibit_notes_in_group and notes_inhibited_in_group.
(diagnostic_context::end_group): Call inhibit_notes_in_group.
(diagnostic_context::pop_nesting_level): Ditto.
* diagnostic.h (diagnostic_context::m_diagnostic_groups): Add
member to track the depth at which a warning has been inhibited.
(diagnostic_context::notes_inhibited_in_group): Declare.
(diagnostic_context::inhibit_notes_in_group): Declare.
* doc/ux.texi: Document diagnostic_group behavior with regards
to inhibited warnings.
gcc/testsuite/ChangeLog:
* g++.dg/diagnostic/incomplete-type-2.C: New test.
* g++.dg/diagnostic/incomplete-type-2a.C: New test.
* g++.dg/diagnostic/inhibit-warn-3.C: New test.
---
gcc/diagnostic.cc | 67 ++++++++++++++++++-
gcc/diagnostic.h | 6 ++
gcc/doc/ux.texi | 5 +-
.../g++.dg/diagnostic/incomplete-type-2.C | 7 ++
.../g++.dg/diagnostic/incomplete-type-2a.C | 13 ++++
.../g++.dg/diagnostic/inhibit-warn-3.C | 15 +++++
6 files changed, 110 insertions(+), 3 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/diagnostic/incomplete-type-2.C
create mode 100644 gcc/testsuite/g++.dg/diagnostic/incomplete-type-2a.C
create mode 100644 gcc/testsuite/g++.dg/diagnostic/inhibit-warn-3.C
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index c2f6714c24a..310497f4309 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -283,6 +283,7 @@ diagnostic_context::initialize (int n_opts)
m_diagnostic_groups.m_group_nesting_depth = 0;
m_diagnostic_groups.m_diagnostic_nesting_level = 0;
m_diagnostic_groups.m_emission_count = 0;
+ m_diagnostic_groups.m_inhibiting_notes_from = 0;
m_output_sinks.safe_push
(new diagnostic_text_output_format (*this, nullptr, true));
m_set_locations_cb = nullptr;
@@ -891,6 +892,7 @@ diagnostic_context::check_max_errors (bool flush)
/* Take any action which is expected to happen after the diagnostic
is written out. This function does not always return. */
+
void
diagnostic_context::action_after_output (diagnostic_t diag_kind)
{
@@ -965,6 +967,50 @@ diagnostic_context::action_after_output (diagnostic_t
diag_kind)
}
}
+/* State whether we should inhibit notes in the current diagnostic_group and
+ its future children if any. */
+
+void
+diagnostic_context::inhibit_notes_in_group (bool inhibit)
+{
+ int curr_depth = (m_diagnostic_groups.m_group_nesting_depth
+ + m_diagnostic_groups.m_diagnostic_nesting_level);
+
+ if (inhibit)
+ {
+ /* If we're already inhibiting, there's nothing to do. */
+ if (m_diagnostic_groups.m_inhibiting_notes_from)
+ return;
+
+ /* Since we're called via warning/error/... that all have their own
+ diagnostic_group, we must consider that we started inhibiting in their
+ parent. */
+ gcc_assert (m_diagnostic_groups.m_group_nesting_depth > 0);
+ m_diagnostic_groups.m_inhibiting_notes_from = curr_depth - 1;
+ }
+ else if (m_diagnostic_groups.m_inhibiting_notes_from)
+ {
+ /* Only cancel inhibition at the depth that set it up. */
+ if (curr_depth >= m_diagnostic_groups.m_inhibiting_notes_from)
+ return;
+
+ m_diagnostic_groups.m_inhibiting_notes_from = 0;
+ }
+}
+
+/* Return whether notes must be inhibited in the current diagnostic_group. */
+
+bool
+diagnostic_context::notes_inhibited_in_group () const
+{
+ if (m_diagnostic_groups.m_inhibiting_notes_from
+ && (m_diagnostic_groups.m_group_nesting_depth
+ + m_diagnostic_groups.m_diagnostic_nesting_level
+ >= m_diagnostic_groups.m_inhibiting_notes_from))
+ return true;
+ return false;
+}
+
/* class logical_location. */
/* Return true iff this is a function or method. */
@@ -1355,7 +1401,10 @@ diagnostic_context::report_diagnostic (diagnostic_info
*diagnostic)
bool was_warning = (diagnostic->kind == DK_WARNING
|| diagnostic->kind == DK_PEDWARN);
if (was_warning && m_inhibit_warnings)
- return false;
+ {
+ inhibit_notes_in_group ();
+ return false;
+ }
if (m_adjust_diagnostic_info)
m_adjust_diagnostic_info (this, diagnostic);
@@ -1397,7 +1446,10 @@ diagnostic_context::report_diagnostic (diagnostic_info
*diagnostic)
not disabled by #pragma GCC diagnostic anywhere along the inlining
stack. . */
if (!diagnostic_enabled (diagnostic))
- return false;
+ {
+ inhibit_notes_in_group ();
+ return false;
+ }
if ((was_warning || diagnostic->kind == DK_WARNING)
&& ((!m_warn_system_headers
@@ -1407,9 +1459,16 @@ diagnostic_context::report_diagnostic (diagnostic_info
*diagnostic)
inlining stack (if there is one) are in system headers. */
return false;
+ if (diagnostic->kind == DK_NOTE && notes_inhibited_in_group ())
+ /* Bail for all the notes in the diagnostic_group that started to inhibit
notes. */
+ return false;
+
if (diagnostic->kind != DK_NOTE && diagnostic->kind != DK_ICE)
check_max_errors (false);
+ /* We are accepting the diagnostic, so should stop inhibiting notes. */
+ inhibit_notes_in_group (/*inhibit=*/false);
+
m_lock++;
if (diagnostic->kind == DK_ICE || diagnostic->kind == DK_ICE_NOBT)
@@ -1743,6 +1802,8 @@ diagnostic_context::end_group ()
sink->on_end_group ();
m_diagnostic_groups.m_emission_count = 0;
}
+ /* We're popping one level, so might need to stop inhibiting notes. */
+ inhibit_notes_in_group (/*inhibit=*/false);
}
void
@@ -1755,6 +1816,8 @@ void
diagnostic_context::pop_nesting_level ()
{
--m_diagnostic_groups.m_diagnostic_nesting_level;
+ /* We're popping one level, so might need to stop inhibiting notes. */
+ inhibit_notes_in_group (/*inhibit=*/false);
}
void
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 202760b2f85..74ec61fe5ec 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -950,8 +950,14 @@ private:
/* How many diagnostics have been emitted since the bottommost
diagnostic_group was pushed. */
int m_emission_count;
+
+ /* The "group+diagnostic" nesting depth from which to inhibit notes. */
+ int m_inhibiting_notes_from;
} m_diagnostic_groups;
+ void inhibit_notes_in_group (bool inhibit = true);
+ bool notes_inhibited_in_group () const;
+
/* The various sinks to which diagnostics are to be outputted
(text vs structured formats such as SARIF).
The sinks are owned by the context; this would be a
diff --git a/gcc/doc/ux.texi b/gcc/doc/ux.texi
index 2428d1a68f9..243b3e08b11 100644
--- a/gcc/doc/ux.texi
+++ b/gcc/doc/ux.texi
@@ -409,7 +409,10 @@ diagnostic subsystem that all diagnostics issued within
the lifetime
of the @code{auto_diagnostic_group} are related. For example,
@option{-fdiagnostics-format=json} will treat the first diagnostic
emitted within the group as a top-level diagnostic, and all subsequent
-diagnostics within the group as its children.
+diagnostics within the group as its children. Also, if a warning in the
+group is inhibited at nesting depth D, all subsequent notes at that depth
+or deeper will be inhibited as well, until an error or another warning
+is emitted, the depth decreases below D, or the group is popped.
@subsection Quoting
Text should be quoted by either using the @samp{q} modifier in a directive
diff --git a/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2.C
b/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2.C
new file mode 100644
index 00000000000..e2fb20a4ae8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2.C
@@ -0,0 +1,7 @@
+// PR c++/118163
+// { dg-do "compile" }
+
+template<class T>
+struct S { // { dg-note "until the closing brace" }
+ S s; // { dg-error "has incomplete type" }
+};
diff --git a/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2a.C
b/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2a.C
new file mode 100644
index 00000000000..d13021d0b68
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/incomplete-type-2a.C
@@ -0,0 +1,13 @@
+// PR c++/118163
+// { dg-do "compile" }
+// { dg-additional-options "-Wno-template-body" }
+
+template<class T>
+struct S { // { dg-bogus "until the closing brace" }
+ S s; // { dg-bogus "has incomplete type" }
+};
+
+// Check that we don't suppress errors outside of the body.
+struct forward_decl; // { dg-note "forward declaration" }
+template<class T>
+void foo (forward_decl) {} // { dg-error "has incomplete type" }
diff --git a/gcc/testsuite/g++.dg/diagnostic/inhibit-warn-3.C
b/gcc/testsuite/g++.dg/diagnostic/inhibit-warn-3.C
new file mode 100644
index 00000000000..bd021df5da4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/inhibit-warn-3.C
@@ -0,0 +1,15 @@
+// PR c++/118392
+// { dg-do "compile" }
+// { dg-additional-options "-w" }
+
+// Fake dg-note to make sure that notes are not pruned and we can use dg-bogus.
+// { dg-note "fake" "" { xfail *-*-* } }
+
+namespace xxx {
+ struct foo {
+ friend void bar(); // { dg-bogus "only here as a friend" }
+ };
+}
+void xxx::bar () {}
+
+void foo () {}
--
2.44.0