[llvm] [CommandLine] Make options of copyable class types get reset to their provided initial values (PR #173026)

Benjamin Stott via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 27 06:58:11 PDT 2026


https://github.com/BStott6 updated https://github.com/llvm/llvm-project/pull/173026

>From f52513ee343230e8c70128e8f468fba021e2dc45 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Fri, 19 Dec 2025 14:31:49 +0000
Subject: [PATCH 1/6] Introduce unit test for resetting option of class type to
 initial value

---
 llvm/unittests/Support/CommandLineTest.cpp | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 9e8a165fe136f..497978dd817e8 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2348,4 +2348,15 @@ TEST(CommandLineTest, HelpWithEmptyCategory) {
   cl::ResetCommandLineParser();
 }
 
+class CopyableClass {
+public:
+  int Val;
+};
+TEST(CommandLineTest, ResetClassTypeOptionToInitialValue) {
+  CopyableClass InitialValue{42};
+  StackOption<CopyableClass> O("a", cl::init(InitialValue));
+  O.reset();
+  EXPECT_EQ(O.getValue().Val, InitialValue.Val)
+      << "Option should be reset to its initial value.";
+}
 } // anonymous namespace

>From e03e366b4e78bdb1cc01bccc347cb5f86717c681 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Fri, 19 Dec 2025 15:24:05 +0000
Subject: [PATCH 2/6] Also test options of scalar types

---
 llvm/unittests/Support/CommandLineTest.cpp | 23 ++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 497978dd817e8..9f8b633630e7c 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2352,11 +2352,26 @@ class CopyableClass {
 public:
   int Val;
 };
-TEST(CommandLineTest, ResetClassTypeOptionToInitialValue) {
+TEST(CommandLineTest, ResetOptionsToInitialValue) {
+  // Option of scalar type.
+  StackOption<int> O1("opt1", cl::init(12));
+  O1.reset();
+  EXPECT_EQ(O1.getValue(), 12)
+      << "Option should be reset to its initial value.";
+
+  // Option of copyable class type.
   CopyableClass InitialValue{42};
-  StackOption<CopyableClass> O("a", cl::init(InitialValue));
-  O.reset();
-  EXPECT_EQ(O.getValue().Val, InitialValue.Val)
+  StackOption<CopyableClass> O2("opt2", cl::init(InitialValue));
+  O2.reset();
+  EXPECT_EQ(O2.getValue().Val, InitialValue.Val)
+      << "Option should be reset to its initial value.";
+
+  // Option of string type (most important case of copyable class).
+  StackOption<std::string> O3("opt3", cl::init("hello"));
+  O3.reset();
+  EXPECT_EQ(O3.getValue(), "hello")
       << "Option should be reset to its initial value.";
+
+  cl::ResetCommandLineParser();
 }
 } // anonymous namespace

>From 56101faf765402794e5a127574ce5c907b304eb0 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Fri, 19 Dec 2025 15:42:09 +0000
Subject: [PATCH 3/6] [CommandLine] Make options of copyable class types get
 reset to their provided initial values

---
 llvm/include/llvm/Support/CommandLine.h | 61 ++++++++-----------------
 llvm/lib/Support/CommandLine.cpp        |  2 -
 2 files changed, 20 insertions(+), 43 deletions(-)

diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index be754f3c159ca..80f7a47973f37 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -547,7 +547,7 @@ template <class DataType> struct OptionValue;
 
 // The default value safely does nothing. Option value printing is only
 // best-effort.
