diff mbox series

PR c++/84810 - constraints on lambdas

Message ID CAEcyUj8YHYqJMKotbDXaR6=D+x8VA6+j1f6q=-XjQUuhut98UA@mail.gmail.com
State New
Headers show
Series PR c++/84810 - constraints on lambdas | expand

Commit Message

Jeff Chapman Oct. 30, 2019, 11:20 a.m. UTC
Hello,

Attached is a patch that adds parsing of the optional requires-clause in a
lambda-expression and lambda-declarator. Additionally, shorthand constraints
from the template-parameter-list are now actually applied and constrain the
synthesized operator().

Previously we were not parsing the requires clauses at all and not saving the
shorthand constraints in the place expected by grokfndecl.

The trailing requires-clause is now also used to suppress synthesis of the
conversion to function pointer for non-capturing non-generic lambdas as per
expr.prim.lambda.closure/7.

This includes a fix to template_class_depth. Previously it was computing the
wrong depth for lambdas in the initializer of a static member of a class
template, exhibited by the concepts-lambda4 test which currently fails on
trunk. The bug was causing grokfndecl to use the constraints from the template
class for the lambda.

gcc/cp/
2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>

	PR c++/84810 - constraints on lambdas
	* lambda.c (maybe_add_lambda_conv_op): Do not synthesize
	conversion if the call operator does not satisfy its constraints.
	* parser.c (cp_parser_lambda_declarator_opt): Parse
	requires-clause on generic lambdas; combine with shorthand
	constraints. Parse trailing requires-clause and attach to the
	synthesized call operator.
	* pt.c (template_class_depth): Only inspect
	LAMBDA_TYPE_EXTRA_SCOPE if it is present. This fixes an
	incorrect depth calculation for lambdas inside the initializer
	of a static data member of a template class.

gcc/testsuite/
2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>

	PR c++/84810 - constraints on lambdas
	* g++.dg/cpp2a/concepts-lambda2.C: New test.
	* g++.dg/cpp2a/concepts-lambda3.C: Ditto.
	* g++.dg/cpp2a/concepts-lambda4.C: Ditto.
	* g++.dg/cpp2a/concepts-pr84810.C: Ditto.

Bootstrapped and tested on x86_64-pc-linux-gnu.

Please let me know if there's any issues.

Thanks,
Jeff Chapman II

Comments

Jason Merrill Oct. 31, 2019, 2:32 a.m. UTC | #1
On 10/30/19 7:20 AM, Jeff Chapman wrote:
> Hello,
> 
> Attached is a patch that adds parsing of the optional requires-clause in a
> lambda-expression and lambda-declarator. Additionally, shorthand constraints
> from the template-parameter-list are now actually applied and constrain the
> synthesized operator().
> 
> Previously we were not parsing the requires clauses at all and not saving the
> shorthand constraints in the place expected by grokfndecl.
> 
> The trailing requires-clause is now also used to suppress synthesis of the
> conversion to function pointer for non-capturing non-generic lambdas as per
> expr.prim.lambda.closure/7.
> 
> This includes a fix to template_class_depth. Previously it was computing the
> wrong depth for lambdas in the initializer of a static member of a class
> template, exhibited by the concepts-lambda4 test which currently fails on
> trunk. The bug was causing grokfndecl to use the constraints from the template
> class for the lambda.
> 
> gcc/cp/
> 2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>
> 
> 	PR c++/84810 - constraints on lambdas
> 	* lambda.c (maybe_add_lambda_conv_op): Do not synthesize
> 	conversion if the call operator does not satisfy its constraints.
> 	* parser.c (cp_parser_lambda_declarator_opt): Parse
> 	requires-clause on generic lambdas; combine with shorthand
> 	constraints. Parse trailing requires-clause and attach to the
> 	synthesized call operator.
> 	* pt.c (template_class_depth): Only inspect
> 	LAMBDA_TYPE_EXTRA_SCOPE if it is present. This fixes an
> 	incorrect depth calculation for lambdas inside the initializer
> 	of a static data member of a template class.
> 
> gcc/testsuite/
> 2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>
> 
> 	PR c++/84810 - constraints on lambdas
> 	* g++.dg/cpp2a/concepts-lambda2.C: New test.
> 	* g++.dg/cpp2a/concepts-lambda3.C: Ditto.
> 	* g++.dg/cpp2a/concepts-lambda4.C: Ditto.
> 	* g++.dg/cpp2a/concepts-pr84810.C: Ditto.
> 
> Bootstrapped and tested on x86_64-pc-linux-gnu.
> 
> Please let me know if there's any issues.

