[flang-commits] [flang] [llvm] [flang][flang-rt] Implement F202X leading-zero control edit descriptors LZ, LZS, and LZP for formatted output (F, E, D, and G editing) (PR #183500)

via flang-commits flang-commits at lists.llvm.org
Fri Mar 20 04:19:25 PDT 2026


https://github.com/laoshd updated https://github.com/llvm/llvm-project/pull/183500

>From e2922a19918f526805dcd58205d770c14c04abdf Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 26 Feb 2026 05:03:53 -0600
Subject: [PATCH 01/12] [flang][flang-rt][F202X][Issue #178494] Implement F202X
 leading-zero control edit descriptors LZ, LZS, and LZP for formatted output
 (F, E, D, and G editing): LZ: processor-dependent (default, flang prints
 leading zero); LZS: suppress the optional leading zero before the decimal
 point; LZP: print the optional leading zero before the decimal point. Changes
 span the source parser, compile-time format validator, runtime format
 processing, and runtime output formatting. Includes semantic test (io16.f90)
 and documentation updates.

---
 .../flang-rt/runtime/format-implementation.h  | 29 +++++-
 flang-rt/include/flang-rt/runtime/format.h    |  7 ++
 flang-rt/lib/runtime/edit-output.cpp          | 47 +++++++--
 flang/docs/F202X.md                           |  9 ++
 flang/docs/FortranStandardsSupport.md         |  2 +-
 flang/include/flang/Common/format.h           | 45 ++++++++-
 .../flang/Parser/format-specification.h       |  3 +
 flang/lib/Parser/io-parsers.cpp               |  9 +-
 flang/lib/Parser/unparse.cpp                  |  3 +
 flang/test/Semantics/io16.f90                 | 99 +++++++++++++++++++
 10 files changed, 235 insertions(+), 18 deletions(-)
 create mode 100644 flang/test/Semantics/io16.f90

diff --git a/flang-rt/include/flang-rt/runtime/format-implementation.h b/flang-rt/include/flang-rt/runtime/format-implementation.h
index d510adbb5ba46..8b341def2b3ce 100644
--- a/flang-rt/include/flang-rt/runtime/format-implementation.h
+++ b/flang-rt/include/flang-rt/runtime/format-implementation.h
@@ -193,7 +193,7 @@ static RT_API_ATTRS bool AbsoluteTabbing(CONTEXT &context, int n) {
 
 template <typename CONTEXT>
 static RT_API_ATTRS void HandleControl(
-    CONTEXT &context, char ch, char next, int n) {
+    CONTEXT &context, char ch, char next, int n, char next2 = '\0') {
   MutableModes &modes{context.mutableModes()};
   switch (ch) {
   case 'B':
@@ -251,6 +251,21 @@ static RT_API_ATTRS void HandleControl(
       return;
     }
     break;
+  case 'L':
+    if (next == 'Z') {
+      if (next2 == 'S') {
+        // LZS - suppress leading zeros
+        modes.leadingZero = MutableModes::LeadingZeroMode::Suppress;
+      } else if (next2 == 'P') {
+        // LZP - print leading zero
+        modes.leadingZero = MutableModes::LeadingZeroMode::Print;
+      } else {
+        // LZ - processor-dependent (default behavior)
+        modes.leadingZero = MutableModes::LeadingZeroMode::Processor;
+      }
+      return;
+    }
+    break;
   case 'S':
     if (next == 'P') {
       modes.editingFlags |= signPlus;
@@ -455,6 +470,7 @@ RT_API_ATTRS int FormatControl<CONTEXT>::CueUpNextDataEdit(
     } else if (ch >= 'A' && ch <= 'Z') {
       int start{offset_ - 1};
       CharType next{'\0'};
+      CharType next2{'\0'};
       if (ch != 'P') { // 1PE5.2 - comma not required (C1302)
         CharType peek{Capitalize(PeekNext())};
         if (peek >= 'A' && peek <= 'Z') {
@@ -464,6 +480,15 @@ RT_API_ATTRS int FormatControl<CONTEXT>::CueUpNextDataEdit(
             // Assume a two-letter edit descriptor
             next = peek;
             ++offset_;
+          } else if (ch == 'L' && peek == 'Z') {
+            // LZ, LZS, or LZP control edit descriptor
+            next = peek;
+            ++offset_;
+            CharType peek2{Capitalize(PeekNext())};
+            if (peek2 == 'S' || peek2 == 'P') {
+              next2 = peek2;
+              ++offset_;
+            }
           } else {
             // extension: assume a comma between 'ch' and 'peek'
           }
@@ -484,7 +509,7 @@ RT_API_ATTRS int FormatControl<CONTEXT>::CueUpNextDataEdit(
           repeat = GetIntField(context);
         }
         HandleControl(context, static_cast<char>(ch), static_cast<char>(next),
-            repeat ? *repeat : 1);
+            repeat ? *repeat : 1, static_cast<char>(next2));
       }
     } else if (ch == '/') {
       context.AdvanceRecord(repeat && *repeat > 0 ? *repeat : 1);
diff --git a/flang-rt/include/flang-rt/runtime/format.h b/flang-rt/include/flang-rt/runtime/format.h
index 79a7dd713b1a1..787772935ce8c 100644
--- a/flang-rt/include/flang-rt/runtime/format.h
+++ b/flang-rt/include/flang-rt/runtime/format.h
@@ -44,6 +44,12 @@ struct MutableModes {
     return editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
   }
 
+  enum class LeadingZeroMode : std::uint8_t {
+    Processor, // LZ: processor-dependent (default)
+    Suppress, // LZS: suppress optional leading zero
+    Print, // LZP: print optional leading zero
+  };
+
   std::uint8_t editingFlags{0}; // BN, DP, SS
   enum decimal::FortranRounding round{
       executionEnvironment
@@ -53,6 +59,7 @@ struct MutableModes {
   short scale{0}; // kP
   bool inNamelist{false}; // skip ! comments
   bool nonAdvancing{false}; // ADVANCE='NO', or $ or \ in FORMAT
+  LeadingZeroMode leadingZero{LeadingZeroMode::Processor}; // LZ/LZS/LZP
 };
 
 // A single edit descriptor extracted from a FORMAT
diff --git a/flang-rt/lib/runtime/edit-output.cpp b/flang-rt/lib/runtime/edit-output.cpp
index 73dba35ff08d9..d0aa8638102a9 100644
--- a/flang-rt/lib/runtime/edit-output.cpp
+++ b/flang-rt/lib/runtime/edit-output.cpp
@@ -411,10 +411,26 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditEorDOutput(
     if (totalLength > width || !exponent) {
       return EmitRepeated(io_, '*', width);
     }
-    if (totalLength < width && digitsBeforePoint == 0 &&
-        zeroesBeforePoint == 0) {
-      zeroesBeforePoint = 1;
-      ++totalLength;
+    if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 && scale <= 0) {
+      // Optional leading zero position (F202X leading zero control).
+      // When scale > 0 (kP with k > 0), digits are moved before the decimal
+      // point, so the leading zero position is not optional -- skip this.
+      // Value has no digits before the decimal point: "0.xxxE+yy" vs ".xxxE+yy"
+      switch (edit.modes.leadingZero) {
+      case MutableModes::LeadingZeroMode::Print:
+        // LZP: always print the optional leading zero
+        zeroesBeforePoint = 1;
+        ++totalLength;
+        break;
+      case MutableModes::LeadingZeroMode::Suppress:
+        // LZS: never print the optional leading zero
+        break;
+      case MutableModes::LeadingZeroMode::Processor:
+        // LZ: processor-defined; flang chooses to print it
+        zeroesBeforePoint = 1;
+        ++totalLength;
+        break;
+      }
     }
     if (totalLength < width && editWidth == 0) {
       width = totalLength;
@@ -544,7 +560,24 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
     if (digitsBeforePoint + zeroesBeforePoint + zeroesAfterPoint +
             digitsAfterPoint + trailingZeroes ==
         0) {
-      zeroesBeforePoint = 1; // "." -> "0."
+      zeroesBeforePoint = 1; // "."--> "0." (bare decimal point)
+    } else if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 &&
+        expo <= 0) {
+      // Optional leading zero position (F202X leading zero control).
+      // Value magnitude < 1: "0.xxx" vs ".xxx"
+      switch (edit.modes.leadingZero) {
+      case MutableModes::LeadingZeroMode::Print:
+        // LZP: always print the optional leading zero
+        zeroesBeforePoint = 1;
+        break;
+      case MutableModes::LeadingZeroMode::Suppress:
+        // LZS: never print the optional leading zero
+        break;
+      case MutableModes::LeadingZeroMode::Processor:
+        // LZ: processor-defined; flang chooses to print it
+        zeroesBeforePoint = 1;
+        break;
+      }
     }
     int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint +
         1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingZeroes +
@@ -553,10 +586,6 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
     if (totalLength > width) {
       return EmitRepeated(io_, '*', width);
     }
-    if (totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0) {
-      zeroesBeforePoint = 1;
-      ++totalLength;
-    }
     return EmitPrefix(edit, totalLength, width) &&
         EmitAscii(io_, convertedStr, signLength + digitsBeforePoint) &&
         EmitRepeated(io_, '0', zeroesBeforePoint) &&
diff --git a/flang/docs/F202X.md b/flang/docs/F202X.md
index d1940a1858db1..6a303981cf264 100644
--- a/flang/docs/F202X.md
+++ b/flang/docs/F202X.md
@@ -261,6 +261,15 @@ The `AT` edit descriptor automatically trims character output.  The `LZP`,
 `LZS`, and `LZ` control edit descriptors and `LEADING_ZERO=` specifier provide a
 means for controlling the output of leading zero digits.
 
+Implementation status:
+- `LZ`, `LZS`, `LZP` control edit descriptors, affect only F, E, D, and G
+  editing of an output statement: Implemented
+  - `LZ` - Processor-dependent, default (e.g., `0.2`)
+  - `LZS` - Suppress leading zeros (e.g., `.2`)
+  - `LZP` - Print leading zero (e.g. `0.2`)
+- `AT` edit descriptor: Not yet implemented
+- `LEADING_ZERO=specifier` in OPEN statement: Not yet implemented
+
 #### Intrinsic Module Extensions
 
 Addressing some issues and omissions in intrinsic modules:
diff --git a/flang/docs/FortranStandardsSupport.md b/flang/docs/FortranStandardsSupport.md
index f57956cd6d6b8..02e4653280f12 100644
--- a/flang/docs/FortranStandardsSupport.md
+++ b/flang/docs/FortranStandardsSupport.md
@@ -48,7 +48,7 @@ status of all important Fortran 2023 features. The table entries are based on th
 | Extensions for c_f_pointer intrinsic                       | Y      | |
 | Procedures for converting between fortran and c strings    | N      | |
 | The at edit descriptor                                     | N      | |
-| Control over leading zeros in output of real values        | N      | |
+| Control over leading zeros in output of real values        | P      | LZ/LZS/LZP edit descriptors implemented; LEADING_ZERO=specifier not yet implemented     |
 | Extensions for Namelist                                    | N      | |
 | Allow an object of a type with a coarray ultimate component to be an array or allocatable | N | |
 | Put with Notify                                            | N      | |
diff --git a/flang/include/flang/Common/format.h b/flang/include/flang/Common/format.h
index 1e64acb823616..60bcc8e81ee38 100644
--- a/flang/include/flang/Common/format.h
+++ b/flang/include/flang/Common/format.h
@@ -114,7 +114,7 @@ struct FormatMessage {
 // This declaration is logically private to class FormatValidator.
 // It is placed here to work around a clang compilation problem.
 ENUM_CLASS(TokenKind, None, A, B, BN, BZ, D, DC, DP, DT, E, EN, ES, EX, F, G, I,
-    L, O, P, RC, RD, RN, RP, RU, RZ, S, SP, SS, T, TL, TR, X, Z, Colon, Slash,
+    L, LZ, LZP, LZS, O, P, RC, RD, RN, RP, RU, RZ, S, SP, SS, T, TL, TR, X, Z, Colon, Slash,
     Backslash, // nonstandard: inhibit newline on output
     Dollar, // nonstandard: inhibit newline on output on terminals
     Star, LParen, RParen, Comma, Point, Sign,
@@ -219,7 +219,7 @@ template <typename CHAR = char> class FormatValidator {
   std::int64_t knrValue_{-1}; // -1 ==> not present
   std::int64_t scaleFactorValue_{}; // signed k in kP
   std::int64_t wValue_{-1};
-  char argString_[3]{}; // 1-2 character msg arg; usually edit descriptor name
+  char argString_[4]{}; // 1-3 character msg arg; usually edit descriptor name
   bool formatHasErrors_{false};
   bool unterminatedFormatError_{false};
   bool suppressMessageCascade_{false};
@@ -390,7 +390,25 @@ template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
     token_.set_kind(TokenKind::I);
     break;
   case 'L':
-    token_.set_kind(TokenKind::L);
+    switch (LookAheadChar()) {
+    case 'Z':
+      // Advance past 'Z', then look ahead for 'S' or 'P'
+      Advance(TokenKind::LZ);
+      switch (LookAheadChar()) {
+      case 'S':
+        Advance(TokenKind::LZS);
+        break;
+      case 'P':
+        Advance(TokenKind::LZP);
+        break;
+      default:
+        break;
+      }
+      break;
+    default:
+      token_.set_kind(TokenKind::L);
+      break;
+    }
     break;
   case 'O':
     token_.set_kind(TokenKind::O);
@@ -674,9 +692,22 @@ template <typename CHAR> bool FormatValidator<CHAR>::Check() {
       ReportError("Unexpected '%s' in format expression", signToken);
     }
     // Default message argument.
-    // Alphabetic edit descriptor names are one or two characters in length.
+    // Alphabetic edit descriptor names are one to three characters in length.
     argString_[0] = toupper(format_[token_.offset()]);
-    argString_[1] = token_.length() > 1 ? toupper(*cursor_) : 0;
+    if (token_.length() > 2) {
+      // Three-character descriptor names (e.g., LZP, LZS).
+      // token_.offset() has the first character and *cursor_ has the last;
+      // find the middle character by scanning past any blanks.
+      const CHAR *mid{format_ + token_.offset() + 1};
+      while (mid < cursor_ && IsWhite(*mid)) {
+        ++mid;
+      }
+      argString_[1] = toupper(*mid);
+      argString_[2] = toupper(*cursor_);
+    } else {
+      argString_[1] = token_.length() > 1 ? toupper(*cursor_) : 0;
+      argString_[2] = 0;
+    }
     // Process one format edit descriptor or do format list management.
     switch (token_.kind()) {
     case TokenKind::A:
@@ -794,6 +825,9 @@ template <typename CHAR> bool FormatValidator<CHAR>::Check() {
     case TokenKind::BZ:
     case TokenKind::DC:
     case TokenKind::DP:
+    case TokenKind::LZ:
+    case TokenKind::LZS:
+    case TokenKind::LZP:
     case TokenKind::RC:
     case TokenKind::RD:
     case TokenKind::RN:
@@ -807,6 +841,7 @@ template <typename CHAR> bool FormatValidator<CHAR>::Check() {
       // R1318 blank-interp-edit-desc -> BN | BZ
       // R1319 round-edit-desc -> RU | RD | RZ | RN | RC | RP
       // R1320 decimal-edit-desc -> DC | DP
+      // F202X leading-zero-edit-desc -> LZ | LZS | LZP
       check_r(false);
       NextToken();
       break;
diff --git a/flang/include/flang/Parser/format-specification.h b/flang/include/flang/Parser/format-specification.h
index 28c8affd7bde0..5d37a9c2c0060 100644
--- a/flang/include/flang/Parser/format-specification.h
+++ b/flang/include/flang/Parser/format-specification.h
@@ -95,6 +95,9 @@ struct ControlEditDesc {
     RP,
     DC,
     DP,
+    LZ, // F202X: processor-dependent leading zero, default
+    LZS, // F202X: suppress leading zeros
+    LZP, // F202X: print leading zero
     Dollar, // extension: inhibit newline on output
     Backslash, // ditto, but only on terminals
   };
diff --git a/flang/lib/Parser/io-parsers.cpp b/flang/lib/Parser/io-parsers.cpp
index cb3e68a05c94d..9da8c4f01d7dc 100644
--- a/flang/lib/Parser/io-parsers.cpp
+++ b/flang/lib/Parser/io-parsers.cpp
@@ -629,7 +629,8 @@ TYPE_PARSER(construct<format::IntrinsicTypeDataEditDesc>(
                     "X " >> pure(format::IntrinsicTypeDataEditDesc::Kind::EX) ||
                     pure(format::IntrinsicTypeDataEditDesc::Kind::E)) ||
             "G " >> pure(format::IntrinsicTypeDataEditDesc::Kind::G) ||
-            "L " >> pure(format::IntrinsicTypeDataEditDesc::Kind::L),
+            ("L "_tok / !letter /* don't occlude LZ, LZS, & LZP */) >>
+                pure(format::IntrinsicTypeDataEditDesc::Kind::L),
         noInt, noInt, noInt)))
 
 // R1307 data-edit-desc (part 2 of 2)
@@ -677,6 +678,12 @@ TYPE_PARSER(construct<format::ControlEditDesc>(
                          pure(format::ControlEditDesc::Kind::BN)) ||
                 "Z " >> construct<format::ControlEditDesc>(
                             pure(format::ControlEditDesc::Kind::BZ))) ||
+    "L " >> ("Z " >> ("S " >> construct<format::ControlEditDesc>(
+                                  pure(format::ControlEditDesc::Kind::LZS)) ||
+                          "P " >> construct<format::ControlEditDesc>(
+                                      pure(format::ControlEditDesc::Kind::LZP)) ||
+                          construct<format::ControlEditDesc>(
+                              pure(format::ControlEditDesc::Kind::LZ)))) ||
     "R " >> ("U " >> construct<format::ControlEditDesc>(
                          pure(format::ControlEditDesc::Kind::RU)) ||
                 "D " >> construct<format::ControlEditDesc>(
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 3d8ea9f703b2f..8ad1f9b8ff618 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1549,6 +1549,9 @@ class UnparseVisitor {
       FMT(RP);
       FMT(DC);
       FMT(DP);
+      FMT(LZ);
+      FMT(LZS);
+      FMT(LZP);
 #undef FMT
     case format::ControlEditDesc::Kind::Dollar:
       Put('$');
diff --git a/flang/test/Semantics/io16.f90 b/flang/test/Semantics/io16.f90
new file mode 100644
index 0000000000000..559d98f0d5ceb
--- /dev/null
+++ b/flang/test/Semantics/io16.f90
@@ -0,0 +1,99 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+! F202X leading-zero control edit descriptors: LZ, LZS, LZP
+
+  ! Valid uses of LZ, LZP, LZS in FORMAT statements
+1001 format(LZ, F10.3)
+1002 format(LZP, F10.3)
+1003 format(LZS, F10.3)
+1004 format(LZ, E10.3)
+1005 format(LZP, E10.3)
+1006 format(LZS, E10.3)
+1007 format(LZS, D10.3)
+1008 format(LZ, G10.3)
+
+  ! Valid uses with blanks inside keywords (Fortran ignores blanks)
+1009 format(L Z, F10.3)
+1010 format(L Z P, F10.3)
+1011 format(L Z S, F10.3)
+
+  ! Combining with other control edit descriptors
+1012 format(LZP, DC, F10.3)
+1013 format(BN, LZS, F10.3)
+1014 format(LZ, SS, RZ, F10.3)
+
+  ! Multiple groups
+1015 format(LZP, 3F10.3, LZS, 2E12.4)
+
+  ! C1302 : multiple edit descriptors without ',' separation; no errors
+1016 format(LZF10.3)
+1017 format(LZPF10.3)
+1018 format(LZSF10.3)
+1019 format(LZE10.3)
+1020 format(LZPE10.3)
+1021 format(LZSD10.3)
+1022 format(LZG10.3)
+1023 format(LZPDCF10.3)
+1024 format(BNLZSF10.3)
+1025 format(LZPF10.3LZSF10.3)
+1026 format(LZP3F10.3LZS2E12.4)
+
+  ! In WRITE format strings
+  write(*, '(LZ, F10.3)') 0.5
+  write(*, '(LZP, F10.3)') 0.5
+  write(*, '(LZS, F10.3)') 0.5
+  write(*, '(LZP,E10.3)') 0.5
+  write(*, '(LZS,D10.3)') 0.5
+
+  ! C1302 : WRITE format strings without ',' separation; no errors
+  write(*, '(LZF10.3)') 0.5
+  write(*, '(LZPF10.3)') 0.5
+  write(*, '(LZSF10.3)') 0.5
+  write(*, '(LZPE10.3)') 0.5
+  write(*, '(LZP3F10.3LZS2E12.4)') 0.5, 0.5, 0.5, 0.5, 0.5
+
+  ! FMT= specifier with comma-separated descriptors
+  write(*, fmt='(LZ, F10.3)') 0.5
+  write(*, fmt='(LZP, F10.3)') 0.5
+  write(*, fmt='(LZS, F10.3)') 0.5
+  write(*, fmt='(LZP, E10.3)') 0.5
+  write(*, fmt='(LZS, D10.3)') 0.5
+  write(*, fmt='(LZP, DC, F10.3)') 0.5
+  write(*, fmt='(BN, LZS, F10.3)') 0.5
+
+  ! FMT= specifier without ',' separation; no errors
+  write(*, fmt='(LZF10.3)') 0.5
+  write(*, fmt='(LZPF10.3)') 0.5
+  write(*, fmt='(LZSF10.3)') 0.5
+  write(*, fmt='(LZPE10.3)') 0.5
+  write(*, fmt='(LZP3F10.3LZS2E12.4)') 0.5, 0.5, 0.5, 0.5, 0.5
+
+  ! FMT= specifier with FORMAT label reference
+  write(*, fmt=1001) 0.5
+  write(*, fmt=1002) 0.5
+  write(*, fmt=1017) 0.5
+
+  ! LZ/LZP/LZS coexisting with abbreviated L (no width) data edit descriptor
+  write(*, '(LZP, F10.3, L)') 0.5, .true.
+  write(*, '(LZS, F10.3, L)') 0.5, .true.
+
+  ! Error: repeat specifier before LZ/LZP/LZS in WRITE format strings
+  !ERROR: Repeat specifier before 'LZ' edit descriptor
+  write(*, '(3LZ, F10.3)') 0.5
+
+  !ERROR: Repeat specifier before 'LZP' edit descriptor
+  write(*, '(2LZP, F10.3)') 0.5
+
+  !ERROR: Repeat specifier before 'LZS' edit descriptor
+  write(*, '(2LZS, F10.3)') 0.5
+
+  ! Error: repeat specifier before LZ/LZP/LZS in FORMAT statements
+  !ERROR: Repeat specifier before 'LZ' edit descriptor
+2001 format(3LZ, F10.3)
+
+  !ERROR: Repeat specifier before 'LZP' edit descriptor
+2002 format(2LZP, F10.3)
+
+  !ERROR: Repeat specifier before 'LZS' edit descriptor
+2003 format(2LZS, F10.3)
+end

>From 4955f1dbb6a27f5ba0a9a75a9f53f86cad6db28d Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 26 Feb 2026 10:58:11 -0600
Subject: [PATCH 02/12] [flang][flang-rt][F202X][Issue #178494] Re-add tests
 for leading-zero control edit descriptors in FORMAT statements,
 flang/test/Semantics/io17.f90. Previously added io16.f90 was used.

---
 flang/test/Semantics/io17.f90 | 99 +++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 flang/test/Semantics/io17.f90

diff --git a/flang/test/Semantics/io17.f90 b/flang/test/Semantics/io17.f90
new file mode 100644
index 0000000000000..559d98f0d5ceb
--- /dev/null
+++ b/flang/test/Semantics/io17.f90
@@ -0,0 +1,99 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+! F202X leading-zero control edit descriptors: LZ, LZS, LZP
+
+  ! Valid uses of LZ, LZP, LZS in FORMAT statements
+1001 format(LZ, F10.3)
+1002 format(LZP, F10.3)
+1003 format(LZS, F10.3)
+1004 format(LZ, E10.3)
+1005 format(LZP, E10.3)
+1006 format(LZS, E10.3)
+1007 format(LZS, D10.3)
+1008 format(LZ, G10.3)
+
+  ! Valid uses with blanks inside keywords (Fortran ignores blanks)
+1009 format(L Z, F10.3)
+1010 format(L Z P, F10.3)
+1011 format(L Z S, F10.3)
+
+  ! Combining with other control edit descriptors
+1012 format(LZP, DC, F10.3)
+1013 format(BN, LZS, F10.3)
+1014 format(LZ, SS, RZ, F10.3)
+
+  ! Multiple groups
+1015 format(LZP, 3F10.3, LZS, 2E12.4)
+
+  ! C1302 : multiple edit descriptors without ',' separation; no errors
+1016 format(LZF10.3)
+1017 format(LZPF10.3)
+1018 format(LZSF10.3)
+1019 format(LZE10.3)
+1020 format(LZPE10.3)
+1021 format(LZSD10.3)
+1022 format(LZG10.3)
+1023 format(LZPDCF10.3)
+1024 format(BNLZSF10.3)
+1025 format(LZPF10.3LZSF10.3)
+1026 format(LZP3F10.3LZS2E12.4)
+
+  ! In WRITE format strings
+  write(*, '(LZ, F10.3)') 0.5
+  write(*, '(LZP, F10.3)') 0.5
+  write(*, '(LZS, F10.3)') 0.5
+  write(*, '(LZP,E10.3)') 0.5
+  write(*, '(LZS,D10.3)') 0.5
+
+  ! C1302 : WRITE format strings without ',' separation; no errors
+  write(*, '(LZF10.3)') 0.5
+  write(*, '(LZPF10.3)') 0.5
+  write(*, '(LZSF10.3)') 0.5
+  write(*, '(LZPE10.3)') 0.5
+  write(*, '(LZP3F10.3LZS2E12.4)') 0.5, 0.5, 0.5, 0.5, 0.5
+
+  ! FMT= specifier with comma-separated descriptors
+  write(*, fmt='(LZ, F10.3)') 0.5
+  write(*, fmt='(LZP, F10.3)') 0.5
+  write(*, fmt='(LZS, F10.3)') 0.5
+  write(*, fmt='(LZP, E10.3)') 0.5
+  write(*, fmt='(LZS, D10.3)') 0.5
+  write(*, fmt='(LZP, DC, F10.3)') 0.5
+  write(*, fmt='(BN, LZS, F10.3)') 0.5
+
+  ! FMT= specifier without ',' separation; no errors
+  write(*, fmt='(LZF10.3)') 0.5
+  write(*, fmt='(LZPF10.3)') 0.5
+  write(*, fmt='(LZSF10.3)') 0.5
+  write(*, fmt='(LZPE10.3)') 0.5
+  write(*, fmt='(LZP3F10.3LZS2E12.4)') 0.5, 0.5, 0.5, 0.5, 0.5
+
+  ! FMT= specifier with FORMAT label reference
+  write(*, fmt=1001) 0.5
+  write(*, fmt=1002) 0.5
+  write(*, fmt=1017) 0.5
+
+  ! LZ/LZP/LZS coexisting with abbreviated L (no width) data edit descriptor
+  write(*, '(LZP, F10.3, L)') 0.5, .true.
+  write(*, '(LZS, F10.3, L)') 0.5, .true.
+
+  ! Error: repeat specifier before LZ/LZP/LZS in WRITE format strings
+  !ERROR: Repeat specifier before 'LZ' edit descriptor
+  write(*, '(3LZ, F10.3)') 0.5
+
+  !ERROR: Repeat specifier before 'LZP' edit descriptor
+  write(*, '(2LZP, F10.3)') 0.5
+
+  !ERROR: Repeat specifier before 'LZS' edit descriptor
+  write(*, '(2LZS, F10.3)') 0.5
+
+  ! Error: repeat specifier before LZ/LZP/LZS in FORMAT statements
+  !ERROR: Repeat specifier before 'LZ' edit descriptor
+2001 format(3LZ, F10.3)
+
+  !ERROR: Repeat specifier before 'LZP' edit descriptor
+2002 format(2LZP, F10.3)
+
+  !ERROR: Repeat specifier before 'LZS' edit descriptor
+2003 format(2LZS, F10.3)
+end

>From b4a013fd28bb64b28538704d7d4588c2675802c6 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 26 Feb 2026 18:33:56 -0600
Subject: [PATCH 03/12] [flang][flang-rt][F202X][Issue #178494] Minor updates
 on comments and docs format; Add flang-rt test, LeadingZeroTest.cpp.

---
 flang-rt/lib/runtime/edit-output.cpp          |   3 +-
 flang-rt/unittests/Runtime/CMakeLists.txt     |   1 +
 .../unittests/Runtime/LeadingZeroTest.cpp     | 256 ++++++++++++++++++
 flang/include/flang/Common/format.h           |   3 +-
 flang/lib/Parser/io-parsers.cpp               |   8 +-
 5 files changed, 264 insertions(+), 7 deletions(-)
 create mode 100644 flang-rt/unittests/Runtime/LeadingZeroTest.cpp

diff --git a/flang-rt/lib/runtime/edit-output.cpp b/flang-rt/lib/runtime/edit-output.cpp
index d0aa8638102a9..fbfcef6e7d13a 100644
--- a/flang-rt/lib/runtime/edit-output.cpp
+++ b/flang-rt/lib/runtime/edit-output.cpp
@@ -561,8 +561,7 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
             digitsAfterPoint + trailingZeroes ==
         0) {
       zeroesBeforePoint = 1; // "."--> "0." (bare decimal point)
-    } else if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 &&
-        expo <= 0) {
+    } else if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 && expo <= 0) {
       // Optional leading zero position (F202X leading zero control).
       // Value magnitude < 1: "0.xxx" vs ".xxx"
       switch (edit.modes.leadingZero) {
diff --git a/flang-rt/unittests/Runtime/CMakeLists.txt b/flang-rt/unittests/Runtime/CMakeLists.txt
index fca064b226200..31f7fcb5da812 100644
--- a/flang-rt/unittests/Runtime/CMakeLists.txt
+++ b/flang-rt/unittests/Runtime/CMakeLists.txt
@@ -22,6 +22,7 @@ add_flangrt_unittest(RuntimeTests
   Format.cpp
   InputExtensions.cpp
   Inquiry.cpp
+  LeadingZeroTest.cpp
   ListInputTest.cpp
   LogicalFormatTest.cpp
   Matmul.cpp
diff --git a/flang-rt/unittests/Runtime/LeadingZeroTest.cpp b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
new file mode 100644
index 0000000000000..95b1e06513d04
--- /dev/null
+++ b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
@@ -0,0 +1,256 @@
+//===-- unittests/Runtime/LeadingZeroTest.cpp --------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for F202X leading-zero control edit descriptors: LZ, LZP, LZS.
+// LZ  - processor-dependent (flang prints leading zero)
+// LZP - print the optional leading zero
+// LZS - suppress the optional leading zero
+//
+//===----------------------------------------------------------------------===//
+
+#include "CrashHandlerFixture.h"
+#include "flang-rt/runtime/descriptor.h"
+#include "flang/Runtime/io-api.h"
+#include <algorithm>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+static bool CompareFormattedStrings(
+    const std::string &expect, const std::string &got) {
+  std::string want{expect};
+  want.resize(got.size(), ' ');
+  return want == got;
+}
+
+// Perform format on a double and return the trimmed result
+static std::string FormatReal(const char *format, double x) {
+  char buffer[800];
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, x));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  return got;
+}
+
+static bool CompareFormatReal(
+    const char *format, double x, const char *expect, std::string &got) {
+  got = FormatReal(format, x);
+  return CompareFormattedStrings(expect, got);
+}
+
+struct LeadingZeroTests : CrashHandlerFixture {};
+
+// LZP with F editing: value < 1 should print "0." before decimal digits
+TEST(LeadingZeroTests, LZP_F_editing) {
+  static constexpr std::pair<const char *, const char *> cases[]{
+      {"(LZP,F6.1)", "   0.2"},
+      {"(LZP,F10.3)", "     0.200"},
+      {"(LZP,F6.1)", "   0.5"},
+      {"(LZP,F4.1)", " 0.1"},
+  };
+  double values[]{0.2, 0.2, 0.5, 0.1};
+  for (int i = 0; i < 4; ++i) {
+    std::string got;
+    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+        << "Failed: format=" << cases[i].first << " value=" << values[i]
+        << ", expected '" << cases[i].second << "', got '" << got << "'";
+  }
+}
+
+// LZS with F editing: value < 1 should suppress the leading zero
+TEST(LeadingZeroTests, LZS_F_editing) {
+  static constexpr std::pair<const char *, const char *> cases[]{
+      {"(LZS,F6.1)", "    .2"},
+      {"(LZS,F10.3)", "      .200"},
+      {"(LZS,F6.1)", "    .5"},
+      {"(LZS,F4.1)", "  .1"},
+  };
+  double values[]{0.2, 0.2, 0.5, 0.1};
+  for (int i = 0; i < 4; ++i) {
+    std::string got;
+    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+        << "Failed: format=" << cases[i].first << " value=" << values[i]
+        << ", expected '" << cases[i].second << "', got '" << got << "'";
+  }
+}
+
+// LZ (processor-dependent, flang prints leading zero) with F editing
+TEST(LeadingZeroTests, LZ_F_editing) {
+  static constexpr std::pair<const char *, const char *> cases[]{
+      {"(LZ,F6.1)", "   0.2"},
+      {"(LZ,F10.3)", "     0.200"},
+  };
+  double values[]{0.2, 0.2};
+  for (int i = 0; i < 2; ++i) {
+    std::string got;
+    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+        << "Failed: format=" << cases[i].first << " value=" << values[i]
+        << ", expected '" << cases[i].second << "', got '" << got << "'";
+  }
+}
+
+// LZP with E editing: value < 1 should print "0." before decimal digits
+TEST(LeadingZeroTests, LZP_E_editing) {
+  static constexpr std::pair<const char *, const char *> cases[]{
+      {"(LZP,E10.3)", " 0.200E+00"},
+      {"(LZP,E12.5)", " 0.20000E+00"},
+  };
+  double values[]{0.2, 0.2};
+  for (int i = 0; i < 2; ++i) {
+    std::string got;
+    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+        << "Failed: format=" << cases[i].first << " value=" << values[i]
+        << ", expected '" << cases[i].second << "', got '" << got << "'";
+  }
+}
+
+// LZS with E editing: value < 1 should suppress the leading zero
+TEST(LeadingZeroTests, LZS_E_editing) {
+  static constexpr std::pair<const char *, const char *> cases[]{
+      {"(LZS,E10.3)", "  .200E+00"},
+      {"(LZS,E12.5)", "  .20000E+00"},
+  };
+  double values[]{0.2, 0.2};
+  for (int i = 0; i < 2; ++i) {
+    std::string got;
+    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+        << "Failed: format=" << cases[i].first << " value=" << values[i]
+        << ", expected '" << cases[i].second << "', got '" << got << "'";
+  }
+}
+
+// LZP with D editing
+TEST(LeadingZeroTests, LZP_D_editing) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZP,D10.3)", 0.2, " 0.200D+00", got))
+      << "Expected ' 0.200D+00', got '" << got << "'";
+}
+
+// LZS with D editing
+TEST(LeadingZeroTests, LZS_D_editing) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZS,D10.3)", 0.2, "  .200D+00", got))
+      << "Expected '  .200D+00', got '" << got << "'";
+}
+
+// LZP with G editing — G routes to F when exponent is in range
+TEST(LeadingZeroTests, LZP_G_editing_F_path) {
+  std::string got;
+  // 0.2 with G10.3: exponent 0 is in [0,3], so G uses F editing
+  ASSERT_TRUE(CompareFormatReal("(LZP,G10.3)", 0.2, " 0.200    ", got))
+      << "Expected ' 0.200    ', got '" << got << "'";
+}
+
+// LZS with G editing — G routes to F when exponent is in range
+TEST(LeadingZeroTests, LZS_G_editing_F_path) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZS,G10.3)", 0.2, "  .200    ", got))
+      << "Expected '  .200    ', got '" << got << "'";
+}
+
+// LZP with G editing — G routes to E when exponent is out of range
+TEST(LeadingZeroTests, LZP_G_editing_E_path) {
+  std::string got;
+  // 0.0002 with G10.3: exponent -3 is < 0, so G uses E editing
+  ASSERT_TRUE(CompareFormatReal("(LZP,G10.3)", 0.0002, " 0.200E-03", got))
+      << "Expected ' 0.200E-03', got '" << got << "'";
+}
+
+// LZS with G editing — G routes to E when exponent is out of range
+TEST(LeadingZeroTests, LZS_G_editing_E_path) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZS,G10.3)", 0.0002, "  .200E-03", got))
+      << "Expected '  .200E-03', got '" << got << "'";
+}
+
+// Switching between LZP and LZS in the same format
+TEST(LeadingZeroTests, SwitchBetweenLZPandLZS) {
+  char buffer[800];
+  const char *format{"(LZP,F6.1,LZS,F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  std::string expect{"   0.5    .5"};
+  ASSERT_TRUE(CompareFormattedStrings(expect, got))
+      << "Expected '" << expect << "', got '" << got << "'";
+}
+
+// LZP/LZS with negative values < 1 in magnitude
+TEST(LeadingZeroTests, NegativeValues) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZP,F7.1)", -0.2, "   -0.2", got))
+      << "Expected '   -0.2', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZS,F7.1)", -0.2, "    -.2", got))
+      << "Expected '    -.2', got '" << got << "'";
+}
+
+// LZP/LZS should not affect values >= 1 (leading zero is not optional)
+TEST(LeadingZeroTests, ValuesGreaterThanOne) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZP,F6.1)", 1.2, "   1.2", got))
+      << "Expected '   1.2', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZS,F6.1)", 1.2, "   1.2", got))
+      << "Expected '   1.2', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZP,F6.1)", 12.3, "  12.3", got))
+      << "Expected '  12.3', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZS,F6.1)", 12.3, "  12.3", got))
+      << "Expected '  12.3', got '" << got << "'";
+}
+
+// LZP/LZS with zero value
+TEST(LeadingZeroTests, ZeroValue) {
+  std::string got;
+  // LZP: zero value still prints leading zero before decimal point
+  ASSERT_TRUE(CompareFormatReal("(LZP,F6.1)", 0.0, "   0.0", got))
+      << "Expected '   0.0', got '" << got << "'";
+  // LZS: zero has magnitude < 1, so the leading zero is optional and suppressed
+  ASSERT_TRUE(CompareFormatReal("(LZS,F6.1)", 0.0, "    .0", got))
+      << "Expected '    .0', got '" << got << "'";
+}
+
+// LZP/LZS with scale factor (1P) — leading zero not optional when scale > 0
+TEST(LeadingZeroTests, WithScaleFactor) {
+  std::string got;
+  // With 1P, E editing puts one digit before the decimal point,
+  // so LZS should not suppress it (it's not an optional zero)
+  ASSERT_TRUE(CompareFormatReal("(LZP,1P,E10.3)", 0.2, " 2.000E-01", got))
+      << "Expected ' 2.000E-01', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZS,1P,E10.3)", 0.2, " 2.000E-01", got))
+      << "Expected ' 2.000E-01', got '" << got << "'";
+}
+
+// LZP without comma separator (C1302 extension)
+TEST(LeadingZeroTests, WithoutCommaSeparator) {
+  std::string got;
+  ASSERT_TRUE(CompareFormatReal("(LZPF6.1)", 0.2, "   0.2", got))
+      << "Expected '   0.2', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZSF6.1)", 0.2, "    .2", got))
+      << "Expected '    .2', got '" << got << "'";
+  ASSERT_TRUE(CompareFormatReal("(LZF6.1)", 0.2, "   0.2", got))
+      << "Expected '   0.2', got '" << got << "'";
+}
diff --git a/flang/include/flang/Common/format.h b/flang/include/flang/Common/format.h
index f7b0835f91287..7c9a763d86bae 100644
--- a/flang/include/flang/Common/format.h
+++ b/flang/include/flang/Common/format.h
@@ -114,7 +114,8 @@ struct FormatMessage {
 // This declaration is logically private to class FormatValidator.
 // It is placed here to work around a clang compilation problem.
 ENUM_CLASS(TokenKind, None, A, B, BN, BZ, D, DC, DP, DT, E, EN, ES, EX, F, G, I,
-    L, LZ, LZP, LZS, O, P, RC, RD, RN, RP, RU, RZ, S, SP, SS, T, TL, TR, X, Z, Colon, Slash,
+    L, LZ, LZP, LZS, O, P, RC, RD, RN, RP, RU, RZ, S, SP, SS, T, TL, TR, X, Z,
+    Colon, Slash,
     Backslash, // nonstandard: inhibit newline on output
     Dollar, // nonstandard: inhibit newline on output on terminals
     Star, LParen, RParen, Comma, Point, Sign,
diff --git a/flang/lib/Parser/io-parsers.cpp b/flang/lib/Parser/io-parsers.cpp
index 9da8c4f01d7dc..bcbeb49f8e01f 100644
--- a/flang/lib/Parser/io-parsers.cpp
+++ b/flang/lib/Parser/io-parsers.cpp
@@ -680,10 +680,10 @@ TYPE_PARSER(construct<format::ControlEditDesc>(
                             pure(format::ControlEditDesc::Kind::BZ))) ||
     "L " >> ("Z " >> ("S " >> construct<format::ControlEditDesc>(
                                   pure(format::ControlEditDesc::Kind::LZS)) ||
-                          "P " >> construct<format::ControlEditDesc>(
-                                      pure(format::ControlEditDesc::Kind::LZP)) ||
-                          construct<format::ControlEditDesc>(
-                              pure(format::ControlEditDesc::Kind::LZ)))) ||
+                         "P " >> construct<format::ControlEditDesc>(
+                                     pure(format::ControlEditDesc::Kind::LZP)) ||
+                         construct<format::ControlEditDesc>(
+                             pure(format::ControlEditDesc::Kind::LZ)))) ||
     "R " >> ("U " >> construct<format::ControlEditDesc>(
                          pure(format::ControlEditDesc::Kind::RU)) ||
                 "D " >> construct<format::ControlEditDesc>(

>From deaaf8a69b40fc25ec6e9c6e099f3ec5b477aac4 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 26 Feb 2026 19:04:38 -0600
Subject: [PATCH 04/12] [flang][flang-rt][F202X][Issue #178494] Update code
 format based on PR review.

---
 flang/lib/Parser/io-parsers.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/flang/lib/Parser/io-parsers.cpp b/flang/lib/Parser/io-parsers.cpp
index bcbeb49f8e01f..93aa4e9d21911 100644
--- a/flang/lib/Parser/io-parsers.cpp
+++ b/flang/lib/Parser/io-parsers.cpp
@@ -680,8 +680,8 @@ TYPE_PARSER(construct<format::ControlEditDesc>(
                             pure(format::ControlEditDesc::Kind::BZ))) ||
     "L " >> ("Z " >> ("S " >> construct<format::ControlEditDesc>(
                                   pure(format::ControlEditDesc::Kind::LZS)) ||
-                         "P " >> construct<format::ControlEditDesc>(
-                                     pure(format::ControlEditDesc::Kind::LZP)) ||
+                         "P " >> construct<format::ControlEditDesc>(pure(
+                                     format::ControlEditDesc::Kind::LZP)) ||
                          construct<format::ControlEditDesc>(
                              pure(format::ControlEditDesc::Kind::LZ)))) ||
     "R " >> ("U " >> construct<format::ControlEditDesc>(

>From 4e7bdcaced4e3f0db85d9042d8a01e24d64619a6 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 26 Feb 2026 19:22:31 -0600
Subject: [PATCH 05/12] [flang][flang-rt] Update code format based on PR
 review.

---
 flang-rt/unittests/Runtime/LeadingZeroTest.cpp | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/flang-rt/unittests/Runtime/LeadingZeroTest.cpp b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
index 95b1e06513d04..d151e03923239 100644
--- a/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
+++ b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
@@ -1,4 +1,5 @@
-//===-- unittests/Runtime/LeadingZeroTest.cpp --------------------*- C++ -*-===//
+//===-- unittests/Runtime/LeadingZeroTest.cpp --------------------*- C++
+//-*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -68,7 +69,8 @@ TEST(LeadingZeroTests, LZP_F_editing) {
   double values[]{0.2, 0.2, 0.5, 0.1};
   for (int i = 0; i < 4; ++i) {
     std::string got;
-    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+    ASSERT_TRUE(
+        CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
         << "Failed: format=" << cases[i].first << " value=" << values[i]
         << ", expected '" << cases[i].second << "', got '" << got << "'";
   }
@@ -85,7 +87,8 @@ TEST(LeadingZeroTests, LZS_F_editing) {
   double values[]{0.2, 0.2, 0.5, 0.1};
   for (int i = 0; i < 4; ++i) {
     std::string got;
-    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+    ASSERT_TRUE(
+        CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
         << "Failed: format=" << cases[i].first << " value=" << values[i]
         << ", expected '" << cases[i].second << "', got '" << got << "'";
   }
@@ -100,7 +103,8 @@ TEST(LeadingZeroTests, LZ_F_editing) {
   double values[]{0.2, 0.2};
   for (int i = 0; i < 2; ++i) {
     std::string got;
-    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+    ASSERT_TRUE(
+        CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
         << "Failed: format=" << cases[i].first << " value=" << values[i]
         << ", expected '" << cases[i].second << "', got '" << got << "'";
   }
@@ -115,7 +119,8 @@ TEST(LeadingZeroTests, LZP_E_editing) {
   double values[]{0.2, 0.2};
   for (int i = 0; i < 2; ++i) {
     std::string got;
-    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+    ASSERT_TRUE(
+        CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
         << "Failed: format=" << cases[i].first << " value=" << values[i]
         << ", expected '" << cases[i].second << "', got '" << got << "'";
   }
@@ -130,7 +135,8 @@ TEST(LeadingZeroTests, LZS_E_editing) {
   double values[]{0.2, 0.2};
   for (int i = 0; i < 2; ++i) {
     std::string got;
-    ASSERT_TRUE(CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
+    ASSERT_TRUE(
+        CompareFormatReal(cases[i].first, values[i], cases[i].second, got))
         << "Failed: format=" << cases[i].first << " value=" << values[i]
         << ", expected '" << cases[i].second << "', got '" << got << "'";
   }

>From 9d453826cb83d3dcefc3f3c8facd4387f15abe33 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 5 Mar 2026 08:25:24 -0600
Subject: [PATCH 06/12] Change file name io17.f90 to io18.f90 in
 flang/test/Semantics to resolve conflict: io17.f90 has been used.

---
 flang/test/Semantics/{io17.f90 => io18.f90} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename flang/test/Semantics/{io17.f90 => io18.f90} (100%)

diff --git a/flang/test/Semantics/io17.f90 b/flang/test/Semantics/io18.f90
similarity index 100%
rename from flang/test/Semantics/io17.f90
rename to flang/test/Semantics/io18.f90

>From c733fab2e00f376d004e536614efcae75fc6da22 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Fri, 6 Mar 2026 10:31:38 -0600
Subject: [PATCH 07/12] Fix a flang-rt failure for 'F0.d': print leading zero
 only when field has room for LZ

---
 flang-rt/lib/runtime/edit-output.cpp | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/flang-rt/lib/runtime/edit-output.cpp b/flang-rt/lib/runtime/edit-output.cpp
index 80d3ee23e895a..e3d39f8874e41 100644
--- a/flang-rt/lib/runtime/edit-output.cpp
+++ b/flang-rt/lib/runtime/edit-output.cpp
@@ -434,9 +434,11 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditEorDOutput(
         // LZS: never print the optional leading zero
         break;
       case MutableModes::LeadingZeroMode::Processor:
-        // LZ: processor-defined; flang chooses to print it
-        zeroesBeforePoint = 1;
-        ++totalLength;
+        // LZ: processor-defined; add leading zero only when field has room
+        if (totalLength < width) {
+          zeroesBeforePoint = 1;
+          ++totalLength;
+        }
         break;
       }
     }
@@ -581,9 +583,7 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
         // LZS: never print the optional leading zero
         break;
       case MutableModes::LeadingZeroMode::Processor:
-        // LZ: processor-defined; flang chooses to print it
-        zeroesBeforePoint = 1;
-        break;
+        break; // handled below after width computation
       }
     }
     int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint +
@@ -593,6 +593,11 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
     if (totalLength > width) {
       return EmitRepeated(io_, '*', width);
     }
+    if (edit.modes.leadingZero == MutableModes::LeadingZeroMode::Processor &&
+        totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0) {
+      zeroesBeforePoint = 1;
+      ++totalLength;
+    }
     return EmitPrefix(edit, totalLength, width) &&
         EmitAscii(io_, convertedStr, signLength + digitsBeforePoint) &&
         EmitRepeated(io_, '0', zeroesBeforePoint) &&

>From 5b68726689eec6c837ff967ac66e56bd345c0898 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Wed, 11 Mar 2026 18:53:27 -0500
Subject: [PATCH 08/12] Remove unnecessary default parameter value.

---
 flang-rt/include/flang-rt/runtime/format-implementation.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang-rt/include/flang-rt/runtime/format-implementation.h b/flang-rt/include/flang-rt/runtime/format-implementation.h
index 8b341def2b3ce..0781816f562e1 100644
--- a/flang-rt/include/flang-rt/runtime/format-implementation.h
+++ b/flang-rt/include/flang-rt/runtime/format-implementation.h
@@ -193,7 +193,7 @@ static RT_API_ATTRS bool AbsoluteTabbing(CONTEXT &context, int n) {
 
 template <typename CONTEXT>
 static RT_API_ATTRS void HandleControl(
-    CONTEXT &context, char ch, char next, int n, char next2 = '\0') {
+    CONTEXT &context, char ch, char next, int n, char next2) {
   MutableModes &modes{context.mutableModes()};
   switch (ch) {
   case 'B':

>From 29253eced419423ea4633f2cd1690aacecf79864 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Thu, 12 Mar 2026 12:06:27 -0500
Subject: [PATCH 09/12] Re-implement with editingFlags replacing enum.

---
 .../flang-rt/runtime/format-implementation.h  |  9 +---
 flang-rt/include/flang-rt/runtime/format.h    | 10 +----
 flang-rt/lib/runtime/edit-output.cpp          | 45 +++----------------
 3 files changed, 11 insertions(+), 53 deletions(-)

diff --git a/flang-rt/include/flang-rt/runtime/format-implementation.h b/flang-rt/include/flang-rt/runtime/format-implementation.h
index 0781816f562e1..e469ba627f32c 100644
--- a/flang-rt/include/flang-rt/runtime/format-implementation.h
+++ b/flang-rt/include/flang-rt/runtime/format-implementation.h
@@ -254,14 +254,9 @@ static RT_API_ATTRS void HandleControl(
   case 'L':
     if (next == 'Z') {
       if (next2 == 'S') {
-        // LZS - suppress leading zeros
-        modes.leadingZero = MutableModes::LeadingZeroMode::Suppress;
-      } else if (next2 == 'P') {
-        // LZP - print leading zero
-        modes.leadingZero = MutableModes::LeadingZeroMode::Print;
+        modes.editingFlags |= leadingZeroSuppress; // LZS
       } else {
-        // LZ - processor-dependent (default behavior)
-        modes.leadingZero = MutableModes::LeadingZeroMode::Processor;
+        modes.editingFlags &= ~leadingZeroSuppress; // LZ or LZP
       }
       return;
     }
diff --git a/flang-rt/include/flang-rt/runtime/format.h b/flang-rt/include/flang-rt/runtime/format.h
index 787772935ce8c..c36abaaf15b55 100644
--- a/flang-rt/include/flang-rt/runtime/format.h
+++ b/flang-rt/include/flang-rt/runtime/format.h
@@ -33,6 +33,7 @@ enum EditingFlags {
   blankZero = 1, // BLANK=ZERO or BZ edit
   decimalComma = 2, // DECIMAL=COMMA or DC edit
   signPlus = 4, // SIGN=PLUS or SP edit
+  leadingZeroSuppress = 8, // LZS edit; clear for LZ & LZP
 };
 
 struct MutableModes {
@@ -44,13 +45,7 @@ struct MutableModes {
     return editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
   }
 
-  enum class LeadingZeroMode : std::uint8_t {
-    Processor, // LZ: processor-dependent (default)
-    Suppress, // LZS: suppress optional leading zero
-    Print, // LZP: print optional leading zero
-  };
-
-  std::uint8_t editingFlags{0}; // BN, DP, SS
+  std::uint8_t editingFlags{0}; // BN, DP, SS, LZS
   enum decimal::FortranRounding round{
       executionEnvironment
           .defaultOutputRoundingMode}; // RP/ROUND='PROCESSOR_DEFAULT'
@@ -59,7 +54,6 @@ struct MutableModes {
   short scale{0}; // kP
   bool inNamelist{false}; // skip ! comments
   bool nonAdvancing{false}; // ADVANCE='NO', or $ or \ in FORMAT
-  LeadingZeroMode leadingZero{LeadingZeroMode::Processor}; // LZ/LZS/LZP
 };
 
 // A single edit descriptor extracted from a FORMAT
diff --git a/flang-rt/lib/runtime/edit-output.cpp b/flang-rt/lib/runtime/edit-output.cpp
index e3d39f8874e41..dc90a2a3a16cc 100644
--- a/flang-rt/lib/runtime/edit-output.cpp
+++ b/flang-rt/lib/runtime/edit-output.cpp
@@ -419,28 +419,11 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditEorDOutput(
     if (totalLength > width || !exponent) {
       return EmitRepeated(io_, '*', width);
     }
-    if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 && scale <= 0) {
-      // Optional leading zero position (F202X leading zero control).
-      // When scale > 0 (kP with k > 0), digits are moved before the decimal
-      // point, so the leading zero position is not optional -- skip this.
-      // Value has no digits before the decimal point: "0.xxxE+yy" vs ".xxxE+yy"
-      switch (edit.modes.leadingZero) {
-      case MutableModes::LeadingZeroMode::Print:
-        // LZP: always print the optional leading zero
-        zeroesBeforePoint = 1;
-        ++totalLength;
-        break;
-      case MutableModes::LeadingZeroMode::Suppress:
-        // LZS: never print the optional leading zero
-        break;
-      case MutableModes::LeadingZeroMode::Processor:
-        // LZ: processor-defined; add leading zero only when field has room
-        if (totalLength < width) {
-          zeroesBeforePoint = 1;
-          ++totalLength;
-        }
-        break;
-      }
+    if (totalLength < width && digitsBeforePoint == 0 &&
+        zeroesBeforePoint == 0 &&
+        !(edit.modes.editingFlags & leadingZeroSuppress)) {
+      zeroesBeforePoint = 1;
+      ++totalLength;
     }
     if (totalLength < width && editWidth == 0) {
       width = totalLength;
@@ -571,20 +554,6 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
             digitsAfterPoint + trailingZeroes ==
         0) {
       zeroesBeforePoint = 1; // "."--> "0." (bare decimal point)
-    } else if (digitsBeforePoint == 0 && zeroesBeforePoint == 0 && expo <= 0) {
-      // Optional leading zero position (F202X leading zero control).
-      // Value magnitude < 1: "0.xxx" vs ".xxx"
-      switch (edit.modes.leadingZero) {
-      case MutableModes::LeadingZeroMode::Print:
-        // LZP: always print the optional leading zero
-        zeroesBeforePoint = 1;
-        break;
-      case MutableModes::LeadingZeroMode::Suppress:
-        // LZS: never print the optional leading zero
-        break;
-      case MutableModes::LeadingZeroMode::Processor:
-        break; // handled below after width computation
-      }
     }
     int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint +
         1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingZeroes +
@@ -593,8 +562,8 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
     if (totalLength > width) {
       return EmitRepeated(io_, '*', width);
     }
-    if (edit.modes.leadingZero == MutableModes::LeadingZeroMode::Processor &&
-        totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0) {
+    if (totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0 &&
+        !(edit.modes.editingFlags & leadingZeroSuppress)) {
       zeroesBeforePoint = 1;
       ++totalLength;
     }

>From f3571876967282f14e1cd9f5966c9e7eacebfff9 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Sat, 14 Mar 2026 00:50:55 -0500
Subject: [PATCH 10/12] Implement LEADING_ZERO= specifier for OPEN, INQUIRE and
 WRITE statements; Improve comments and doc wordings.

---
 .../flang-rt/runtime/format-implementation.h  |   4 +-
 flang-rt/lib/runtime/edit-output.cpp          |   2 +-
 flang-rt/lib/runtime/io-api.cpp               |  23 ++++
 flang-rt/lib/runtime/io-stmt.cpp              |   8 ++
 .../unittests/Runtime/LeadingZeroTest.cpp     | 117 ++++++++++++++++++
 flang/docs/F202X.md                           |   8 +-
 flang/docs/FortranStandardsSupport.md         |   2 +-
 flang/include/flang/Parser/parse-tree.h       |  16 ++-
 flang/include/flang/Runtime/io-api.h          |   6 +-
 flang/include/flang/Support/Fortran.h         |   6 +-
 flang/lib/Lower/IO.cpp                        |  13 +-
 flang/lib/Parser/io-parsers.cpp               |  11 ++
 flang/lib/Parser/unparse.cpp                  |  16 ++-
 flang/lib/Semantics/check-io.cpp              |  16 +++
 flang/test/Semantics/io18.f90                 |  27 ++++
 15 files changed, 252 insertions(+), 23 deletions(-)

diff --git a/flang-rt/include/flang-rt/runtime/format-implementation.h b/flang-rt/include/flang-rt/runtime/format-implementation.h
index e469ba627f32c..812802b07ac9f 100644
--- a/flang-rt/include/flang-rt/runtime/format-implementation.h
+++ b/flang-rt/include/flang-rt/runtime/format-implementation.h
@@ -193,7 +193,7 @@ static RT_API_ATTRS bool AbsoluteTabbing(CONTEXT &context, int n) {
 
 template <typename CONTEXT>
 static RT_API_ATTRS void HandleControl(
-    CONTEXT &context, char ch, char next, int n, char next2) {
+    CONTEXT &context, char ch, char next, char next2, int n) {
   MutableModes &modes{context.mutableModes()};
   switch (ch) {
   case 'B':
@@ -504,7 +504,7 @@ RT_API_ATTRS int FormatControl<CONTEXT>::CueUpNextDataEdit(
           repeat = GetIntField(context);
         }
         HandleControl(context, static_cast<char>(ch), static_cast<char>(next),
-            repeat ? *repeat : 1, static_cast<char>(next2));
+            static_cast<char>(next2), repeat ? *repeat : 1);
       }
     } else if (ch == '/') {
       context.AdvanceRecord(repeat && *repeat > 0 ? *repeat : 1);
diff --git a/flang-rt/lib/runtime/edit-output.cpp b/flang-rt/lib/runtime/edit-output.cpp
index dc90a2a3a16cc..ded76f073aa1a 100644
--- a/flang-rt/lib/runtime/edit-output.cpp
+++ b/flang-rt/lib/runtime/edit-output.cpp
@@ -553,7 +553,7 @@ RT_API_ATTRS bool RealOutputEditing<KIND>::EditFOutput(const DataEdit &edit) {
     if (digitsBeforePoint + zeroesBeforePoint + zeroesAfterPoint +
             digitsAfterPoint + trailingZeroes ==
         0) {
-      zeroesBeforePoint = 1; // "."--> "0." (bare decimal point)
+      zeroesBeforePoint = 1; // "." -> "0." (avoid bare decimal point)
     }
     int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint +
         1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingZeroes +
diff --git a/flang-rt/lib/runtime/io-api.cpp b/flang-rt/lib/runtime/io-api.cpp
index f2a1666a0571d..aa3ad9254fe0c 100644
--- a/flang-rt/lib/runtime/io-api.cpp
+++ b/flang-rt/lib/runtime/io-api.cpp
@@ -688,6 +688,29 @@ bool IODEF(SetSign)(Cookie cookie, const char *keyword, std::size_t length) {
   }
 }
 
+bool IODEF(SetLeadingZero)(
+    Cookie cookie, const char *keyword, std::size_t length) {
+  IoStatementState &io{*cookie};
+  if (auto *open{io.get_if<OpenStatementState>()}) {
+    open->set_mustBeFormatted();
+  }
+  static const char *keywords[]{
+      "PRINT", "PROCESSOR_DEFINED", "SUPPRESS", nullptr};
+  switch (IdentifyValue(keyword, length, keywords)) {
+  case 0: // LZP, print leading zero, if the field has room for it
+  case 1: // LZ, processor default, treated as LZP
+    io.mutableModes().editingFlags &= ~leadingZeroSuppress;
+    return true;
+  case 2:
+    io.mutableModes().editingFlags |= leadingZeroSuppress;
+    return true;
+  default:
+    io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
+        "Invalid LEADING_ZERO='%.*s'", static_cast<int>(length), keyword);
+    return false;
+  }
+}
+
 bool IODEF(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
   IoStatementState &io{*cookie};
   auto *open{io.get_if<OpenStatementState>()};
diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp
index 6d3b01af6c792..62044a1d957cf 100644
--- a/flang-rt/lib/runtime/io-stmt.cpp
+++ b/flang-rt/lib/runtime/io-stmt.cpp
@@ -1278,6 +1278,12 @@ bool InquireUnitState::Inquire(
         : mutableModes().editingFlags & decimalComma ? "COMMA"
                                                      : "POINT";
     break;
+  case HashInquiryKeyword("Leading_Zero"):
+    str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
+        ? "UNDEFINED"
+        : mutableModes().editingFlags & leadingZeroSuppress ? "SUPPRESS"
+                                                           : "PRINT";
+    break;
   case HashInquiryKeyword("DELIM"):
     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
       str = "UNDEFINED";
@@ -1503,6 +1509,7 @@ bool InquireNoUnitState::Inquire(
   case HashInquiryKeyword("DECIMAL"):
   case HashInquiryKeyword("DELIM"):
   case HashInquiryKeyword("FORM"):
+  case HashInquiryKeyword("Leading_Zero"):
   case HashInquiryKeyword("NAME"):
   case HashInquiryKeyword("PAD"):
   case HashInquiryKeyword("POSITION"):
@@ -1591,6 +1598,7 @@ bool InquireUnconnectedFileState::Inquire(
   case HashInquiryKeyword("DECIMAL"):
   case HashInquiryKeyword("DELIM"):
   case HashInquiryKeyword("FORM"):
+  case HashInquiryKeyword("Leading_Zero"):
   case HashInquiryKeyword("PAD"):
   case HashInquiryKeyword("POSITION"):
   case HashInquiryKeyword("ROUND"):
diff --git a/flang-rt/unittests/Runtime/LeadingZeroTest.cpp b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
index d151e03923239..a36a95b3c0476 100644
--- a/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
+++ b/flang-rt/unittests/Runtime/LeadingZeroTest.cpp
@@ -260,3 +260,120 @@ TEST(LeadingZeroTests, WithoutCommaSeparator) {
   ASSERT_TRUE(CompareFormatReal("(LZF6.1)", 0.2, "   0.2", got))
       << "Expected '   0.2', got '" << got << "'";
 }
+
+// LEADING_ZERO= specifier via SetLeadingZero runtime API
+TEST(LeadingZeroTests, SetLeadingZero_Suppress) {
+  // LEADING_ZERO='SUPPRESS' should suppress the optional leading zero
+  char buffer[800];
+  const char *format{"(F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  IONAME(SetLeadingZero)(cookie, "SUPPRESS", 8);
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("    .5", got))
+      << "Expected '    .5', got '" << got << "'";
+}
+
+TEST(LeadingZeroTests, SetLeadingZero_Print) {
+  // LEADING_ZERO='PRINT' should print the optional leading zero
+  char buffer[800];
+  const char *format{"(F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  IONAME(SetLeadingZero)(cookie, "PRINT", 5);
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("   0.5", got))
+      << "Expected '   0.5', got '" << got << "'";
+}
+
+TEST(LeadingZeroTests, SetLeadingZero_ProcessorDefined) {
+  // LEADING_ZERO='PROCESSOR_DEFINED' should behave like PRINT (flang default)
+  char buffer[800];
+  const char *format{"(F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  IONAME(SetLeadingZero)(cookie, "PROCESSOR_DEFINED", 17);
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("   0.5", got))
+      << "Expected '   0.5', got '" << got << "'";
+}
+
+// LEADING_ZERO= overridden by LZS/LZP edit descriptors in format
+TEST(LeadingZeroTests, SetLeadingZero_OverriddenByEditDescriptor) {
+  // Set LEADING_ZERO='PRINT' but format uses LZS — LZS should win
+  char buffer[800];
+  const char *format{"(LZS,F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  IONAME(SetLeadingZero)(cookie, "PRINT", 5);
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("    .5", got))
+      << "Expected '    .5', got '" << got << "'";
+}
+
+// LEADING_ZERO= specifier via SetLeadingZero runtime API
+TEST(LeadingZeroTests, SetLeadingZeroSuppressViaAPI) {
+  char buffer[800];
+  const char *format{"(F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  // Set LEADING_ZERO='SUPPRESS'
+  EXPECT_TRUE(IONAME(SetLeadingZero)(cookie, "SUPPRESS", 8));
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("    .5", got))
+      << "Expected '    .5', got '" << got << "'";
+}
+
+TEST(LeadingZeroTests, SetLeadingZeroPrintViaAPI) {
+  char buffer[800];
+  const char *format{"(F6.1)"};
+  auto cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  // Set LEADING_ZERO='PRINT'
+  EXPECT_TRUE(IONAME(SetLeadingZero)(cookie, "PRINT", 5));
+  EXPECT_TRUE(IONAME(OutputReal64)(cookie, 0.5));
+  auto status{IONAME(EndIoStatement)(cookie)};
+  EXPECT_EQ(status, 0);
+  std::string got{buffer, sizeof buffer};
+  auto lastNonBlank{got.find_last_not_of(" ")};
+  if (lastNonBlank != std::string::npos) {
+    got.resize(lastNonBlank + 1);
+  }
+  ASSERT_TRUE(CompareFormattedStrings("   0.5", got))
+      << "Expected '   0.5', got '" << got << "'";
+}
diff --git a/flang/docs/F202X.md b/flang/docs/F202X.md
index 6a303981cf264..c510b03b1820c 100644
--- a/flang/docs/F202X.md
+++ b/flang/docs/F202X.md
@@ -264,11 +264,11 @@ means for controlling the output of leading zero digits.
 Implementation status:
 - `LZ`, `LZS`, `LZP` control edit descriptors, affect only F, E, D, and G
   editing of an output statement: Implemented
-  - `LZ` - Processor-dependent, default (e.g., `0.2`)
-  - `LZS` - Suppress leading zeros (e.g., `.2`)
-  - `LZP` - Print leading zero (e.g. `0.2`)
+  - `LZ` - Processor-dependent (flang treats as LZP)
+  - `LZS` - Suppress leading zero (e.g., `.2`)
+  - `LZP` - Print leading zero when the field is wide enough (e.g., `0.2`)
 - `AT` edit descriptor: Not yet implemented
-- `LEADING_ZERO=specifier` in OPEN statement: Not yet implemented
+- `LEADING_ZERO=` specifier in OPEN, WRITE and INQUIRE statements: Implemented
 
 #### Intrinsic Module Extensions
 
diff --git a/flang/docs/FortranStandardsSupport.md b/flang/docs/FortranStandardsSupport.md
index 02e4653280f12..06a2fce637167 100644
--- a/flang/docs/FortranStandardsSupport.md
+++ b/flang/docs/FortranStandardsSupport.md
@@ -48,7 +48,7 @@ status of all important Fortran 2023 features. The table entries are based on th
 | Extensions for c_f_pointer intrinsic                       | Y      | |
 | Procedures for converting between fortran and c strings    | N      | |
 | The at edit descriptor                                     | N      | |
-| Control over leading zeros in output of real values        | P      | LZ/LZS/LZP edit descriptors implemented; LEADING_ZERO=specifier not yet implemented     |
+| Control over leading zeros in output of real values        | Y      | |
 | Extensions for Namelist                                    | N      | |
 | Allow an object of a type with a coarray ultimate component to be an array or allocatable | N | |
 | Put with Notify                                            | N      | |
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 4aec99c80bdae..034b2d63983de 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -2630,6 +2630,7 @@ using FileNameExpr = ScalarDefaultCharExpr;
 //         ENCODING = scalar-default-char-expr | ERR = label |
 //         FILE = file-name-expr | FORM = scalar-default-char-expr |
 //         IOMSG = iomsg-variable | IOSTAT = scalar-int-variable |
+//         LEADING_ZERO = scalar-default-char-expr |
 //         NEWUNIT = scalar-int-variable | PAD = scalar-default-char-expr |
 //         POSITION = scalar-default-char-expr | RECL = scalar-int-expr |
 //         ROUND = scalar-default-char-expr | SIGN = scalar-default-char-expr |
@@ -2644,7 +2645,7 @@ struct ConnectSpec {
   UNION_CLASS_BOILERPLATE(ConnectSpec);
   struct CharExpr {
     ENUM_CLASS(Kind, Access, Action, Asynchronous, Blank, Decimal, Delim,
-        Encoding, Form, Pad, Position, Round, Sign,
+        Encoding, Form, Leading_Zero, Pad, Position, Round, Sign,
         /* extensions: */ Carriagecontrol, Convert, Dispose)
     TUPLE_CLASS_BOILERPLATE(CharExpr);
     std::tuple<Kind, ScalarDefaultCharExpr> t;
@@ -2692,7 +2693,9 @@ WRAPPER_CLASS(IdVariable, ScalarIntVariable);
 //         DECIMAL = scalar-default-char-expr |
 //         DELIM = scalar-default-char-expr | END = label | EOR = label |
 //         ERR = label | ID = id-variable | IOMSG = iomsg-variable |
-//         IOSTAT = scalar-int-variable | PAD = scalar-default-char-expr |
+//         IOSTAT = scalar-int-variable |
+//         LEADING_ZERO = scalar-default-char-expr |
+//         PAD = scalar-default-char-expr |
 //         POS = scalar-int-expr | REC = scalar-int-expr |
 //         ROUND = scalar-default-char-expr | SIGN = scalar-default-char-expr |
 //         SIZE = scalar-int-variable
@@ -2701,7 +2704,8 @@ WRAPPER_CLASS(EorLabel, Label);
 struct IoControlSpec {
   UNION_CLASS_BOILERPLATE(IoControlSpec);
   struct CharExpr {
-    ENUM_CLASS(Kind, Advance, Blank, Decimal, Delim, Pad, Round, Sign)
+    ENUM_CLASS(Kind, Advance, Blank, Decimal, Delim, Leading_Zero, Pad, Round,
+        Sign)
     TUPLE_CLASS_BOILERPLATE(CharExpr);
     std::tuple<Kind, ScalarDefaultCharExpr> t;
   };
@@ -2837,6 +2841,7 @@ WRAPPER_CLASS(FlushStmt, std::list<PositionOrFlushSpec>);
 //         FORMATTED = scalar-default-char-variable |
 //         ID = scalar-int-expr | IOMSG = iomsg-variable |
 //         IOSTAT = scalar-int-variable |
+//         LEADING_ZERO = scalar-default-char-variable |
 //         NAME = scalar-default-char-variable |
 //         NAMED = scalar-logical-variable |
 //         NEXTREC = scalar-int-variable | NUMBER = scalar-int-variable |
@@ -2861,8 +2866,9 @@ struct InquireSpec {
   UNION_CLASS_BOILERPLATE(InquireSpec);
   struct CharVar {
     ENUM_CLASS(Kind, Access, Action, Asynchronous, Blank, Decimal, Delim,
-        Direct, Encoding, Form, Formatted, Iomsg, Name, Pad, Position, Read,
-        Readwrite, Round, Sequential, Sign, Stream, Status, Unformatted, Write,
+        Direct, Encoding, Form, Formatted, Iomsg, Leading_Zero, Name, Pad,
+        Position, Read, Readwrite, Round, Sequential, Sign, Stream, Status,
+        Unformatted, Write,
         /* extensions: */ Carriagecontrol, Convert, Dispose)
     TUPLE_CLASS_BOILERPLATE(CharVar);
     std::tuple<Kind, ScalarDefaultCharVariable> t;
diff --git a/flang/include/flang/Runtime/io-api.h b/flang/include/flang/Runtime/io-api.h
index fe49af2f61683..86cd4490c2990 100644
--- a/flang/include/flang/Runtime/io-api.h
+++ b/flang/include/flang/Runtime/io-api.h
@@ -238,6 +238,8 @@ bool IODECL(SetRec)(Cookie, std::int64_t);
 bool IODECL(SetRound)(Cookie, const char *, std::size_t);
 // SIGN=PLUS, SUPPRESS, PROCESSOR_DEFINED
 bool IODECL(SetSign)(Cookie, const char *, std::size_t);
+// LEADING_ZERO=PRINT, PROCESSOR_DEFINED, SUPPRESS
+bool IODECL(SetLeadingZero)(Cookie, const char *, std::size_t);
 
 // Data item transfer for modes other than NAMELIST:
 // Any data object that can be passed as an actual argument without the
@@ -298,8 +300,8 @@ bool IODECL(InputDerivedType)(
 
 // Additional specifier interfaces for the connection-list of
 // on OPEN statement (only).  SetBlank(), SetDecimal(),
-// SetDelim(), GetIoMsg(), SetPad(), SetRound(), SetSign(),
-// & SetAsynchronous() are also acceptable for OPEN.
+// SetDelim(), GetIoMsg(), SetLeadingZero(), SetPad(), SetRound(),
+// SetSign(), & SetAsynchronous() are also acceptable for OPEN.
 // ACCESS=SEQUENTIAL, DIRECT, STREAM
 bool IODECL(SetAccess)(Cookie, const char *, std::size_t);
 // ACTION=READ, WRITE, or READWRITE
diff --git a/flang/include/flang/Support/Fortran.h b/flang/include/flang/Support/Fortran.h
index 5ca7882da32fd..dc6f7ec900e74 100644
--- a/flang/include/flang/Support/Fortran.h
+++ b/flang/include/flang/Support/Fortran.h
@@ -48,9 +48,9 @@ ENUM_CLASS(Intent, Default, In, Out, InOut)
 // Union of specifiers for all I/O statements.
 ENUM_CLASS(IoSpecKind, Access, Action, Advance, Asynchronous, Blank, Decimal,
     Delim, Direct, Encoding, End, Eor, Err, Exist, File, Fmt, Form, Formatted,
-    Id, Iomsg, Iostat, Name, Named, Newunit, Nextrec, Nml, Number, Opened, Pad,
-    Pending, Pos, Position, Read, Readwrite, Rec, Recl, Round, Sequential, Sign,
-    Size, Status, Stream, Unformatted, Unit, Write,
+    Id, Iomsg, Iostat, Leading_Zero, Name, Named, Newunit, Nextrec, Nml, Number,
+    Opened, Pad, Pending, Pos, Position, Read, Readwrite, Rec, Recl, Round,
+    Sequential, Sign, Size, Status, Stream, Unformatted, Unit, Write,
     Carriagecontrol, // nonstandard
     Convert, // nonstandard
     Dispose, // nonstandard
diff --git a/flang/lib/Lower/IO.cpp b/flang/lib/Lower/IO.cpp
index de2afb70636d5..e38bee79abb9c 100644
--- a/flang/lib/Lower/IO.cpp
+++ b/flang/lib/Lower/IO.cpp
@@ -84,8 +84,9 @@ static constexpr std::tuple<
     mkIOKey(SetAccess), mkIOKey(SetAction), mkIOKey(SetAdvance),
     mkIOKey(SetAsynchronous), mkIOKey(SetBlank), mkIOKey(SetCarriagecontrol),
     mkIOKey(SetConvert), mkIOKey(SetDecimal), mkIOKey(SetDelim),
-    mkIOKey(SetEncoding), mkIOKey(SetFile), mkIOKey(SetForm), mkIOKey(SetPad),
-    mkIOKey(SetPos), mkIOKey(SetPosition), mkIOKey(SetRec), mkIOKey(SetRecl),
+    mkIOKey(SetEncoding), mkIOKey(SetFile), mkIOKey(SetForm),
+    mkIOKey(SetLeadingZero), mkIOKey(SetPad), mkIOKey(SetPos),
+    mkIOKey(SetPosition), mkIOKey(SetRec), mkIOKey(SetRecl),
     mkIOKey(SetRound), mkIOKey(SetSign), mkIOKey(SetStatus)>
     newIOTable;
 } // namespace Fortran::lower
@@ -1246,6 +1247,10 @@ mlir::Value genIOOption<Fortran::parser::ConnectSpec::CharExpr>(
   case Fortran::parser::ConnectSpec::CharExpr::Kind::Form:
     ioFunc = fir::runtime::getIORuntimeFunc<mkIOKey(SetForm)>(loc, builder);
     break;
+  case Fortran::parser::ConnectSpec::CharExpr::Kind::Leading_Zero:
+    ioFunc =
+        fir::runtime::getIORuntimeFunc<mkIOKey(SetLeadingZero)>(loc, builder);
+    break;
   case Fortran::parser::ConnectSpec::CharExpr::Kind::Pad:
     ioFunc = fir::runtime::getIORuntimeFunc<mkIOKey(SetPad)>(loc, builder);
     break;
@@ -1312,6 +1317,10 @@ mlir::Value genIOOption<Fortran::parser::IoControlSpec::CharExpr>(
   case Fortran::parser::IoControlSpec::CharExpr::Kind::Delim:
     ioFunc = fir::runtime::getIORuntimeFunc<mkIOKey(SetDelim)>(loc, builder);
     break;
+  case Fortran::parser::IoControlSpec::CharExpr::Kind::Leading_Zero:
+    ioFunc =
+        fir::runtime::getIORuntimeFunc<mkIOKey(SetLeadingZero)>(loc, builder);
+    break;
   case Fortran::parser::IoControlSpec::CharExpr::Kind::Pad:
     ioFunc = fir::runtime::getIORuntimeFunc<mkIOKey(SetPad)>(loc, builder);
     break;
diff --git a/flang/lib/Parser/io-parsers.cpp b/flang/lib/Parser/io-parsers.cpp
index 4e969fdec77ad..2d046f613b86d 100644
--- a/flang/lib/Parser/io-parsers.cpp
+++ b/flang/lib/Parser/io-parsers.cpp
@@ -96,6 +96,9 @@ TYPE_PARSER(first(construct<ConnectSpec>(maybe("UNIT ="_tok) >> fileUnitNumber),
         scalarDefaultCharExpr)),
     construct<ConnectSpec>("IOMSG =" >> msgVariable),
     construct<ConnectSpec>("IOSTAT =" >> statVariable),
+    construct<ConnectSpec>(construct<ConnectSpec::CharExpr>(
+        "LEADING_ZERO =" >> pure(ConnectSpec::CharExpr::Kind::Leading_Zero),
+        scalarDefaultCharExpr)),
     construct<ConnectSpec>(construct<ConnectSpec::Newunit>(
         "NEWUNIT =" >> scalar(integer(variable)))),
     construct<ConnectSpec>(construct<ConnectSpec::CharExpr>(
@@ -217,6 +220,10 @@ TYPE_PARSER(first(construct<IoControlSpec>("UNIT =" >> ioUnit),
     construct<IoControlSpec>("ID =" >> idVariable),
     construct<IoControlSpec>("IOMSG = " >> msgVariable),
     construct<IoControlSpec>("IOSTAT = " >> statVariable),
+    construct<IoControlSpec>("LEADING_ZERO =" >>
+        construct<IoControlSpec::CharExpr>(
+            pure(IoControlSpec::CharExpr::Kind::Leading_Zero),
+            scalarDefaultCharExpr)),
     construct<IoControlSpec>("PAD =" >>
         construct<IoControlSpec::CharExpr>(
             pure(IoControlSpec::CharExpr::Kind::Pad), scalarDefaultCharExpr)),
@@ -430,6 +437,10 @@ TYPE_PARSER(first(construct<InquireSpec>(maybe("UNIT ="_tok) >> fileUnitNumber),
     construct<InquireSpec>("IOSTAT =" >>
         construct<InquireSpec::IntVar>(pure(InquireSpec::IntVar::Kind::Iostat),
             scalar(integer(variable)))),
+    construct<InquireSpec>(
+        "LEADING_ZERO =" >> construct<InquireSpec::CharVar>(
+                                pure(InquireSpec::CharVar::Kind::Leading_Zero),
+                                scalarDefaultCharVariable)),
     construct<InquireSpec>("NAME =" >>
         construct<InquireSpec::CharVar>(
             pure(InquireSpec::CharVar::Kind::Name), scalarDefaultCharVariable)),
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index c31eac0b3ff68..26c542b5232eb 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1258,7 +1258,10 @@ class UnparseVisitor {
                                return true;
                              },
                              [&](const ConnectSpec::CharExpr &y) {
-                               Walk(y.t, "=");
+                               Walk(std::get<ConnectSpec::CharExpr::Kind>(
+                                   y.t));
+                               Put('=');
+                               Walk(std::get<ScalarDefaultCharExpr>(y.t));
                                return false;
                              },
                              [&](const MsgVariable &) {
@@ -1343,7 +1346,11 @@ class UnparseVisitor {
             [&](const IoUnit &) { Word("UNIT="); },
             [&](const Format &) { Word("FMT="); },
             [&](const Name &) { Word("NML="); },
-            [&](const IoControlSpec::CharExpr &y) { Walk(y.t, "="); },
+            [&](const IoControlSpec::CharExpr &y) {
+              Walk(std::get<IoControlSpec::CharExpr::Kind>(y.t));
+              Put('=');
+              Walk(std::get<ScalarDefaultCharExpr>(y.t));
+            },
             [&](const IoControlSpec::Asynchronous &) { Word("ASYNCHRONOUS="); },
             [&](const EndLabel &) { Word("END="); },
             [&](const EorLabel &) { Word("EOR="); },
@@ -1425,7 +1432,10 @@ class UnparseVisitor {
                                return true;
                              },
                              [&](const InquireSpec::CharVar &y) {
-                               Walk(y.t, "=");
+                               Walk(std::get<InquireSpec::CharVar::Kind>(
+                                   y.t));
+                               Put('=');
+                               Walk(std::get<ScalarDefaultCharVariable>(y.t));
                                return false;
                              },
                              [&](const InquireSpec::IntVar &y) {
diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 2d7e419e76ce0..2c1f8658448b2 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -137,6 +137,9 @@ void IoChecker::Enter(const parser::ConnectSpec::CharExpr &spec) {
   case ParseKind::Form:
     specKind = IoSpecKind::Form;
     break;
+  case ParseKind::Leading_Zero:
+    specKind = IoSpecKind::Leading_Zero;
+    break;
   case ParseKind::Pad:
     specKind = IoSpecKind::Pad;
     break;
@@ -380,6 +383,9 @@ void IoChecker::Enter(const parser::InquireSpec::CharVar &spec) {
   case ParseKind::Iomsg:
     specKind = IoSpecKind::Iomsg;
     break;
+  case ParseKind::Leading_Zero:
+    specKind = IoSpecKind::Leading_Zero;
+    break;
   case ParseKind::Name:
     specKind = IoSpecKind::Name;
     break;
@@ -520,6 +526,9 @@ void IoChecker::Enter(const parser::IoControlSpec::CharExpr &spec) {
   case ParseKind::Delim:
     specKind = IoSpecKind::Delim;
     break;
+  case ParseKind::Leading_Zero:
+    specKind = IoSpecKind::Leading_Zero;
+    break;
   case ParseKind::Pad:
     specKind = IoSpecKind::Pad;
     break;
@@ -827,6 +836,7 @@ void IoChecker::Leave(const parser::ReadStmt &readStmt) {
   LeaveReadWrite();
   CheckForProhibitedSpecifier(IoSpecKind::Delim); // C1212
   CheckForProhibitedSpecifier(IoSpecKind::Sign); // C1212
+  CheckForProhibitedSpecifier(IoSpecKind::Leading_Zero); // C1212
   CheckForProhibitedSpecifier(IoSpecKind::Rec, IoSpecKind::End); // C1220
   if (specifierSet_.test(IoSpecKind::Size)) {
     // F'2023 C1214 - allow with a warning
@@ -882,6 +892,8 @@ void IoChecker::Leave(const parser::WriteStmt &writeStmt) {
   CheckForProhibitedSpecifier(IoSpecKind::Size); // C1213
   CheckForRequiredSpecifier(
       IoSpecKind::Sign, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227
+  CheckForRequiredSpecifier(IoSpecKind::Leading_Zero,
+      flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227
   CheckForRequiredSpecifier(IoSpecKind::Delim,
       flags_.test(Flag::StarFmt) || specifierSet_.test(IoSpecKind::Nml),
       "FMT=* or NML"); // C1228
@@ -922,6 +934,8 @@ void IoChecker::LeaveReadWrite() const {
       "FMT or NML"); // C1227
   CheckForRequiredSpecifier(IoSpecKind::Round, flags_.test(Flag::FmtOrNml),
       "FMT or NML"); // C1227
+  CheckForRequiredSpecifier(IoSpecKind::Leading_Zero,
+      flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227
   CheckForUselessIomsg();
 }
 
@@ -956,6 +970,8 @@ void IoChecker::CheckStringValue(IoSpecKind specKind, const std::string &value,
       {IoSpecKind::Round,
           {"COMPATIBLE", "DOWN", "NEAREST", "PROCESSOR_DEFINED", "UP", "ZERO"}},
       {IoSpecKind::Sign, {"PLUS", "PROCESSOR_DEFINED", "SUPPRESS"}},
+      {IoSpecKind::Leading_Zero,
+          {"PRINT", "PROCESSOR_DEFINED", "SUPPRESS"}},
       {IoSpecKind::Status,
           // Open values; Close values are {"DELETE", "KEEP"}.
           {"NEW", "OLD", "REPLACE", "SCRATCH", "UNKNOWN"}},
diff --git a/flang/test/Semantics/io18.f90 b/flang/test/Semantics/io18.f90
index 559d98f0d5ceb..686ba648ec3dd 100644
--- a/flang/test/Semantics/io18.f90
+++ b/flang/test/Semantics/io18.f90
@@ -2,6 +2,9 @@
 
 ! F202X leading-zero control edit descriptors: LZ, LZS, LZP
 
+  real :: x
+  character(20) :: lz_val
+
   ! Valid uses of LZ, LZP, LZS in FORMAT statements
 1001 format(LZ, F10.3)
 1002 format(LZP, F10.3)
@@ -96,4 +99,28 @@
 
   !ERROR: Repeat specifier before 'LZS' edit descriptor
 2003 format(2LZS, F10.3)
+
+  ! LEADING_ZERO= specifier tests
+
+  ! Valid LEADING_ZERO= on OPEN
+  open(10, file='test.dat', form='formatted', leading_zero='print')
+  open(10, file='test.dat', form='formatted', leading_zero='suppress')
+  open(10, file='test.dat', form='formatted', leading_zero='processor_defined')
+
+  ! Valid LEADING_ZERO= on WRITE
+  write(10, '(F10.3)', leading_zero='print') 0.5
+  write(10, '(F10.3)', leading_zero='suppress') 0.5
+
+  ! Error: LEADING_ZERO= on READ (prohibited, like SIGN=)
+  !ERROR: READ statement must not have a LEADING_ZERO specifier
+  read(10, '(F10.3)', leading_zero='print') x
+
+  ! Error: invalid LEADING_ZERO= value
+  !ERROR: Invalid LEADING_ZERO value 'bogus'
+  open(10, file='test.dat', form='formatted', leading_zero='bogus')
+
+  ! Valid LEADING_ZERO= on INQUIRE
+  inquire(10, leading_zero=lz_val)
+
+  close(10)
 end

>From 4ced144808bc0f143b7441ffdbf3075550403223 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Sat, 14 Mar 2026 01:29:45 -0500
Subject: [PATCH 11/12] Update code format to be compliant with
 git-clang-format

---
 flang-rt/lib/runtime/io-stmt.cpp        | 2 +-
 flang/include/flang/Parser/parse-tree.h | 4 ++--
 flang/lib/Lower/IO.cpp                  | 4 ++--
 flang/lib/Parser/unparse.cpp            | 6 ++----
 flang/lib/Semantics/check-io.cpp        | 3 +--
 5 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp
index 62044a1d957cf..9eb2dad8e457d 100644
--- a/flang-rt/lib/runtime/io-stmt.cpp
+++ b/flang-rt/lib/runtime/io-stmt.cpp
@@ -1282,7 +1282,7 @@ bool InquireUnitState::Inquire(
     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
         ? "UNDEFINED"
         : mutableModes().editingFlags & leadingZeroSuppress ? "SUPPRESS"
-                                                           : "PRINT";
+                                                            : "PRINT";
     break;
   case HashInquiryKeyword("DELIM"):
     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 034b2d63983de..a0106cac84620 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -2704,8 +2704,8 @@ WRAPPER_CLASS(EorLabel, Label);
 struct IoControlSpec {
   UNION_CLASS_BOILERPLATE(IoControlSpec);
   struct CharExpr {
-    ENUM_CLASS(Kind, Advance, Blank, Decimal, Delim, Leading_Zero, Pad, Round,
-        Sign)
+    ENUM_CLASS(
+        Kind, Advance, Blank, Decimal, Delim, Leading_Zero, Pad, Round, Sign)
     TUPLE_CLASS_BOILERPLATE(CharExpr);
     std::tuple<Kind, ScalarDefaultCharExpr> t;
   };
diff --git a/flang/lib/Lower/IO.cpp b/flang/lib/Lower/IO.cpp
index e38bee79abb9c..d9bbf12dc108b 100644
--- a/flang/lib/Lower/IO.cpp
+++ b/flang/lib/Lower/IO.cpp
@@ -86,8 +86,8 @@ static constexpr std::tuple<
     mkIOKey(SetConvert), mkIOKey(SetDecimal), mkIOKey(SetDelim),
     mkIOKey(SetEncoding), mkIOKey(SetFile), mkIOKey(SetForm),
     mkIOKey(SetLeadingZero), mkIOKey(SetPad), mkIOKey(SetPos),
-    mkIOKey(SetPosition), mkIOKey(SetRec), mkIOKey(SetRecl),
-    mkIOKey(SetRound), mkIOKey(SetSign), mkIOKey(SetStatus)>
+    mkIOKey(SetPosition), mkIOKey(SetRec), mkIOKey(SetRecl), mkIOKey(SetRound),
+    mkIOKey(SetSign), mkIOKey(SetStatus)>
     newIOTable;
 } // namespace Fortran::lower
 
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 26c542b5232eb..2a95f121bd306 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1258,8 +1258,7 @@ class UnparseVisitor {
                                return true;
                              },
                              [&](const ConnectSpec::CharExpr &y) {
-                               Walk(std::get<ConnectSpec::CharExpr::Kind>(
-                                   y.t));
+                               Walk(std::get<ConnectSpec::CharExpr::Kind>(y.t));
                                Put('=');
                                Walk(std::get<ScalarDefaultCharExpr>(y.t));
                                return false;
@@ -1432,8 +1431,7 @@ class UnparseVisitor {
                                return true;
                              },
                              [&](const InquireSpec::CharVar &y) {
-                               Walk(std::get<InquireSpec::CharVar::Kind>(
-                                   y.t));
+                               Walk(std::get<InquireSpec::CharVar::Kind>(y.t));
                                Put('=');
                                Walk(std::get<ScalarDefaultCharVariable>(y.t));
                                return false;
diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 2c1f8658448b2..e46df33b4b85c 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -970,8 +970,7 @@ void IoChecker::CheckStringValue(IoSpecKind specKind, const std::string &value,
       {IoSpecKind::Round,
           {"COMPATIBLE", "DOWN", "NEAREST", "PROCESSOR_DEFINED", "UP", "ZERO"}},
       {IoSpecKind::Sign, {"PLUS", "PROCESSOR_DEFINED", "SUPPRESS"}},
-      {IoSpecKind::Leading_Zero,
-          {"PRINT", "PROCESSOR_DEFINED", "SUPPRESS"}},
+      {IoSpecKind::Leading_Zero, {"PRINT", "PROCESSOR_DEFINED", "SUPPRESS"}},
       {IoSpecKind::Status,
           // Open values; Close values are {"DELETE", "KEEP"}.
           {"NEW", "OLD", "REPLACE", "SCRATCH", "UNKNOWN"}},

>From ee77ec7b67eb0ee117dc3dccd89db60e9178dc44 Mon Sep 17 00:00:00 2001
From: Shandong Lao <shandong.lao at hpe.com>
Date: Fri, 20 Mar 2026 06:13:28 -0500
Subject: [PATCH 12/12] Add SetLeadingZero function to RuntimeFunctions.inc and
 update tests in set-runtime-call-attributes.fir and
 verify-known-runtime-functions.fir

---
 .../flang/Optimizer/Transforms/RuntimeFunctions.inc  |  1 +
 .../test/Transforms/set-runtime-call-attributes.fir  | 12 ++++++++++++
 .../Transforms/verify-known-runtime-functions.fir    |  1 +
 3 files changed, 14 insertions(+)

diff --git a/flang/include/flang/Optimizer/Transforms/RuntimeFunctions.inc b/flang/include/flang/Optimizer/Transforms/RuntimeFunctions.inc
index cb4bf4ecf559d..22243c96eced0 100644
--- a/flang/include/flang/Optimizer/Transforms/RuntimeFunctions.inc
+++ b/flang/include/flang/Optimizer/Transforms/RuntimeFunctions.inc
@@ -96,6 +96,7 @@ KNOWN_IO_FUNC(SetDelim),
 KNOWN_IO_FUNC(SetEncoding),
 KNOWN_IO_FUNC(SetFile),
 KNOWN_IO_FUNC(SetForm),
+KNOWN_IO_FUNC(SetLeadingZero),
 KNOWN_IO_FUNC(SetPad),
 KNOWN_IO_FUNC(SetPos),
 KNOWN_IO_FUNC(SetPosition),
diff --git a/flang/test/Transforms/set-runtime-call-attributes.fir b/flang/test/Transforms/set-runtime-call-attributes.fir
index bdc47c84f4d13..c3d6e4dd5797f 100644
--- a/flang/test/Transforms/set-runtime-call-attributes.fir
+++ b/flang/test/Transforms/set-runtime-call-attributes.fir
@@ -868,6 +868,17 @@ module {
     %0 = fir.call @_FortranAioSetForm(%arg0, %arg1, %arg2) : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
     return %0 : i1
   }
+// CHECK-LABEL:   func.func @test__FortranAioSetLeadingZero(
+// CHECK-SAME:                                              %[[VAL_0:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i8>,
+// CHECK-SAME:                                              %[[VAL_1:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i8>,
+// CHECK-SAME:                                              %[[VAL_2:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: i64) -> i1 {
+// CHECK:           %[[VAL_3:.*]] = fir.call @_FortranAioSetLeadingZero(%[[VAL_0]], %[[VAL_1]], %[[VAL_2]]) {fir.llvm_memory = #llvm.memory_effects<other = none, argMem = readwrite, inaccessibleMem = readwrite, errnoMem = none, targetMem0 = none, targetMem1 = none>, llvm.nocallback, llvm.nosync} : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+// CHECK:           return %[[VAL_3]] : i1
+// CHECK:         }
+  func.func @test__FortranAioSetLeadingZero(%arg0: !fir.ref<i8>, %arg1: !fir.ref<i8>, %arg2: i64) -> i1 {
+    %0 = fir.call @_FortranAioSetLeadingZero(%arg0, %arg1, %arg2) : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+    return %0 : i1
+  }
 // CHECK-LABEL:   func.func @test__FortranAioSetPad(
 // CHECK-SAME:                                      %[[VAL_0:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i8>,
 // CHECK-SAME:                                      %[[VAL_1:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i8>,
@@ -1028,6 +1039,7 @@ module {
   func.func private @_FortranAioSetEncoding(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
   func.func private @_FortranAioSetFile(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
   func.func private @_FortranAioSetForm(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
+  func.func private @_FortranAioSetLeadingZero(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
   func.func private @_FortranAioSetPad(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
   func.func private @_FortranAioSetPos(!fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
   func.func private @_FortranAioSetPosition(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
diff --git a/flang/test/Transforms/verify-known-runtime-functions.fir b/flang/test/Transforms/verify-known-runtime-functions.fir
index 902d701424f6f..e87fac5601c56 100644
--- a/flang/test/Transforms/verify-known-runtime-functions.fir
+++ b/flang/test/Transforms/verify-known-runtime-functions.fir
@@ -90,6 +90,7 @@
 // CHECK-NEXT: func.func private @_FortranAioSetEncoding(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
 // CHECK-NEXT: func.func private @_FortranAioSetFile(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
 // CHECK-NEXT: func.func private @_FortranAioSetForm(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
+// CHECK-NEXT: func.func private @_FortranAioSetLeadingZero(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
 // CHECK-NEXT: func.func private @_FortranAioSetPad(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
 // CHECK-NEXT: func.func private @_FortranAioSetPos(!fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}
 // CHECK-NEXT: func.func private @_FortranAioSetPosition(!fir.ref<i8>, !fir.ref<i8>, i64) -> i1 attributes {fir.io, fir.runtime}



More information about the flang-commits mailing list