[clang] [clang-repl] Evaluate a void expression that has no trailing semicolon (PR #228334)

Jonas Rembser via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 1 22:50:50 PDT 2026


https://github.com/guitargeek created https://github.com/llvm/llvm-project/pull/228334

An expression statement without a semicolon at the end of an input is replaced by a call to `__clang_Interpreter_SetValueNoAlloc` that captures its value. For an expression of type `void`, the expression was not an argument of that call and was dropped, so `f()` did not call `f`.

Build `(E, __clang_Interpreter_SetValueNoAlloc(...))` instead.

Fixes #219800.

Assisted-by: Claude Opus 5.5

>From 4346f221cd3c60b27e5fa9ebac1a0b77465c90c7 Mon Sep 17 00:00:00 2001
From: Jonas Rembser <jonas.rembser at cern.ch>
Date: Wed, 30 Sep 2026 16:53:50 +0000
Subject: [PATCH] [clang-repl] Evaluate a void expression that has no trailing
 semicolon

An expression statement without a semicolon at the end of an input is
replaced by a call to `__clang_Interpreter_SetValueNoAlloc` that captures
its value. For an expression of type `void`, the expression was not an
argument of that call and was dropped, so `f()` did not call `f`.

Build `(E, __clang_Interpreter_SetValueNoAlloc(...))` instead.

Fixes #219800.

Assisted-by: Claude Opus 5.5
---
 clang/lib/Interpreter/InterpreterValuePrinter.cpp | 10 +++++++---
 clang/test/Interpreter/pretty-print.cpp           | 11 +++++++++++
 clang/unittests/Interpreter/InterpreterTest.cpp   | 15 +++++++++++++++
 3 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Interpreter/InterpreterValuePrinter.cpp b/clang/lib/Interpreter/InterpreterValuePrinter.cpp
index 79f1e2b6571c6..6842656e87f44 100644
--- a/clang/lib/Interpreter/InterpreterValuePrinter.cpp
+++ b/clang/lib/Interpreter/InterpreterValuePrinter.cpp
@@ -424,9 +424,8 @@ class InterfaceKindVisitor
       Args.push_back(E);
     else if (Ty->isIntegralOrEnumerationType())
       HandleIntegralOrEnumType(Ty);
-    else if (Ty->isVoidType()) {
-      // Do we need to still run `E`?
-    }
+    // A void expression is not an argument of the call; convertExprToValue
+    // evaluates it before the call instead.
 
     return InterfaceKind::NoAlloc;
   }
@@ -618,6 +617,11 @@ llvm::Expected<Expr *> Interpreter::convertExprToValue(Expr *E) {
     SetValueE =
         S.ActOnCallExpr(Scope, ValuePrintingInfo[InterfaceKind::NoAlloc],
                         E->getBeginLoc(), AdjustedArgs, E->getEndLoc());
+    // A void expression must still be evaluated:
+    // `(E, __clang_Interpreter_SetValueNoAlloc(...))`.
+    if (DesugaredTy->isVoidType() && !SetValueE.isInvalid())
+      SetValueE =
+          S.CreateBuiltinBinOp(E->getEndLoc(), BO_Comma, E, SetValueE.get());
     break;
   }
   default:
diff --git a/clang/test/Interpreter/pretty-print.cpp b/clang/test/Interpreter/pretty-print.cpp
index 204939eb3b523..ed430d2db6d22 100644
--- a/clang/test/Interpreter/pretty-print.cpp
+++ b/clang/test/Interpreter/pretty-print.cpp
@@ -89,6 +89,17 @@ const decltype(N::D()) decl3; decl3
 __remove_extent(N::D)* decl4; decl4
 // CHECK-NEXT: (N::D *)
 
+// A call to a void function without a semicolon must still be executed.
+// https://github.com/llvm/llvm-project/issues/219800
+int void_x = 10;
+void set_void_x() { void_x = 100; }
+set_void_x()
+void_x
+// CHECK-NEXT: (int) 100
+void print_foo() { printf("foo()\n"); }
+print_foo()
+// CHECK-NEXT: foo()
+
 // int i = 12;
 // int &iref = i;
 // iref
diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp
index 3becd00c12820..a0ed7e671ddbe 100644
--- a/clang/unittests/Interpreter/InterpreterTest.cpp
+++ b/clang/unittests/Interpreter/InterpreterTest.cpp
@@ -469,6 +469,21 @@ TEST_F(InterpreterTest, Value) {
   EXPECT_STREQ(prettyPrint.c_str(), "(D) (One) : unsigned int 1\n");
 }
 
+TEST_F(InterpreterTest, ValueOfVoidCallExecutesTheCall) {
+  std::unique_ptr<Interpreter> Interp = createInterpreter();
+
+  llvm::cantFail(
+      Interp->ParseAndExecute("int calls = 0; void bump() { ++calls; }"));
+  Value V;
+  llvm::cantFail(Interp->ParseAndExecute("bump()", &V));
+  EXPECT_TRUE(V.isValid());
+  EXPECT_EQ(V.getKind(), Value::K_Void);
+
+  Value Calls;
+  llvm::cantFail(Interp->ParseAndExecute("calls", &Calls));
+  EXPECT_EQ(Calls.getInt(), 1);
+}
+
 // Regression: Value::setRawBits's NBytes parameter must be interpreted as a
 // byte count end-to-end. Before this was fixed, the parameter was named
 // NBits and the memcpy divided by 8, so a caller passing sizeof(T) (the



More information about the cfe-commits mailing list