On 6/13/25 1:36 PM, Patrick Palka wrote:
On Fri, 13 Jun 2025, Jason Merrill wrote:
Tested x86_64-pc-linux-gnu, any comments? Bikeshedding?
-- 8< --
We already error about a type definition causing a concept check to change
value, but it would be useful to diagnose this for other SFINAE contexts as
well; the memoization problem also affects templates. So
-Wsfinae-incomplete remembers if we've failed a requirement for a complete
type in a non-tf_error context, and later warns if the type becomes
complete.
It seems useful to also warn for function return type deduction failure
followed by defining the function, to have parity with the satisfaction
value tracking.
Sure, done.
+/* A map of where types were found to be incomplete in SFINAE context, for
+ warning if they are later completed. */
+
+static GTY((cache)) hash_map<tree, location_t> *failed_completions_map;
For sake of PCH I think this needs to be a decl_location_traits (or a
new type_location_traits) so that the hash function uses DECL_UID /
TYPE_UID instead of unstable pointer identity.
Indeed, fixed.
From 312924ee7de40b1270a65c89c1bfa6fd445ce6ac Mon Sep 17 00:00:00 2001
From: Jason Merrill <ja...@redhat.com>
Date: Thu, 12 Jun 2025 11:19:19 -0400
Subject: [PATCH] c++: add -Wsfinae-incomplete
To: gcc-patches@gcc.gnu.org
We already error about a type or function definition causing a concept check
to change value, but it would be useful to diagnose this for other SFINAE
contexts as well; the memoization problem also affects templates. So
-Wsfinae-incomplete remembers if we've failed a requirement for a complete
type/deduced return type in a non-tf_error context, and later warns if the
type/function becomes complete.
This warning is enabled by default; I think the signal-to-noise ratio is
high enough to warrant that, and it catches things that are likely to make
the program "ill-formed, no diagnostic required".
friend87.C is an interesting case; this could be considered a false positive
because it is using friend injection to define the auto function to
implement a compile-time counter. I think this is sufficiently pathological
that it's fine to expect people who want to play this sort of game to
suppress the warning.
The data for this warning uses GTY((cache)) to persist through GC, but allow
entries to be discarded if the key is not otherwise marked.
I don't think it's desirable to export/import this information in modules,
it makes sense for it to be local to a single TU.
-Wsfinae-incomplete=2 adds a warning at the point of failure, which is
primarily intended to help with debugging warnings from the default mode.
gcc/ChangeLog:
* doc/invoke.texi: Document -Wsfinae-incomplete.
gcc/c-family/ChangeLog:
* c.opt: Add -Wsfinae-incomplete.
* c.opt.urls: Regenerate.
gcc/cp/ChangeLog:
* constraint.cc (failed_completions_map): New.
(note_failed_type_completion): Rename from
note_failed_type_completion_for_satisfaction. Add
-Wsfinae-incomplete handling.
(failed_completion_location): New.
* class.cc (finish_struct_1): Add -Wsfinae-incomplete warning.
* decl.cc (require_deduced_type): Adjust.
(finish_function): Add -Wsfinae-incomplete warning.
* typeck.cc (complete_type_or_maybe_complain): Adjust.
(cxx_sizeof_or_alignof_type): Call note_failed_type_completion.
* pt.cc (dependent_template_arg_p): No longer static.
* cp-tree.h: Adjust.
libstdc++-v3/ChangeLog:
* testsuite/20_util/is_complete_or_unbounded/memoization.cc
* testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc:
Expect -Wsfinae-incomplete.
gcc/testsuite/ChangeLog:
* g++.dg/template/friend87.C
* g++.dg/cpp2a/concepts-complete1.C
* g++.dg/cpp2a/concepts-complete2.C
* g++.dg/cpp2a/concepts-complete3.C
* g++.dg/cpp2a/concepts-complete4.C: Expect -Wsfinae-incomplete.
---
gcc/doc/invoke.texi | 16 ++++-
gcc/c-family/c.opt | 8 +++
gcc/cp/cp-tree.h | 4 +-
gcc/cp/class.cc | 11 ++++
gcc/cp/constraint.cc | 58 +++++++++++++++++--
gcc/cp/decl.cc | 15 ++++-
gcc/cp/pt.cc | 1 -
gcc/cp/typeck.cc | 13 ++++-
.../g++.dg/cpp2a/concepts-complete1.C | 2 +-
.../g++.dg/cpp2a/concepts-complete2.C | 2 +-
.../g++.dg/cpp2a/concepts-complete3.C | 2 +-
.../g++.dg/cpp2a/concepts-complete4.C | 2 +-
gcc/testsuite/g++.dg/template/friend87.C | 4 +-
.../is_complete_or_unbounded/memoization.cc | 2 +-
.../memoization_neg.cc | 2 +-
gcc/c-family/c.opt.urls | 6 ++
16 files changed, 128 insertions(+), 20 deletions(-)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 382cc9fa7a8..dec3c7a1b80 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -266,7 +266,7 @@ in the following sections.
-Wnoexcept -Wnoexcept-type -Wnon-virtual-dtor
-Wpessimizing-move -Wno-placement-new -Wplacement-new=@var{n}
-Wrange-loop-construct -Wredundant-move -Wredundant-tags
--Wreorder -Wregister
+-Wreorder -Wregister -Wno-sfinae-incomplete
-Wstrict-null-sentinel -Wno-subobject-linkage -Wtemplates
-Wno-non-template-friend -Wold-style-cast
-Woverloaded-virtual -Wno-pmf-conversions -Wself-move -Wsign-promo
@@ -4448,6 +4448,20 @@ to filter out those warnings.
Disable the warning about the case when an exception handler is shadowed by
another handler, which can point out a wrong ordering of exception handlers.
+@opindex Wsfinae-incomplete
+@opindex Wno-sfinae-incomplete
+Warn about a class that is found to be incomplete, or a function with
+auto return type that has not yet been deduced, in a context where
+that causes substitution failure rather than an error, and then the
+class or function is defined later in the translation unit. This is
+problematic because template instantiations or concept checks could
+have different results if they first occur either before or after the
+definition.
+
+This warning is enabled by default. @option{-Wsfinae-incomplete=2}
+adds a warning at the point of substitution failure, to make it easier
+to track down problems flagged by the default mode.
+
@opindex Wstrict-null-sentinel
@opindex Wno-strict-null-sentinel
@item -Wstrict-null-sentinel @r{(C++ and Objective-C++ only)}
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 50ba856fedb..8af466d1ed1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1319,6 +1319,14 @@ Wsequence-point
C ObjC C++ ObjC++ Var(warn_sequence_point) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
Warn about possible violations of sequence point rules.
+Wsfinae-incomplete=
+C++ ObjC++ Var(warn_sfinae_incomplete) Warning Init(1) Joined RejectNegative UInteger IntegerRange(0, 2)
+Warn about an incomplete type affecting semantics in a non-error context.
+
+Wsfinae-incomplete
+C++ ObjC++ Warning Alias(Wsfinae-incomplete=, 1, 0)
+Warn about an incomplete type affecting semantics in a non-error context.
+
Wshadow-ivar
ObjC ObjC++ Var(warn_shadow_ivar) EnabledBy(Wshadow) Init(1) Warning
Warn if a local declaration hides an instance variable.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index d663d6ec225..4bf02a1890f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7813,6 +7813,7 @@ extern bool type_dependent_expression_p_push (tree);
extern bool value_dependent_expression_p (tree);
extern bool instantiation_dependent_uneval_expression_p (tree);
extern bool any_value_dependent_elements_p (const_tree);
+extern bool dependent_template_arg_p (tree);
extern bool dependent_omp_for_p (tree, tree, tree, tree, tree);
extern tree resolve_typename_type (tree, bool);
extern tree template_for_substitution (tree);
@@ -8842,7 +8843,8 @@ extern hashval_t iterative_hash_constraint (tree, hashval_t);
extern hashval_t hash_atomic_constraint (tree);
extern void diagnose_constraints (location_t, tree, tree);
-extern void note_failed_type_completion_for_satisfaction (tree);
+extern void note_failed_type_completion (tree, tsubst_flags_t);
+extern location_t failed_completion_location (tree);
/* in logic.cc */
extern bool subsumes (tree, tree);
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index db39e579870..f30cf3fcd09 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -7920,6 +7920,17 @@ finish_struct_1 (tree t)
return;
}
+ if (location_t fcloc = failed_completion_location (t))
+ {
+ auto_diagnostic_group adg;
+ if (warning (OPT_Wsfinae_incomplete_,
+ "defining %qT, which previously failed to be complete "
+ "in a SFINAE context", t)
+ && warn_sfinae_incomplete == 1)
+ inform (fcloc, "here. Use %qs for a diagnostic at that point",
+ "-Wsfinae-incomplete=2");
+ }
+
/* If this type was previously laid out as a forward reference,
make sure we lay it out again. */
TYPE_SIZE (t) = NULL_TREE;
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 90625707043..17d20696e87 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -1836,7 +1836,7 @@ tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain, tree in_
static bool satisfying_constraint;
/* A vector of incomplete types (and of declarations with undeduced return type),
- appended to by note_failed_type_completion_for_satisfaction. The
+ appended to by note_failed_type_completion. The
satisfaction caches use this in order to keep track of "potentially unstable"
satisfaction results.
@@ -1845,19 +1845,67 @@ static bool satisfying_constraint;
static GTY((deletable)) vec<tree, va_gc> *failed_type_completions;
+/* A map of where types were found to be incomplete in SFINAE context, for
+ warning if they are later completed. */
+
+static GTY((cache)) hash_map<tree, location_t, decl_location_traits> *failed_completions_map;
+
/* Called whenever a type completion (or return type deduction) failure occurs
that definitely affects the meaning of the program, by e.g. inducing
substitution failure. */
void
-note_failed_type_completion_for_satisfaction (tree t)
+note_failed_type_completion (tree t, tsubst_flags_t complain)
{
+ if (dependent_template_arg_p (t))
+ return;
+
+ gcc_checking_assert ((TYPE_P (t) && !COMPLETE_TYPE_P (t))
+ || (DECL_P (t) && undeduced_auto_decl (t)));
+
if (satisfying_constraint)
+ vec_safe_push (failed_type_completions, t);
+
+ if (TYPE_P (t))
{
- gcc_checking_assert ((TYPE_P (t) && !COMPLETE_TYPE_P (t))
- || (DECL_P (t) && undeduced_auto_decl (t)));
- vec_safe_push (failed_type_completions, t);
+ if (!CLASS_TYPE_P (t))
+ return;
+ t = TYPE_MAIN_DECL (t);
}
+ if (!(complain & tf_error)
+ && warning_enabled_at (DECL_SOURCE_LOCATION (t),
+ OPT_Wsfinae_incomplete_))
+ {
+ if (warn_sfinae_incomplete > 1)
+ {
+ if (TREE_CODE (t) == TYPE_DECL)
+ warning (OPT_Wsfinae_incomplete_,
+ "failed to complete %qT in SFINAE context", TREE_TYPE (t));
+ else
+ warning (OPT_Wsfinae_incomplete_,
+ "failed to deduce %qD in SFINAE context", t);
+ }
+ if (!failed_completions_map)
+ failed_completions_map
+ = hash_map<tree, location_t, decl_location_traits>::create_ggc ();
+ failed_completions_map->put (t, input_location);
+ }
+}
+
+/* If T was previously found to be incomplete in SFINAE context, return the
+ location where that happened, otherwise UNKNOWN_LOCATION. */
+
+location_t
+failed_completion_location (tree t)
+{
+ if (failed_completions_map)
+ {
+ if (TYPE_P (t))
+ t = TYPE_MAIN_DECL (t);
+ if (location_t *p = failed_completions_map->get (t))
+ return *p;
+ }
+ return UNKNOWN_LOCATION;
}
/* Returns true if the range [BEGIN, END) of elements within the
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4c8a2052aee..febdc89f89d 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -19336,6 +19336,19 @@ finish_function (bool inline_p)
}
}
+ if (FNDECL_USED_AUTO (fndecl)
+ && TREE_TYPE (fntype) != DECL_SAVED_AUTO_RETURN_TYPE (fndecl))
+ if (location_t fcloc = failed_completion_location (fndecl))
+ {
+ auto_diagnostic_group adg;
+ if (warning (OPT_Wsfinae_incomplete_,
+ "defining %qD, which previously failed to be deduced "
+ "in a SFINAE context", fndecl)
+ && warn_sfinae_incomplete == 1)
+ inform (fcloc, "here. Use %qs for a diagnostic at that point",
+ "-Wsfinae-incomplete=2");
+ }
+
/* Remember that we were in class scope. */
if (current_class_name)
ctype = current_class_type;
@@ -19989,7 +20002,7 @@ require_deduced_type (tree decl, tsubst_flags_t complain)
/* We probably already complained about deduction failure. */;
else if (complain & tf_error)
error ("use of %qD before deduction of %<auto%>", decl);
- note_failed_type_completion_for_satisfaction (decl);
+ note_failed_type_completion (decl, complain);
return false;
}
return true;
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 752b0ea8544..deb0106b158 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -202,7 +202,6 @@ static tree for_each_template_parm_r (tree *, int *, void *);
static tree copy_default_args_to_explicit_spec_1 (tree, tree);
static void copy_default_args_to_explicit_spec (tree);
static bool invalid_nontype_parm_type_p (tree, tsubst_flags_t);
-static bool dependent_template_arg_p (tree);
static bool dependent_type_p_r (tree);
static tree tsubst_stmt (tree, tree, tsubst_flags_t, tree);
static tree tsubst_decl (tree, tree, tsubst_flags_t, bool = true);
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index ac1eb397f01..f4b49b792e3 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -156,7 +156,7 @@ complete_type_or_maybe_complain (tree type, tree value, tsubst_flags_t complain)
{
if (complain & tf_error)
cxx_incomplete_type_diagnostic (value, type, DK_ERROR);
- note_failed_type_completion_for_satisfaction (type);
+ note_failed_type_completion (type, complain);
return NULL_TREE;
}
else
@@ -2084,7 +2084,14 @@ cxx_sizeof_or_alignof_type (location_t loc, tree type, enum tree_code op,
bool dependent_p = dependent_type_p (type);
if (!dependent_p)
- complete_type (type);
+ {
+ complete_type (type);
+ if (!COMPLETE_TYPE_P (type))
+ /* Call this here because the incompleteness diagnostic comes from
+ c_sizeof_or_alignof_type instead of
+ complete_type_or_maybe_complain. */
+ note_failed_type_completion (type, complain);
+ }
if (dependent_p
/* VLA types will have a non-constant size. In the body of an
uninstantiated template, we don't need to try to compute the
@@ -2106,7 +2113,7 @@ cxx_sizeof_or_alignof_type (location_t loc, tree type, enum tree_code op,
return c_sizeof_or_alignof_type (loc, complete_type (type),
op == SIZEOF_EXPR, std_alignof,
- complain);
+ complain & (tf_warning_or_error));
}
/* Return the size of the type, without producing any warnings for
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C
index e8487bf9c09..9f2e9259f7f 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C
@@ -12,7 +12,7 @@ template <class T> char f() { return 0; }
struct A;
static_assert (sizeof (f<A>()) == 1); // { dg-message "first evaluated to 'false' from here" }
-struct A { typedef int type; };
+struct A { typedef int type; }; // { dg-warning Wsfinae-incomplete }
static_assert (sizeof (f<A>()) > 1); // { dg-error "assert" }
// { dg-message "required from here" "" { target *-*-* } .-1 }
static_assert (sizeof (f<A>()) > 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C
index b2c11606737..46952a4e59c 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C
@@ -18,6 +18,6 @@ template <class T> char f() { return 0; }
struct A;
static_assert (sizeof (f<A>()) == 1); // { dg-message "first evaluated to 'false' from here" }
-struct A { typedef int type; };
+struct A { typedef int type; }; // { dg-warning Wsfinae-incomplete }
static_assert (sizeof (f<A>()) > 1); // { dg-error "assert" }
static_assert (sizeof (f<A>()) > 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C
index 5b07371a6be..38d2456de8a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C
@@ -11,6 +11,6 @@ template <class T> char f() { return 0; }
struct A { auto foo(); };
static_assert (sizeof (f<A>()) == 1); // { dg-message "first evaluated to 'false' from here" }
-auto A::foo() { }
+auto A::foo() { } // { dg-warning Wsfinae-incomplete }
static_assert (sizeof (f<A>()) > 1); // { dg-error "assert" }
static_assert (sizeof (f<A>()) > 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C
index 988b0ddcfdd..7be9f50af6b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C
@@ -8,6 +8,6 @@ struct A;
static_assert(!C<A>);
-struct A { static constexpr bool value = false; };
+struct A { static constexpr bool value = false; }; // { dg-warning Wsfinae-incomplete }
static_assert(C<A>); // { dg-error "assert" }
diff --git a/gcc/testsuite/g++.dg/template/friend87.C b/gcc/testsuite/g++.dg/template/friend87.C
index 94c0dfc5292..7bbd96b0317 100644
--- a/gcc/testsuite/g++.dg/template/friend87.C
+++ b/gcc/testsuite/g++.dg/template/friend87.C
@@ -14,14 +14,14 @@ struct CounterWriter {
static constexpr size_t value = current;
template<typename>
- friend auto counterFlag(CounterReader<tag, current>) noexcept {}
+ friend auto counterFlag(CounterReader<tag, current>) noexcept {} // { dg-warning -Wsfinae-incomplete }
};
template<auto tag, auto unique, size_t current = 0, size_t mask = size_t(1) << (sizeof(size_t) * 8 - 1)>
[[nodiscard]] constexpr size_t counterAdvance() noexcept {
if constexpr (!mask) {
return CounterWriter<tag, current + 1>::value;
- } else if constexpr (requires { counterFlag<void>(CounterReader<tag, current | mask>()); }) {
+ } else if constexpr (requires { counterFlag<void>(CounterReader<tag, current | mask>()); }) { // { dg-message "here" }
return counterAdvance<tag, unique, current | mask, (mask >> 1)>();
}
else {
diff --git a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization.cc b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization.cc
index 256b84df60f..59af024969f 100644
--- a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization.cc
+++ b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization.cc
@@ -23,7 +23,7 @@ struct X;
static_assert(
!std::__is_complete_or_unbounded(std::__type_identity<X>{}), "error");
-struct X{};
+struct X{}; // { dg-warning Wsfinae-incomplete }
static_assert(
std::__is_complete_or_unbounded(std::__type_identity<X>{}),
"Result memoized. This leads to worse diagnostics");
diff --git a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
index 8e207b584dc..264efa77996 100644
--- a/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc
@@ -25,5 +25,5 @@
struct X;
constexpr bool res_incomplete = std::is_move_constructible<X>::value; // { dg-error "required from here" }
-struct X{};
+struct X{}; // { dg-warning Wsfinae-incomplete }
constexpr bool res_complete = std::is_default_constructible<X>::value; // { dg-bogus "required from here" }
diff --git a/gcc/c-family/c.opt.urls b/gcc/c-family/c.opt.urls
index ad6d8a0b387..65d1221c4ad 100644
--- a/gcc/c-family/c.opt.urls
+++ b/gcc/c-family/c.opt.urls
@@ -756,6 +756,12 @@ UrlSuffix(gcc/Warning-Options.html#index-Wno-self-move)
Wsequence-point
UrlSuffix(gcc/Warning-Options.html#index-Wno-sequence-point)
+Wsfinae-incomplete=
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-Wno-sfinae-incomplete)
+
+Wsfinae-incomplete
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-Wno-sfinae-incomplete)
+
Wshadow-ivar
UrlSuffix(gcc/Warning-Options.html#index-Wno-shadow-ivar)
--
2.49.0