Implement P0315R4, Lambdas in unevaluated contexts.
authorJason Merrill <jason@redhat.com>
Tue, 13 Nov 2018 04:49:09 +0000 (23:49 -0500)
committerJason Merrill <jason@gcc.gnu.org>
Tue, 13 Nov 2018 04:49:09 +0000 (23:49 -0500)
When lambdas were added in C++11 they were banned from unevaluated contexts
as a way to avoid needing to deal with them in mangling or SFINAE.  This
proposal avoids that with a more narrow proposal: lambdas never compare as
equivalent (so we don't need to mangle them), and substitution failures
within a lambda are hard errors.  Lambdas appearing in places that types
couldn't previously have been declared introduces various complications; in
particular, it seems likely to mean types with no linkage being used more
broadly, risking ODR violations.  I want to follow up this patch with some
related diagnostics.

* decl2.c (min_vis_expr_r): Handle LAMBDA_EXPR.
* mangle.c (write_expression): Handle LAMBDA_EXPR.
* parser.c (cp_parser_lambda_expression): Allow lambdas in
unevaluated context.  Start the tentative firewall sooner.
(cp_parser_lambda_body): Use cp_evaluated.
* pt.c (iterative_hash_template_arg): Handle LAMBDA_EXPR.
(tsubst_function_decl): Substitute a lambda even if it isn't
dependent.
(tsubst_lambda_expr): Use cp_evaluated.  Always complain.
(tsubst_copy_and_build) [LAMBDA_EXPR]: Do nothing if tf_partial.
* semantics.c (begin_class_definition): Allow in template parm list.
* tree.c (strip_typedefs_expr): Pass through LAMBDA_EXPR.
(cp_tree_equal): Handle LAMBDA_EXPR.

From-SVN: r266056

22 files changed:
gcc/cp/ChangeLog
gcc/cp/decl2.c
gcc/cp/mangle.c
gcc/cp/parser.c
gcc/cp/pt.c
gcc/cp/semantics.c
gcc/cp/tree.c
gcc/testsuite/g++.dg/cpp0x/lambda/lambda-ice6.C
gcc/testsuite/g++.dg/cpp0x/lambda/lambda-sfinae1.C
gcc/testsuite/g++.dg/cpp0x/lambda/lambda-uneval.C
gcc/testsuite/g++.dg/cpp0x/lambda/lambda-uneval2.C
gcc/testsuite/g++.dg/cpp2a/lambda-uneval1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval7.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval8.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.cc [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.h [new file with mode: 0644]

index 5cd1471..7e80c4e 100644 (file)
@@ -1,5 +1,20 @@
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
+       Implement P0315R4, Lambdas in unevaluated contexts.
+       * decl2.c (min_vis_expr_r): Handle LAMBDA_EXPR.
+       * mangle.c (write_expression): Handle LAMBDA_EXPR.
+       * parser.c (cp_parser_lambda_expression): Allow lambdas in
+       unevaluated context.  Start the tentative firewall sooner.
+       (cp_parser_lambda_body): Use cp_evaluated.
+       * pt.c (iterative_hash_template_arg): Handle LAMBDA_EXPR.
+       (tsubst_function_decl): Substitute a lambda even if it isn't
+       dependent.
+       (tsubst_lambda_expr): Use cp_evaluated.  Always complain.
+       (tsubst_copy_and_build) [LAMBDA_EXPR]: Do nothing if tf_partial.
+       * semantics.c (begin_class_definition): Allow in template parm list.
+       * tree.c (strip_typedefs_expr): Pass through LAMBDA_EXPR.
+       (cp_tree_equal): Handle LAMBDA_EXPR.
+
        * pt.c (fn_type_unification): If we have a full set of explicit
        arguments, go straight to substitution.
 
index 74b9f4e..0453741 100644 (file)
@@ -2288,6 +2288,7 @@ min_vis_expr_r (tree *tp, int */*walk_subtrees*/, void *data)
     case DYNAMIC_CAST_EXPR:
     case NEW_EXPR:
     case CONSTRUCTOR:
+    case LAMBDA_EXPR:
       tpvis = type_visibility (TREE_TYPE (*tp));
       break;
 
index b9d8ee2..6441589 100644 (file)
@@ -3139,6 +3139,16 @@ write_expression (tree expr)
        write_expression (val);
       write_char ('E');
     }
+  else if (code == LAMBDA_EXPR)
+    {
+      /* [temp.over.link] Two lambda-expressions are never considered
+        equivalent.
+
+        So just use the closure type mangling.  */
+      write_string ("tl");
+      write_type (LAMBDA_EXPR_CLOSURE (expr));
+      write_char ('E');
+    }
   else if (dependent_name (expr))
     {
       write_unqualified_id (dependent_name (expr));
index 0428f6d..db0f033 100644 (file)
@@ -10175,12 +10175,15 @@ cp_parser_lambda_expression (cp_parser* parser)
 
   LAMBDA_EXPR_LOCATION (lambda_expr) = token->location;
 
-  if (cp_unevaluated_operand)
+  if (cxx_dialect >= cxx2a)
+    /* C++20 allows lambdas in unevaluated context.  */;
+  else if (cp_unevaluated_operand)
     {
       if (!token->error_reported)
        {
          error_at (LAMBDA_EXPR_LOCATION (lambda_expr),
-                   "lambda-expression in unevaluated context");
+                   "lambda-expression in unevaluated context"
+                   " only available with -std=c++2a or -std=gnu++2a");
          token->error_reported = true;
        }
       ok = false;
@@ -10189,7 +10192,8 @@ cp_parser_lambda_expression (cp_parser* parser)
     {
       if (!token->error_reported)
        {
-         error_at (token->location, "lambda-expression in template-argument");
+         error_at (token->location, "lambda-expression in template-argument"
+                   " only available with -std=c++2a or -std=gnu++2a");
          token->error_reported = true;
        }
       ok = false;
@@ -10200,6 +10204,8 @@ cp_parser_lambda_expression (cp_parser* parser)
   push_deferring_access_checks (dk_no_deferred);
 
   cp_parser_lambda_introducer (parser, lambda_expr);
+  if (cp_parser_error_occurred (parser))
+    return error_mark_node;
 
   type = begin_lambda_type (lambda_expr);
   if (type == error_mark_node)
@@ -10238,6 +10244,9 @@ cp_parser_lambda_expression (cp_parser* parser)
     /* By virtue of defining a local class, a lambda expression has access to
        the private variables of enclosing classes.  */
 
+    if (cp_parser_start_tentative_firewall (parser))
+      start = token;
+
     ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr);
 
     if (ok && cp_parser_error_occurred (parser))
@@ -10245,9 +10254,6 @@ cp_parser_lambda_expression (cp_parser* parser)
 
     if (ok)
       {
-       if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)
-           && cp_parser_start_tentative_firewall (parser))
-         start = token;
        cp_parser_lambda_body (parser, lambda_expr);
       }
     else if (cp_parser_require (parser, CPP_OPEN_BRACE, RT_OPEN_BRACE))
