On Thu, 26 Mar 2020, Jason Merrill wrote: > On 3/26/20 5:24 PM, Patrick Palka wrote: > > On Wed, 25 Mar 2020, Jason Merrill wrote: > > > > > On 3/25/20 12:17 PM, Patrick Palka wrote: > > > > This PR reports that the requires-expression in > > > > > > > > auto f = [] { }; > > > > static_assert(requires { f(); }); > > > > > > > > erroneously evaluates to false. The way we end up evaluating to false > > > > goes > > > > as > > > > follows. During the parsing of the requires-expression, we call > > > > finish_call_expr from cp_parser_requires_expression with the arguments > > > > > > > > fn = VIEW_CONVERT_EXPR<struct ._anon_0>(f); > > > > args = {} > > > > > > > > which does the full processing of the call (since we're not inside a > > > > template) > > > > and returns > > > > > > > > <lambda()>::operator() (&f); > > > > > > > > Later, when evaluating the requires-expression, we call finish_call_expr > > > > again, > > > > this time from tsubst_expr from satisfy_atom, with the arguments > > > > > > > > fn = operator() > > > > args = { &f } > > > > > > > > (which, as expected, correspond to the CALL_EXPR returned by > > > > finish_call_expr > > > > the first time). But this time, finish_call_expr returns > > > > error_mark_node > > > > because > > > > it doesn't expect to see an explicit 'this' argument in the args array, > > > > treating > > > > it instead as a user-written argument which causes the only candidate > > > > function > > > > to be discarded. This causes the requires-expression to evaluate to > > > > false. > > > > > > > > In short, it seems finish_call_expr is not idempotent on certain inputs > > > > when > > > > !processing_template_decl. Assuming this idempotency issue is not > > > > specific > > > > to > > > > finish_call_expr, it seems that the safest thing to do is to avoid doing > > > > full > > > > semantic processing twice when parsing and evaluating a > > > > requires-expression > > > > that > > > > lives outside of a template definition. > > > > > > Absolutely. We shouldn't call tsubst_expr on non-template trees. > > > > > > > This patch achieves this by temporarily setting processing_template_decl > > > > to > > > > non-zero when parsing the body of a requires-expression. This way, full > > > > semantic processing will always be done only during evaluation and not > > > > during > > > > parsing. > > > > > > Hmm, interesting approach, but I think the standard requires us to treat > > > requires-expressions outside of template context like normal non-template > > > code: "[Note: If a requires-expression contains invalid > > > types or expressions in its requirements, and it does not appear within > > > the > > > declaration of a templated entity, then the program is ill-formed. --end > > > note]" > > > > > > So I think better to avoid the tsubst_expr later, either by immediately > > > evaluating the REQUIRES_EXPR or marking the ATOMIC_CONSTR. We could do > > > that > > > by immediately resolving the requires-expression in non-template context, > > > or > > > by marking it up somehow to prevent the substitution. > > > > If we go the route of marking the REQUIRES_EXPR or its subtrees, then we > > would need to change tsubst_requires_expr and its callees as well as > > changing diagnose_requires_expr and its callees so that they > > conditionally avoid doing tsubst_expr, which seems likes like an > > undesirable amount of churn. > > > > Another downside is that we apparently already always pretend we're > > inside a template when parsing a nested-requirement, which means marking > > a REQUIRES_EXPR based on processing_template_decl won't work correctly > > for a REQUIRES_EXPR inside a nested-requirement like this one: > > > > requires { requires requires { ... }; }; > > > > These two points nudged me to instead go the route of pretending we're > > inside a template when parsing the body of a requires-expr, and then > > immediately afterwards doing tsubst_requires_expr on the body to perform > > the full semantic processing. > > > > Does the following look OK? > > > > > > > > I notice that we currently fail to handle requires-expressions in regular > > > expression context: > > > > > > int main() { return requires { 42; }; } // ICE > > > > This remains unchanged with the patch. We could return the result of > > tsubst_requires_expr from cp_parser_requires_expression instead of > > throwing it away, but that would break our support for explaining an > > unsatisfied REQUIRES_EXPR inside a static_assert, that the testcase > > g++.dg/concepts/diagnostic7.C verifies. > > > > What would be the best way to handle these stray REQUIRES_EXPRs? > > We probably want to lower them in cp_genericize_r. And I suppose if we're > doing that we may not need the tsubst_requires_expr at parse time.
Sounds good, I'll look into this. > > > -- >8 -- > > > > gcc/cp/ChangeLog: > > > > PR c++/94252 > > * constraint.cc (tsubst_compound_requirement): Always suppress errors > > from type_deducible_p and expression_convertible_p, as they're not > > substitution errors. > > (diagnose_atomic_constraint) <case INTEGER_CST>: Remove this case so > > that we diagnose INTEGER_CST expressions of non-bool type via the > > default case. > > * parser.c (cp_parser_requires_expression): Always parse the > > requirement > > body as if we're processing a template, by temporarily incrementing > > processing_template_decl. Afterwards, if we're not actually in a > > template context, perform semantic processing to diagnose any invalid > > types and expressions. > > * pt.c (tsubst_copy_and_build) <case REQUIRES_EXPR>: Remove dead code. > > Why is this dead code? Because tsubst_requires_expr never returns error_mark_node, AFAICT.. > > > * semantics.c (finish_static_assert): Also explain an assertion > > failure > > when the condition is a REQUIRES_EXPR. > > > > gcc/testsuite/ChangeLog: > > > > PR c++/94252 > > * g++.dg/concepts/diagnostic7.C: New test. > > * g++.dg/concepts/pr94252.C: New test. > > * g++.dg/cpp2a/concepts-requires18.C: Adjust to expect another > > diagnostic. > > --- > > gcc/cp/constraint.cc | 10 +++---- > > gcc/cp/parser.c | 9 ++++++- > > gcc/cp/pt.c | 2 -- > > gcc/cp/semantics.c | 6 +++-- > > gcc/testsuite/g++.dg/concepts/diagnostic7.C | 12 +++++++++ > > gcc/testsuite/g++.dg/concepts/pr94252.C | 27 +++++++++++++++++++ > > .../g++.dg/cpp2a/concepts-requires18.C | 2 +- > > 7 files changed, 56 insertions(+), 12 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/concepts/diagnostic7.C > > create mode 100644 gcc/testsuite/g++.dg/concepts/pr94252.C > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc > > index a86bcdf603a..a2f450520fd 100644 > > --- a/gcc/cp/constraint.cc > > +++ b/gcc/cp/constraint.cc > > @@ -1980,15 +1980,17 @@ tsubst_compound_requirement (tree t, tree args, > > subst_info info) > > if (type == error_mark_node) > > return error_mark_node; > > + subst_info quiet (tf_none, info.in_decl); > > + > > /* Check expression against the result type. */ > > if (type) > > { > > if (tree placeholder = type_uses_auto (type)) > > { > > - if (!type_deducible_p (expr, type, placeholder, args, info)) > > + if (!type_deducible_p (expr, type, placeholder, args, quiet)) > > return error_mark_node; > > } > > - else if (!expression_convertible_p (expr, type, info)) > > + else if (!expression_convertible_p (expr, type, quiet)) > > return error_mark_node; > > } > > @@ -3362,10 +3364,6 @@ diagnose_atomic_constraint (tree t, tree map, tree > > result, subst_info info) > > case REQUIRES_EXPR: > > diagnose_requires_expr (expr, map, info.in_decl); > > break; > > - case INTEGER_CST: > > - /* This must be either 0 or false. */ > > - inform (loc, "%qE is never satisfied", expr); > > - break; > > default: > > tree a = copy_node (t); > > ATOMIC_CONSTR_MAP (a) = map; > > diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c > > index 7b03bdf5218..91d306da6af 100644 > > --- a/gcc/cp/parser.c > > +++ b/gcc/cp/parser.c > > @@ -27712,7 +27712,9 @@ cp_parser_requires_expression (cp_parser *parser) > > parms = NULL_TREE; > > /* Parse the requirement body. */ > > + ++processing_template_decl; > > reqs = cp_parser_requirement_body (parser); > > + --processing_template_decl; > > if (reqs == error_mark_node) > > return error_mark_node; > > } > > @@ -27721,7 +27723,12 @@ cp_parser_requires_expression (cp_parser *parser) > > the parm chain. */ > > grokparms (parms, &parms); > > loc = make_location (loc, loc, parser->lexer); > > - return finish_requires_expr (loc, parms, reqs); > > + tree expr = finish_requires_expr (loc, parms, reqs); > > + if (!processing_template_decl) > > + /* Perform semantic processing now to diagnose any invalid types and > > + expressions. */ > > + tsubst_requires_expr (expr, NULL_TREE, tf_warning_or_error, NULL_TREE); > > + return expr; > > } > > /* Parse a parameterized requirement. > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > > index 496bf7c33ba..95cd35f82c8 100644 > > --- a/gcc/cp/pt.c > > +++ b/gcc/cp/pt.c > > @@ -20321,8 +20321,6 @@ tsubst_copy_and_build (tree t, > > case REQUIRES_EXPR: > > { > > tree r = tsubst_requires_expr (t, args, tf_none, in_decl); > > - if (r == error_mark_node && (complain & tf_error)) > > - tsubst_requires_expr (t, args, complain, in_decl); > > RETURN (r); > > } > > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c > > index bcb2e72fbb5..e998b373af4 100644 > > --- a/gcc/cp/semantics.c > > +++ b/gcc/cp/semantics.c > > @@ -9691,8 +9691,10 @@ finish_static_assert (tree condition, tree message, > > location_t location, > > error ("static assertion failed: %s", > > TREE_STRING_POINTER (message)); > > - /* Actually explain the failure if this is a concept check. */ > > - if (concept_check_p (orig_condition)) > > + /* Actually explain the failure if this is a concept check or a > > + requires-expression. */ > > + if (concept_check_p (orig_condition) > > + || TREE_CODE (orig_condition) == REQUIRES_EXPR) > > diagnose_constraints (location, orig_condition, NULL_TREE); > > } > > else if (condition && condition != error_mark_node) > > diff --git a/gcc/testsuite/g++.dg/concepts/diagnostic7.C > > b/gcc/testsuite/g++.dg/concepts/diagnostic7.C > > new file mode 100644 > > index 00000000000..f78e9bb8240 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/concepts/diagnostic7.C > > @@ -0,0 +1,12 @@ > > +// { dg-do compile { target c++2a } } > > + > > +template<typename A, typename B> > > + concept same_as = __is_same(A, B); // { dg-message ".void. is not the > > same as .int." } > > + > > +void f(); > > + > > +static_assert(requires { { f() } noexcept -> same_as<int>; }); > > +// { dg-error "static assertion failed" "" { target *-*-* } .-1 } > > +// { dg-message "not .noexcept." "" { target *-*-* } .-2 } > > +// { dg-message "return-type-requirement" "" { target *-*-* } .-3 } > > +// { dg-error "does not satisfy placeholder constraints" "" { target *-*-* > > } .-4 } > > diff --git a/gcc/testsuite/g++.dg/concepts/pr94252.C > > b/gcc/testsuite/g++.dg/concepts/pr94252.C > > new file mode 100644 > > index 00000000000..ee05044abef > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/concepts/pr94252.C > > @@ -0,0 +1,27 @@ > > +// PR c++/94252 > > +// { dg-do compile { target c++2a } } > > + > > +auto f = []{ return 0; }; > > +static_assert(requires { f(); }); > > +static_assert(requires { requires requires { f(); }; }); > > + > > +template<typename A, typename B> > > + concept same_as = __is_same(A, B); > > + > > +struct S { int f(int) noexcept; }; > > +static_assert(requires(S o, int i) { > > + o.f(i); > > + { o.f(i) } noexcept -> same_as<int>; > > +}); > > + > > +template<typename T> > > + concept c = requires (T t) { requires (T)5; }; // { dg-error "has type > > .int." } > > + > > +void > > +foo() > > +{ > > + requires { requires c<int>; }; > > + requires { requires 5; }; // { dg-error "has type .int." } > > + requires { { 5 } -> same_as<bool>; }; > > + requires { requires !requires { { 5 } -> same_as<bool>; }; }; > > +} > > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C > > b/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C > > index c76b12c6414..c97704565a1 100644 > > --- a/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C > > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-requires18.C > > @@ -4,7 +4,7 @@ template<typename T> > > concept integer = __is_same_as(T, int); > > template<typename T> > > -concept subst = requires (T x) { requires true; }; > > +concept subst = requires (T x) { requires true; }; // { dg-error "parameter > > type .void." } > > template<typename T> > > concept c1 = requires { requires integer<T> || subst<T&>; }; // { > > dg-message "in requirements" } > > > >