-template <class DataType, bool isClass>
+template <class DataType, bool isCopyable>
 struct OptionValueBase : GenericOptionValue {
   // Temporary storage for argument passing.
   using WrapperType = OptionValue<DataType>;
@@ -590,13 +590,24 @@ template <class DataType> class OptionValueCopy : public GenericOptionValue {
     return Value;
   }
 
-  void setValue(const DataType &V) {
+  template <class DT, std::enable_if_t<std::is_assignable_v<DataType &, DT>,
+                                       std::nullptr_t> = nullptr>
+  void setValue(const DT &V) {
     Valid = true;
     Value = V;
   }
 
   // Returns whether this instance matches V.
-  bool compare(const DataType &V) const { return Valid && (Value == V); }
+  bool compare(const DataType &V) const {
+    // FIXME: With C++23, use `std::equality_comparable` to see if `DataType`
+    // may be a class with `operator==` and, if so, use it instead of silently
+    // returning false.
+    if constexpr (std::is_class_v<DataType>) {
+      return false;
+    } else {
+      return Valid && (Value == V);
+    }
+  }
 
   bool compare(const GenericOptionValue &V) const override {
     const OptionValueCopy<DataType> &VC =
@@ -607,9 +618,9 @@ template <class DataType> class OptionValueCopy : public GenericOptionValue {
   }
 };
 
-// Non-class option values.
+// Copyable option values.
 template <class DataType>
-struct OptionValueBase<DataType, false> : OptionValueCopy<DataType> {
+struct OptionValueBase<DataType, true> : OptionValueCopy<DataType> {
   using WrapperType = DataType;
 
 protected:
@@ -622,7 +633,10 @@ struct OptionValueBase<DataType, false> : OptionValueCopy<DataType> {
 // Top-level option class.
 template <class DataType>
 struct OptionValue final
-    : OptionValueBase<DataType, std::is_class_v<DataType>> {
+    : OptionValueBase<DataType, std::conjunction_v<
+                                    std::is_copy_constructible<DataType>,
+                                    std::is_copy_assignable<DataType>,
+                                    std::is_default_constructible<DataType>>> {
   OptionValue() = default;
 
   OptionValue(const DataType &V) { this->setValue(V); }
@@ -634,42 +648,7 @@ struct OptionValue final
   }
 };
 
-// Other safe-to-copy-by-value common option types.
 enum boolOrDefault { BOU_UNSET, BOU_TRUE, BOU_FALSE };
-template <>
-struct LLVM_ABI OptionValue<cl::boolOrDefault> final
-    : OptionValueCopy<cl::boolOrDefault> {
-  using WrapperType = cl::boolOrDefault;
-
-  OptionValue() = default;
-
-  OptionValue(const cl::boolOrDefault &V) { this->setValue(V); }
-
-  OptionValue<cl::boolOrDefault> &operator=(const cl::boolOrDefault &V) {
-    setValue(V);
-    return *this;
-  }
-
-private:
-  void anchor() override;
-};
-
-template <>
-struct LLVM_ABI OptionValue<std::string> final : OptionValueCopy<std::string> {
-  using WrapperType = StringRef;
-
-  OptionValue() = default;
-
-  OptionValue(const std::string &V) { this->setValue(V); }
-
-  OptionValue<std::string> &operator=(const std::string &V) {
-    setValue(V);
-    return *this;
-  }
-
-private:
-  void anchor() override;
-};
 
 //===----------------------------------------------------------------------===//
 // Enum valued command line option
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index 98d80c62466c0..ed78557f73df2 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -86,8 +86,6 @@ template class LLVM_EXPORT_TEMPLATE opt<unsigned>;
 
 // Pin the vtables to this file.
 void GenericOptionValue::anchor() {}
-void OptionValue<boolOrDefault>::anchor() {}
-void OptionValue<std::string>::anchor() {}
 void Option::anchor() {}
 void basic_parser_impl::anchor() {}
 void parser<bool>::anchor() {}

>From dde9b1a8c58a056e3548cc6f546f300f7fa74134 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Mon, 22 Dec 2025 10:24:49 +0000
Subject: [PATCH 4/6] Apply suggestions, add test cases for comparing option
 values

---
 llvm/include/llvm/Support/CommandLine.h    | 13 +++---
 llvm/unittests/Support/CommandLineTest.cpp | 52 ++++++++++++++++++++++
 2 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index 80f7a47973f37..0b4979de4a57e 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -590,8 +590,8 @@ template <class DataType> class OptionValueCopy : public GenericOptionValue {
     return Value;
   }
 
-  template <class DT, std::enable_if_t<std::is_assignable_v<DataType &, DT>,
-                                       std::nullptr_t> = nullptr>
+  template <class DT,
+            class = std::enable_if_t<std::is_assignable_v<DataType &, DT>>>
   void setValue(const DT &V) {
     Valid = true;
     Value = V;
@@ -599,13 +599,10 @@ template <class DataType> class OptionValueCopy : public GenericOptionValue {
 
   // Returns whether this instance matches V.
   bool compare(const DataType &V) const {
-    // FIXME: With C++23, use `std::equality_comparable` to see if `DataType`
-    // may be a class with `operator==` and, if so, use it instead of silently
-    // returning false.
-    if constexpr (std::is_class_v<DataType>) {
-      return false;
-    } else {
+    if constexpr (has_equality_comparison_v<DataType>) {
       return Valid && (Value == V);
+    } else {
+      return false;
     }
   }
 
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 9f8b633630e7c..bc65ad6145f52 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2374,4 +2374,56 @@ TEST(CommandLineTest, ResetOptionsToInitialValue) {
 
   cl::ResetCommandLineParser();
 }
+
+class WithEqualityComparison {
+public:
+  bool operator==(const WithEqualityComparison &Other) const {
+    return Val == Other.Val;
+  }
+
+  int Val;
+};
+
+class NoEqualityComparison {
+public:
+  int Val;
+};
+
+TEST(CommandLineTest, CompareOptionValues) {
+  // Scalar option.
+  {
+    StackOption<int> O1("opt1", cl::init(42));
+    StackOption<int> O2("opt2", cl::init(42));
+    StackOption<int> O3("opt3", cl::init(3));
+    EXPECT_TRUE(O1.Default.compare(O2.Default));
+    EXPECT_FALSE(O1.Default.compare(O3.Default));
+    cl::ResetCommandLineParser();
+  }
+
+  // Class with equality comparison operator.
+  {
+    StackOption<WithEqualityComparison> O1(
+        "opt1", cl::init(WithEqualityComparison{42}));
+    StackOption<WithEqualityComparison> O2(
+        "opt2", cl::init(WithEqualityComparison{42}));
+    StackOption<WithEqualityComparison> O3("opt3",
+                                           cl::init(WithEqualityComparison{3}));
+    EXPECT_TRUE(O1.Default.compare(O2.Default));
+    EXPECT_FALSE(O1.Default.compare(O3.Default));
+    cl::ResetCommandLineParser();
+  }
+
+  // Class with no equality comparison operator.
+  {
+    StackOption<NoEqualityComparison> O1("opt1",
+                                         cl::init(NoEqualityComparison{42}));
+    StackOption<NoEqualityComparison> O2("opt2",
+                                         cl::init(NoEqualityComparison{42}));
+    StackOption<NoEqualityComparison> O3("opt3",
+                                         cl::init(NoEqualityComparison{3}));
+    EXPECT_FALSE(O1.Default.compare(O2.Default));
+    EXPECT_FALSE(O1.Default.compare(O3.Default));
+    cl::ResetCommandLineParser();
+  }
+}
 } // anonymous namespace

>From 96154d222cda242269f682a74890f1baffed6815 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Fri, 16 Jan 2026 15:10:29 +0000
Subject: [PATCH 5/6] Update test to assign the option a different value before
 resetting

---
 llvm/unittests/Support/CommandLineTest.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index bc65ad6145f52..c779bce1c7386 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2355,6 +2355,7 @@ class CopyableClass {
 TEST(CommandLineTest, ResetOptionsToInitialValue) {
   // Option of scalar type.
   StackOption<int> O1("opt1", cl::init(12));
+  O1.setValue(13);
   O1.reset();
   EXPECT_EQ(O1.getValue(), 12)
       << "Option should be reset to its initial value.";
@@ -2362,12 +2363,14 @@ TEST(CommandLineTest, ResetOptionsToInitialValue) {
   // Option of copyable class type.
   CopyableClass InitialValue{42};
   StackOption<CopyableClass> O2("opt2", cl::init(InitialValue));
+  O2.setValue(CopyableClass{404});
   O2.reset();
   EXPECT_EQ(O2.getValue().Val, InitialValue.Val)
       << "Option should be reset to its initial value.";
 
   // Option of string type (most important case of copyable class).
   StackOption<std::string> O3("opt3", cl::init("hello"));
+  O3.setValue("goodbye");
   O3.reset();
   EXPECT_EQ(O3.getValue(), "hello")
       << "Option should be reset to its initial value.";

>From e4ed79a3adc376edbe8e2130663b009413be3c65 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Fri, 16 Jan 2026 15:12:45 +0000
Subject: [PATCH 6/6] Update comment on OptionValueCopy::compare to clarify its
 behaviour

---
 llvm/include/llvm/Support/CommandLine.h | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index 0b4979de4a57e..16970aab8598c 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -597,7 +597,9 @@ template <class DataType> class OptionValueCopy : public GenericOptionValue {
     Value = V;
   }
 
-  // Returns whether this instance matches V.
+  // If the option has a value assigned and the data type is equality-comparable
+  // with itself, returns the result of comparing `V` with the stored value.
+  // Otherwise, returns false.
   bool compare(const DataType &V) const {
     if constexpr (has_equality_comparison_v<DataType>) {
       return Valid && (Value == V);



More information about the llvm-commits mailing list