[flang-commits] [flang] [llvm] [Flang] Introduce *Value classes with unittests (PR #216958)

Michael Kruse via flang-commits flang-commits at lists.llvm.org
Wed Sep 9 05:58:29 PDT 2026


================
@@ -0,0 +1,615 @@
+//===-- lib/Evaluate/character-value-impl.cpp -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "character-value-impl.h"
+#include "flang/Common/idioms.h"
+#include "flang/Evaluate/common.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <algorithm>
+#include <cstring>
+
+namespace Fortran::evaluate::value {
+
+CharacterValueImpl::CharacterValueImpl(int kind, std::size_t n, char32_t c) {
+  withCharProto(kind, [this, n, c](auto ct) {
+    using CharT = std::decay_t<decltype(ct)>;
+    storage_ = std::basic_string<CharT>(n, static_cast<CharT>(c));
+  });
+}
+
+CharacterValueImpl CharacterValueImpl::Zero(int kind) {
+  return withCharProto(kind, [kind](auto c) {
+    using Char = std::decay_t<decltype(c)>;
+    return CharacterValueImpl{kind, std::basic_string<Char>{}};
+  });
+}
+
+CharacterValueImpl CharacterValueImpl::FromRawBytes(
+    int kind, const void *raw, size_t size) {
+  return withCharProto(kind, [kind, raw, size](auto charProto) {
+    using CharT = decltype(charProto);
+    CHECK(size % sizeof(CharT) == 0);
+    std::basic_string<CharT> s;
+    if (size > 0) {
+      s.assign(static_cast<const CharT *>(raw), size / sizeof(CharT));
+    }
+    return CharacterValueImpl{kind, std::move(s)};
+  });
+}
+
+void CharacterValueImpl::print(llvm::raw_ostream &os) const {
+  os << kind() << '_';
+  withStdString(
+      [&](const auto &s) { os << parser::QuoteCharacterLiteral(s, true); });
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void CharacterValueImpl::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+std::size_t CharacterValueImpl::charSize() const {
+  return common::visit(
+      [](const auto &s) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          return sizeof(typename std::decay_t<decltype(s)>::value_type);
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::size() const {
+  return common::visit(
+      [](const auto &s) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          return 0;
+        } else {
+          return s.size();
+        }
+      },
+      storage_);
+}
+
+void *CharacterValueImpl::charData() {
+  return common::visit(
+      [](auto &s) -> void * {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // No data available in monostate
+          return nullptr;
+        } else {
+          return static_cast<void *>(s.data());
+        }
+      },
+      storage_);
+}
+
+const void *CharacterValueImpl::charData() const {
+  return common::visit(
+      [](const auto &s) -> const void * {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // No data available in monostate
+          return nullptr;
+        } else {
+          return static_cast<const void *>(s.data());
+        }
+      },
+      storage_);
+}
+
+Ordering CharacterValueImpl::Compare(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> Ordering {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorhpic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return Fortran::evaluate::Compare(xs, ys);
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return Fortran::evaluate::Compare(YS{}, ys);
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return Fortran::evaluate::Compare(xs, XS{});
+        } else {
+          DIE("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+bool CharacterValueImpl::operator<(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> bool {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorphic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return xs < ys;
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return YS{} < ys;
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return xs < XS{};
+        } else {
+          DIE("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+bool CharacterValueImpl::operator==(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> bool {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorhpic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return xs == ys;
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return YS{} == ys;
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return xs == XS{};
+        } else {
+          DIE("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+void CharacterValueImpl::assign(int kind, std::size_t n, char32_t c) {
+  return withCharProto(kind, [this, n, c](auto ct) {
+    using CharT = decltype(ct);
+    storage_ = std::basic_string<CharT>(n, static_cast<CharT>(c));
+  });
+}
+
+void CharacterValueImpl::erase(std::size_t pos) {
+  common::visit(
+      [pos](auto &s) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          s.erase(pos);
+        }
+      },
+      storage_);
+}
+
+void CharacterValueImpl::append(std::size_t n, char32_t c) {
+  common::visit(
+      [n, c](auto &s) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          using CharT = typename std::decay_t<decltype(s)>::value_type;
+          s.append(n, static_cast<CharT>(c));
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl &CharacterValueImpl::replace(
+    std::size_t pos, std::size_t len, const CharacterValueImpl &other) {
+  common::visit(
+      [pos, len](auto &s, const auto &o) {
+        if constexpr (!std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate> &&
+            !std::is_same_v<std::decay_t<decltype(o)>, std::monostate> &&
+            std::is_same_v<std::decay_t<decltype(s)>,
+                std::decay_t<decltype(o)>>) {
+          s.replace(pos, len, o);
+        } else {
+          DIE("operation not supported on uninitialized value or "
+              "values of different kinds");
+        }
+      },
+      storage_, other.storage_);
+  return *this;
+}
+
+CharacterValueImpl CharacterValueImpl::substr(std::size_t pos) const {
+  return common::visit(
+      [pos](const auto &s) -> CharacterValueImpl {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<StringT, std::monostate>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          return CharacterValueImpl{
+              sizeof(typename StringT::value_type), s.substr(pos)};
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::substr(
+    std::size_t pos, std::size_t len) const {
+  return common::visit(
+      [pos, len](const auto &s) -> CharacterValueImpl {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          return CharacterValueImpl{
+              sizeof(typename StringT::value_type), s.substr(pos, len)};
+        }
+      },
+      storage_);
+}
+
+std::optional<llvm::StringRef> CharacterValueImpl::AsStringRef() const {
+  if (IsMonostate()) {
+    return llvm::StringRef{};
+  }
+  if (const auto *s{std::get_if<std::string>(&storage_)}) {
+    return *s;
+  }
+  return std::nullopt;
+}
+
+/// Return the string as std::string if kind==1, or nullopt otherwise.
+std::optional<std::string> CharacterValueImpl::AsStdString() const {
+  if (IsMonostate()) {
+    return std::string{};
+  }
+
+  if (const auto *s{std::get_if<std::string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::optional<std::u16string> CharacterValueImpl::AsU16String() const {
+  if (IsMonostate()) {
+    return std::u16string{};
+  }
+
+  if (const auto *s{std::get_if<std::u16string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::optional<std::u32string> CharacterValueImpl::AsU32String() const {
+  if (IsMonostate()) {
+    return std::u32string{};
+  }
+
+  if (const auto *s{std::get_if<std::u32string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::string CharacterValueImpl::ToStdString() const {
+  return common::visit(
+      [](const auto &s) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<StringT, std::monostate>) {
+          return std::string{};
+        } else if constexpr (std::is_same_v<StringT, std::string>) {
+          return s;
+        } else {
+          std::string result(s.size(), '\0');
+          for (auto [i, c] : llvm::enumerate(s)) {
+            result[i] = c;
+          }
+          return result;
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::ToAscii(int kind) const {
+  if (IsMonostate()) {
+    return Zero(kind);
----------------
Meinersbur wrote:

Monostate represents zero with unknown kind. This is because Flang's scalar classes default-initialize to zero (0, 0.0, false, empty string), but I cannot pass the kind in the default-initializer which has no argument. 
In methods that receive a kind argument we can now insert the actual zero with known kind. This also includes some binary operations (e.g.  "some string" + Monostate = "some string") . This saves us some explicit initializations. Monostate behaving more like NaN (propagating whenever used) would also be possible. I chose because it closer resembles the behavior of the current scalar classes.

https://github.com/llvm/llvm-project/pull/216958


More information about the flang-commits mailing list