@@ -10736,6 +10742,10 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr)
   bool local_variables_forbidden_p = parser->local_variables_forbidden_p;
   bool in_function_body = parser->in_function_body;
 
+  /* The body of a lambda-expression is not a subexpression of the enclosing
+     expression.  */
+  cp_evaluated ev;
+
   if (nested)
     push_function_context ();
   else
index f948aef..b58ec06 100644 (file)
@@ -1814,10 +1814,11 @@ iterative_hash_template_arg (tree arg, hashval_t val)
       return iterative_hash_template_arg (TREE_OPERAND (arg, 2), val);
 
     case LAMBDA_EXPR:
-      /* A lambda can't appear in a template arg, but don't crash on
-        erroneous input.  */
-      gcc_assert (seen_error ());
-      return val;
+      /* [temp.over.link] Two lambda-expressions are never considered
+        equivalent.
+
+         So just hash the closure type.  */
+      return iterative_hash_template_arg (TREE_TYPE (arg), val);
 
     case CAST_EXPR:
     case IMPLICIT_CONV_EXPR:
@@ -12842,7 +12843,8 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
   if (TREE_CODE (DECL_TI_TEMPLATE (t)) == TEMPLATE_DECL)
     {
       /* If T is not dependent, just return it.  */
-      if (!uses_template_parms (DECL_TI_ARGS (t)))
+      if (!uses_template_parms (DECL_TI_ARGS (t))
+         && !LAMBDA_FUNCTION_P (t))
        return t;
 
       /* Calculate the most general template of which R is a
@@ -17957,6 +17959,10 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       /* Let finish_function set this.  */
       DECL_DECLARED_CONSTEXPR_P (fn) = false;
 
+      /* The body of a lambda-expression is not a subexpression of the
+        enclosing expression.  */
+      cp_evaluated ev;
+
       bool nested = cfun;
       if (nested)
        push_function_context ();
@@ -17992,6 +17998,11 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
          current_function_infinite_loop = ol->infinite_loop;
        }
 