Applied, thanks.

Jason
diff mbox series

Patch

From d196b03e6f6924935a521a8d140d621d03ae18f2 Mon Sep 17 00:00:00 2001
From: Jeff Chapman II <jchapman@lock3software.com>
Date: Mon, 28 Oct 2019 12:57:36 -0400
Subject: [PATCH 1/1] PR c++/84810 - constraints on lambdas

gcc/cp/
2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>

	PR c++/84810 - constraints on lambdas
	* lambda.c (maybe_add_lambda_conv_op): Do not synthesize
	conversion if the call operator does not satisfy its constraints.
	* parser.c (cp_parser_lambda_declarator_opt): Parse
	requires-clause on generic lambdas; combine with shorthand
	constraints. Parse trailing requires-clause and attach to the
	synthesized call operator.
	* pt.c (template_class_depth): Only inspect
	LAMBDA_TYPE_EXTRA_SCOPE if it is present. This fixes an
	incorrect depth calculation for lambdas inside the initializer
	of a static data member of a template class.

gcc/testsuite/
2019-10-30  Jeff Chapman II  <jchapman@lock3software.com>

	PR c++/84810 - constraints on lambdas
	* g++.dg/cpp2a/concepts-lambda2.C: New test.
	* g++.dg/cpp2a/concepts-lambda3.C: Ditto.
	* g++.dg/cpp2a/concepts-lambda4.C: Ditto.
	* g++.dg/cpp2a/concepts-pr84810.C: Ditto.
---
 gcc/cp/lambda.c                               |   6 +
 gcc/cp/parser.c                               |  21 ++-
 gcc/cp/pt.c                                   |   2 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-lambda2.C | 153 ++++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/concepts-lambda3.C |  64 ++++++++
 gcc/testsuite/g++.dg/cpp2a/concepts-lambda4.C |  14 ++
 gcc/testsuite/g++.dg/cpp2a/concepts-pr84810.C |  13 ++
 7 files changed, 270 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-lambda4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-pr84810.C

diff --git a/gcc/cp/lambda.c b/gcc/cp/lambda.c
index f128ed800f6..d621beca2eb 100644
--- a/gcc/cp/lambda.c
+++ b/gcc/cp/lambda.c
@@ -1046,6 +1046,12 @@  maybe_add_lambda_conv_op (tree type)
       return;
     }
 
+  /* Non-generic non-capturing lambdas only have a conversion function to
+     pointer to function when the trailing requires-clause's constraints are
+     satisfied.  */
+  if (!generic_lambda_p && !constraints_satisfied_p (callop))
+    return;
+
   /* Non-template conversion operators are defined directly with build_call_a
      and using DIRECT_ARGVEC for arguments (including 'this').  Templates are
      deferred and the CALL is built in-place.  In the case of a deduced return
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 3857fe47d67..bbdf8d69077 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -10854,11 +10854,13 @@  cp_parser_lambda_introducer (cp_parser* parser, tree lambda_expr)
 
    lambda-declarator:
      < template-parameter-list [opt] >
+       requires-clause [opt]
      ( parameter-declaration-clause [opt] )
        attribute-specifier [opt]
        decl-specifier-seq [opt]
        exception-specification [opt]
        lambda-return-type-clause [opt]
+       requires-clause [opt]
 
    LAMBDA_EXPR is the current representation of the lambda expression.  */
 
