https://gcc.gnu.org/g:117782e0c2a81a4b8170f87f0fe7190ee22548e2

commit r16-1527-g117782e0c2a81a4b8170f87f0fe7190ee22548e2
Author: Jason Merrill <ja...@redhat.com>
Date:   Thu Jun 12 11:19:19 2025 -0400

    c++: add -Wsfinae-incomplete
    
    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.

Diff:
---
 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 +++--
 gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C    |  2 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C    |  2 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C    |  2 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C    |  2 +-
 gcc/testsuite/g++.dg/template/friend87.C           |  4 +-
 .../is_complete_or_unbounded/memoization.cc        |  2 +-
 .../is_complete_or_unbounded/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 382cc9fa7a82..dec3c7a1b805 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 50ba856fedba..8af466d1ed1f 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 d663d6ec2256..4bf02a1890f7 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 db39e579870c..f30cf3fcd095 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 90625707043f..17d20696e874 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 4c8a2052aee2..febdc89f89dd 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 752b0ea8544c..deb0106b1589 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 ac1eb397f011..f4b49b792e3b 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 e8487bf9c091..9f2e9259f7f9 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 b2c116067375..46952a4e59c0 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 5b07371a6be4..38d2456de8ae 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 988b0ddcfdd9..7be9f50af6bf 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 94c0dfc52924..7bbd96b03172 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 256b84df60fb..59af024969f2 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 8e207b584dc9..264efa77996b 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 ad6d8a0b3873..65d1221c4ad8 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)

Reply via email to