+      /* [temp.deduct] A lambda-expression appearing in a function type or a
+        template parameter is not considered part of the immediate context for
+        the purposes of template argument deduction. */
+      complain = tf_warning_or_error;
+
       tsubst_expr (DECL_SAVED_TREE (oldfn), args, complain, r,
                   /*constexpr*/false);
 
@@ -19285,6 +19296,13 @@ tsubst_copy_and_build (tree t,
 
     case LAMBDA_EXPR:
       {
+       if (complain & tf_partial)
+         {
+           /* We don't have a full set of template arguments yet; don't touch
+              the lambda at all.  */
+           gcc_assert (processing_template_decl);
+           return t;
+         }
        tree r = tsubst_lambda_expr (t, args, complain, in_decl);
 
        RETURN (build_lambda_object (r));
index 182d360..20fd9c4 100644 (file)
@@ -2988,12 +2988,6 @@ begin_class_definition (tree t)
   if (error_operand_p (t) || error_operand_p (TYPE_MAIN_DECL (t)))
     return error_mark_node;
 
-  if (processing_template_parmlist)
-    {
-      error ("definition of %q#T inside template parameter list", t);
-      return error_mark_node;
-    }
-
   /* According to the C++ ABI, decimal classes defined in ISO/IEC TR 24733
      are passed the same as decimal scalar types.  */
   if (TREE_CODE (t) == RECORD_TYPE
index 5e21fce..02a9856 100644 (file)
@@ -1834,8 +1834,7 @@ strip_typedefs_expr (tree t, bool *remove_attributes)
       }
 
     case LAMBDA_EXPR:
-      error ("lambda-expression in a constant expression");
-      return error_mark_node;
+      return t;
 
     case STATEMENT_LIST:
       error ("statement-expression in a constant expression");
@@ -2777,7 +2776,18 @@ no_linkage_check (tree t, bool relaxed_p)
 {
   tree r;
 
-  /* There's no point in checking linkage on template functions; we
+  /* Lambda types that don't have mangling scope have no linkage.  We
+     check CLASSTYPE_LAMBDA_EXPR for error_mark_node because
+     when we get here from pushtag none of the lambda information is
+     set up yet, so we want to assume that the lambda has linkage and
+     fix it up later if not.  We need to check this even in templates so
+     that we properly handle a lambda-expression in the signature.  */
+  if (LAMBDA_TYPE_P (t)
+      && CLASSTYPE_LAMBDA_EXPR (t) != error_mark_node
+      && LAMBDA_TYPE_EXTRA_SCOPE (t) == NULL_TREE)
+    return t;
+
+  /* Otherwise there's no point in checking linkage on template functions; we
      can't know their complete types.  */
   if (processing_template_decl)
     return NULL_TREE;
@@ -2787,15 +2797,6 @@ no_linkage_check (tree t, bool relaxed_p)
     case RECORD_TYPE:
       if (TYPE_PTRMEMFUNC_P (t))
        goto ptrmem;
-      /* Lambda types that don't have mangling scope have no linkage.  We
-        check CLASSTYPE_LAMBDA_EXPR for error_mark_node because
-        when we get here from pushtag none of the lambda information is
-        set up yet, so we want to assume that the lambda has linkage and
-        fix it up later if not.  */
-      if (CLASSTYPE_LAMBDA_EXPR (t)
-         && CLASSTYPE_LAMBDA_EXPR (t) != error_mark_node
-         && LAMBDA_TYPE_EXTRA_SCOPE (t) == NULL_TREE)
-       return t;
       /* Fall through.  */
     case UNION_TYPE:
       if (!CLASS_TYPE_P (t))
@@ -3849,6 +3850,10 @@ cp_tree_equal (tree t1, tree t2)
                                  DECL_NAME (t2)));
       return false;
 
+    case LAMBDA_EXPR:
+      /* Two lambda-expressions are never considered equivalent.  */
+      return false;
+
     default:
       break;
     }
index 408af42..67b669f 100644 (file)
@@ -1,4 +1,4 @@
 // PR c++/51464
 // { dg-do compile { target c++11 } }
 
