diff mbox series

[RFC/PATCH] c++: Unwrap type traits defined in terms of builtins within concept diagnostics [PR117294]

Message ID 672cbcfa.170a0220.27cfec.2f87@mx.google.com
State New
Headers show
Series [RFC/PATCH] c++: Unwrap type traits defined in terms of builtins within concept diagnostics [PR117294] | expand

Commit Message

Nathaniel Shead Nov. 7, 2024, 1:13 p.m. UTC
Does this approach seem reasonable?  I'm pretty sure that the way I've
handled the templating here is unideal but I'm not sure what a neat way
to do what I'm trying to do here would be; any comments are welcome.

-- >8 --

Currently, concept failures of standard type traits just report
'expression X<T> evaluates to false'.  However, many type traits are
actually defined in terms of compiler builtins; we can do better here.
For instance, 'is_constructible_v' could go on to explain why the type
is not constructible, or 'is_invocable_v' could list potential
candidates.

As a first step to supporting that we need to be able to map the
standard type traits to the builtins that they use.  Rather than adding
another list that would need to be kept up-to-date whenever a builtin is
added, this patch instead tries to detect any variable template defined
directly in terms of a TRAIT_EXPR.

To avoid false positives, we ignore any variable templates that have any
specialisations (partial or explicit), even if we wouldn't have chosen
that specialisation anyway.  This shouldn't affect any of the standard
library type traits that I could see.

The new diagnostics from this patch are not immediately much better;
however, it would be relatively straight-forward to update the messages
in 'diagnose_trait_expr' to provide these new details.

This logic could also perhaps be used by 'diagnose_failing_condition' so
that cases like 'static_assert(std::is_constructible_v<T>)' get the same
treatment; this patch doesn't attempt to update this yet.

	PR c++/117294
	PR c++/113854

gcc/cp/ChangeLog:

	* constraint.cc (diagnose_trait_expr): Take location to diagnose
	at explicitly.
	(maybe_unwrap_standard_trait): New function.
	(diagnose_atomic_constraint): Use it; pass in the location of
	the atomic constraint to diagnose_trait_expr.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-traits4.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/constraint.cc                          | 52 +++++++++++++++++--
 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 31 +++++++++++
 2 files changed, 79 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C

Comments

Patrick Palka Nov. 27, 2024, 4:45 p.m. UTC | #1
On Fri, 8 Nov 2024, Nathaniel Shead wrote:

> Does this approach seem reasonable?  I'm pretty sure that the way I've
> handled the templating here is unideal but I'm not sure what a neat way
> to do what I'm trying to do here would be; any comments are welcome.

Clever approach, I like it!

> 
> -- >8 --
> 
> Currently, concept failures of standard type traits just report
> 'expression X<T> evaluates to false'.  However, many type traits are
> actually defined in terms of compiler builtins; we can do better here.
> For instance, 'is_constructible_v' could go on to explain why the type
> is not constructible, or 'is_invocable_v' could list potential
> candidates.

That'd be great improvement.

> 
> As a first step to supporting that we need to be able to map the
> standard type traits to the builtins that they use.  Rather than adding
> another list that would need to be kept up-to-date whenever a builtin is
> added, this patch instead tries to detect any variable template defined
> directly in terms of a TRAIT_EXPR.
> 
> To avoid false positives, we ignore any variable templates that have any
> specialisations (partial or explicit), even if we wouldn't have chosen
> that specialisation anyway.  This shouldn't affect any of the standard
> library type traits that I could see.

You should be able to tsubst the TEMPLATE_ID_EXPR directly and look at
its TI_PARTIAL_INFO in order to determine which (if any) partial
specialization was selected.  And if an explicit specialization was
selected the resulting VAR_DECL will have DECL_TEMPLATE_SPECIALIZATION
set.