@@ -10877,6 +10879,7 @@  cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
   tree template_param_list = NULL_TREE;
   tree tx_qual = NULL_TREE;
   tree return_type = NULL_TREE;
+  tree trailing_requires_clause = NULL_TREE;
   cp_decl_specifier_seq lambda_specs;
   clear_decl_specs (&lambda_specs);
 
@@ -10896,9 +10899,20 @@  cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       cp_lexer_consume_token (parser->lexer);
 
       template_param_list = cp_parser_template_parameter_list (parser);
-
       cp_parser_skip_to_end_of_template_parameter_list (parser);
 
+      /* We may have a constrained generic lambda; parse the requires-clause
+	 immediately after the template-parameter-list and combine with any
+	 shorthand constraints present.  */
+      tree dreqs = cp_parser_requires_clause_opt (parser);
+      if (flag_concepts)
+	{
+	  tree reqs = get_shorthand_constraints (current_template_parms);
+	  if (dreqs)
+	    reqs = combine_constraint_expressions (reqs, dreqs);
+	  TEMPLATE_PARMS_CONSTRAINTS (current_template_parms) = reqs;
+	}
+
       /* We just processed one more parameter list.  */
       ++parser->num_template_parameter_lists;
     }
@@ -10962,6 +10976,9 @@  cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       if (cp_next_tokens_can_be_gnu_attribute_p (parser))
 	gnu_attrs = cp_parser_gnu_attributes_opt (parser);
 
+      /* Parse optional trailing requires clause.  */
+      trailing_requires_clause = cp_parser_requires_clause_opt (parser);
+
       /* The function parameters must be in scope all the way until after the
          trailing-return-type in case of decltype.  */
       pop_bindings_and_leave_scope ();
@@ -11008,7 +11025,7 @@  cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
 				       tx_qual,
 				       exception_spec,
                                        return_type,
