Ignore FullExpr when traversing cast sub-expressions
authorAaron Ballman <aaron@aaronballman.com>
Mon, 21 Mar 2022 19:05:53 +0000 (15:05 -0400)
committerAaron Ballman <aaron@aaronballman.com>
Mon, 21 Mar 2022 19:05:53 +0000 (15:05 -0400)
Full-expressions are Sema-generated implicit nodes that cover
constant-expressions and expressions-with-cleanup for temporaries.

Ignore those as part of implicit-ignore, and also remove too-aggressive
IgnoreImplicit (which includes nested ImplicitCastExprs, for example)
on unpacked sub-expressions.

Add some unittests to demonstrate that RecursiveASTVisitor sees through
ConstantExpr nodes correctly.

Adjust cxx2a-consteval test to cover diagnostics for nested consteval
expressions that were previously missed.

Fixes bug #53044.

clang/lib/AST/Expr.cpp
clang/test/SemaCXX/cxx2a-consteval.cpp
clang/unittests/Tooling/CastExprTest.cpp

index e4c441a..49765b8 100644 (file)
@@ -1871,6 +1871,9 @@ Expr *ignoreImplicitSemaNodes(Expr *E) {
   if (auto *Binder = dyn_cast<CXXBindTemporaryExpr>(E))
     return Binder->getSubExpr();
 
+  if (auto *Full = dyn_cast<FullExpr>(E))
+    return Full->getSubExpr();
+
   return E;
 }
 } // namespace