> 
> The new diagnostics from this patch are not immediately much better;
> however, it would be relatively straight-forward to update the messages
> in 'diagnose_trait_expr' to provide these new details.
> 
> This logic could also perhaps be used by 'diagnose_failing_condition' so
> that cases like 'static_assert(std::is_constructible_v<T>)' get the same
> treatment; this patch doesn't attempt to update this yet.
> 
> 	PR c++/117294
> 	PR c++/113854
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (diagnose_trait_expr): Take location to diagnose
> 	at explicitly.
> 	(maybe_unwrap_standard_trait): New function.
> 	(diagnose_atomic_constraint): Use it; pass in the location of
> 	the atomic constraint to diagnose_trait_expr.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-traits4.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/constraint.cc                          | 52 +++++++++++++++++--
>  gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 31 +++++++++++
>  2 files changed, 79 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 8a36b9c88c4..c683e6a44dd 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3108,10 +3108,8 @@ get_constraint_error_location (tree t)
>  /* Emit a diagnostic for a failed trait.  */
>  
>  static void
> -diagnose_trait_expr (tree expr, tree args)
> +diagnose_trait_expr (location_t loc, tree expr, tree args)
>  {
> -  location_t loc = cp_expr_location (expr);
> -
>    /* Build a "fake" version of the instantiated trait, so we can
>       get the instantiated types from result.  */
>    ++processing_template_decl;
> @@ -3323,6 +3321,51 @@ diagnose_trait_expr (tree expr, tree args)
>      }
>  }
>  
> +/* Attempt to detect if this is a standard type trait, defined in terms
> +   of a compiler builtin (above).  If so, this will allow us to provide
> +   slightly more helpful diagnostics.
> +
> +   However, don't unwrap if the type has been specialized (even if we
> +   wouldn't have used said specialization)..  */
> +
> +static void
> +maybe_unwrap_standard_trait (tree *expr, tree *args)
> +{
> +  if (TREE_CODE (*expr) != TEMPLATE_ID_EXPR)
> +    return;
> +
> +  tree templ = TREE_OPERAND (*expr, 0);
> +  if (TREE_CODE (templ) != TEMPLATE_DECL
> +      || !variable_template_p (templ))
> +    return;
> +
> +  tree gen_tmpl = most_general_template (templ);
> +  if (DECL_TEMPLATE_SPECIALIZATIONS (gen_tmpl))
> +    return;
> +
> +  for (tree inst = DECL_TEMPLATE_INSTANTIATIONS (gen_tmpl);
> +       inst; inst = TREE_CHAIN (inst))
> +    if (DECL_TEMPLATE_SPECIALIZATION (TREE_VALUE (inst)))
> +      return;
> +
> +  tree pattern = DECL_TEMPLATE_RESULT (gen_tmpl);
> +  tree initial = DECL_INITIAL (pattern);
> +  if (TREE_CODE (initial) != TRAIT_EXPR)
> +    return;
> +
> +  /* At this point we're definitely providing a TRAIT_EXPR, update
> +     *expr to point at it and provide remapped *args for it.  */
> +  tree parms = DECL_INNERMOST_TEMPLATE_PARMS (gen_tmpl);
> +  tree targs = TREE_OPERAND (*expr, 1);
> +  if (targs)
> +    targs = tsubst_template_args (targs, *args, tf_none, NULL_TREE);
> +  targs = add_outermost_template_args (templ, targs);
> +  targs = coerce_template_parms (parms, targs, templ, tf_none);

If we substituted the TEMPLATE_ID_EXPR as a whole we could use the
DECL_TI_ARGS of that IIUC?

> +
> +  *expr = initial;
> +  *args = targs;
> +}
> +
>  /* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
>  
>  static void
> @@ -3347,10 +3390,11 @@ diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
>    /* Generate better diagnostics for certain kinds of expressions.  */
>    tree expr = ATOMIC_CONSTR_EXPR (t);
>    STRIP_ANY_LOCATION_WRAPPER (expr);
> +  maybe_unwrap_standard_trait (&expr, &args);
>    switch (TREE_CODE (expr))
>      {
>      case TRAIT_EXPR:
> -      diagnose_trait_expr (expr, args);
> +      diagnose_trait_expr (loc, expr, args);
>        break;
>      case REQUIRES_EXPR:
>        gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> new file mode 100644
> index 00000000000..a2f9609c4e1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> @@ -0,0 +1,31 @@
> +// PR c++/117294
> +// { dg-do compile { target c++20 } }
> +
> +template <typename T> constexpr bool normal = __is_constructible(T);
> +
> +template <typename T> constexpr bool partial_spec = __is_constructible(T);
> +template <typename T> constexpr bool partial_spec<T*> = false;
> +
> +template <typename T> constexpr bool explicit_spec = __is_constructible(T);
> +template <> constexpr bool explicit_spec<int> = false;
> +
> +template <typename T>
> +concept test_normal = normal<T>;
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
> +
> +template <typename T>
> +concept test_partial_spec = partial_spec<T>;
> +// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
> +// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
> +
> +template <typename T>
> +concept test_explicit_spec = explicit_spec<T>;
> +// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
> +// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
> +
> +static_assert(test_normal<void>);  // { dg-error "assert" }
> +static_assert(test_partial_spec<void>);  // { dg-error "assert" }
> +static_assert(test_explicit_spec<void>);  // { dg-error "assert" }
> +
> +static_assert(test_partial_spec<int*>);  // { dg-error "assert" }
> +static_assert(test_explicit_spec<int>);  // { dg-error "assert" }
> -- 
> 2.47.0
> 
>
diff mbox series

Patch

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 8a36b9c88c4..c683e6a44dd 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3108,10 +3108,8 @@  get_constraint_error_location (tree t)
 /* Emit a diagnostic for a failed trait.  */
 
 static void
-diagnose_trait_expr (tree expr, tree args)
+diagnose_trait_expr (location_t loc, tree expr, tree args)
 {
-  location_t loc = cp_expr_location (expr);
-
   /* Build a "fake" version of the instantiated trait, so we can
      get the instantiated types from result.  */
   ++processing_template_decl;
@@ -3323,6 +3321,51 @@  diagnose_trait_expr (tree expr, tree args)
     }
 }
 
