[flang-commits] [flang] [flang] Add traits to more AST nodes (PR #175578)

Krzysztof Parzyszek via flang-commits flang-commits at lists.llvm.org
Tue Jan 20 07:22:05 PST 2026


https://github.com/kparzysz updated https://github.com/llvm/llvm-project/pull/175578

>From 845e9155bcdbe85ae7ff34b575d7d87c1070c1fa Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 9 Jan 2026 14:43:12 -0600
Subject: [PATCH 1/6] [flang] Remove unnecessary overloads of Walk in parse
 tree visitor

These actions are now performed by the trait-based Walk functions.
---
 .../include/flang/Parser/parse-tree-visitor.h | 72 -------------------
 1 file changed, 72 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree-visitor.h b/flang/include/flang/Parser/parse-tree-visitor.h
index b7d41254c75c4..61470f8c30604 100644
--- a/flang/include/flang/Parser/parse-tree-visitor.h
+++ b/flang/include/flang/Parser/parse-tree-visitor.h
@@ -433,35 +433,6 @@ struct ParseTreeVisitorLookupScope {
         x, mutator);
   }
 
-  template <typename V> static void Walk(const Designator &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      Walk(x.u, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(Designator &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      Walk(x.u, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(const FunctionReference &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      Walk(x.v, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(FunctionReference &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      Walk(x.v, mutator);
-      mutator.Post(x);
-    }
-  }
   template <typename V> static void Walk(const CallStmt &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.source, visitor);
@@ -513,22 +484,6 @@ struct ParseTreeVisitorLookupScope {
     }
   }
   template <typename V>
-  static void Walk(const SignedIntLiteralConstant &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      Walk(x.t, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M>
-  static void Walk(SignedIntLiteralConstant &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      Walk(x.t, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
   static void Walk(const RealLiteralConstant &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.real, visitor);
@@ -736,33 +691,6 @@ struct ParseTreeVisitorLookupScope {
       mutator.Post(x);
     }
   }
-  template <typename V>
-  static void Walk(const CompilerDirective &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      Walk(x.u, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(CompilerDirective &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      Walk(x.u, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(const CompilerDirective::Unrecognized &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      visitor.Post(x);
-    }
-  }
-  template <typename M>
-  static void Walk(CompilerDirective::Unrecognized &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      mutator.Post(x);
-    }
-  }
 };
 } // namespace detail
 

>From f7b0374e8dce1abc307d1a3aca74f1be33ad6811 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Fri, 9 Jan 2026 15:50:38 -0600
Subject: [PATCH 2/6] [flang] Add traits to more AST nodes

Follow-up to PR175211.

There are still a few AST nodes that don't have any of the standard
traits (Wrapper/Tuple/etc). Because of that they require special
handling in the parse tree visitor.

Convert a subset of these nodes to the typical format, and remove the
special cases from the parse tree visitor.
---
 .../include/flang/Parser/parse-tree-visitor.h | 110 ------------------
 flang/include/flang/Parser/parse-tree.h       |  53 +++------
 flang/lib/Lower/Bridge.cpp                    |   3 +-
 flang/lib/Lower/PFTBuilder.cpp                |   6 +-
 flang/lib/Parser/parse-tree.cpp               |  16 +--
 flang/lib/Parser/unparse.cpp                  |  38 +++---
 flang/lib/Semantics/check-do-forall.cpp       |   4 +-
 flang/lib/Semantics/expression.cpp            |  20 ++--
 flang/lib/Semantics/program-tree.cpp          |   5 +-
 flang/lib/Semantics/resolve-names.cpp         |  72 ++++++++----
 flang/lib/Semantics/tools.cpp                 |   6 +-
 11 files changed, 121 insertions(+), 212 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree-visitor.h b/flang/include/flang/Parser/parse-tree-visitor.h
index 61470f8c30604..191e74ee89f1c 100644
--- a/flang/include/flang/Parser/parse-tree-visitor.h
+++ b/flang/include/flang/Parser/parse-tree-visitor.h
@@ -433,38 +433,6 @@ struct ParseTreeVisitorLookupScope {
         x, mutator);
   }
 
-  template <typename V> static void Walk(const CallStmt &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      Walk(x.call, visitor);
-      Walk(x.chevrons, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(CallStmt &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      Walk(x.call, mutator);
-      Walk(x.chevrons, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V> static void Walk(const PartRef &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.name, visitor);
-      Walk(x.subscripts, visitor);
-      Walk(x.imageSelector, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(PartRef &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.name, mutator);
-      Walk(x.subscripts, mutator);
-      Walk(x.imageSelector, mutator);
-      mutator.Post(x);
-    }
-  }
   template <typename V> static void Walk(const ReadStmt &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.iounit, visitor);
@@ -484,35 +452,6 @@ struct ParseTreeVisitorLookupScope {
     }
   }
   template <typename V>
-  static void Walk(const RealLiteralConstant &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.real, visitor);
-      Walk(x.kind, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(RealLiteralConstant &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.real, mutator);
-      Walk(x.kind, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(const RealLiteralConstant::Real &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.source, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M>
-  static void Walk(RealLiteralConstant::Real &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.source, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
   static void Walk(const StructureComponent &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.base, visitor);
@@ -527,55 +466,6 @@ struct ParseTreeVisitorLookupScope {
       mutator.Post(x);
     }
   }
-  template <typename V> static void Walk(const Suffix &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.binding, visitor);
-      Walk(x.resultName, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(Suffix &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.binding, mutator);
-      Walk(x.resultName, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(const TypeBoundProcedureStmt::WithInterface &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.interfaceName, visitor);
-      Walk(x.attributes, visitor);
-      Walk(x.bindingNames, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M>
-  static void Walk(TypeBoundProcedureStmt::WithInterface &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.interfaceName, mutator);
-      Walk(x.attributes, mutator);
-      Walk(x.bindingNames, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(
-      const TypeBoundProcedureStmt::WithoutInterface &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.attributes, visitor);
-      Walk(x.declarations, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M>
-  static void Walk(TypeBoundProcedureStmt::WithoutInterface &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.attributes, mutator);
-      Walk(x.declarations, mutator);
-      mutator.Post(x);
-    }
-  }
   template <typename V> static void Walk(const UseStmt &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.nature, visitor);
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 37c0f699361eb..1d72c1cb1c80e 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -807,16 +807,14 @@ enum class Sign { Positive, Negative };
 // R715 significand -> digit-string . [digit-string] | . digit-string
 // R717 exponent -> signed-digit-string
 struct RealLiteralConstant {
-  BOILERPLATE(RealLiteralConstant);
+  TUPLE_CLASS_BOILERPLATE(RealLiteralConstant);
   struct Real {
+    using EmptyTrait = std::true_type;
     COPY_AND_ASSIGN_BOILERPLATE(Real);
     Real() {}
     CharBlock source;
   };
-  RealLiteralConstant(Real &&r, std::optional<KindParam> &&k)
-      : real{std::move(r)}, kind{std::move(k)} {}
-  Real real;
-  std::optional<KindParam> kind;
+  std::tuple<Real, std::optional<KindParam>> t;
 };
 
 // R713 signed-real-literal-constant -> [sign] real-literal-constant
@@ -1133,21 +1131,12 @@ struct TypeBoundProcDecl {
 struct TypeBoundProcedureStmt {
   UNION_CLASS_BOILERPLATE(TypeBoundProcedureStmt);
   struct WithoutInterface {
-    BOILERPLATE(WithoutInterface);
-    WithoutInterface(
-        std::list<BindAttr> &&as, std::list<TypeBoundProcDecl> &&ds)
-        : attributes(std::move(as)), declarations(std::move(ds)) {}
-    std::list<BindAttr> attributes;
-    std::list<TypeBoundProcDecl> declarations;
+    TUPLE_CLASS_BOILERPLATE(WithoutInterface);
+    std::tuple<std::list<BindAttr>, std::list<TypeBoundProcDecl>> t;
   };
   struct WithInterface {
-    BOILERPLATE(WithInterface);
-    WithInterface(Name &&n, std::list<BindAttr> &&as, std::list<Name> &&bs)
-        : interfaceName(std::move(n)), attributes(std::move(as)),
-          bindingNames(std::move(bs)) {}
-    Name interfaceName;
-    std::list<BindAttr> attributes;
-    std::list<Name> bindingNames;
+    TUPLE_CLASS_BOILERPLATE(WithInterface);
+    std::tuple<Name, std::list<BindAttr>, std::list<Name>> t;
   };
   std::variant<WithoutInterface, WithInterface> u;
 };
@@ -1794,14 +1783,8 @@ struct Expr {
 
 // R912 part-ref -> part-name [( section-subscript-list )] [image-selector]
 struct PartRef {
-  BOILERPLATE(PartRef);
-  PartRef(Name &&n, std::list<SectionSubscript> &&ss,
-      std::optional<ImageSelector> &&is)
-      : name{std::move(n)}, subscripts(std::move(ss)),
-        imageSelector{std::move(is)} {}
-  Name name;
-  std::list<SectionSubscript> subscripts;
-  std::optional<ImageSelector> imageSelector;
+  TUPLE_CLASS_BOILERPLATE(PartRef);
+  std::tuple<Name, std::list<SectionSubscript>, std::optional<ImageSelector>> t;
 };
 
 // R911 data-ref -> part-ref [% part-ref]...
@@ -3121,13 +3104,10 @@ struct PrefixSpec {
 //         proc-language-binding-spec [RESULT ( result-name )] |
 //         RESULT ( result-name ) [proc-language-binding-spec]
 struct Suffix {
-  BOILERPLATE(Suffix);
+  TUPLE_CLASS_BOILERPLATE(Suffix);
   Suffix(LanguageBindingSpec &&lbs, std::optional<Name> &&rn)
-      : binding(std::move(lbs)), resultName(std::move(rn)) {}
-  Suffix(Name &&rn, std::optional<LanguageBindingSpec> &&lbs)
-      : binding(std::move(lbs)), resultName(std::move(rn)) {}
-  std::optional<LanguageBindingSpec> binding;
-  std::optional<Name> resultName;
+      : t(std::move(rn), std::move(lbs)) {}
+  std::tuple<std::optional<Name>, std::optional<LanguageBindingSpec>> t;
 };
 
 // R1530 function-stmt ->
@@ -3262,7 +3242,7 @@ struct FunctionReference {
 // (CUDA) chevrons -> <<< * | scalar-expr, scalar-expr [,
 //          scalar-expr [, scalar-int-expr ] ] >>>
 struct CallStmt {
-  BOILERPLATE(CallStmt);
+  TUPLE_CLASS_BOILERPLATE(CallStmt);
   WRAPPER_CLASS(StarOrExpr, std::optional<ScalarExpr>);
   struct Chevrons {
     TUPLE_CLASS_BOILERPLATE(Chevrons);
@@ -3271,10 +3251,9 @@ struct CallStmt {
         t;
   };
   explicit CallStmt(ProcedureDesignator &&pd, std::optional<Chevrons> &&ch,
-      std::list<ActualArgSpec> &&args)
-      : call{std::move(pd), std::move(args)}, chevrons{std::move(ch)} {}
-  Call call;
-  std::optional<Chevrons> chevrons;
+      std::list<ActualArgSpec> &&args) :
+      CallStmt(Call{std::move(pd), std::move(args)}, std::move(ch)) {}
+  std::tuple<Call, std::optional<Chevrons>> t;
   CharBlock source;
   mutable TypedCall typedCall; // filled by semantics
 };
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 9224bc2be1028..609050511f6c9 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2048,8 +2048,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     llvm::SmallVector<int64_t> indexList;
     llvm::SmallVector<Fortran::parser::Label> labelList;
     int64_t index = 0;
+    const auto &call{std::get<Fortran::parser::Call>(stmt.t)};
     for (const Fortran::parser::ActualArgSpec &arg :
-         std::get<std::list<Fortran::parser::ActualArgSpec>>(stmt.call.t)) {
+         std::get<std::list<Fortran::parser::ActualArgSpec>>(call.t)) {
       const auto &actual = std::get<Fortran::parser::ActualArg>(arg.t);
       if (const auto *altReturn =
               std::get_if<Fortran::parser::AltReturnSpec>(&actual.u)) {
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 2dc7032b85f42..1a7fb41f3273c 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -142,8 +142,9 @@ class PFTBuilder {
   ///  - 17.4p5 (The rounding modes)
   ///  - 17.6p1 (Halting)
   void checkForFPEnvironmentCalls(const parser::CallStmt &callStmt) {
+    const auto &call = std::get<parser::Call>(callStmt.t);
     const auto *callName = std::get_if<parser::Name>(
-        &std::get<parser::ProcedureDesignator>(callStmt.call.t).u);
+        &std::get<parser::ProcedureDesignator>(call.t).u);
     if (!callName)
       return;
     const Fortran::semantics::Symbol &procSym = callName->symbol->GetUltimate();
@@ -919,8 +920,9 @@ class PFTBuilder {
           // Action statements (except IO statements)
           [&](const parser::CallStmt &s) {
             // Look for alternate return specifiers.
+            const auto &call = std::get<parser::Call>(s.t);
             const auto &args =
-                std::get<std::list<parser::ActualArgSpec>>(s.call.t);
+                std::get<std::list<parser::ActualArgSpec>>(call.t);
             for (const auto &arg : args) {
               const auto &actual = std::get<parser::ActualArg>(arg.t);
               if (const auto *altReturn =
diff --git a/flang/lib/Parser/parse-tree.cpp b/flang/lib/Parser/parse-tree.cpp
index dae1912afa99e..819470a43b0c8 100644
--- a/flang/lib/Parser/parse-tree.cpp
+++ b/flang/lib/Parser/parse-tree.cpp
@@ -50,20 +50,22 @@ bool Designator::EndsInBareName() const {
 }
 
 // R911 data-ref -> part-ref [% part-ref]...
-DataRef::DataRef(std::list<PartRef> &&prl) : u{std::move(prl.front().name)} {
+DataRef::DataRef(std::list<PartRef> &&prl)
+    : u{std::move(std::get<Name>(prl.front().t))} {
   for (bool first{true}; !prl.empty(); first = false, prl.pop_front()) {
-    PartRef &pr{prl.front()};
+    auto &&[name, subscripts, imageSelector]{prl.front().t};
+    // PartRef &pr{prl.front()};
     if (!first) {
       u = common::Indirection<StructureComponent>::Make(
-          std::move(*this), std::move(pr.name));
+          std::move(*this), std::move(name));
     }
-    if (!pr.subscripts.empty()) {
+    if (!subscripts.empty()) {
       u = common::Indirection<ArrayElement>::Make(
-          std::move(*this), std::move(pr.subscripts));
+          std::move(*this), std::move(subscripts));
     }
-    if (pr.imageSelector) {
+    if (imageSelector) {
       u = common::Indirection<CoindexedNamedObject>::Make(
-          std::move(*this), std::move(*pr.imageSelector));
+          std::move(*this), std::move(*imageSelector));
     }
   }
 }
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 9b31454537df5..0465b67aed08d 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -198,7 +198,8 @@ class UnparseVisitor {
     Put(x == Sign::Negative ? '-' : '+');
   }
   void Unparse(const RealLiteralConstant &x) { // R714, R715
-    Put(x.real.source.ToString()), Walk("_", x.kind);
+    const auto &[real, kind]{x.t};
+    Put(real.source.ToString()), Walk("_", kind);
   }
   void Unparse(const ComplexLiteralConstant &x) { // R718 - R720
     Put('('), Walk(x.t, ","), Put(')');
@@ -371,13 +372,15 @@ class UnparseVisitor {
     Word("PRIVATE");
   }
   void Unparse(const TypeBoundProcedureStmt::WithoutInterface &x) { // R749
-    Word("PROCEDURE"), Walk(", ", x.attributes, ", ");
-    Put(" :: "), Walk(x.declarations, ", ");
+    const auto &[attributes, declarations]{x.t};
+    Word("PROCEDURE"), Walk(", ", attributes, ", ");
+    Put(" :: "), Walk(declarations, ", ");
   }
   void Unparse(const TypeBoundProcedureStmt::WithInterface &x) {
-    Word("PROCEDURE("), Walk(x.interfaceName), Put("), ");
-    Walk(x.attributes);
-    Put(" :: "), Walk(x.bindingNames, ", ");
+    const auto &[interfaceName, attributes, bindingNames]{x.t};
+    Word("PROCEDURE("), Walk(interfaceName), Put("), ");
+    Walk(attributes);
+    Put(" :: "), Walk(bindingNames, ", ");
   }
   void Unparse(const TypeBoundProcDecl &x) { // R750
     Walk(std::get<Name>(x.t));
@@ -789,9 +792,10 @@ class UnparseVisitor {
     Walk(x.t, ":");
   }
   void Unparse(const PartRef &x) { // R912
-    Walk(x.name);
-    Walk("(", x.subscripts, ",", ")");
-    Walk(x.imageSelector);
+    const auto &[name, subscripts, imageSelector]{x.t};
+    Walk(name);
+    Walk("(", subscripts, ",", ")");
+    Walk(imageSelector);
   }
   void Unparse(const StructureComponent &x) { // R913
     Walk(x.base);
@@ -1692,15 +1696,16 @@ class UnparseVisitor {
     Put('('), Walk(std::get<std::list<ActualArgSpec>>(x.v.t), ", "), Put(')');
   }
   void Unparse(const CallStmt &x) { // R1521
+    const auto &[call, chevrons]{x.t};
     if (asFortran_ && x.typedCall.get()) {
       Put(' ');
       asFortran_->call(out_, *x.typedCall);
       Put('\n');
     } else {
-      const auto &pd{std::get<ProcedureDesignator>(x.call.t)};
+      const auto &pd{std::get<ProcedureDesignator>(call.t)};
       Word("CALL "), Walk(pd);
-      Walk("<<<", x.chevrons, ">>>");
-      const auto &args{std::get<std::list<ActualArgSpec>>(x.call.t)};
+      Walk("<<<", chevrons, ">>>");
+      const auto &args{std::get<std::list<ActualArgSpec>>(call.t)};
       if (args.empty()) {
         if (std::holds_alternative<ProcComponentRef>(pd.u)) {
           Put("()"); // pgf90 crashes on CALL to tbp without parentheses
@@ -1745,11 +1750,12 @@ class UnparseVisitor {
     Walk(" ", std::get<std::optional<Suffix>>(x.t)), Indent();
   }
   void Unparse(const Suffix &x) { // R1532
-    if (x.resultName) {
-      Word("RESULT("), Walk(x.resultName), Put(')');
-      Walk(" ", x.binding);
+    const auto &[resultName, binding]{x.t};
+    if (resultName) {
+      Word("RESULT("), Walk(resultName), Put(')');
+      Walk(" ", binding);
     } else {
-      Walk(x.binding);
+      Walk(binding);
     }
   }
   void Unparse(const EndFunctionStmt &x) { // R1533
diff --git a/flang/lib/Semantics/check-do-forall.cpp b/flang/lib/Semantics/check-do-forall.cpp
index 8a473406b8200..c90479d9e352e 100644
--- a/flang/lib/Semantics/check-do-forall.cpp
+++ b/flang/lib/Semantics/check-do-forall.cpp
@@ -1101,8 +1101,8 @@ static void CheckIfArgIsDoVar(const evaluate::ActualArgument &arg,
 // messages.
 void DoForallChecker::Leave(const parser::CallStmt &callStmt) {
   if (const auto &typedCall{callStmt.typedCall}) {
-    const auto &parsedArgs{
-        std::get<std::list<parser::ActualArgSpec>>(callStmt.call.t)};
+    const auto &call{std::get<parser::Call>(callStmt.t)};
+    const auto &parsedArgs{std::get<std::list<parser::ActualArgSpec>>(call.t)};
     auto parsedArgIter{parsedArgs.begin()};
     const evaluate::ActualArguments &checkedArgs{typedCall->arguments()};
     for (const auto &checkedOptionalArg : checkedArgs) {
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index b3643e0d35d5f..ece5d23bb59d2 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -869,19 +869,20 @@ struct RealTypeVisitor {
 
 // Reads a real literal constant and encodes it with the right kind.
 MaybeExpr ExpressionAnalyzer::Analyze(const parser::RealLiteralConstant &x) {
+  const auto &[xreal, xkind](x.t);
   // Use a local message context around the real literal for better
   // provenance on any messages.
-  auto restorer{GetContextualMessages().SetLocation(x.real.source)};
+  auto restorer{GetContextualMessages().SetLocation(xreal.source)};
   // If a kind parameter appears, it defines the kind of the literal and the
   // letter used in an exponent part must be 'E' (e.g., the 'E' in
   // "6.02214E+23").  In the absence of an explicit kind parameter, any
   // exponent letter determines the kind.  Otherwise, defaults apply.
   auto &defaults{context_.defaultKinds()};
   int defaultKind{defaults.GetDefaultKind(TypeCategory::Real)};
-  const char *end{x.real.source.end()};
+  const char *end{xreal.source.end()};
   char expoLetter{' '};
   std::optional<int> letterKind;
-  for (const char *p{x.real.source.begin()}; p < end; ++p) {
+  for (const char *p{xreal.source.begin()}; p < end; ++p) {
     if (parser::IsLetter(*p)) {
       expoLetter = *p;
       switch (expoLetter) {
@@ -905,20 +906,20 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::RealLiteralConstant &x) {
   }
   // C716 requires 'E' as an exponent.
   // Extension: allow exponent-letter matching the kind-param.
-  auto kind{AnalyzeKindParam(x.kind, defaultKind)};
+  auto kind{AnalyzeKindParam(xkind, defaultKind)};
   if (letterKind && expoLetter != 'e') {
     if (kind != *letterKind) {
       Warn(common::LanguageFeature::ExponentMatchingKindParam,
           "Explicit kind parameter on real constant disagrees with exponent letter '%c'"_warn_en_US,
           expoLetter);
-    } else if (x.kind) {
+    } else if (xkind) {
       Warn(common::LanguageFeature::ExponentMatchingKindParam,
           "Explicit kind parameter together with non-'E' exponent letter is not standard"_port_en_US);
     }
   }
-  bool isDefaultKind{!x.kind && letterKind.value_or('e') == 'e'};
+  bool isDefaultKind{!xkind && letterKind.value_or('e') == 'e'};
   auto result{common::SearchTypes(RealTypeVisitor{
-      kind, x.real.source, GetFoldingContext(), isDefaultKind})};
+      kind, xreal.source, GetFoldingContext(), isDefaultKind})};
   if (!result) { // C717
     Say("Unsupported REAL(KIND=%d)"_err_en_US, kind);
   }
@@ -3404,7 +3405,8 @@ std::optional<Chevrons> ExpressionAnalyzer::AnalyzeChevrons(
         which);
     return false;
   }};
-  if (const auto &chevrons{call.chevrons}) {
+  if (const auto &chevrons{
+          std::get<std::optional<parser::CallStmt::Chevrons>>(call.t)}) {
     auto &starOrExpr{std::get<0>(chevrons->t)};
     if (starOrExpr.v) {
       if (auto expr{Analyze(*starOrExpr.v)};
@@ -3504,7 +3506,7 @@ static bool HasAlternateReturns(const evaluate::ActualArguments &args) {
 }
 
 void ExpressionAnalyzer::Analyze(const parser::CallStmt &callStmt) {
-  const parser::Call &call{callStmt.call};
+  const auto &call{std::get<parser::Call>(callStmt.t)};
   auto restorer{GetContextualMessages().SetLocation(callStmt.source)};
   ArgumentAnalyzer analyzer{*this, callStmt.source, true /* isProcedureCall */};
   const auto &actualArgList{std::get<std::list<parser::ActualArgSpec>>(call.t)};
diff --git a/flang/lib/Semantics/program-tree.cpp b/flang/lib/Semantics/program-tree.cpp
index 86085e78803a2..89517982de67c 100644
--- a/flang/lib/Semantics/program-tree.cpp
+++ b/flang/lib/Semantics/program-tree.cpp
@@ -161,8 +161,9 @@ std::optional<ProgramTree> ProgramTree::Build(
   const parser::LanguageBindingSpec *bindingSpec{};
   if (const auto &suffix{
           std::get<std::optional<parser::Suffix>>(stmt.statement.t)}) {
-    if (suffix->binding) {
-      bindingSpec = &*suffix->binding;
+    if (const auto &binding{
+            std::get<std::optional<parser::LanguageBindingSpec>>(suffix->t)}) {
+      bindingSpec = &*binding;
     }
   }
   return BuildSubprogramTree(name, context, x)
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 527be8645ff81..04d734781265e 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -4660,17 +4660,17 @@ bool SubprogramVisitor::HandleStmtFunction(const parser::StmtFunctionStmt &x) {
 }
 
 bool SubprogramVisitor::Pre(const parser::Suffix &suffix) {
-  if (suffix.resultName) {
+  if (const auto &resultName{std::get<std::optional<parser::Name>>(suffix.t)}) {
     if (IsFunction(currScope())) {
       if (FuncResultStack::FuncInfo * info{funcResultStack().Top()}) {
         if (info->inFunctionStmt) {
-          info->resultName = &suffix.resultName.value();
+          info->resultName = &resultName.value();
         } else {
           // will check the result name in Post(EntryStmt)
         }
       }
     } else {
-      Message &msg{Say(*suffix.resultName,
+      Message &msg{Say(*resultName,
           "RESULT(%s) may appear only in a function"_err_en_US)};
       if (const Symbol * subprogram{InclusiveScope().symbol()}) {
         msg.Attach(subprogram->name(), "Containing subprogram"_en_US);
@@ -4846,7 +4846,10 @@ void SubprogramVisitor::Post(const parser::InterfaceBody::Function &x) {
   const auto &stmt{std::get<parser::Statement<parser::FunctionStmt>>(x.t)};
   const auto &maybeSuffix{
       std::get<std::optional<parser::Suffix>>(stmt.statement.t)};
-  EndSubprogram(stmt.source, maybeSuffix ? &maybeSuffix->binding : nullptr);
+  EndSubprogram(stmt.source,
+      maybeSuffix ? &std::get<std::optional<parser::LanguageBindingSpec>>(
+                        maybeSuffix->t)
+                  : nullptr);
 }
 
 bool SubprogramVisitor::Pre(const parser::SubroutineStmt &stmt) {
@@ -4961,7 +4964,7 @@ Symbol &SubprogramVisitor::PostSubprogramStmt() {
 
 void SubprogramVisitor::Post(const parser::EntryStmt &stmt) {
   if (const auto &suffix{std::get<std::optional<parser::Suffix>>(stmt.t)}) {
-    Walk(suffix->binding);
+    Walk(std::get<std::optional<parser::LanguageBindingSpec>>(suffix->t));
   }
   PostEntryStmt(stmt);
   EndAttrs();
@@ -5000,9 +5003,17 @@ void SubprogramVisitor::CreateEntry(
           : Symbol::Flag::Subroutine};
   Attrs attrs;
   const auto &suffix{std::get<std::optional<parser::Suffix>>(stmt.t)};
-  bool hasGlobalBindingName{outer.IsGlobal() && suffix && suffix->binding &&
-      std::get<std::optional<parser::ScalarDefaultCharConstantExpr>>(
-          suffix->binding->t)
+  const auto *binding{[&]() {
+    if (suffix) {
+      if (auto &b{std::get<std::optional<parser::LanguageBindingSpec>>(
+              suffix->t)}) {
+        return &*b;
+      }
+    }
+    return static_cast<const parser::LanguageBindingSpec *>(nullptr);
+  }()};
+  bool hasGlobalBindingName{outer.IsGlobal() && binding &&
+      std::get<std::optional<parser::ScalarDefaultCharConstantExpr>>(binding->t)
           .has_value()};
   if (!hasGlobalBindingName) {
     if (Symbol * extant{FindSymbol(outer, entryName)}) {
@@ -5019,10 +5030,17 @@ void SubprogramVisitor::CreateEntry(
       attrs = extant->attrs();
     }
   }
+  const auto *resultName{[&]() {
+    if (suffix) {
+      if (auto &n{std::get<std::optional<parser::Name>>(suffix->t)}) {
+        return &*n;
+      }
+    }
+    return static_cast<const parser::Name *>(nullptr);
+  }()};
   std::optional<SourceName> distinctResultName;
-  if (suffix && suffix->resultName &&
-      suffix->resultName->source != entryName.source) {
-    distinctResultName = suffix->resultName->source;
+  if (resultName && resultName->source != entryName.source) {
+    distinctResultName = resultName->source;
   }
   if (outer.IsModule() && !attrs.test(Attr::PRIVATE)) {
     attrs.set(Attr::PUBLIC);
@@ -5060,7 +5078,7 @@ void SubprogramVisitor::CreateEntry(
     if (distinctResultName) {
       // An explicit RESULT() can also be an explicit RESULT()
       // of the function or another ENTRY.
-      if (auto iter{currScope().find(suffix->resultName->source)};
+      if (auto iter{currScope().find(resultName->source)};
           iter != currScope().end()) {
         result = &*iter->second;
       }
@@ -5076,7 +5094,7 @@ void SubprogramVisitor::CreateEntry(
         result = nullptr;
       }
       if (result) {
-        Resolve(*suffix->resultName, *result);
+        Resolve(*resultName, *result);
       }
     } else {
       result = &MakeSymbol(entryName.source, Attrs{}, std::move(resultDetails));
@@ -5315,7 +5333,8 @@ void SubprogramVisitor::EndSubprogram(
       if (const auto &suffix{
               std::get<std::optional<parser::Suffix>>(entryStmt.t)}) {
         const auto &name{std::get<parser::Name>(entryStmt.t)};
-        HandleLanguageBinding(name.symbol, name.source, &suffix->binding);
+        HandleLanguageBinding(name.symbol, name.source,
+            &std::get<std::optional<parser::LanguageBindingSpec>>(suffix->t));
       }
     }
   }
@@ -6812,7 +6831,8 @@ void DeclarationVisitor::Post(
   if (GetAttrs().test(Attr::DEFERRED)) { // C783
     Say("DEFERRED is only allowed when an interface-name is provided"_err_en_US);
   }
-  for (auto &declaration : x.declarations) {
+  const auto &declarations{std::get<std::list<parser::TypeBoundProcDecl>>(x.t)};
+  for (auto &declaration : declarations) {
     auto &bindingName{std::get<parser::Name>(declaration.t)};
     auto &optName{std::get<std::optional<parser::Name>>(declaration.t)};
     const parser::Name &procedureName{optName ? *optName : bindingName};
@@ -6835,7 +6855,9 @@ void DeclarationVisitor::Post(
 void DeclarationVisitor::CheckBindings(
     const parser::TypeBoundProcedureStmt::WithoutInterface &tbps) {
   CHECK(currScope().IsDerivedType());
-  for (auto &declaration : tbps.declarations) {
+  const auto &declarations{
+      std::get<std::list<parser::TypeBoundProcDecl>>(tbps.t)};
+  for (auto &declaration : declarations) {
     auto &bindingName{std::get<parser::Name>(declaration.t)};
     if (Symbol * binding{FindInScope(bindingName)}) {
       if (auto *details{binding->detailsIf<ProcBindingDetails>()}) {
@@ -6869,8 +6891,9 @@ void DeclarationVisitor::Post(
   if (!GetAttrs().test(Attr::DEFERRED)) { // C783
     Say("DEFERRED is required when an interface-name is provided"_err_en_US);
   }
-  if (Symbol * interface{NoteInterfaceName(x.interfaceName)}) {
-    for (auto &bindingName : x.bindingNames) {
+  const auto &[interfaceName, _, bindingNames]{x.t};
+  if (Symbol * interface{NoteInterfaceName(interfaceName)}) {
+    for (auto &bindingName : bindingNames) {
       if (auto *s{
               MakeTypeSymbol(bindingName, ProcBindingDetails{*interface})}) {
         SetPassNameOn(*s);
@@ -8706,8 +8729,9 @@ bool ResolveNamesVisitor::Pre(const parser::FunctionReference &x) {
   return false;
 }
 bool ResolveNamesVisitor::Pre(const parser::CallStmt &x) {
-  HandleCall(Symbol::Flag::Subroutine, x.call);
-  Walk(x.chevrons);
+  const auto &[call, chevrons]{x.t};
+  HandleCall(Symbol::Flag::Subroutine, call);
+  Walk(chevrons);
   return false;
 }
 
@@ -10398,7 +10422,8 @@ class ExecutionPartCallSkimmer : public ExecutionPartSkimmerBase {
     NoteCall(Symbol::Flag::Function, fr.v, false);
   }
   void Post(const parser::CallStmt &cs) {
-    NoteCall(Symbol::Flag::Subroutine, cs.call, cs.chevrons.has_value());
+    const auto &[call, chevrons]{cs.t};
+    NoteCall(Symbol::Flag::Subroutine, call, chevrons.has_value());
   }
 
 private:
@@ -10591,7 +10616,8 @@ void ResolveNamesVisitor::EndScopeForNode(const ProgramTree &node) {
               if (const auto &maybeSuffix{
                       std::get<std::optional<parser::Suffix>>(
                           stmt->statement.t)}) {
-                binding = &maybeSuffix->binding;
+                binding = &std::get<std::optional<parser::LanguageBindingSpec>>(
+                    maybeSuffix->t);
               }
             }
           },
@@ -10661,7 +10687,7 @@ class DeferredCheckVisitor {
     return false;
   }
   void Post(const parser::TypeBoundProcedureStmt::WithInterface &tbps) {
-    resolver_.CheckExplicitInterface(tbps.interfaceName);
+    resolver_.CheckExplicitInterface(std::get<parser::Name>(tbps.t));
   }
   void Post(const parser::TypeBoundProcedureStmt::WithoutInterface &tbps) {
     if (outerScope_) {
diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index cf1e5e7d44565..baa8e6d7f59a3 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -953,13 +953,13 @@ class ImageControlStmtHelper {
     return false;
   }
   bool operator()(const parser::CallStmt &stmt) {
+    const auto &call{std::get<parser::Call>(stmt.t)};
     const auto &procedureDesignator{
-        std::get<parser::ProcedureDesignator>(stmt.call.t)};
+        std::get<parser::ProcedureDesignator>(call.t)};
     if (auto *name{std::get_if<parser::Name>(&procedureDesignator.u)}) {
       // TODO: also ensure that the procedure is, in fact, an intrinsic
       if (name->source == "move_alloc") {
-        const auto &args{
-            std::get<std::list<parser::ActualArgSpec>>(stmt.call.t)};
+        const auto &args{std::get<std::list<parser::ActualArgSpec>>(call.t)};
         if (!args.empty()) {
           const parser::ActualArg &actualArg{
               std::get<parser::ActualArg>(args.front().t)};

>From 0899be9a1ae6fe8b427ab44098b29f5c381e5240 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 12 Jan 2026 09:46:25 -0600
Subject: [PATCH 3/6] Remove leftover comment

---
 flang/lib/Parser/parse-tree.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flang/lib/Parser/parse-tree.cpp b/flang/lib/Parser/parse-tree.cpp
index 819470a43b0c8..e0c668ddc328b 100644
--- a/flang/lib/Parser/parse-tree.cpp
+++ b/flang/lib/Parser/parse-tree.cpp
@@ -54,7 +54,6 @@ DataRef::DataRef(std::list<PartRef> &&prl)
     : u{std::move(std::get<Name>(prl.front().t))} {
   for (bool first{true}; !prl.empty(); first = false, prl.pop_front()) {
     auto &&[name, subscripts, imageSelector]{prl.front().t};
-    // PartRef &pr{prl.front()};
     if (!first) {
       u = common::Indirection<StructureComponent>::Make(
           std::move(*this), std::move(name));

>From 38023b284365cac9ac1b41dc45f70190b0918c73 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 12 Jan 2026 09:51:33 -0600
Subject: [PATCH 4/6] format

---
 flang/include/flang/Parser/parse-tree.h | 4 ++--
 flang/lib/Semantics/expression.cpp      | 4 ++--
 flang/lib/Semantics/resolve-names.cpp   | 6 +++---
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 1d72c1cb1c80e..c03823b60f0de 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3251,8 +3251,8 @@ struct CallStmt {
         t;
   };
   explicit CallStmt(ProcedureDesignator &&pd, std::optional<Chevrons> &&ch,
-      std::list<ActualArgSpec> &&args) :
-      CallStmt(Call{std::move(pd), std::move(args)}, std::move(ch)) {}
+      std::list<ActualArgSpec> &&args)
+      : CallStmt(Call{std::move(pd), std::move(args)}, std::move(ch)) {}
   std::tuple<Call, std::optional<Chevrons>> t;
   CharBlock source;
   mutable TypedCall typedCall; // filled by semantics
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index ece5d23bb59d2..d0296e4b1f36c 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -918,8 +918,8 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::RealLiteralConstant &x) {
     }
   }
   bool isDefaultKind{!xkind && letterKind.value_or('e') == 'e'};
-  auto result{common::SearchTypes(RealTypeVisitor{
-      kind, xreal.source, GetFoldingContext(), isDefaultKind})};
+  auto result{common::SearchTypes(
+      RealTypeVisitor{kind, xreal.source, GetFoldingContext(), isDefaultKind})};
   if (!result) { // C717
     Say("Unsupported REAL(KIND=%d)"_err_en_US, kind);
   }
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 04d734781265e..2f15ab1e82e2b 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -4670,9 +4670,9 @@ bool SubprogramVisitor::Pre(const parser::Suffix &suffix) {
         }
       }
     } else {
-      Message &msg{Say(*resultName,
-          "RESULT(%s) may appear only in a function"_err_en_US)};
-      if (const Symbol * subprogram{InclusiveScope().symbol()}) {
+      Message &msg{Say(
+          *resultName, "RESULT(%s) may appear only in a function"_err_en_US)};
+      if (const Symbol *subprogram{InclusiveScope().symbol()}) {
         msg.Attach(subprogram->name(), "Containing subprogram"_en_US);
       }
     }

>From a3f6a10e94f29c96390b0be7c37ef74e1668bb5b Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Thu, 8 Jan 2026 09:05:10 -0600
Subject: [PATCH 5/6] [flang] Add traits to more AST nodes

Follow-up to PR175211.

There are still a few AST nodes that don't have any of the standard
traits (Wrapper/Tuple/etc). Because of that they require special
handling in the parse tree visitor.

Convert a subset of these nodes to the typical format, and remove the
special cases from the parse tree visitor.

The members of these nodes were frequently used, so instead of extracting
them by hand each time use helper member functions to access them.
---
 .../include/flang/Parser/parse-tree-visitor.h | 65 ----------------
 flang/include/flang/Parser/parse-tree.h       | 45 +++++------
 flang/lib/Lower/Bridge.cpp                    | 14 ++--
 flang/lib/Lower/IO.cpp                        | 10 +--
 flang/lib/Lower/OpenACC.cpp                   | 16 ++--
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 10 +--
 flang/lib/Lower/OpenMP/Utils.cpp              | 22 +++---
 flang/lib/Lower/PFTBuilder.cpp                |  2 +-
 flang/lib/Parser/parse-tree.cpp               | 12 ++-
 flang/lib/Parser/tools.cpp                    | 20 +++--
 flang/lib/Parser/unparse.cpp                  | 14 ++--
 flang/lib/Semantics/check-cuda.cpp            |  8 +-
 flang/lib/Semantics/check-data.cpp            |  4 +-
 flang/lib/Semantics/check-deallocate.cpp      |  6 +-
 flang/lib/Semantics/check-do-forall.cpp       | 20 ++---
 flang/lib/Semantics/check-nullify.cpp         |  2 +-
 flang/lib/Semantics/check-omp-loop.cpp        |  2 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 18 ++---
 flang/lib/Semantics/data-to-inits.cpp         | 14 ++--
 flang/lib/Semantics/expression.cpp            | 78 ++++++++++---------
 flang/lib/Semantics/resolve-directives.cpp    | 18 ++---
 flang/lib/Semantics/resolve-names-utils.cpp   |  4 +-
 flang/lib/Semantics/resolve-names.cpp         | 39 +++++-----
 23 files changed, 194 insertions(+), 249 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree-visitor.h b/flang/include/flang/Parser/parse-tree-visitor.h
index 191e74ee89f1c..5aff5152a3793 100644
--- a/flang/include/flang/Parser/parse-tree-visitor.h
+++ b/flang/include/flang/Parser/parse-tree-visitor.h
@@ -314,56 +314,6 @@ struct ParseTreeVisitorLookupScope {
     }
   }
 
-  template <typename V> static void Walk(const ArrayElement &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.base, visitor);
-      Walk(x.subscripts, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(ArrayElement &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.base, mutator);
-      Walk(x.subscripts, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename V>
-  static void Walk(const CoindexedNamedObject &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.base, visitor);
-      Walk(x.imageSelector, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(CoindexedNamedObject &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.base, mutator);
-      Walk(x.imageSelector, mutator);
-      mutator.Post(x);
-    }
-  }
-  template <typename A, typename B, typename V>
-  static void Walk(const LoopBounds<A, B> &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.name, visitor);
-      Walk(x.lower, visitor);
-      Walk(x.upper, visitor);
-      Walk(x.step, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename A, typename B, typename M>
-  static void Walk(LoopBounds<A, B> &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.name, mutator);
-      Walk(x.lower, mutator);
-      Walk(x.upper, mutator);
-      Walk(x.step, mutator);
-      mutator.Post(x);
-    }
-  }
-
   // Expr traversal uses iteration rather than recursion to avoid
   // blowing out the stack on very deep expression parse trees.
   // It replaces implementations that looked like:
@@ -451,21 +401,6 @@ struct ParseTreeVisitorLookupScope {
       mutator.Post(x);
     }
   }
-  template <typename V>
-  static void Walk(const StructureComponent &x, V &visitor) {
-    if (visitor.Pre(x)) {
-      Walk(x.base, visitor);
-      Walk(x.component, visitor);
-      visitor.Post(x);
-    }
-  }
-  template <typename M> static void Walk(StructureComponent &x, M &mutator) {
-    if (mutator.Pre(x)) {
-      Walk(x.base, mutator);
-      Walk(x.component, mutator);
-      mutator.Post(x);
-    }
-  }
   template <typename V> static void Walk(const UseStmt &x, V &visitor) {
     if (visitor.Pre(x)) {
       Walk(x.nature, visitor);
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index c03823b60f0de..6c1aace66275f 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -1256,15 +1256,13 @@ WRAPPER_CLASS(ArrayConstructor, AcSpec);
 using DoVariable = Scalar<Integer<Name>>;
 
 template <typename VAR, typename BOUND> struct LoopBounds {
-  LoopBounds(LoopBounds &&that) = default;
-  LoopBounds(
-      VAR &&name, BOUND &&lower, BOUND &&upper, std::optional<BOUND> &&step)
-      : name{std::move(name)}, lower{std::move(lower)}, upper{std::move(upper)},
-        step{std::move(step)} {}
-  LoopBounds &operator=(LoopBounds &&) = default;
-  VAR name;
-  BOUND lower, upper;
-  std::optional<BOUND> step;
+  TUPLE_CLASS_BOILERPLATE(LoopBounds);
+  std::tuple<VAR, BOUND, BOUND, std::optional<BOUND>> t;
+
+  const VAR &Name() const { return std::get<0>(t); }
+  const BOUND &Lower() const { return std::get<1>(t); }
+  const BOUND &Upper() const { return std::get<2>(t); }
+  const std::optional<BOUND> &Step() const { return std::get<3>(t); }
 };
 
 using ScalarName = Scalar<Name>;
@@ -1858,11 +1856,11 @@ using ScalarIntVariable = Scalar<Integer<Variable>>;
 
 // R913 structure-component -> data-ref
 struct StructureComponent {
-  BOILERPLATE(StructureComponent);
-  StructureComponent(DataRef &&dr, Name &&n)
-      : base{std::move(dr)}, component(std::move(n)) {}
-  DataRef base;
-  Name component;
+  TUPLE_CLASS_BOILERPLATE(StructureComponent);
+  std::tuple<DataRef, Name> t;
+
+  const DataRef &Base() const { return std::get<DataRef>(t); }
+  const Name &Component() const { return std::get<Name>(t); }
 };
 
 // R1039 proc-component-ref -> scalar-variable % procedure-component-name
@@ -1873,23 +1871,22 @@ struct ProcComponentRef {
 
 // R914 coindexed-named-object -> data-ref
 struct CoindexedNamedObject {
-  BOILERPLATE(CoindexedNamedObject);
-  CoindexedNamedObject(DataRef &&dr, ImageSelector &&is)
-      : base{std::move(dr)}, imageSelector{std::move(is)} {}
-  DataRef base;
-  ImageSelector imageSelector;
+  TUPLE_CLASS_BOILERPLATE(CoindexedNamedObject);
+  std::tuple<DataRef, ImageSelector> t;
 };
 
 // R917 array-element -> data-ref
 struct ArrayElement {
-  BOILERPLATE(ArrayElement);
-  ArrayElement(DataRef &&dr, std::list<SectionSubscript> &&ss)
-      : base{std::move(dr)}, subscripts(std::move(ss)) {}
+  TUPLE_CLASS_BOILERPLATE(ArrayElement);
   Substring ConvertToSubstring();
   StructureConstructor ConvertToStructureConstructor(
       const semantics::DerivedTypeSpec &);
-  DataRef base;
-  std::list<SectionSubscript> subscripts;
+  std::tuple<DataRef, std::list<SectionSubscript>> t;
+
+  const DataRef &Base() const { return std::get<DataRef>(t); }
+  const std::list<SectionSubscript> &Subscripts() const {
+    return std::get<std::list<SectionSubscript>>(t);
+  }
 };
 
 // R933 allocate-object -> variable-name | structure-component
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 609050511f6c9..656940986809d 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2491,8 +2491,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
                        &loopControl->u)) {
       // Non-concurrent increment loop.
       IncrementLoopInfo &info = incrementLoopNestInfo.emplace_back(
-          *bounds->name.thing.symbol, bounds->lower, bounds->upper,
-          bounds->step);
+          *bounds->Name().thing.symbol, bounds->Lower(), bounds->Upper(),
+          bounds->Step());
       if (unstructuredContext) {
         maybeStartBlock(preheaderBlock);
         info.hasRealControl = info.loopVariableSym->GetType()->IsNumeric(
@@ -3842,22 +3842,22 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         assert(bounds && "Expected bounds on the loop construct");
 
         Fortran::semantics::Symbol &ivSym =
-            bounds->name.thing.symbol->GetUltimate();
+            bounds->Name().thing.symbol->GetUltimate();
         ivValues.push_back(getSymbolAddress(ivSym));
 
         lbs.push_back(builder->createConvert(
             crtLoc, idxTy,
             fir::getBase(genExprValue(
-                *Fortran::semantics::GetExpr(bounds->lower), stmtCtx))));
+                *Fortran::semantics::GetExpr(bounds->Lower()), stmtCtx))));
         ubs.push_back(builder->createConvert(
             crtLoc, idxTy,
             fir::getBase(genExprValue(
-                *Fortran::semantics::GetExpr(bounds->upper), stmtCtx))));
-        if (bounds->step)
+                *Fortran::semantics::GetExpr(bounds->Upper()), stmtCtx))));
+        if (auto &step = bounds->Step())
           steps.push_back(builder->createConvert(
               crtLoc, idxTy,
               fir::getBase(genExprValue(
-                  *Fortran::semantics::GetExpr(bounds->step), stmtCtx))));
+                  *Fortran::semantics::GetExpr(step), stmtCtx))));
         else // If `step` is not present, assume it is `1`.
           steps.push_back(builder->createIntegerConstant(loc, idxTy, 1));
 
diff --git a/flang/lib/Lower/IO.cpp b/flang/lib/Lower/IO.cpp
index c3f9c1a882423..de2afb70636d5 100644
--- a/flang/lib/Lower/IO.cpp
+++ b/flang/lib/Lower/IO.cpp
@@ -951,7 +951,7 @@ static void genIoLoop(Fortran::lower::AbstractConverter &converter,
   const auto &itemList = std::get<0>(ioImpliedDo.t);
   const auto &control = std::get<1>(ioImpliedDo.t);
   const auto &loopSym =
-      *Fortran::parser::UnwrapRef<Fortran::parser::Name>(control.name).symbol;
+      *Fortran::parser::UnwrapRef<Fortran::parser::Name>(control.Name()).symbol;
   mlir::Value loopVar = fir::getBase(converter.genExprAddr(
       Fortran::evaluate::AsGenericExpr(loopSym).value(), stmtCtx));
   auto genControlValue = [&](const Fortran::parser::ScalarIntExpr &expr) {
@@ -959,11 +959,11 @@ static void genIoLoop(Fortran::lower::AbstractConverter &converter,
         converter.genExprValue(*Fortran::semantics::GetExpr(expr), stmtCtx));
     return builder.createConvert(loc, builder.getIndexType(), v);
   };
-  mlir::Value lowerValue = genControlValue(control.lower);
-  mlir::Value upperValue = genControlValue(control.upper);
+  mlir::Value lowerValue = genControlValue(control.Lower());
+  mlir::Value upperValue = genControlValue(control.Upper());
   mlir::Value stepValue =
-      control.step.has_value()
-          ? genControlValue(*control.step)
+      control.Step().has_value()
+          ? genControlValue(*control.Step())
           : mlir::arith::ConstantIndexOp::create(builder, loc, 1);
   auto genItemList = [&](const D &ioImpliedDo) {
     if constexpr (std::is_same_v<D, Fortran::parser::InputImpliedDo>)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 52fee7baf9de1..64e39b610aa6e 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -305,13 +305,13 @@ getSymbolFromAccObject(const Fortran::parser::AccObject &accObject) {
             Fortran::parser::Unwrap<Fortran::parser::ArrayElement>(
                 *designator)) {
       const Fortran::parser::Name &name =
-          Fortran::parser::GetLastName(arrayElement->base);
+          Fortran::parser::GetLastName(arrayElement->Base());
       return *name.symbol;
     }
     if (const auto *component =
             Fortran::parser::Unwrap<Fortran::parser::StructureComponent>(
                 *designator)) {
-      return *component->component.symbol;
+      return *component->Component().symbol;
     }
   } else if (const auto *name =
                  std::get_if<Fortran::parser::Name>(&accObject.u)) {
@@ -2173,18 +2173,18 @@ static void processDoLoopBounds(
             mlir::Location loc) {
           locs.push_back(loc);
           lowerbounds.push_back(fir::getBase(converter.genExprValue(
-              *Fortran::semantics::GetExpr(bounds.lower), stmtCtx)));
+              *Fortran::semantics::GetExpr(bounds.Lower()), stmtCtx)));
           upperbounds.push_back(fir::getBase(converter.genExprValue(
-              *Fortran::semantics::GetExpr(bounds.upper), stmtCtx)));
-          if (bounds.step)
+              *Fortran::semantics::GetExpr(bounds.Upper()), stmtCtx)));
+          if (auto &step = bounds.Step())
             steps.push_back(fir::getBase(converter.genExprValue(
-                *Fortran::semantics::GetExpr(bounds.step), stmtCtx)));
+                *Fortran::semantics::GetExpr(step), stmtCtx)));
           else // If `step` is not present, assume it is `1`.
             steps.push_back(builder.createIntegerConstant(
                 currentLocation, upperbounds[upperbounds.size() - 1].getType(),
                 1));
           Fortran::semantics::Symbol &ivSym =
-              bounds.name.thing.symbol->GetUltimate();
+              bounds.Name().thing.symbol->GetUltimate();
           privatizeIv(converter, ivSym, currentLocation, ivTypes, ivLocs,
                       privateOperands, ivPrivate);
 
@@ -2355,7 +2355,7 @@ static void privatizeInductionVariables(
                        mlir::Location loc) {
                      locs.push_back(loc);
                      Fortran::semantics::Symbol &ivSym =
-                         bounds.name.thing.symbol->GetUltimate();
+                         bounds.Name().thing.symbol->GetUltimate();
                      privatizeIv(converter, ivSym, currentLocation, ivTypes,
                                  ivLocs, privateOperands, ivPrivate);
                    });
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 989e370870f33..1c84d8a38d6b6 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2090,13 +2090,13 @@ static void genCanonicalLoopNest(
     assert(bounds && "Expected bounds for canonical loop");
     lower::StatementContext stmtCtx;
     mlir::Value loopLBVar = fir::getBase(
-        converter.genExprValue(*semantics::GetExpr(bounds->lower), stmtCtx));
+        converter.genExprValue(*semantics::GetExpr(bounds->Lower()), stmtCtx));
     mlir::Value loopUBVar = fir::getBase(
-        converter.genExprValue(*semantics::GetExpr(bounds->upper), stmtCtx));
+        converter.genExprValue(*semantics::GetExpr(bounds->Upper()), stmtCtx));
     mlir::Value loopStepVar = [&]() {
-      if (bounds->step) {
+      if (auto &step = bounds->Step()) {
         return fir::getBase(
-            converter.genExprValue(*semantics::GetExpr(bounds->step), stmtCtx));
+            converter.genExprValue(*semantics::GetExpr(step), stmtCtx));
       }
 
       // If `step` is not present, assume it is `1`.
@@ -2105,7 +2105,7 @@ static void genCanonicalLoopNest(
     }();
 
     // Get the integer kind for the loop variable and cast the loop bounds
-    size_t loopVarTypeSize = bounds->name.thing.symbol->GetUltimate().size();
+    size_t loopVarTypeSize = bounds->Name().thing.symbol->GetUltimate().size();
     mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize);
     loopVarTypes.push_back(loopVarType);
     loopLBVar = firOpBuilder.createConvert(loc, loopVarType, loopLBVar);
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index a818d635668de..dce8580856664 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -31,6 +31,7 @@
 #include <flang/Semantics/tools.h>
 #include <flang/Semantics/type.h>
 #include <flang/Utils/OpenMP.h>
+#include <llvm/ADT/STLExtras.h>
 #include <llvm/ADT/SmallPtrSet.h>
 #include <llvm/ADT/StringRef.h>
 #include <llvm/Support/CommandLine.h>
@@ -255,9 +256,10 @@ getIterationVariableSymbol(const lower::pft::Evaluation &eval) {
         if (const auto &maybeCtrl = doLoop.GetLoopControl()) {
           using LoopControl = parser::LoopControl;
           if (auto *bounds = std::get_if<LoopControl::Bounds>(&maybeCtrl->u)) {
-            static_assert(std::is_same_v<decltype(bounds->name),
-                                         parser::Scalar<parser::Name>>);
-            return bounds->name.thing.symbol;
+            using NameType = llvm::remove_cvref_t<decltype(bounds->Name())>;
+            static_assert(
+                std::is_same_v<NameType, parser::Scalar<parser::Name>>);
+            return bounds->Name().thing.symbol;
           }
         }
         return static_cast<semantics::Symbol *>(nullptr);
@@ -894,19 +896,19 @@ void collectLoopRelatedInfo(
     assert(bounds && "Expected bounds for worksharing do loop");
     lower::StatementContext stmtCtx;
     result.loopLowerBounds.push_back(fir::getBase(
-        converter.genExprValue(*semantics::GetExpr(bounds->lower), stmtCtx)));
+        converter.genExprValue(*semantics::GetExpr(bounds->Lower()), stmtCtx)));
     result.loopUpperBounds.push_back(fir::getBase(
-        converter.genExprValue(*semantics::GetExpr(bounds->upper), stmtCtx)));
-    if (bounds->step) {
+        converter.genExprValue(*semantics::GetExpr(bounds->Upper()), stmtCtx)));
+    if (auto &step = bounds->Step()) {
       result.loopSteps.push_back(fir::getBase(
-          converter.genExprValue(*semantics::GetExpr(bounds->step), stmtCtx)));
+          converter.genExprValue(*semantics::GetExpr(step), stmtCtx)));
     } else { // If `step` is not present, assume it as `1`.
       result.loopSteps.push_back(firOpBuilder.createIntegerConstant(
           currentLocation, firOpBuilder.getIntegerType(32), 1));
     }
-    iv.push_back(bounds->name.thing.symbol);
-    loopVarTypeSize = std::max(loopVarTypeSize,
-                               bounds->name.thing.symbol->GetUltimate().size());
+    iv.push_back(bounds->Name().thing.symbol);
+    loopVarTypeSize = std::max(
+        loopVarTypeSize, bounds->Name().thing.symbol->GetUltimate().size());
     if (--collapseValue)
       doConstructEval = getNestedDoConstruct(*doConstructEval);
   } while (collapseValue > 0);
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 1a7fb41f3273c..308db726f073c 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -1055,7 +1055,7 @@ class PFTBuilder {
             eval.controlSuccessor = &evaluationList.back();
             if (const auto *bounds =
                     std::get_if<parser::LoopControl::Bounds>(&loopControl->u)) {
-              if (bounds->name.thing.symbol->GetType()->IsNumeric(
+              if (bounds->Name().thing.symbol->GetType()->IsNumeric(
                       common::TypeCategory::Real))
                 eval.isUnstructured = true; // real-valued loop control
             } else if (std::get_if<parser::ScalarLogicalExpr>(
diff --git a/flang/lib/Parser/parse-tree.cpp b/flang/lib/Parser/parse-tree.cpp
index e0c668ddc328b..afe28182f8627 100644
--- a/flang/lib/Parser/parse-tree.cpp
+++ b/flang/lib/Parser/parse-tree.cpp
@@ -102,8 +102,9 @@ static Designator MakeArrayElementRef(
     const Name &name, std::list<Expr> &&subscripts) {
   ArrayElement arrayElement{DataRef{Name{name}}, std::list<SectionSubscript>{}};
   for (Expr &expr : subscripts) {
-    arrayElement.subscripts.push_back(
-        SectionSubscript{Integer{common::Indirection{std::move(expr)}}});
+    std::get<std::list<SectionSubscript>>(arrayElement.t)
+        .push_back(
+            SectionSubscript{Integer{common::Indirection{std::move(expr)}}});
   }
   return Designator{DataRef{common::Indirection{std::move(arrayElement)}}};
 }
@@ -113,8 +114,9 @@ static Designator MakeArrayElementRef(
   ArrayElement arrayElement{DataRef{common::Indirection{std::move(sc)}},
       std::list<SectionSubscript>{}};
   for (Expr &expr : subscripts) {
-    arrayElement.subscripts.push_back(
-        SectionSubscript{Integer{common::Indirection{std::move(expr)}}});
+    std::get<std::list<SectionSubscript>>(arrayElement.t)
+        .push_back(
+            SectionSubscript{Integer{common::Indirection{std::move(expr)}}});
   }
   return Designator{DataRef{common::Indirection{std::move(arrayElement)}}};
 }
@@ -186,6 +188,7 @@ StructureConstructor FunctionReference::ConvertToStructureConstructor(
 
 StructureConstructor ArrayElement::ConvertToStructureConstructor(
     const semantics::DerivedTypeSpec &derived) {
+  auto &[base, subscripts]{t};
   Name name{std::get<parser::Name>(base.u)};
   std::list<ComponentSpec> components;
   for (auto &subscript : subscripts) {
@@ -198,6 +201,7 @@ StructureConstructor ArrayElement::ConvertToStructureConstructor(
 }
 
 Substring ArrayElement::ConvertToSubstring() {
+  auto &[base, subscripts]{t};
   auto iter{subscripts.begin()};
   CHECK(iter != subscripts.end());
   auto &triplet{std::get<SubscriptTriplet>(iter->u)};
diff --git a/flang/lib/Parser/tools.cpp b/flang/lib/Parser/tools.cpp
index ed6d194c17dc3..ff0538ae565b2 100644
--- a/flang/lib/Parser/tools.cpp
+++ b/flang/lib/Parser/tools.cpp
@@ -13,7 +13,7 @@ namespace Fortran::parser {
 const Name &GetLastName(const Name &x) { return x; }
 
 const Name &GetLastName(const StructureComponent &x) {
-  return GetLastName(x.component);
+  return GetLastName(x.Component());
 }
 
 const Name &GetLastName(const DataRef &x) {
@@ -23,10 +23,12 @@ const Name &GetLastName(const DataRef &x) {
           [](const common::Indirection<StructureComponent> &sc)
               -> const Name & { return GetLastName(sc.value()); },
           [](const common::Indirection<ArrayElement> &sc) -> const Name & {
-            return GetLastName(sc.value().base);
+            return GetLastName(sc.value().Base());
           },
           [](const common::Indirection<CoindexedNamedObject> &ci)
-              -> const Name & { return GetLastName(ci.value().base); },
+              -> const Name & {
+            return GetLastName(std::get<DataRef>(ci.value().t));
+          },
       },
       x.u);
 }
@@ -71,7 +73,7 @@ const Name &GetLastName(const AllocateObject &x) {
 const Name &GetFirstName(const Name &x) { return x; }
 
 const Name &GetFirstName(const StructureComponent &x) {
-  return GetFirstName(x.base);
+  return GetFirstName(x.Base());
 }
 
 const Name &GetFirstName(const DataRef &x) {
@@ -81,10 +83,12 @@ const Name &GetFirstName(const DataRef &x) {
           [](const common::Indirection<StructureComponent> &sc)
               -> const Name & { return GetFirstName(sc.value()); },
           [](const common::Indirection<ArrayElement> &sc) -> const Name & {
-            return GetFirstName(sc.value().base);
+            return GetFirstName(sc.value().Base());
           },
           [](const common::Indirection<CoindexedNamedObject> &ci)
-              -> const Name & { return GetFirstName(ci.value().base); },
+              -> const Name & {
+            return GetFirstName(std::get<DataRef>(ci.value().t));
+          },
       },
       x.u);
 }
@@ -134,7 +138,7 @@ const CoindexedNamedObject *GetCoindexedNamedObject(const DataRef &base) {
           [](const common::Indirection<CoindexedNamedObject> &x)
               -> const CoindexedNamedObject * { return &x.value(); },
           [](const auto &x) -> const CoindexedNamedObject * {
-            return GetCoindexedNamedObject(x.value().base);
+            return GetCoindexedNamedObject(x.value().Base());
           },
       },
       base.u);
@@ -168,7 +172,7 @@ const CoindexedNamedObject *GetCoindexedNamedObject(
   return common::visit(
       common::visitors{
           [](const StructureComponent &x) -> const CoindexedNamedObject * {
-            return GetCoindexedNamedObject(x.base);
+            return GetCoindexedNamedObject(x.Base());
           },
           [](const auto &) -> const CoindexedNamedObject * { return nullptr; },
       },
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 0465b67aed08d..fb7a7ec8517f4 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -440,8 +440,8 @@ class UnparseVisitor {
     Walk(std::get<std::list<AcValue>>(x.t), ", ");
   }
   template <typename A, typename B> void Unparse(const LoopBounds<A, B> &x) {
-    Walk(x.name), Put('='), Walk(x.lower), Put(','), Walk(x.upper);
-    Walk(",", x.step);
+    Walk(x.Name()), Put('='), Walk(x.Lower()), Put(','), Walk(x.Upper());
+    Walk(",", x.Step());
   }
   void Unparse(const AcImpliedDo &x) { // R774
     Put('('), Walk(std::get<std::list<AcValue>>(x.t), ", ");
@@ -798,18 +798,18 @@ class UnparseVisitor {
     Walk(imageSelector);
   }
   void Unparse(const StructureComponent &x) { // R913
-    Walk(x.base);
-    if (structureComponents_.find(x.component.source) !=
+    Walk(x.Base());
+    if (structureComponents_.find(x.Component().source) !=
         structureComponents_.end()) {
       Put('.');
     } else {
       Put('%');
     }
-    Walk(x.component);
+    Walk(x.Component());
   }
   void Unparse(const ArrayElement &x) { // R917
-    Walk(x.base);
-    Put('('), Walk(x.subscripts, ","), Put(')');
+    Walk(x.Base());
+    Put('('), Walk(x.Subscripts(), ","), Put(')');
   }
   void Unparse(const SubscriptTriplet &x) { // R921
     Walk(std::get<0>(x.t)), Put(':'), Walk(std::get<1>(x.t));
diff --git a/flang/lib/Semantics/check-cuda.cpp b/flang/lib/Semantics/check-cuda.cpp
index caa9bdd28f1f4..13c523da13c25 100644
--- a/flang/lib/Semantics/check-cuda.cpp
+++ b/flang/lib/Semantics/check-cuda.cpp
@@ -514,10 +514,10 @@ template <bool IsCUFKernelDo> class DeviceContextChecker {
     Check(uS.statement, uS.source);
   }
   void Check(const parser::LoopControl::Bounds &bounds) {
-    Check(bounds.lower);
-    Check(bounds.upper);
-    if (bounds.step) {
-      Check(*bounds.step);
+    Check(bounds.Lower());
+    Check(bounds.Upper());
+    if (auto &step{bounds.Step()}) {
+      Check(*step);
     }
   }
   void Check(const parser::LoopControl::Concurrent &x) {
diff --git a/flang/lib/Semantics/check-data.cpp b/flang/lib/Semantics/check-data.cpp
index 9b41a879ad700..adbcc3776b763 100644
--- a/flang/lib/Semantics/check-data.cpp
+++ b/flang/lib/Semantics/check-data.cpp
@@ -26,7 +26,7 @@ namespace Fortran::semantics {
 // represented as such in the "body" of the implied DO loop.
 void DataChecker::Enter(const parser::DataImpliedDo &x) {
   const auto &name{parser::UnwrapRef<parser::Name>(
-      std::get<parser::DataImpliedDo::Bounds>(x.t).name)};
+      std::get<parser::DataImpliedDo::Bounds>(x.t).Name())};
   int kind{evaluate::ResultType<evaluate::ImpliedDoIndex>::kind};
   if (const auto dynamicType{evaluate::DynamicType::From(DEREF(name.symbol))}) {
     if (dynamicType->category() == TypeCategory::Integer) {
@@ -38,7 +38,7 @@ void DataChecker::Enter(const parser::DataImpliedDo &x) {
 
 void DataChecker::Leave(const parser::DataImpliedDo &x) {
   const auto &name{parser::UnwrapRef<parser::Name>(
-      std::get<parser::DataImpliedDo::Bounds>(x.t).name)};
+      std::get<parser::DataImpliedDo::Bounds>(x.t).Name())};
   exprAnalyzer_.RemoveImpliedDo(name.source);
 }
 
diff --git a/flang/lib/Semantics/check-deallocate.cpp b/flang/lib/Semantics/check-deallocate.cpp
index e6ce1b30a59f5..92b75b9d2ca1d 100644
--- a/flang/lib/Semantics/check-deallocate.cpp
+++ b/flang/lib/Semantics/check-deallocate.cpp
@@ -98,10 +98,10 @@ void DeallocateChecker::Leave(const parser::DeallocateStmt &deallocateStmt) {
             [&](const parser::StructureComponent &structureComponent) {
               // Only perform structureComponent checks if it was successfully
               // analyzed by expression analysis.
-              source = structureComponent.component.source;
+              source = structureComponent.Component().source;
               if (const auto *expr{GetExpr(context_, allocateObject)}) {
-                if (const Symbol *symbol{structureComponent.component.symbol
-                            ? &structureComponent.component.symbol
+                if (const Symbol *symbol{structureComponent.Component().symbol
+                            ? &structureComponent.Component().symbol
                                   ->GetUltimate()
                             : nullptr};
                     !IsAllocatableOrObjectPointer(symbol)) { // F'2023 C936
diff --git a/flang/lib/Semantics/check-do-forall.cpp b/flang/lib/Semantics/check-do-forall.cpp
index c90479d9e352e..bf92d920f282e 100644
--- a/flang/lib/Semantics/check-do-forall.cpp
+++ b/flang/lib/Semantics/check-do-forall.cpp
@@ -71,7 +71,7 @@ static const Bounds &GetBounds(const parser::DoConstruct &doConstruct) {
 static const parser::Name &GetDoVariable(
     const parser::DoConstruct &doConstruct) {
   const Bounds &bounds{GetBounds(doConstruct)};
-  return bounds.name.thing;
+  return bounds.Name().thing;
 }
 
 static parser::MessageFixedText GetEnclosingDoMsg() {
@@ -546,14 +546,14 @@ class DoContext {
     // C1120 -- types of DO variables must be INTEGER, extended by allowing
     // REAL and DOUBLE PRECISION
     const Bounds &bounds{GetBounds(doConstruct)};
-    CheckDoVariable(bounds.name);
-    CheckDoExpression(bounds.lower);
-    CheckDoExpression(bounds.upper);
-    if (bounds.step) {
-      CheckDoExpression(*bounds.step);
-      if (IsZero(*bounds.step)) {
+    CheckDoVariable(bounds.Name());
+    CheckDoExpression(bounds.Lower());
+    CheckDoExpression(bounds.Upper());
+    if (auto &step{bounds.Step()}) {
+      CheckDoExpression(*step);
+      if (IsZero(*step)) {
         context_.Warn(common::UsageWarning::ZeroDoStep,
-            parser::UnwrapRef<parser::Expr>(bounds.step).source,
+            parser::UnwrapRef<parser::Expr>(step).source,
             "DO step expression should not be zero"_warn_en_US);
       }
     }
@@ -1194,13 +1194,13 @@ static void CheckIoImpliedDoIndex(
 void DoForallChecker::Leave(const parser::OutputImpliedDo &outputImpliedDo) {
   CheckIoImpliedDoIndex(context_,
       parser::UnwrapRef<parser::Name>(
-          std::get<parser::IoImpliedDoControl>(outputImpliedDo.t).name));
+          std::get<parser::IoImpliedDoControl>(outputImpliedDo.t).Name()));
 }
 
 void DoForallChecker::Leave(const parser::InputImpliedDo &inputImpliedDo) {
   CheckIoImpliedDoIndex(context_,
       parser::UnwrapRef<parser::Name>(
-          std::get<parser::IoImpliedDoControl>(inputImpliedDo.t).name));
+          std::get<parser::IoImpliedDoControl>(inputImpliedDo.t).Name()));
 }
 
 void DoForallChecker::Leave(const parser::StatVariable &statVariable) {
diff --git a/flang/lib/Semantics/check-nullify.cpp b/flang/lib/Semantics/check-nullify.cpp
index 452a891fe9bd8..db9738a452d2e 100644
--- a/flang/lib/Semantics/check-nullify.cpp
+++ b/flang/lib/Semantics/check-nullify.cpp
@@ -37,7 +37,7 @@ void NullifyChecker::Leave(const parser::NullifyStmt &nullifyStmt) {
               }
             },
             [&](const parser::StructureComponent &structureComponent) {
-              const auto &component{structureComponent.component};
+              const auto &component{structureComponent.Component()};
               SourceName at{component.source};
               if (const auto *checkedExpr{GetExpr(context_, pointerObject)}) {
                 if (auto whyNot{WhyNotDefinable(at, scope,
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index bff00a222ba92..246ebd5ab03cc 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -489,7 +489,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
 const parser::Name OmpStructureChecker::GetLoopIndex(
     const parser::DoConstruct *x) {
   using Bounds = parser::LoopControl::Bounds;
-  return std::get<Bounds>(x->GetLoopControl()->u).name.thing;
+  return std::get<Bounds>(x->GetLoopControl()->u).Name().thing;
 }
 
 void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 2acf0dee1f77e..7ec21a25ee891 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -3637,7 +3637,7 @@ void OmpStructureChecker::CheckReductionObjects(
     // a language identifier.
     for (const parser::OmpObject &object : objects.v) {
       if (auto *elem{GetArrayElementFromObj(object)}) {
-        const parser::DataRef &base = elem->base;
+        const parser::DataRef &base = elem->Base();
         if (!std::holds_alternative<parser::Name>(base.u)) {
           auto source{GetObjectSource(object)};
           context_.Say(source ? *source : GetContext().clauseSource,
@@ -3954,7 +3954,7 @@ bool OmpStructureChecker::IsDataRefTypeParamInquiry(
   bool dataRefIsTypeParamInquiry{false};
   if (const auto *structComp{
           parser::Unwrap<parser::StructureComponent>(dataRef)}) {
-    if (const auto *compSymbol{structComp->component.symbol}) {
+    if (const auto *compSymbol{structComp->Component().symbol}) {
       if (const auto *compSymbolMiscDetails{
               std::get_if<MiscDetails>(&compSymbol->details())}) {
         const auto detailsKind = compSymbolMiscDetails->kind();
@@ -4624,7 +4624,7 @@ void OmpStructureChecker::CheckDoacross(const parser::OmpDoacross &doa) {
       // Do-construct, collect the induction variable.
       if (auto &control{(*doc)->GetLoopControl()}) {
         if (auto *b{std::get_if<parser::LoopControl::Bounds>(&control->u)}) {
-          inductionVars.insert(b->name.thing.symbol);
+          inductionVars.insert(b->Name().thing.symbol);
         }
       }
     } else {
@@ -5036,10 +5036,10 @@ void OmpStructureChecker::CheckDependList(const parser::DataRef &d) {
       common::visitors{
           [&](const common::Indirection<parser::ArrayElement> &elem) {
             // Check if the base element is valid on Depend Clause
-            CheckDependList(elem.value().base);
+            CheckDependList(elem.value().Base());
           },
           [&](const common::Indirection<parser::StructureComponent> &comp) {
-            CheckDependList(comp.value().base);
+            CheckDependList(comp.value().Base());
           },
           [&](const common::Indirection<parser::CoindexedNamedObject> &) {
             context_.Say(GetContext().clauseSource,
@@ -5063,7 +5063,7 @@ void OmpStructureChecker::CheckArraySection(
   // looking for strings.
   if (!IsAssumedSizeArray(*name.symbol)) {
     evaluate::ExpressionAnalyzer ea{context_};
-    if (MaybeExpr expr = ea.Analyze(arrayElement.base)) {
+    if (MaybeExpr expr = ea.Analyze(arrayElement.Base())) {
       if (expr->Rank() == 0) {
         // Not an array: rank 0
         if (std::optional<evaluate::DynamicType> type = expr->GetType()) {
@@ -5082,8 +5082,8 @@ void OmpStructureChecker::CheckArraySection(
       }
     }
   }
-  if (!arrayElement.subscripts.empty()) {
-    for (const auto &subscript : arrayElement.subscripts) {
+  if (!arrayElement.Subscripts().empty()) {
+    for (const auto &subscript : arrayElement.Subscripts()) {
       if (const auto *triplet{
               std::get_if<parser::SubscriptTriplet>(&subscript.u)}) {
         if (std::get<0>(triplet->t) && std::get<1>(triplet->t)) {
@@ -5376,7 +5376,7 @@ struct NameHelper {
     return Visit(std::get<parser::DataRef>(x.t));
   }
   static const parser::Name *Visit(const parser::ArrayElement &x) {
-    return Visit(x.base);
+    return Visit(x.Base());
   }
   static const parser::Name *Visit(const parser::Designator &x) {
     return common::visit([](auto &&s) { return Visit(s); }, x.u);
diff --git a/flang/lib/Semantics/data-to-inits.cpp b/flang/lib/Semantics/data-to-inits.cpp
index 9012396ebf3b3..d9e79340a6d91 100644
--- a/flang/lib/Semantics/data-to-inits.cpp
+++ b/flang/lib/Semantics/data-to-inits.cpp
@@ -179,14 +179,14 @@ bool DataInitializationCompiler<DSV>::Scan(
 template <typename DSV>
 bool DataInitializationCompiler<DSV>::Scan(const parser::DataImpliedDo &ido) {
   const auto &bounds{std::get<parser::DataImpliedDo::Bounds>(ido.t)};
-  const auto &name{parser::UnwrapRef<parser::Name>(bounds.name)};
-  const auto *lowerExpr{GetExpr(
-      exprAnalyzer_.context(), parser::UnwrapRef<parser::Expr>(bounds.lower))};
-  const auto *upperExpr{GetExpr(
-      exprAnalyzer_.context(), parser::UnwrapRef<parser::Expr>(bounds.upper))};
-  const auto *stepExpr{bounds.step
+  const auto &name{parser::UnwrapRef<parser::Name>(bounds.Name())};
+  const auto *lowerExpr{GetExpr(exprAnalyzer_.context(),
+      parser::UnwrapRef<parser::Expr>(bounds.Lower()))};
+  const auto *upperExpr{GetExpr(exprAnalyzer_.context(),
+      parser::UnwrapRef<parser::Expr>(bounds.Upper()))};
+  const auto *stepExpr{bounds.Step()
           ? GetExpr(exprAnalyzer_.context(),
-                parser::UnwrapRef<parser::Expr>(bounds.step))
+                parser::UnwrapRef<parser::Expr>(bounds.Step()))
           : nullptr};
   if (lowerExpr && upperExpr) {
     // Fold the bounds expressions (again) in case any of them depend
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index d0296e4b1f36c..6cca0c670a5f8 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -587,12 +587,12 @@ static std::optional<parser::Substring> FixMisparsedSubstringDataRef(
           std::get_if<common::Indirection<parser::ArrayElement>>(&dataRef.u)}) {
     // ...%a(j:k) and "a" is a character scalar
     parser::ArrayElement &arrElement{ae->value()};
-    if (arrElement.subscripts.size() == 1) {
+    if (arrElement.Subscripts().size() == 1) {
       if (auto *triplet{std::get_if<parser::SubscriptTriplet>(
-              &arrElement.subscripts.front().u)}) {
+              &arrElement.Subscripts().front().u)}) {
         if (!std::get<2 /*stride*/>(triplet->t).has_value()) {
           if (const Symbol *symbol{
-                  parser::GetLastName(arrElement.base).symbol}) {
+                  parser::GetLastName(arrElement.Base()).symbol}) {
             const Symbol &ultimate{symbol->GetUltimate()};
             if (const semantics::DeclTypeSpec *type{ultimate.GetType()}) {
               if (ultimate.Rank() == 0 &&
@@ -623,10 +623,10 @@ MaybeExpr ExpressionAnalyzer::FixMisparsedSubstring(
     if (auto *sc{std::get_if<common::Indirection<parser::StructureComponent>>(
             &dataRef->u)}) {
       parser::StructureComponent &structComponent{sc->value()};
-      parser::CharBlock which{structComponent.component.source};
+      parser::CharBlock which{structComponent.Component().source};
       if (which == "kind" || which == "len") {
-        if (auto substring{
-                FixMisparsedSubstringDataRef(structComponent.base)}) {
+        if (auto substring{FixMisparsedSubstringDataRef(
+                std::get<parser::DataRef>(structComponent.t))}) {
           // ...%a(j:k)%kind or %len and "a" is a character scalar
           mutate.u = std::move(*substring);
           if (MaybeExpr substringExpr{Analyze(d)}) {
@@ -1378,10 +1378,10 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::ArrayElement &ae) {
   MaybeExpr baseExpr;
   {
     auto restorer{AllowWholeAssumedSizeArray()};
-    baseExpr = Analyze(ae.base);
+    baseExpr = Analyze(ae.Base());
   }
   if (baseExpr) {
-    if (ae.subscripts.empty()) {
+    if (ae.Subscripts().empty()) {
       // will be converted to function call later or error reported
     } else if (baseExpr->Rank() == 0) {
       if (const Symbol *symbol{GetLastSymbol(*baseExpr)}) {
@@ -1399,14 +1399,14 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::ArrayElement &ae) {
     } else if (std::optional<DataRef> dataRef{
                    ExtractDataRef(std::move(*baseExpr))}) {
       return ApplySubscripts(
-          std::move(*dataRef), AnalyzeSectionSubscripts(ae.subscripts));
+          std::move(*dataRef), AnalyzeSectionSubscripts(ae.Subscripts()));
     } else {
       Say("Subscripts may be applied only to an object, component, or array constant"_err_en_US);
     }
   }
   // error was reported: analyze subscripts without reporting more errors
   auto restorer{GetContextualMessages().DiscardMessages()};
-  AnalyzeSectionSubscripts(ae.subscripts);
+  AnalyzeSectionSubscripts(ae.Subscripts());
   return std::nullopt;
 }
 
@@ -1460,7 +1460,7 @@ std::optional<Component> ExpressionAnalyzer::CreateComponent(DataRef &&base,
 
 // Derived type component references and type parameter inquiries
 MaybeExpr ExpressionAnalyzer::Analyze(const parser::StructureComponent &sc) {
-  Symbol *sym{sc.component.symbol};
+  Symbol *sym{sc.Component().symbol};
   if (context_.HasError(sym)) {
     return std::nullopt;
   }
@@ -1472,14 +1472,14 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::StructureComponent &sc) {
   MaybeExpr base;
   if (isTypeParamInquiry) {
     auto restorer{AllowWholeAssumedSizeArray()};
-    base = Analyze(sc.base);
+    base = Analyze(sc.Base());
   } else {
-    base = Analyze(sc.base);
+    base = Analyze(sc.Base());
   }
   if (!base) {
     return std::nullopt;
   }
-  const auto &name{sc.component.source};
+  const auto &name{sc.Component().source};
   if (auto *dtExpr{UnwrapExpr<Expr<SomeDerived>>(*base)}) {
     const auto *dtSpec{GetDerivedTypeSpec(dtExpr->GetType())};
     if (isTypeParamInquiry) {
@@ -1551,7 +1551,8 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::StructureComponent &sc) {
 }
 
 MaybeExpr ExpressionAnalyzer::Analyze(const parser::CoindexedNamedObject &x) {
-  if (auto dataRef{ExtractDataRef(Analyze(x.base))}) {
+  const auto &[base, selector]{x.t};
+  if (auto dataRef{ExtractDataRef(Analyze(base))}) {
     if (!std::holds_alternative<ArrayRef>(dataRef->u) &&
         dataRef->GetLastSymbol().Rank() > 0) { // F'2023 C916
       Say("Subscripts must appear in a coindexed reference when its base is an array"_err_en_US);
@@ -1559,7 +1560,7 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::CoindexedNamedObject &x) {
     std::vector<Expr<SubscriptInteger>> cosubscripts;
     bool cosubsOk{true};
     for (const auto &cosub :
-        std::get<std::list<parser::Cosubscript>>(x.imageSelector.t)) {
+        std::get<std::list<parser::Cosubscript>>(selector.t)) {
       MaybeExpr coex{Analyze(cosub)};
       if (auto *intExpr{UnwrapExpr<Expr<SomeInteger>>(coex)}) {
         cosubscripts.push_back(
@@ -1578,7 +1579,7 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::CoindexedNamedObject &x) {
     }
     CoarrayRef coarrayRef{std::move(*dataRef), std::move(cosubscripts)};
     for (const auto &imageSelSpec :
-        std::get<std::list<parser::ImageSelectorSpec>>(x.imageSelector.t)) {
+        std::get<std::list<parser::ImageSelectorSpec>>(selector.t)) {
       common::visit(
           common::visitors{
               [&](const parser::ImageSelectorSpec::Notify &x) {
@@ -1969,8 +1970,8 @@ void ArrayConstructorContext::Add(const parser::Expr &expr) {
 void ArrayConstructorContext::Add(const parser::AcImpliedDo &impliedDo) {
   const auto &control{std::get<parser::AcImpliedDoControl>(impliedDo.t)};
   const auto &bounds{std::get<parser::AcImpliedDoControl::Bounds>(control.t)};
-  exprAnalyzer_.Analyze(bounds.name);
-  const auto &parsedName{parser::UnwrapRef<parser::Name>(bounds.name)};
+  exprAnalyzer_.Analyze(bounds.Name());
+  const auto &parsedName{parser::UnwrapRef<parser::Name>(bounds.Name())};
   parser::CharBlock name{parsedName.source};
   int kind{ImpliedDoIntType::kind};
   if (const Symbol *symbol{parsedName.symbol}) {
@@ -1981,12 +1982,12 @@ void ArrayConstructorContext::Add(const parser::AcImpliedDo &impliedDo) {
     }
   }
   std::optional<Expr<ImpliedDoIntType>> lower{
-      GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.lower)};
+      GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.Lower())};
   std::optional<Expr<ImpliedDoIntType>> upper{
-      GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.upper)};
+      GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.Upper())};
   if (lower && upper) {
     std::optional<Expr<ImpliedDoIntType>> stride{
-        GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.step)};
+        GetSpecificIntExpr<ImpliedDoIntType::kind>(bounds.Step())};
     if (!stride) {
       stride = Expr<ImpliedDoIntType>{1};
     }
@@ -1998,7 +1999,8 @@ void ArrayConstructorContext::Add(const parser::AcImpliedDo &impliedDo) {
       auto cUpper{ToInt64(upper)};
       auto cStride{ToInt64(stride)};
       if (!(messageDisplayedSet_ & 0x10) && cStride && *cStride == 0) {
-        exprAnalyzer_.SayAt(parser::UnwrapRef<parser::Expr>(bounds.step).source,
+        exprAnalyzer_.SayAt(
+            parser::UnwrapRef<parser::Expr>(bounds.Step()).source,
             "The stride of an implied DO loop must not be zero"_err_en_US);
         messageDisplayedSet_ |= 0x10;
       }
@@ -2565,15 +2567,15 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
     const parser::ProcComponentRef &pcr, ActualArguments &&arguments,
     bool isSubroutine) -> std::optional<CalleeAndArguments> {
   const auto &sc{parser::UnwrapRef<parser::StructureComponent>(pcr)};
-  if (MaybeExpr base{Analyze(sc.base)}) {
-    if (const Symbol *sym{sc.component.symbol}) {
+  if (MaybeExpr base{Analyze(sc.Base())}) {
+    if (const Symbol *sym{sc.Component().symbol}) {
       if (context_.HasError(sym)) {
         return std::nullopt;
       }
       if (!IsProcedure(*sym)) {
         AttachDeclaration(
-            Say(sc.component.source, "'%s' is not a procedure"_err_en_US,
-                sc.component.source),
+            Say(sc.Component().source, "'%s' is not a procedure"_err_en_US,
+                sc.Component().source),
             *sym);
         return std::nullopt;
       }
@@ -2628,7 +2630,7 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
               sym = latest;
             }
           }
-          sc.component.symbol = const_cast<Symbol *>(sym);
+          sc.Component().symbol = const_cast<Symbol *>(sym);
         }
         std::optional<DataRef> dataRef{ExtractDataRef(std::move(*dtExpr))};
         if (dataRef && !CheckDataRef(*dataRef)) {
@@ -2641,11 +2643,11 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
             // enforce it.
             AttachDeclaration(
                 Warn(common::LanguageFeature::NopassScalarBase,
-                    sc.component.source,
+                    sc.Component().source,
                     "Base of NOPASS type-bound procedure reference should be scalar"_port_en_US),
                 *sym);
           } else if (IsProcedurePointer(*sym)) { // C919
-            Say(sc.component.source,
+            Say(sc.Component().source,
                 "Base of procedure component reference must be scalar"_err_en_US);
           }
         }
@@ -2657,10 +2659,10 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
         } else if (dataRef.has_value()) {
           if (ExtractCoarrayRef(*dataRef)) {
             if (IsProcedurePointer(*sym)) {
-              Say(sc.component.source,
+              Say(sc.Component().source,
                   "Base of procedure component reference may not be coindexed"_err_en_US);
             } else {
-              Say(sc.component.source,
+              Say(sc.Component().source,
                   "A procedure binding may not be coindexed unless it can be resolved at compilation time"_err_en_US);
             }
           }
@@ -2674,7 +2676,7 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
                     std::move(arguments)};
               }
             }
-            Say(sc.component.source,
+            Say(sc.Component().source,
                 "Component is not in scope of base derived type"_err_en_US);
             return std::nullopt;
           } else {
@@ -2686,7 +2688,7 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
           }
         }
       }
-      Say(sc.component.source,
+      Say(sc.Component().source,
           "Base of procedure component reference is not a derived-type object"_err_en_US);
     }
   }
@@ -3360,7 +3362,7 @@ static const Symbol *AssumedTypePointerOrAllocatableDummy(const A &object) {
   return common::visit(
       common::visitors{
           [&](const parser::StructureComponent &x) {
-            return AssumedTypeDummy(x.component);
+            return AssumedTypeDummy(x.Component());
           },
           [&](const parser::Name &x) { return AssumedTypeDummy(x); },
       },
@@ -4072,7 +4074,7 @@ static bool CheckFuncRefToArrayElement(semantics::SemanticsContext &context,
   if (!name) {
     name = &parser::UnwrapRef<parser::StructureComponent>(
         std::get<parser::ProcComponentRef>(proc.u))
-                .component;
+                .Component();
   }
   if (!name->symbol) {
     return false;
@@ -4128,7 +4130,7 @@ static void FixMisparsedFunctionReference(
                 [&](parser::Name &name) { return name.symbol; },
                 [&](parser::ProcComponentRef &pcr) {
                   return parser::UnwrapRef<parser::StructureComponent>(pcr)
-                      .component.symbol;
+                      .Component().symbol;
                 },
             },
             proc.u)}) {
@@ -5458,7 +5460,7 @@ void ExprChecker::Post(const parser::DataStmtObject &obj) {
 bool ExprChecker::Pre(const parser::DataImpliedDo &ido) {
   parser::Walk(std::get<parser::DataImpliedDo::Bounds>(ido.t), *this);
   const auto &bounds{std::get<parser::DataImpliedDo::Bounds>(ido.t)};
-  const auto &name{parser::UnwrapRef<parser::Name>(bounds.name)};
+  const auto &name{parser::UnwrapRef<parser::Name>(bounds.Name())};
   int kind{evaluate::ResultType<evaluate::ImpliedDoIndex>::kind};
   if (const auto dynamicType{evaluate::DynamicType::From(DEREF(name.symbol))}) {
     if (dynamicType->category() == TypeCategory::Integer) {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 6467abf872c16..101ab46a568e5 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -226,7 +226,7 @@ class AccAttributeVisitor : DirectiveAttributeVisitor<llvm::acc::Directive> {
   bool Pre(const parser::LoopBounds<parser::ScalarName, A> &x) {
     if (!dirContext_.empty() && GetContext().withinConstruct) {
       if (auto *symbol{ResolveAcc(
-              x.name.thing, Symbol::Flag::AccPrivate, currScope())}) {
+              x.Name().thing, Symbol::Flag::AccPrivate, currScope())}) {
         AddToContextObjectWithDSA(*symbol, Symbol::Flag::AccPrivate);
       }
     }
@@ -786,9 +786,9 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
           }
           if (auto *procRef{
                   parser::Unwrap<parser::ProcComponentRef>(procD->u)}) {
-            if (!procRef->v.thing.component.symbol) {
-              if (!ResolveName(&procRef->v.thing.component)) {
-                createDummyProcSymbol(&procRef->v.thing.component);
+            if (!procRef->v.thing.Component().symbol) {
+              if (!ResolveName(&procRef->v.thing.Component())) {
+                createDummyProcSymbol(&procRef->v.thing.Component());
               }
             }
           }
@@ -1109,9 +1109,9 @@ std::tuple<const parser::Name *, const parser::ScalarExpr *,
 DirectiveAttributeVisitor<T>::GetLoopBounds(const parser::DoConstruct &x) {
   using Bounds = parser::LoopControl::Bounds;
   if (x.GetLoopControl()) {
-    if (const Bounds * b{std::get_if<Bounds>(&x.GetLoopControl()->u)}) {
-      auto &step = b->step;
-      return {&b->name.thing, &b->lower, &b->upper,
+    if (const Bounds *b{std::get_if<Bounds>(&x.GetLoopControl()->u)}) {
+      const auto &step = b->Step();
+      return {&b->Name().thing, &b->Lower(), &b->Upper(),
           step.has_value() ? &step.value() : nullptr};
     }
   } else {
@@ -1655,12 +1655,12 @@ void AccAttributeVisitor::CheckAssociatedLoop(
       if (auto *symbol{ResolveAcc(*ivName, flag, currScope())}) {
         if (auto &control{loop->GetLoopControl()}) {
           if (const Bounds * b{std::get_if<Bounds>(&control->u)}) {
-            if (auto lowerExpr{semantics::AnalyzeExpr(context_, b->lower)}) {
+            if (auto lowerExpr{semantics::AnalyzeExpr(context_, b->Lower())}) {
               semantics::UnorderedSymbolSet lowerSyms =
                   evaluate::CollectSymbols(*lowerExpr);
               checkExprHasSymbols(ivs, lowerSyms);
             }
-            if (auto upperExpr{semantics::AnalyzeExpr(context_, b->upper)}) {
+            if (auto upperExpr{semantics::AnalyzeExpr(context_, b->Upper())}) {
               semantics::UnorderedSymbolSet upperSyms =
                   evaluate::CollectSymbols(*upperExpr);
               checkExprHasSymbols(ivs, upperSyms);
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index ac67799938013..ef34c89182f7f 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -519,8 +519,8 @@ bool EquivalenceSets::CheckDataRef(
             return false;
           },
           [&](const common::Indirection<parser::ArrayElement> &elem) {
-            bool ok{CheckDataRef(source, elem.value().base)};
-            for (const auto &subscript : elem.value().subscripts) {
+            bool ok{CheckDataRef(source, elem.value().Base())};
+            for (const auto &subscript : elem.value().Subscripts()) {
               ok &= common::visit(
                   common::visitors{
                       [&](const parser::SubscriptTriplet &) {
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 2f15ab1e82e2b..bc5f80f287636 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -1532,7 +1532,7 @@ bool AccVisitor::Pre(const parser::AccClause::UseDevice &x) {
                       common::Indirection<parser::ArrayElement>;
                   if (auto *ind{std::get_if<ElementIndirection>(&dataRef->u)}) {
                     const parser::ArrayElement &arrayElement{ind->value()};
-                    const parser::DataRef &base{arrayElement.base};
+                    const parser::DataRef &base{arrayElement.Base()};
                     if (auto *name{std::get_if<parser::Name>(&base.u)}) {
                       CopySymbolWithDevice(name);
                     }
@@ -2142,7 +2142,7 @@ class ResolveNamesVisitor : public virtual ScopeHandler,
   void Post(const parser::SubstringInquiry &);
   template <typename A, typename B>
   void Post(const parser::LoopBounds<A, B> &x) {
-    ResolveName(parser::UnwrapRef<parser::Name>(x.name));
+    ResolveName(parser::UnwrapRef<parser::Name>(x.Name()));
   }
   void Post(const parser::ProcComponentRef &);
   bool Pre(const parser::FunctionReference &);
@@ -7936,13 +7936,13 @@ bool ConstructVisitor::Pre(const parser::AcImpliedDo &x) {
   // of the implied DO variable appears in one of the bound expressions. Thus
   // this extension, which shrinks the scope of the variable to exclude the
   // expressions in the bounds.
-  auto restore{BeginCheckOnIndexUseInOwnBounds(bounds.name)};
-  Walk(bounds.lower);
-  Walk(bounds.upper);
-  Walk(bounds.step);
+  auto restore{BeginCheckOnIndexUseInOwnBounds(bounds.Name())};
+  Walk(bounds.Lower());
+  Walk(bounds.Upper());
+  Walk(bounds.Step());
   EndCheckOnIndexUseInOwnBounds(restore);
   PushScope(Scope::Kind::ImpliedDos, nullptr);
-  DeclareStatementEntity(bounds.name, type);
+  DeclareStatementEntity(bounds.Name(), type);
   Walk(values);
   PopScope();
   return false;
@@ -7953,13 +7953,13 @@ bool ConstructVisitor::Pre(const parser::DataImpliedDo &x) {
   auto &type{std::get<std::optional<parser::IntegerTypeSpec>>(x.t)};
   auto &bounds{std::get<parser::DataImpliedDo::Bounds>(x.t)};
   // See comment in Pre(AcImpliedDo) above.
-  auto restore{BeginCheckOnIndexUseInOwnBounds(bounds.name)};
-  Walk(bounds.lower);
-  Walk(bounds.upper);
-  Walk(bounds.step);
+  auto restore{BeginCheckOnIndexUseInOwnBounds(bounds.Name())};
+  Walk(bounds.Lower());
+  Walk(bounds.Upper());
+  Walk(bounds.Step());
   EndCheckOnIndexUseInOwnBounds(restore);
   PushScope(Scope::Kind::ImpliedDos, nullptr);
-  DeclareStatementEntity(bounds.name, type);
+  DeclareStatementEntity(bounds.Name(), type);
   Walk(objects);
   PopScope();
   return false;
@@ -8016,7 +8016,7 @@ bool ConstructVisitor::Pre(const parser::DataStmtValue &x) {
   const auto &data{std::get<parser::DataStmtConstant>(x.t)};
   auto &mutableData{const_cast<parser::DataStmtConstant &>(data)};
   if (auto *elem{parser::Unwrap<parser::ArrayElement>(mutableData)}) {
-    if (const auto *name{std::get_if<parser::Name>(&elem->base.u)}) {
+    if (const auto *name{std::get_if<parser::Name>(&elem->Base().u)}) {
       if (const Symbol * symbol{FindSymbol(*name)};
           symbol && symbol->GetUltimate().has<DerivedTypeDetails>()) {
         mutableData.u = elem->ConvertToStructureConstructor(
@@ -8793,7 +8793,7 @@ bool ResolveNamesVisitor::Pre(const parser::ImportStmt &x) {
 
 const parser::Name *DeclarationVisitor::ResolveStructureComponent(
     const parser::StructureComponent &x) {
-  return FindComponent(ResolveDataRef(x.base), x.component);
+  return FindComponent(ResolveDataRef(x.Base()), x.Component());
 }
 
 const parser::Name *DeclarationVisitor::ResolveDesignator(
@@ -8818,8 +8818,8 @@ const parser::Name *DeclarationVisitor::ResolveDataRef(
             return ResolveStructureComponent(y.value());
           },
           [&](const Indirection<parser::ArrayElement> &y) {
-            Walk(y.value().subscripts);
-            const parser::Name *name{ResolveDataRef(y.value().base)};
+            Walk(y.value().Subscripts());
+            const parser::Name *name{ResolveDataRef(y.value().Base())};
             if (name && name->symbol) {
               if (!IsProcedure(*name->symbol)) {
                 ConvertToObjectEntity(*name->symbol);
@@ -8832,8 +8832,9 @@ const parser::Name *DeclarationVisitor::ResolveDataRef(
             return name;
           },
           [&](const Indirection<parser::CoindexedNamedObject> &y) {
-            Walk(y.value().imageSelector);
-            return ResolveDataRef(y.value().base);
+            const auto &[base, selector]{y.value().t};
+            Walk(selector);
+            return ResolveDataRef(base);
           },
       },
       x.u);
@@ -9340,7 +9341,7 @@ void ResolveNamesVisitor::HandleCall(
           [&](const parser::Name &x) { HandleProcedureName(procFlag, x); },
           [&](const parser::ProcComponentRef &x) {
             Walk(x);
-            const parser::Name &name{x.v.thing.component};
+            const parser::Name &name{x.v.thing.Component()};
             if (Symbol * symbol{name.symbol}) {
               if (IsProcedure(*symbol)) {
                 SetProcFlag(name, *symbol, procFlag);

>From 277b444e9d77d8125476f41be54d4990d8fcee51 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Mon, 12 Jan 2026 10:51:11 -0600
Subject: [PATCH 6/6] format

---
 flang/lib/Lower/Bridge.cpp               | 4 ++--
 flang/lib/Semantics/check-deallocate.cpp | 4 ++--
 flang/lib/Semantics/expression.cpp       | 3 ++-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 656940986809d..86959a8ee6e82 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3856,8 +3856,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         if (auto &step = bounds->Step())
           steps.push_back(builder->createConvert(
               crtLoc, idxTy,
-              fir::getBase(genExprValue(
-                  *Fortran::semantics::GetExpr(step), stmtCtx))));
+              fir::getBase(
+                  genExprValue(*Fortran::semantics::GetExpr(step), stmtCtx))));
         else // If `step` is not present, assume it is `1`.
           steps.push_back(builder->createIntegerConstant(loc, idxTy, 1));
 
diff --git a/flang/lib/Semantics/check-deallocate.cpp b/flang/lib/Semantics/check-deallocate.cpp
index 92b75b9d2ca1d..729e92aefc04c 100644
--- a/flang/lib/Semantics/check-deallocate.cpp
+++ b/flang/lib/Semantics/check-deallocate.cpp
@@ -101,8 +101,8 @@ void DeallocateChecker::Leave(const parser::DeallocateStmt &deallocateStmt) {
               source = structureComponent.Component().source;
               if (const auto *expr{GetExpr(context_, allocateObject)}) {
                 if (const Symbol *symbol{structureComponent.Component().symbol
-                            ? &structureComponent.Component().symbol
-                                  ->GetUltimate()
+                            ? &structureComponent.Component()
+                                  .symbol->GetUltimate()
                             : nullptr};
                     !IsAllocatableOrObjectPointer(symbol)) { // F'2023 C936
                   context_.Say(source,
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 6cca0c670a5f8..b63554815b6f5 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -4130,7 +4130,8 @@ static void FixMisparsedFunctionReference(
                 [&](parser::Name &name) { return name.symbol; },
                 [&](parser::ProcComponentRef &pcr) {
                   return parser::UnwrapRef<parser::StructureComponent>(pcr)
-                      .Component().symbol;
+                      .Component()
+                      .symbol;
                 },
             },
             proc.u)}) {



More information about the flang-commits mailing list