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.
>
> 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".
>
> 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.
> * typeck.cc (complete_type_or_maybe_complain): Adjust.
> (cxx_sizeof_or_alignof_type): Call note_failed_type_completion.
> * 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/cpp2a/concepts-complete1.C
> * g++.dg/cpp2a/concepts-complete2.C
> * g++.dg/cpp2a/concepts-complete4.C: Expect -Wsfinae-incomplete.
> ---
> gcc/doc/invoke.texi | 13 ++++++++
> gcc/c-family/c.opt | 8 +++++
> gcc/cp/cp-tree.h | 3 +-
> gcc/cp/class.cc | 10 ++++++
> gcc/cp/constraint.cc | 32 +++++++++++++++++--
> gcc/cp/decl.cc | 2 +-
> gcc/cp/typeck.cc | 13 ++++++--
> .../g++.dg/cpp2a/concepts-complete1.C | 2 +-
> .../g++.dg/cpp2a/concepts-complete2.C | 2 +-
> .../g++.dg/cpp2a/concepts-complete4.C | 2 +-
> .../is_complete_or_unbounded/memoization.cc | 2 +-
> .../memoization_neg.cc | 2 +-
> gcc/c-family/c.opt.urls | 6 ++++
> 13 files changed, 85 insertions(+), 12 deletions(-)
>
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 382cc9fa7a8..e3a6d2aced3 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -4448,6 +4448,19 @@ 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 in a context where
> +that causes substitution failure rather than an error, and then 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. To fix
> +this warning, avoid depending on its completeness until after it.
> +
> +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..ebfc1bb4a96 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -8842,7 +8842,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..cdfc3c3e596 100644
> --- a/gcc/cp/class.cc
> +++ b/gcc/cp/class.cc
> @@ -7920,6 +7920,16 @@ 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))
> + 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..e46df11ca24 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,12 +1845,17 @@ 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> *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.
> +
> /* 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 (satisfying_constraint)
> {
> @@ -1858,6 +1863,29 @@ note_failed_type_completion_for_satisfaction (tree t)
> || (DECL_P (t) && undeduced_auto_decl (t)));
> vec_safe_push (failed_type_completions, t);
> }
> + if (!(complain & tf_error) && CLASS_TYPE_P (t) && !dependent_type_p (t)
> + && warning_enabled_at (DECL_SOURCE_LOCATION (TYPE_MAIN_DECL (t)),
> + OPT_Wsfinae_incomplete_))
> + {
> + if (warn_sfinae_incomplete > 1)
> + warning (OPT_Wsfinae_incomplete_,
> + "failed to complete %qT in SFINAE context", t);
> + if (!failed_completions_map)
> + failed_completions_map = hash_map<tree, location_t>::create_ggc ();
> + failed_completions_map->put (TYPE_MAIN_VARIANT (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 (location_t *p = failed_completions_map->get (TYPE_MAIN_VARIANT (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..718d0e5ede2 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -19989,7 +19989,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/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-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/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)
>
>
> base-commit: ad56e4632b05e66da35d6c23e45312b3cfbb646c
> --
> 2.49.0
>
>