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

Michael Kruse via flang-commits flang-commits at lists.llvm.org
Mon Sep 21 04:21:36 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 {
+
+/// Using std::monostate_t as the first type of a std::variant is a common idom
+/// to make the default-initialized state explicit, e.g. because no other of the
+/// std::variant's types is default-initializable.
+template <typename T>
+constexpr bool is_monostate = std::is_same_v<std::decay_t<T>, std::monostate>;
+
+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 CharT = std::decay_t<decltype(c)>;
+    return CharacterValueImpl{kind, std::basic_string<CharT>{}};
+  });
+}
+
+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 {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          return sizeof(typename std::decay_t<StringT>::value_type);
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::size() const {
+  return common::visit(
+      [](const auto &s) -> std::size_t {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          return 0;
+        } else {
+          return s.size();
+        }
+      },
+      storage_);
+}
+
+void *CharacterValueImpl::charData() {
+  return common::visit(
+      [](auto &s) -> void * {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          // No data available in the null state
+          return nullptr;
+        } else {
+          return static_cast<void *>(s.data());
+        }
+      },
+      storage_);
+}
+
+const void *CharacterValueImpl::charData() const {
+  return common::visit(
+      [](const auto &s) -> const void * {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          // No data available in the null state
+          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)>;
+
+        // The null state 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 (is_monostate<XS> && !is_monostate<YS>) {
+          return Fortran::evaluate::Compare(YS{}, ys);
+        } else if constexpr (!is_monostate<XS> && is_monostate<YS>) {
+          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)>;
+
+        // The null state 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 (is_monostate<XS> && !is_monostate<YS>) {
+          return YS{} < ys;
+        } else if constexpr (!is_monostate<XS> && is_monostate<YS>) {
+          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)>;
+
+        // The null state 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 (is_monostate<XS> && !is_monostate<YS>) {
+          return YS{} == ys;
+        } else if constexpr (!is_monostate<XS> && is_monostate<YS>) {
+          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) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          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) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          using CharT = typename StringT::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) {
+        using XS = std::decay_t<decltype(s)>;
+        using XO = std::decay_t<decltype(o)>;
+
+        if constexpr (!is_monostate<XS> && !is_monostate<XO> &&
+            std::is_same_v<XS, XO>) {
+          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 (is_monostate<StringT>) {
+          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 (is_monostate<StringT>) {
+          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 (IsNull()) {
+    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 (IsNull()) {
+    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 (IsNull()) {
+    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 (IsNull()) {
+    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 (is_monostate<StringT>) {
+          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 (IsNull()) {
+    return Zero(kind);
+  }
+
+  return withStdString([kind](const auto &s) -> CharacterValueImpl {
+    return withCharProto(kind, [&s](auto ct) -> CharacterValueImpl {
+      using CharT = std::decay_t<decltype(ct)>;
+      using StringT = std::basic_string<CharT>;
+
+      // Fortran character conversion is well defined between distinct kinds
+      // only when the actual characters are valid 7-bit ASCII.
+      StringT str;
+      for (auto iter{s.cbegin()}; iter != s.cend(); ++iter) {
+        if (static_cast<std::uint64_t>(*iter) > 127) {
+          return Zero(sizeof(ct));
+        }
+        str.push_back(static_cast<CharT>(*iter));
+      }
+      return CharacterValueImpl{sizeof(CharT), str};
+    });
+  });
+}
+
+void CharacterValueImpl::reserve(std::size_t n) {
+  common::visit(
+      [n](auto &s) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (!is_monostate<StringT>) {
+          s.reserve(n);
+        }
+      },
+      storage_);
+}
+
+char32_t CharacterValueImpl::operator[](std::size_t i) const {
+  return common::visit(
+      [i](const auto &s) -> char32_t {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          // Use unsigned type to avoid platform-dependent widening
+          using CharT = typename StringT::value_type;
+          using UnsignedCharT = std::make_unsigned_t<CharT>;
+          return static_cast<UnsignedCharT>(s[i]);
+        }
+        return 0;
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::operator+(
+    const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &a, const auto &b) -> CharacterValueImpl {
+        using XA = std::decay_t<decltype(a)>;
+        using XB = std::decay_t<decltype(b)>;
+
+        if constexpr (std::is_same_v<XA, XB> && !is_monostate<XA>) {
+          return CharacterValueImpl{sizeof(typename XA::value_type), a + b};
+        } else {
+          DIE("operation not supported on uninitialized value or values of "
+              "different kinds");
+        }
+        return CharacterValueImpl{};
+      },
+      storage_, y.storage_);
+}
+
+CharacterValueImpl &CharacterValueImpl::operator+=(
+    const CharacterValueImpl &y) {
+  common::visit(
+      [](auto &a, const auto &b) {
+        using XA = std::decay_t<decltype(a)>;
+        using XB = std::decay_t<decltype(b)>;
+
+        if constexpr (std::is_same_v<XA, XB> && !is_monostate<XA>) {
+          a += b;
+        } else {
+          DIE("operation not supported on uninitialized value or "
+              "values of different kinds");
+        }
+      },
+      storage_, y.storage_);
+  return *this;
+}
+
+CharacterValueImpl &CharacterValueImpl::operator+=(char32_t c) {
+  common::visit(
+      [c](auto &s) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (is_monostate<StringT>) {
+          DIE("operation not supported on uninitialized value");
+        } else {
+          using CharT = typename StringT::value_type;
+          s.push_back(c);
+        }
+      },
+      storage_);
+  return *this;
+}
+
+std::size_t CharacterValueImpl::find_first_not_of(char32_t c) const {
+  return common::visit(
+      [c](const auto &s) -> std::size_t {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (!is_monostate<StringT>) {
+          using CharT = typename StringT::value_type;
+          return s.find_first_not_of(static_cast<CharT>(c));
+        } else {
+          DIE("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::find_last_not_of(char32_t c) const {
+  return common::visit(
+      [c](const auto &s) -> std::size_t {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (!is_monostate<StringT>) {
+          using CharT = typename StringT::value_type;
+          return s.find_last_not_of(static_cast<CharT>(c));
+        } else {
+          DIE("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::find_first_not_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        using XS = std::decay_t<decltype(s)>;
+        using XP = std::decay_t<decltype(p)>;
+
+        if constexpr (is_monostate<XS>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<XS, XP>) {
+          return s.find_first_not_of(p);
+        } else {
+          DIE("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+std::size_t CharacterValueImpl::find_last_not_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        using XS = std::decay_t<decltype(s)>;
+        using XP = std::decay_t<decltype(p)>;
+
+        if constexpr (is_monostate<XS>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<XS, XP>) {
+          return s.find_last_not_of(p);
+        } else {
+          DIE("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+std::size_t CharacterValueImpl::find(const CharacterValueImpl &pattern) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        using XS = std::decay_t<decltype(s)>;
+        using XP = std::decay_t<decltype(p)>;
+
+        if constexpr (is_monostate<XP>) {
+          // Empty string always matches beginning
+          return 0;
+        } else if constexpr (is_monostate<XS>) {
+          // Nothing to find in an empty string, unless the pattern is itself an
+          // empty string
+          return p.empty() ? 0 : std::string::npos;
+        } else if constexpr (std::is_same_v<XS, XP>) {
+          return s.find(p);
+        } else {
+          DIE("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, pattern.storage_);
+}
+
+std::size_t CharacterValueImpl::rfind(const CharacterValueImpl &pattern) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        using XS = std::decay_t<decltype(s)>;
+        using XP = std::decay_t<decltype(p)>;
+
+        if constexpr (is_monostate<XS>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<XS, XP>) {
+          return s.rfind(p);
+        }
+        DIE("Unsupported combination of character kinds");
----------------
Meinersbur wrote:

I implemented and added unittests for the use of null in all variants of `find` with a new helper `GetBasicString()`. The helper is the same as `GetWord` used by integer and real as by the previous comment. Since the type is known when called, the kind mismatch and null -> "" conversion is conveniently handled by this function. It also avoids the two-argument `visit` pattern.

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


More information about the flang-commits mailing list