[flang-commits] [flang] adb2433 - [flang][Parser] Make CharBlock inherit from llvm::StringRef (#228482)

via flang-commits flang-commits at lists.llvm.org
Tue Oct 6 06:49:27 PDT 2026


Author: Krzysztof Parzyszek
Date: 2026-10-06T13:49:20Z
New Revision: adb24333dee0d4d1161276dca39bed762846fa59

URL: https://github.com/llvm/llvm-project/commit/adb24333dee0d4d1161276dca39bed762846fa59
DIFF: https://github.com/llvm/llvm-project/commit/adb24333dee0d4d1161276dca39bed762846fa59.diff

LOG: [flang][Parser] Make CharBlock inherit from llvm::StringRef (#228482)

These two classes implement very similar functionality, with StringRef
having a much richer interface.

There are some minor functional differences:
1. CharBlock(nullptr) constructor is deleted, the default constructor
should be used instead.
2. operator[]/front()/back() now return char by value, not const char&.
Use *x.begin()[i] instead of x[i] when a reference is required.

Added: 
    flang/test/Semantics/modfile88.f90

Modified: 
    flang/include/flang/Parser/char-block.h
    flang/include/flang/Semantics/tools.h
    flang/lib/Parser/token-sequence.cpp
    flang/lib/Semantics/check-directive-structure.h
    flang/lib/Semantics/resolve-labels.cpp

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Parser/char-block.h b/flang/include/flang/Parser/char-block.h
index 2ab28d08c184b..0b7ec380ef0de 100644
--- a/flang/include/flang/Parser/char-block.h
+++ b/flang/include/flang/Parser/char-block.h
@@ -11,7 +11,8 @@
 
 // Describes a contiguous block of characters; does not own their storage.
 
-#include "flang/Common/interval.h"
+#include "llvm/ADT/StringRef.h"
+
 #include <algorithm>
 #include <cstddef>
 #include <cstring>
@@ -23,47 +24,37 @@ class raw_ostream;
 
 namespace Fortran::parser {
 
-class CharBlock {
+class CharBlock : public llvm::StringRef {
 public:
-  constexpr CharBlock() {}
-  constexpr CharBlock(const char *x, std::size_t n = 1) : interval_{x, n} {}
-  constexpr CharBlock(const char *b, const char *ep1)
-      : interval_{b, static_cast<std::size_t>(ep1 - b)} {}
-  CharBlock(const std::string &s) : interval_{s.data(), s.size()} {}
-  constexpr CharBlock(const CharBlock &) = default;
-  constexpr CharBlock(CharBlock &&) = default;
-  constexpr CharBlock &operator=(const CharBlock &) = default;
-  constexpr CharBlock &operator=(CharBlock &&) = default;
-
-  constexpr bool empty() const { return interval_.empty(); }
-  constexpr std::size_t size() const { return interval_.size(); }
-  constexpr const char *begin() const { return interval_.start(); }
-  constexpr const char *end() const {
-    return interval_.start() + interval_.size();
-  }
-  constexpr const char &operator[](std::size_t j) const {
-    return interval_.start()[j];
-  }
-  constexpr const char &front() const { return (*this)[0]; }
-  constexpr const char &back() const { return (*this)[size() - 1]; }
+  using llvm::StringRef::StringRef;
+  CharBlock(const char *begin, const char *end)
+      : llvm::StringRef(begin, end - begin) {}
+  CharBlock(const char *begin) : llvm::StringRef(begin, 1) {}
 
+  // Checks whether the address range of "that" is contained in the
+  // address range of "this".
   bool Contains(const CharBlock &that) const {
-    return interval_.Contains(that.interval_);
+    uintptr_t thisBegin{reinterpret_cast<uintptr_t>(begin())};
+    uintptr_t thisEnd{reinterpret_cast<uintptr_t>(end())};
+    uintptr_t thatBegin{reinterpret_cast<uintptr_t>(that.begin())};
+    uintptr_t thatEnd{reinterpret_cast<uintptr_t>(that.end())};
+    return thisBegin <= thatBegin && thatEnd <= thisEnd;
   }
 
   void ExtendToCover(const CharBlock &that) {
-    interval_.ExtendToCover(that.interval_);
+    if (empty()) {
+      *this = that;
+    } else if (!that.empty()) {
+      *this = CharBlock(
+          std::min(begin(), that.begin()), std::max(end(), that.end()));
+    }
   }
 
   // Returns the block's first non-blank character, if it has
   // one; otherwise ' '.
   char FirstNonBlank() const {
-    for (char ch : *this) {
-      if (ch != ' ' && ch != '\t') {
-        return ch;
-      }
-    }
-    return ' '; // non no-blank character
+    size_t idx{LocateFirstNonBlank()};
+    return idx != npos ? data()[idx] : ' ';
   }
 
   // Returns the block's only non-blank character, if it has
@@ -82,107 +73,26 @@ class CharBlock {
     return result;
   }
 
-  std::size_t CountLeadingBlanks() const {
-    std::size_t n{size()};
-    std::size_t j{0};
-    for (; j < n; ++j) {
-      char ch{(*this)[j]};
-      if (ch != ' ' && ch != '\t') {
-        break;
-      }
-    }
-    return j;
+  size_t CountLeadingBlanks() const {
+    size_t idx{LocateFirstNonBlank()};
+    return idx != npos ? idx : size();
   }
 
-  bool IsBlank() const { return FirstNonBlank() == ' '; }
+  bool IsBlank() const { return LocateFirstNonBlank() == npos; }
 
-  std::string ToString() const {
-    return std::string{interval_.start(), interval_.size()};
-  }
+  std::string ToString() const { return str(); }
 
   // Convert to string, stopping early at any embedded '\0'.
   std::string NULTerminatedToString() const {
-    return std::string{interval_.start(),
-        /*not in std::*/ strnlen(interval_.start(), interval_.size())};
+    return std::string{begin(), strnlen(begin(), size())};
   }
 
-  bool operator<(const CharBlock &that) const { return Compare(that) < 0; }
-  bool operator<=(const CharBlock &that) const { return Compare(that) <= 0; }
-  bool operator==(const CharBlock &that) const { return Compare(that) == 0; }
-  bool operator!=(const CharBlock &that) const { return Compare(that) != 0; }
-  bool operator>=(const CharBlock &that) const { return Compare(that) >= 0; }
-  bool operator>(const CharBlock &that) const { return Compare(that) > 0; }
-
-  bool operator<(const char *that) const { return Compare(that) < 0; }
-  bool operator<=(const char *that) const { return Compare(that) <= 0; }
-  bool operator==(const char *that) const { return Compare(that) == 0; }
-  bool operator!=(const char *that) const { return Compare(that) != 0; }
-  bool operator>=(const char *that) const { return Compare(that) >= 0; }
-  bool operator>(const char *that) const { return Compare(that) > 0; }
-
-  friend bool operator<(const char *, const CharBlock &);
-  friend bool operator<=(const char *, const CharBlock &);
-  friend bool operator==(const char *, const CharBlock &);
-  friend bool operator!=(const char *, const CharBlock &);
-  friend bool operator>=(const char *, const CharBlock &);
-  friend bool operator>(const char *, const CharBlock &);
-
 private:
-  int Compare(const CharBlock &that) const {
-    // "memcmp" in glibc has "nonnull" attributes on the input pointers.
-    // Avoid passing null pointers, since it would result in an undefined
-    // behavior.
-    if (size() == 0) {
-      return that.size() == 0 ? 0 : -1;
-    } else if (that.size() == 0) {
-      return 1;
-    } else {
-      std::size_t bytes{std::min(size(), that.size())};
-      int cmp{std::memcmp(static_cast<const void *>(begin()),
-          static_cast<const void *>(that.begin()), bytes)};
-      if (cmp != 0) {
-        return cmp;
-      } else {
-        return size() < that.size() ? -1 : size() > that.size();
-      }
-    }
+  size_t LocateFirstNonBlank() const {
+    return find_if_not([](char c) { return c == ' ' || c == '\t'; });
   }
-
-  int Compare(const char *that) const {
-    std::size_t bytes{size()};
-    // strncmp is undefined if either pointer is null.
-    if (!bytes) {
-      return that == nullptr ? 0 : -1;
-    } else if (!that) {
-      return 1;
-    } else if (int cmp{std::strncmp(begin(), that, bytes)}) {
-      return cmp;
-    }
-    return that[bytes] == '\0' ? 0 : -1;
-  }
-
-  common::Interval<const char *> interval_{nullptr, 0};
 };
 
-inline bool operator<(const char *left, const CharBlock &right) {
-  return right > left;
-}
-inline bool operator<=(const char *left, const CharBlock &right) {
-  return right >= left;
-}
-inline bool operator==(const char *left, const CharBlock &right) {
-  return right == left;
-}
-inline bool operator!=(const char *left, const CharBlock &right) {
-  return right != left;
-}
-inline bool operator>=(const char *left, const CharBlock &right) {
-  return right <= left;
-}
-inline bool operator>(const char *left, const CharBlock &right) {
-  return right < left;
-}
-
 // An alternative comparator based on pointer values; use with care!
 struct CharBlockPointerComparator {
   bool operator()(CharBlock x, CharBlock y) const {

diff  --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h
index 1a3c6805cbee9..d91a7d11a38c9 100644
--- a/flang/include/flang/Semantics/tools.h
+++ b/flang/include/flang/Semantics/tools.h
@@ -724,8 +724,8 @@ class LabelEnforce {
 private:
   SemanticsContext &context_;
   std::set<parser::Label> labels_;
-  parser::CharBlock currentStatementSourcePosition_{nullptr};
-  parser::CharBlock constructSourcePosition_{nullptr};
+  parser::CharBlock currentStatementSourcePosition_;
+  parser::CharBlock constructSourcePosition_;
   const char *construct_{nullptr};
 
   parser::MessageFormattedText GetEnclosingConstructMsg();

diff  --git a/flang/lib/Parser/token-sequence.cpp b/flang/lib/Parser/token-sequence.cpp
index 9deb513e4f64f..eefe5bd4e16e2 100644
--- a/flang/lib/Parser/token-sequence.cpp
+++ b/flang/lib/Parser/token-sequence.cpp
@@ -151,7 +151,7 @@ void TokenSequence::Put(
 void TokenSequence::Put(const CharBlock &t, Provenance provenance) {
   // Avoid t[0] if t is empty: it would create a reference to nullptr,
   // which is UB.
-  const char *addr{t.size() ? &t[0] : nullptr};
+  const char *addr{t.size() ? t.begin() : nullptr};
   Put(addr, t.size(), provenance);
 }
 
@@ -287,8 +287,9 @@ TokenSequence &TokenSequence::ClipComment(
       }
       bool isSentinel{false};
       if (tok.size() > blanks + 5) {
-        isSentinel = prescanner.IsCompilerDirectiveSentinel(&tok[blanks + 1])
-                         .has_value();
+        isSentinel =
+            prescanner.IsCompilerDirectiveSentinel(&tok.begin()[blanks + 1])
+                .has_value();
       }
       if (isSentinel) {
       } else if (skipFirst) {

diff  --git a/flang/lib/Semantics/check-directive-structure.h b/flang/lib/Semantics/check-directive-structure.h
index f750bb283b2c4..b23168e927a25 100644
--- a/flang/lib/Semantics/check-directive-structure.h
+++ b/flang/lib/Semantics/check-directive-structure.h
@@ -222,8 +222,8 @@ class DirectiveStructureChecker : public virtual BaseChecker {
     DirectiveContext(parser::CharBlock source, D d)
         : directiveSource{source}, directive{d} {}
 
-    parser::CharBlock directiveSource{nullptr};
-    parser::CharBlock clauseSource{nullptr};
+    parser::CharBlock directiveSource;
+    parser::CharBlock clauseSource;
     D directive;
     ClauseSetTy allowedClauses{};
     ClauseSetTy allowedOnceClauses{};

diff  --git a/flang/lib/Semantics/resolve-labels.cpp b/flang/lib/Semantics/resolve-labels.cpp
index 288d1cbffa9d9..52b35a9030246 100644
--- a/flang/lib/Semantics/resolve-labels.cpp
+++ b/flang/lib/Semantics/resolve-labels.cpp
@@ -955,7 +955,7 @@ static LabeledStatementInfoTuplePOD GetLabel(
     const TargetStmtMap &labels, const parser::Label &label) {
   const auto iter{labels.find(label)};
   if (iter == labels.cend()) {
-    return {0u, nullptr, LabeledStmtClassificationSet{}, false};
+    return {0u, parser::CharBlock{}, LabeledStmtClassificationSet{}, false};
   } else {
     return iter->second;
   }

diff  --git a/flang/test/Semantics/modfile88.f90 b/flang/test/Semantics/modfile88.f90
new file mode 100644
index 0000000000000..5345d7d88ede5
--- /dev/null
+++ b/flang/test/Semantics/modfile88.f90
@@ -0,0 +1,73 @@
+! RUN: %python %S/test_modfile.py %s %flang_fc1
+! The order of COMMON blocks in a module file follows source order, with
+! blank COMMON last. More than 16 blocks are needed: below that, std::sort
+! uses insertion sort, which can hide an invalid source-position comparator.
+module m
+  common /q/ q1
+  common /c/ c1
+  common /m/ m1
+  common /t/ t1
+  common /a/ a1
+  common // blank1
+  common /x/ x1
+  common /e/ e1
+  common /k/ k1
+  common /r/ r1
+  common /b/ b1
+  common /z/ z1
+  common /g/ g1
+  common /n/ n1
+  common /h/ h1
+  common /w/ w1
+  common /d/ d1
+  common /s/ s1
+  common /f/ f1
+  common /p/ p1
+  common /y/ y1
+end
+
+!Expect: m.mod
+!module m
+!  real(4)::q1
+!  real(4)::c1
+!  integer(4)::m1
+!  real(4)::t1
+!  real(4)::a1
+!  real(4)::blank1
+!  real(4)::x1
+!  real(4)::e1
+!  integer(4)::k1
+!  real(4)::r1
+!  real(4)::b1
+!  real(4)::z1
+!  real(4)::g1
+!  integer(4)::n1
+!  real(4)::h1
+!  real(4)::w1
+!  real(4)::d1
+!  real(4)::s1
+!  real(4)::f1
+!  real(4)::p1
+!  real(4)::y1
+!  common/q/q1
+!  common/c/c1
+!  common/m/m1
+!  common/t/t1
+!  common/a/a1
+!  common/x/x1
+!  common/e/e1
+!  common/k/k1
+!  common/r/r1
+!  common/b/b1
+!  common/z/z1
+!  common/g/g1
+!  common/n/n1
+!  common/h/h1
+!  common/w/w1
+!  common/d/d1
+!  common/s/s1
+!  common/f/f1
+!  common/p/p1
+!  common/y/y1
+!  common//blank1
+!end


        


More information about the flang-commits mailing list