-                                       /*requires_clause*/NULL_TREE);
+				       trailing_requires_clause);
     declarator->std_attributes = std_attrs;
 
     fco = grokmethod (&return_type_specs,
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index c5675dd8e3f..4de3fb7db4c 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -383,7 +383,7 @@  template_class_depth (tree type)
 
       if (DECL_P (type))
 	type = CP_DECL_CONTEXT (type);
-      else if (LAMBDA_TYPE_P (type))
+      else if (LAMBDA_TYPE_P (type) && LAMBDA_TYPE_EXTRA_SCOPE (type))
 	type = LAMBDA_TYPE_EXTRA_SCOPE (type);
       else
 	type = CP_TYPE_CONTEXT (type);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda2.C
new file mode 100644
index 00000000000..ffad95cb77a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda2.C
@@ -0,0 +1,153 @@ 
+// { dg-do compile { target c++2a } }
+
+template<typename T>
+concept False = false;
+
+template<typename T>
+concept C1 = __is_same_as(T, int)
+  || __is_same_as(T, long long)
+  || __is_same_as(T, char);
+
+template<typename T>
+concept IsNotLarge = !__is_same_as(T, long long);
+
+template<typename T>
+concept IsNotTiny = !__is_same_as(T, char);
+
+template<IsNotLarge T>
+struct Foo
+{
+  static constexpr auto a = [](auto n) { return n; };
+  template<IsNotTiny S>
+  auto b()
+  {
+    return [](False auto n) { return n; };
+  }
+};
+
+template<IsNotTiny T>
+struct Bar
+{
+  static constexpr auto a =
+    []<IsNotTiny R>(R t) { return t; }('a'); // { dg-error "no match" }
+  char b =
+    []<IsNotTiny R>(R t) { return t; }('b'); // { dg-error "no match" }
+
+  static constexpr auto a2 =
+    [](char t) requires false { return t; }('a'); // { dg-error "no match" }
+  char b2 =
+    [](char t) requires false { return t; }('b'); // { dg-error "no match" }
+};
+
+template<IsNotLarge S>
+S c =
+  []<IsNotTiny R>(R t) { return t; }('c'); // { dg-error "no match" }
+template<IsNotLarge S>
+S c2 =
+  [](char t) requires false { return t; }('c'); // { dg-error "no match" }
+
+Bar<long long> bar;
+
+void test0()
+{
+  auto bar_a = bar.a;
+  auto bar_b = bar.b;
+  auto c = ::c<char>;
+  auto bar_a2 = bar.a2;
+  auto bar_b2 = bar.b2;
+  auto c2 = ::c2<char>;
+
+  auto g0 = []<False T>(T t) { return t; };
+  auto g1 = []<typename T> requires False<T> (T t) { return t; };
+  auto g2 = []<typename T>(T t) requires False<decltype(t)> { return t; };
+  auto g3 = [](int t) requires False<decltype(t)> { return t; };
+  auto g4 = [](False auto t) { return t; };
+  auto g5 = [](auto t) requires False<decltype(t)> { return t; };
+  auto g6 = [](int t) requires False<int> { return t; };
+  auto g7 = [](int t) requires false { return t; };
+  g0(0); // { dg-error "no match" }
+  g1(0); // { dg-error "no match" }
+  g2(0); // { dg-error "no match" }
+  g3(0); // { dg-error "no match" }
+  g4(0); // { dg-error "no match" }
+  g5(0); // { dg-error "no match" }
+  g6(0); // { dg-error "no match" }
+  g7(0); // { dg-error "no match" }
+}
+
+void test1()
+{
+  int var{-1};
+  auto g0 = [&]<False T>(T t) { return t; };
+  auto g1 = [&]<typename T> requires False<T> (T t) { return t; };
+  auto g2 = [&]<typename T>(T t) requires False<decltype(t)> { return t; };
+  auto g3 = [&](int t) requires False<decltype(t)> { return t; };
+  auto g4 = [&](False auto t) { return t; };
+  auto g5 = [&](auto t) requires False<decltype(t)> { return t; };
+  auto g6 = [&](int t) requires False<int> { return t; };
+  auto g7 = [&](int t) requires false { return t; };
+  g0(0); // { dg-error "no match" }
+  g1(0); // { dg-error "no match" }
+  g2(0); // { dg-error "no match" }
+  g3(0); // { dg-error "no match" }
+  g4(0); // { dg-error "no match" }
+  g5(0); // { dg-error "no match" }
+  g6(0); // { dg-error "no match" }
+  g7(0); // { dg-error "no match" }
+}
+
+void test2()
+{
+  auto x = []<IsNotTiny T>(auto a, T t, auto b)
+    requires IsNotTiny<decltype(a)> && IsNotLarge<decltype(b)>
+    { return a + t + (T)b; };
+  x(5LL, 2LL, 1);
+
+  x('0', 2LL, 1LL); // { dg-error "no match" }
+  x(5LL, '0', 1LL); // { dg-error "no match" }
+  x(5LL, 2LL, 1LL); // { dg-error "no match" }
+}
+
+void test3()
+{
+  auto x = []<IsNotTiny T>(IsNotTiny auto a, T t, IsNotLarge auto b)
+    { return a + t + (T)b; };
+  x(5LL, 2LL, 1);
+
+  x('0', 2LL, 1LL); // { dg-error "no match" }
+  x(5LL, '0', 1LL); // { dg-error "no match" }
+  x(5LL, 2LL, 1LL); // { dg-error "no match" }
+}
+
+void test4()
+{
+  auto g = []<C1 T> requires IsNotTiny<T>(T t) -> T
+    requires IsNotLarge<decltype(t)> { return t; };
+  g(5.5); // { dg-error "no match" }
+  g('a'); // { dg-error "no match" }
+  g(1LL); // { dg-error "no match" }
+}
+
+void test5()
+{
+  Foo<int> foo1;
+  foo1.a(5.5);
+  foo1.a(1LL);
+  foo1.b<char>(); // { dg-error "no match" }
+  foo1.b<long long>()(5); // { dg-error "no match" }
+
+  Foo<double> foo2;
+  foo2.a(5.5);
+  foo2.a(1LL);
+  foo2.b<char>(); // { dg-error "no match" }
+  foo2.b<long long>()(5); // { dg-error "no match" }
+}
+
+using Func = int(*)(int);
+
+void test6()
+{
+  Func f1 = [](int a) requires false { return a; }; // { dg-error "cannot convert" }
+  Func f2 = [](auto a) requires false { return a; }; // { dg-error "cannot convert" }
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda3.C
new file mode 100644
index 00000000000..7e668ffeddd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda3.C
@@ -0,0 +1,64 @@ 
+// { dg-do run { target c++2a } }
+
+template<typename T>
+concept C1 = __is_same_as(T, int)
+  || __is_same_as(T, long long)
+  || __is_same_as(T, char);
+
+template<typename T>
+concept IsNotLarge = !__is_same_as(T, long long);
+
+template<typename T>
+concept IsNotTiny = !__is_same_as(T, char);
+
+template<IsNotLarge T>
+struct Foo
+{
+  static constexpr auto a = [](auto n) { return n; };
+  template<IsNotTiny S>
+  auto b()
+  {
+    return [](auto n) { return n; };
+  }
+};
+
+using Func = int(*)(int);
+
+int main(int, char**)
+{
+  auto g = []<C1 T> requires IsNotTiny<T>(T t) -> T
+    requires IsNotLarge<decltype(t)> { return t; };
+  g(5);
+  g.operator()<int>(5.5);
+
+  auto z = []<typename T, int N = 5>(T t) requires (N < 4) { return t; };
+  z.operator()<int, 3>(5);
+
+  [](int t) requires true { return t; }(5);
+  [](C1 auto t) { return t; }(5);
+
+  auto a0 = [](IsNotLarge auto a) { return [](auto b){ return b; }; };
+  auto a1 = a0(1);
+  auto a2 = a1(5LL);
+
+  auto b0 = [](auto a) { return [](IsNotLarge auto b){ return b; }; };
+  auto b1 = b0(5LL);
+  auto b2 = b1(1);
+
+  Foo<int> foo1;
+  foo1.a(5.5);
+  foo1.a(1LL);
+  foo1.b<int>()(5);
+  foo1.b<long long>()(5);
+
+  Foo<double> foo2;
+  foo2.a(5.5);
+  foo2.a(1LL);
+  foo2.b<int>()(5);
+  foo2.b<long long>()(5);
+
+  Func m1 = [](int a) -> int requires true { return a; };
+
+  return 0;
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-lambda4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda4.C
new file mode 100644
index 00000000000..dfb5f3bcc5e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-lambda4.C
@@ -0,0 +1,14 @@ 
+// { dg-do compile { target c++2a } }
+
+struct a {};
+template <bool> using b = a;
+
+template <typename> struct c;
+template <typename d>
+  requires requires(d e) { e[0]; }
+struct c<d> {
+  static constexpr bool f = [] { return false; }.operator()();
+};
+
+b<c<unsigned[]>::f> b0{};
+
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr84810.C b/gcc/testsuite/g++.dg/cpp2a/concepts-pr84810.C
new file mode 100644
index 00000000000..b330e4be051
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr84810.C
@@ -0,0 +1,13 @@ 
+// { dg-do compile { target c++2a } }
+template<class> constexpr bool is_int = false;
+template<> constexpr bool is_int<int> = true;
+
+template <class T>
+concept Int = is_int<T>;
+
+int main() {
+    auto x = []<Int T>(T t) { return 42; };
+    auto y = x(42);
+    auto z = x(""); // { dg-error "no match" }
+    return z;
+}
-- 
2.23.0