]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: add -Wsfinae-incomplete
authorJason Merrill <jason@redhat.com>
Thu, 12 Jun 2025 15:19:19 +0000 (11:19 -0400)
committerJason Merrill <jason@redhat.com>
Mon, 16 Jun 2025 15:34:21 +0000 (11:34 -0400)
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.

16 files changed:
gcc/c-family/c.opt
gcc/c-family/c.opt.urls
gcc/cp/class.cc
gcc/cp/constraint.cc
gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/pt.cc
gcc/cp/typeck.cc
gcc/doc/invoke.texi
gcc/testsuite/g++.dg/cpp2a/concepts-complete1.C
gcc/testsuite/g++.dg/cpp2a/concepts-complete2.C
gcc/testsuite/g++.dg/cpp2a/concepts-complete3.C
gcc/testsuite/g++.dg/cpp2a/concepts-complete4.C
gcc/testsuite/g++.dg/template/friend87.C
libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization.cc
libstdc++-v3/testsuite/20_util/is_complete_or_unbounded/memoization_neg.cc

index 50ba856fedba3712d1cfeda8b7af7a9da244fffa..8af466d1ed1f34384d9a0b3206a724d58a081a5b 100644 (file)
@@ -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.
index ad6d8a0b387339e90d4cd7097a46a1784b72577c..65d1221c4ad83921f236e4ee549f8f8a98c36c24 100644 (file)
@@ -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)
 
index db39e579870c44f0c5b13c446470f9f01455d579..f30cf3fcd095872d3c4c74e1fcd5cf71287f51a5 100644 (file)
@@ -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;
index 90625707043fd7070abdbe7e19e69e7c6884d7ac..17d20696e874adf3dcff6ff65a623a48fd98b158 100644 (file)
@@ -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))
+    {
+      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)
     {
-      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 (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
index d663d6ec2256deea5dba6b64cc4978c817a0b610..4bf02a1890f7e78d019e771ed8aa591c4434fa29 100644 (file)
@@ -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);
index 4c8a2052aee245446958de78e1e39c559292045a..febdc89f89ddda0a8a2fde940531405126d735ed 100644 (file)
@@ -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;
index 752b0ea8544c2498ff5bf841b80c880ae111a399..deb0106b1589d620ff3be3790a62011ccec49c70 100644 (file)
@@ -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);
index ac1eb397f0113e99a201679549ba83f908e6f097..f4b49b792e3b54c9091d4fc0142c6b0439bdcb36 100644 (file)
@@ -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
index 382cc9fa7a828c7663948a051cd52b7e5d30a998..dec3c7a1b805fcad10008074803cd2484f8c41a5 100644 (file)
@@ -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)}
index e8487bf9c0918d3d5b7ea9b5b3e6e38e3aae3087..9f2e9259f7f9624d6b6c9d08f7498303c266a024 100644 (file)
@@ -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);
index b2c1160673756e73102b9f3081949195e5f59dfa..46952a4e59c0ca7e57dec02049304250d401a0c1 100644 (file)
@@ -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);
index 5b07371a6be4fb76136834436790d0e73b255e87..38d2456de8ae3f83720fbb0e19f5f449459c60a6 100644 (file)
@@ -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);
index 988b0ddcfdd9886e01f9a092c1e5a192ddb9cf9a..7be9f50af6bf9d614ea186813ccc91eb8600939d 100644 (file)
@@ -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" }
index 94c0dfc529248eec83314296baba1fd7e7522909..7bbd96b031720d1f62cdf7297aa53dc4ceeb2df6 100644 (file)
@@ -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 {
index 256b84df60fb95312a5ffeeef19fd5773d655dd0..59af024969f2abc0d05c9341dd90f036ca0038da 100644 (file)
@@ -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");
index 8e207b584dc9e37d52959306237b2bd7bddc0f3a..264efa77996bf7f141defaedd5f4d808915029d6 100644 (file)
@@ -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" }