@@ -1884,11 +1887,9 @@ Expr *CastExpr::getSubExprAsWritten() {
     // Conversions by constructor and conversion functions have a
     // subexpression describing the call; strip it off.
     if (E->getCastKind() == CK_ConstructorConversion) {
-      SubExpr = IgnoreExprNodes(
-          cast<CXXConstructExpr>(SubExpr->IgnoreImplicit())->getArg(0),
-          ignoreImplicitSemaNodes);
+      SubExpr = IgnoreExprNodes(cast<CXXConstructExpr>(SubExpr)->getArg(0),
+                                ignoreImplicitSemaNodes);
     } else if (E->getCastKind() == CK_UserDefinedConversion) {
-      SubExpr = SubExpr->IgnoreImplicit();
       assert((isa<CXXMemberCallExpr>(SubExpr) || isa<BlockExpr>(SubExpr)) &&
              "Unexpected SubExpr for CK_UserDefinedConversion.");
       if (auto *MCE = dyn_cast<CXXMemberCallExpr>(SubExpr))
index 4d59261..275e5df 100644 (file)
@@ -359,22 +359,34 @@ void test() {
   // expected-note@-1 {{is not a constant expression}}
   { A k = to_lvalue_ref(A()); } // expected-error {{is not a constant expression}}
   // expected-note@-1 {{is not a constant expression}} expected-note@-1 {{temporary created here}}
-  { A k = to_lvalue_ref(A().ret_a()); } // expected-error {{is not a constant expression}}
-  // expected-note@-1 {{is not a constant expression}} expected-note@-1 {{temporary created here}}
+  { A k = to_lvalue_ref(A().ret_a()); }
+  // expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
+  // expected-note@-2 {{heap-allocated object is not a constant expression}}
+  // expected-error@-3 {{'alloc::to_lvalue_ref' is not a constant expression}}
+  // expected-note@-4 {{reference to temporary is not a constant expression}}
+  // expected-note@-5 {{temporary created here}}
   { int k = A().ret_a().ret_i(); }
+  // expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
+  // expected-note@-2 {{heap-allocated object is not a constant expression}}
   { int k = by_value_a(A()); }
   { int k = const_a_ref(A()); }
   { int k = const_a_ref(a); }
   { int k = rvalue_ref(A()); }
   { int k = rvalue_ref(std::move(a)); }
   { int k = const_a_ref(A().ret_a()); }
+  // expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
+  // expected-note@-2 {{is not a constant expression}}
   { int k = const_a_ref(to_lvalue_ref(A().ret_a())); }
+  // expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
+  // expected-note@-2 {{is not a constant expression}}
   { int k = const_a_ref(to_lvalue_ref(std::move(a))); }
   { int k = by_value_a(A().ret_a()); }
   { int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
   { int k = (A().ret_a(), A().ret_i()); }// expected-error {{is not a constant expression}}
   // expected-note@-1 {{is not a constant expression}}
   { int k = (const_a_ref(A().ret_a()), A().ret_i()); }
+  // expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
+  // expected-note@-2 {{is not a constant expression}}
 }
 
 }
index a07eff3..eab23a5 100644 (file)
@@ -14,12 +14,19 @@ namespace {
 
 struct CastExprVisitor : TestVisitor<CastExprVisitor> {
   std::function<void(ExplicitCastExpr *)> OnExplicitCast;
+  std::function<void(CastExpr *)> OnCast;
 
   bool VisitExplicitCastExpr(ExplicitCastExpr *Expr) {
     if (OnExplicitCast)
       OnExplicitCast(Expr);
     return true;
   }
+
+  bool VisitCastExpr(CastExpr *Expr) {
+    if (OnCast)
+      OnCast(Expr);
+    return true;
+  }
 };
 
 TEST(CastExprTest, GetSubExprAsWrittenThroughMaterializedTemporary) {
@@ -54,4 +61,57 @@ TEST(CastExprTest, GetSubExprAsWrittenThroughConstantExpr) {
                   CastExprVisitor::Lang_CXX2a);
 }
 
+// Verify that getConversionFunction looks through a ConstantExpr for implicit
+// constructor conversions (https://github.com/llvm/llvm-project/issues/53044):
+//
+// `-ImplicitCastExpr 'X' <ConstructorConversion>
+//   `-ConstantExpr 'X'
+//     |-value: Struct
+//     `-CXXConstructExpr 'X' 'void (const char *)'
+//       `-ImplicitCastExpr 'const char *' <ArrayToPointerDecay>
+//         `-StringLiteral 'const char [7]' lvalue "foobar"
+TEST(CastExprTest, GetCtorConversionFunctionThroughConstantExpr) {
+  CastExprVisitor Visitor;
+  Visitor.OnCast = [](CastExpr *Expr) {
+    if (Expr->getCastKind() == CK_ConstructorConversion) {
+      auto *Conv = Expr->getConversionFunction();
+      EXPECT_TRUE(isa<CXXConstructorDecl>(Conv))
+          << "Expected CXXConstructorDecl, but saw " << Conv->getDeclKindName();
+    }
+  };
+  Visitor.runOver("struct X { consteval X(const char *) {} };\n"
+                  "void f() { X x = \"foobar\"; }\n",
+                  CastExprVisitor::Lang_CXX2a);
+}
+
+// Verify that getConversionFunction looks through a ConstantExpr for implicit
+// user-defined conversions.
+//
+// `-ImplicitCastExpr 'const char *' <UserDefinedConversion>
+//   `-ConstantExpr 'const char *'
+//     |-value: LValue
+//     `-CXXMemberCallExpr 'const char *'
+//       `-MemberExpr '<bound member function type>' .operator const char *
+//         `-DeclRefExpr 'const X' lvalue Var 'x' 'const X'
+TEST(CastExprTest, GetUserDefinedConversionFunctionThroughConstantExpr) {
+  CastExprVisitor Visitor;
+  Visitor.OnCast = [](CastExpr *Expr) {
+    if (Expr->getCastKind() == CK_UserDefinedConversion) {
+      auto *Conv = Expr->getConversionFunction();
+      EXPECT_TRUE(isa<CXXMethodDecl>(Conv))
+          << "Expected CXXMethodDecl, but saw " << Conv->getDeclKindName();
+    }
+  };
+  Visitor.runOver("struct X {\n"
+                  "  consteval operator const char *() const {\n"
+                  "    return nullptr;\n"
+                  "  }\n"
+                  "};\n"
+                  "const char *f() {\n"
+                  "  constexpr X x;\n"
+                  "  return x;\n"
+                  "}\n",
+                  CastExprVisitor::Lang_CXX2a);
+}
+
 } // namespace