-template<int = sizeof([])> struct A {}; // { dg-error "lambda" } 
+template<int = sizeof([])> struct A {}; // { dg-error "" } 
index 40abcb9..5928894 100644 (file)
@@ -8,7 +8,7 @@ struct AddRvalueReferenceImpl { typedef T type; };
 
 template <typename T>
 struct AddRvalueReferenceImpl<T, typename BoolSink<false &&
-      [] {                     // { dg-error "lambda" }
+      [] {                     // { dg-error "lambda" "" { target c++17_down } }
          extern T &&tref;
       }>::type> {
    typedef T &&type;
@@ -19,7 +19,7 @@ struct AddRvalueReference : AddRvalueReferenceImpl<T, void> { };
 
 namespace ImplHelpers {
    template <typename T>
-   typename AddRvalueReference<T>::type create(void) { }
+   typename AddRvalueReference<T>::type create(void);
 }
 
 template <typename T, typename U, typename ...Args>
@@ -27,9 +27,8 @@ struct IsConstructibleImpl { enum { value = 0 }; };
 
 template <typename T, typename ...Args>
 struct IsConstructibleImpl<T, typename BoolSink<false &&
-      [] {                     // { dg-error "lambda" }
-         T t( ::ImplHelpers::create<Args>() ...);
-      }>::type, Args ...> {
+      [] { T t( ::ImplHelpers::create<Args>() ...); } // { dg-error "" }
+  >::type, Args ...> {
    enum { value = 1 };
 };
 
index dcea169..1b1f03e 100644 (file)
@@ -3,7 +3,7 @@
 
 template <class T>
 struct A { };
-A<decltype([]{ return 1; }())> a; // { dg-error "lambda.*unevaluated context" }
+A<decltype([]{ return 1; }())> a; // { dg-error "lambda.*unevaluated context" "" { target c++17_down } }
 
 // { dg-prune-output "template argument" }
 // { dg-prune-output "invalid type" }
index 14cb298..f887a7d 100644 (file)
@@ -3,5 +3,8 @@
 
 struct A
 {
-  decltype( [](){ return this; }() ) x; // { dg-error "unevaluated" }
+  decltype( [](){ return this; }() ) x; // { dg-error "unevaluated" "" { target c++17_down } }
+  // { dg-error "not captured" "" { target c++2a } .-1 }
 };
+
+// { dg-prune-output "declared void" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval1.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval1.C
new file mode 100644 (file)
index 0000000..8a17033
--- /dev/null
@@ -0,0 +1,16 @@
+// { dg-do compile { target c++2a } }
+
+typedef decltype([]{}) C; // the closure type has no name for linkage purposes
+
+// { dg-final { scan-assembler-not "globl\[ \t]*_Z1f" } }
+// { dg-final { scan-assembler-not "_Z1f1C" } }
+void f(C) {}
+
+int main()
+{
+  C c;
+  c();
+  f(c);
+}
+
+
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval2.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval2.C
new file mode 100644 (file)
index 0000000..f29a59e
--- /dev/null
@@ -0,0 +1,54 @@
+// { dg-do compile { target c++2a } }
+
+// ill-formed, no diagnostic required: the two expressions are
+// functionally equivalent but not equivalent
+template <int N> void foo(const char (&s)[([]{}, N)]);
+template <int N> void foo(const char (&s)[([]{}, N)]);
+
+// two different declarations because the non-dependent portions are not
+// considered equivalent
+template <class T> void spam(decltype([]{}) (*s)[sizeof(T)]);
+template <class T> void spam(decltype([]{}) (*s)[sizeof(T)]);
+
+template <class T>
+using A = decltype([] { });
+// A<int> and A<char> refer to different closure types
+
+template <class T>
+auto f(T) -> decltype([]() { T::invalid; } ()); // { dg-error "invalid" }
+void f(...);
+
+template <class T, unsigned = sizeof([]() { T::invalid; })> // { dg-error "invalid" }
+void g(T);
+void g(...);
+
+template <class T>
+auto h(T) -> decltype([x = T::invalid]() { });
+void h(...);
+
+template <class T>
+auto i(T) -> decltype([]() -> typename T::invalid { });
+void i(...);
+
+template <class T>
+auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t));
+void j(...);
+
+template <class,class> struct different {};
+template <class T> struct different<T,T> { typename T::invalid t; };
+
+template <class,class> struct same;
+template <class T> struct same<T,T> {};
+
+int main()
+{
+  foo<1>("");   // { dg-error "ambiguous" }
+  spam<char>(nullptr);         // { dg-error "ambiguous" }
+  different<A<int>,A<char>>();
+  same<A<int>,A<int>>();
+  f(0); // error: invalid expression not part of the immediate context
+  g(0); // error: invalid expression not part of the immediate context
+  h(0); // error: invalid expression not part of the immediate context
+  i(0); // error: invalid expression not part of the immediate context
+  j(0); // deduction fails on #1, calls #2
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval3.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval3.C
new file mode 100644 (file)
index 0000000..3c9b1e1
--- /dev/null
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++2a } }
+
+template <int N> void foo(const char (*s)[([]{}, N)]) {}
+template <class T> void spam(decltype([]{}) (*s)[sizeof(T)]) {}
+
+int main()
+{
+  foo<1>(nullptr);
+  spam<char>(nullptr);
+}
+
+// { dg-final { scan-assembler-not "weak.*_Z" } }
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval4.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval4.C
new file mode 100644 (file)
index 0000000..e75a127
--- /dev/null
@@ -0,0 +1,8 @@
+// { dg-do link { target c++2a } }
+
+template <class T> T f(T t) { return t; }
+using L = decltype([]{ return f(42); });
+int main()
+{
+  return L()();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval5.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval5.C
new file mode 100644 (file)
index 0000000..7fb05c3
--- /dev/null
@@ -0,0 +1,5 @@
+// { dg-do compile { target c++2a } }
+
+using L = decltype([]{ });
+void f(L) { }
+// { dg-final { scan-assembler-not "globl.*_Z1f" } }
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval6.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval6.C
new file mode 100644 (file)
index 0000000..0396f9f
--- /dev/null
@@ -0,0 +1,26 @@
+// { dg-do compile { target c++2a } }
+
+static decltype([] { }) f();
+static decltype([] { }) f(); // { dg-error "ambiguating" }
+
+static decltype([] { }) g();
+static decltype(g()) g(); // okay
+
+static void h(decltype([] { }) *) { }
+static void h(decltype([] { }) *) { }
+void x1() { h(nullptr); } // { dg-error "ambiguous" }
+
+using A = decltype([] { });
+static void i(A *);
+static void i(A *) { }
+void x2() { i(nullptr); } // okay
+
+template <typename T>
+using B = decltype([] { });
+static void j(B<char16_t> *) { }
+static void j(B<char32_t> *) { }
+void x3() { j(nullptr); } // { dg-error "ambiguous" }
+
+template <int N> static void k(decltype([]{ return 0; }()));
+template <int N> static void k(decltype([]{ return 0; }())); // okay
+template <int N> static void k(int); // okay
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval7.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval7.C
new file mode 100644 (file)
index 0000000..4102882
--- /dev/null
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++2a } }
+
+template <int N>
+struct A { };
+
+template <int N>
+void g(A<[]{return N;}()>) {}
+
+int main()
+{
+  g<1>({});
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval8.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval8.C
new file mode 100644 (file)
index 0000000..3692154
--- /dev/null
@@ -0,0 +1,13 @@
+// { dg-do compile { target c++2a } }
+
+template <auto N>
+struct A {
+  static constexpr auto n = N;
+};
+
+template <auto N>
+constexpr auto g(A<[]{return N;}> a) {
+  return a.n();
+}
+
+static_assert(g<42>({}) == 42);
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.C b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.C
new file mode 100644 (file)
index 0000000..e32d447
--- /dev/null
@@ -0,0 +1,12 @@
+// { dg-do run { target c++2a } }
+// { dg-additional-sources "lambda-uneval9.cc" }
+
+#include "lambda-uneval9.h"
+int foo() { return f(); }
+extern int bar();
+
+int main()
+{
+  if (foo() != 1) __builtin_abort();
+  if (bar() != 2) __builtin_abort();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.cc b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.cc
new file mode 100644 (file)
index 0000000..0ff3b12
--- /dev/null
@@ -0,0 +1,3 @@
+#include "lambda-uneval9.h"
+
+int bar() { return f(); }
diff --git a/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.h b/gcc/testsuite/g++.dg/cpp2a/lambda-uneval9.h
new file mode 100644 (file)
index 0000000..0979748
--- /dev/null
@@ -0,0 +1,9 @@
+// a.h:
+template <typename T>
+int counter() {
+  static int cnt = 0;
+  return ++cnt;
+}
+inline int f() {
+  return counter<decltype([] {})>();
+}