+/* Attempt to detect if this is a standard type trait, defined in terms
+   of a compiler builtin (above).  If so, this will allow us to provide
+   slightly more helpful diagnostics.
+
+   However, don't unwrap if the type has been specialized (even if we
+   wouldn't have used said specialization)..  */
+
+static void
+maybe_unwrap_standard_trait (tree *expr, tree *args)
+{
+  if (TREE_CODE (*expr) != TEMPLATE_ID_EXPR)
+    return;
+
+  tree templ = TREE_OPERAND (*expr, 0);
+  if (TREE_CODE (templ) != TEMPLATE_DECL
+      || !variable_template_p (templ))
+    return;
+
+  tree gen_tmpl = most_general_template (templ);
+  if (DECL_TEMPLATE_SPECIALIZATIONS (gen_tmpl))
+    return;
+
+  for (tree inst = DECL_TEMPLATE_INSTANTIATIONS (gen_tmpl);
+       inst; inst = TREE_CHAIN (inst))
+    if (DECL_TEMPLATE_SPECIALIZATION (TREE_VALUE (inst)))
+      return;
+
+  tree pattern = DECL_TEMPLATE_RESULT (gen_tmpl);
+  tree initial = DECL_INITIAL (pattern);
+  if (TREE_CODE (initial) != TRAIT_EXPR)
+    return;
+
+  /* At this point we're definitely providing a TRAIT_EXPR, update
+     *expr to point at it and provide remapped *args for it.  */
+  tree parms = DECL_INNERMOST_TEMPLATE_PARMS (gen_tmpl);
+  tree targs = TREE_OPERAND (*expr, 1);
+  if (targs)
+    targs = tsubst_template_args (targs, *args, tf_none, NULL_TREE);
+  targs = add_outermost_template_args (templ, targs);
+  targs = coerce_template_parms (parms, targs, templ, tf_none);
+
+  *expr = initial;
+  *args = targs;
+}
+
 /* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
 
 static void
@@ -3347,10 +3390,11 @@  diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
   /* Generate better diagnostics for certain kinds of expressions.  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
   STRIP_ANY_LOCATION_WRAPPER (expr);
+  maybe_unwrap_standard_trait (&expr, &args);
   switch (TREE_CODE (expr))
     {
     case TRAIT_EXPR:
-      diagnose_trait_expr (expr, args);
+      diagnose_trait_expr (loc, expr, args);
       break;
     case REQUIRES_EXPR:
       gcc_checking_assert (info.diagnose_unsatisfaction_p ());
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
new file mode 100644
index 00000000000..a2f9609c4e1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
@@ -0,0 +1,31 @@ 
+// PR c++/117294
+// { dg-do compile { target c++20 } }
+
+template <typename T> constexpr bool normal = __is_constructible(T);
+
+template <typename T> constexpr bool partial_spec = __is_constructible(T);
+template <typename T> constexpr bool partial_spec<T*> = false;
+
+template <typename T> constexpr bool explicit_spec = __is_constructible(T);
+template <> constexpr bool explicit_spec<int> = false;
+
+template <typename T>
+concept test_normal = normal<T>;
+// { dg-message "'void' is not default constructible" "" { target *-*-* } .-1 }
+
+template <typename T>
+concept test_partial_spec = partial_spec<T>;
+// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
+// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
+
+template <typename T>
+concept test_explicit_spec = explicit_spec<T>;
+// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
+// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
+
+static_assert(test_normal<void>);  // { dg-error "assert" }
+static_assert(test_partial_spec<void>);  // { dg-error "assert" }
+static_assert(test_explicit_spec<void>);  // { dg-error "assert" }
+
+static_assert(test_partial_spec<int*>);  // { dg-error "assert" }
+static_assert(test_explicit_spec<int>);  // { dg-error "assert" }