[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