[flang][FIRToMemRef] Fix lowering of complex array component slices (z%re, z%im) (#191846)

fir.slice with a path component (z%re, z%im) was silently dropped by
FIRToMemRef. Since memref.reinterpret_cast cannot change element type,
layout must come from the projected box descriptor via
fir.box_dims/fir.box_elesize rather than the triplets. Only
complex-array projections are handled here —
sizeof(complex<T>)/sizeof(T) = 2 is always exact for divsi. Derived-type
component projections bail out to downstream FIR-to-LLVM lowering where
strides can be non-integer.
diff --git a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
index 07a87a4..ec58d6f 100644
--- a/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToMemRef.cpp
@@ -142,10 +142,18 @@
 
   Value canonicalizeIndex(Value, PatternRewriter &) const;
 
+  // Logical section information used by FIRToMemRef. For projected slices, the
+  // descriptor still owns the physical layout, so `sliceVec` intentionally
+  // stays empty while `shapeVec`/`shiftVec` remain available for index math.
+  struct SliceInfo {
+    SmallVector<Value> shapeVec;
+    SmallVector<Value> shiftVec;
+    SmallVector<Value> sliceVec;
+    bool hasProjectedSlice = false;
+  };
+
   template <typename OpTy>
-  void getShapeFrom(OpTy op, SmallVector<Value> &shapeVec,
-                    SmallVector<Value> &shiftVec,
-                    SmallVector<Value> &sliceVec) const;
+  void collectSliceInfoFrom(OpTy op, SliceInfo &info) const;
 
   void populateShapeAndShift(SmallVectorImpl<Value> &shapeVec,
                              SmallVectorImpl<Value> &shiftVec,
@@ -155,6 +163,14 @@
 
   void populateShape(SmallVectorImpl<Value> &vec, fir::ShapeOp shape) const;
 
+  static fir::SliceOp getSliceOp(Value sliceVal) {
+    return sliceVal ? sliceVal.getDefiningOp<fir::SliceOp>() : fir::SliceOp{};
+  }
+
+  static bool hasProjectedSlice(fir::SliceOp sliceOp) {
+    return sliceOp && !sliceOp.getFields().empty();
+  }
+
   unsigned getRankFromEmbox(fir::EmboxOp embox) const {
     auto memrefType = embox.getMemref().getType();
     Type unwrappedType = fir::unwrapRefType(memrefType);
@@ -278,9 +294,7 @@
 }
 
 template <typename OpTy>
-void FIRToMemRef::getShapeFrom(OpTy op, SmallVector<Value> &shapeVec,
-                               SmallVector<Value> &shiftVec,
-                               SmallVector<Value> &sliceVec) const {
+void FIRToMemRef::collectSliceInfoFrom(OpTy op, SliceInfo &info) const {
   if constexpr (std::is_same_v<OpTy, fir::ArrayCoorOp> ||
                 std::is_same_v<OpTy, fir::ReboxOp> ||
                 std::is_same_v<OpTy, fir::EmboxOp>) {
@@ -290,19 +304,23 @@
       Operation *shapeValOp = shapeVal.getDefiningOp();
 
       if (auto shapeOp = dyn_cast<fir::ShapeOp>(shapeValOp)) {
-        populateShape(shapeVec, shapeOp);
+        populateShape(info.shapeVec, shapeOp);
       } else if (auto shapeShiftOp = dyn_cast<fir::ShapeShiftOp>(shapeValOp)) {
-        populateShapeAndShift(shapeVec, shiftVec, shapeShiftOp);
+        populateShapeAndShift(info.shapeVec, info.shiftVec, shapeShiftOp);
       } else if (auto shiftOp = dyn_cast<fir::ShiftOp>(shapeValOp)) {
-        populateShift(shiftVec, shiftOp);
+        populateShift(info.shiftVec, shiftOp);
       }
     }
 
-    Value sliceVal = op.getSlice();
-    if (sliceVal) {
-      if (auto sliceOp = sliceVal.getDefiningOp<fir::SliceOp>()) {
+    if (auto sliceOp = getSliceOp(op.getSlice())) {
+      // A slice path changes the physical projection of the boxed entity (for
+      // example, `complex -> real` for `%re`). Preserve shape/shift for logical
+      // indexing, but do not treat the triplets alone as layout information.
+      if (hasProjectedSlice(sliceOp)) {
+        info.hasProjectedSlice = true;
+      } else {
         auto triples = sliceOp.getTriples();
-        sliceVec.append(triples.begin(), triples.end());
+        info.sliceVec.append(triples.begin(), triples.end());
       }
     }
   }
@@ -431,15 +449,35 @@
   IndexType indexTy = rewriter.getIndexType();
   SmallVector<Value> indices;
   Location loc = arrayCoorOp->getLoc();
-  SmallVector<Value> shiftVec, shapeVec, sliceVec;
+  SliceInfo sliceInfo;
   int rank = arrayCoorOp.getIndices().size();
-  getShapeFrom<fir::ArrayCoorOp>(arrayCoorOp, shapeVec, shiftVec, sliceVec);
+  collectSliceInfoFrom(arrayCoorOp, sliceInfo);
 
+  // Only collect shape/shift from fir.embox, never from fir.rebox.
+  //
+  // The distinction is a Fortran descriptor invariant:
+  //
+  //                 | fir.embox              | fir.rebox
+  //   --------------|------------------------|---------------------------
+  //   base_addr     | raw pointer            | pre-adjusted by (lb-1)*stride
+  //   Index formula | index - lb             | index - 1
+  //   Collect shift | yes                    | no
+  //
+  // fir.embox creates a box from a raw pointer so base_addr is not adjusted;
+  // the shift must be collected so index arithmetic subtracts lb correctly.
+  // fir.rebox re-boxes a live descriptor whose base_addr the runtime has
+  // already adjusted downward by (lb-1)*stride; collecting the shift here
+  // would subtract the lower bound a second time, giving the wrong element.
   if (auto embox = dyn_cast_or_null<fir::EmboxOp>(memref)) {
-    getShapeFrom<fir::EmboxOp>(embox, shapeVec, shiftVec, sliceVec);
+    collectSliceInfoFrom(embox, sliceInfo);
     rank = getRankFromEmbox(embox);
   }
 
+  // Projected boxed slices leave `sliceVec` empty on purpose: indices are
+  // computed in the logical section coordinate space, while stride/base come
+  // later from the box descriptor.
+  SmallVector<Value> &shiftVec = sliceInfo.shiftVec;
+  SmallVector<Value> &sliceVec = sliceInfo.sliceVec;
   SmallVector<Value> sliceLbs, sliceStrides;
   for (size_t i = 0; i < sliceVec.size(); i += 3) {
     sliceLbs.push_back(castTypeToIndexType(sliceVec[i], rewriter));
@@ -604,18 +642,22 @@
   SmallVector<Value> strides;
   strides.reserve(rank);
 
-  SmallVector<Value> shapeVec, shiftVec, sliceVec;
-  getShapeFrom<fir::ArrayCoorOp>(arrayCoorOp, shapeVec, shiftVec, sliceVec);
+  SliceInfo sliceInfo;
+  collectSliceInfoFrom(arrayCoorOp, sliceInfo);
 
   Value box = firMemref;
   if (!isa<BlockArgument>(firMemref)) {
-    if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>())
-      getShapeFrom<fir::EmboxOp>(embox, shapeVec, shiftVec, sliceVec);
-    else if (auto rebox = firMemref.getDefiningOp<fir::ReboxOp>())
-      getShapeFrom<fir::ReboxOp>(rebox, shapeVec, shiftVec, sliceVec);
+    if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>()) {
+      collectSliceInfoFrom(embox, sliceInfo);
+    } else if (auto rebox = firMemref.getDefiningOp<fir::ReboxOp>()) {
+      collectSliceInfoFrom(rebox, sliceInfo);
+    }
   }
 
-  if (shapeVec.empty()) {
+  SmallVector<Value> &shapeVec = sliceInfo.shapeVec;
+  if (sliceInfo.hasProjectedSlice || shapeVec.empty()) {
+    // Projected slices carry their physical layout in the descriptor. Rebuild
+    // the MemRef view from box metadata instead of from slice triplets.
     auto boxElementSize =
         fir::BoxEleSizeOp::create(rewriter, loc, indexTy, box);
 
@@ -722,13 +764,36 @@
       };
 
       if (auto embox = dyn_cast_or_null<fir::EmboxOp>(baseOp)) {
-        if (!sameBaseBoxTypes(embox.getType(), embox.getMemref().getType())) {
+        // A projected slice changes the element type of the boxed view.  We
+        // can only lower it here when the storage element is complex<T> and
+        // the projection is the real or imaginary part (i.e. %re / %im).  For
+        // such cases sizeof(complex<T>) == 2*sizeof(T), so
+        // divsi(byte_stride, elesize) is always an exact integer.
+        //
+        // Derived-type component projections (e.g. a%x, a%y) may produce a
+        // non-integer element-unit stride (e.g. sizeof(T)=24,
+        // sizeof(complex<f64>)=16 -> 24/16 = 1 after truncation, which is
+        // wrong).  For those, the type-restriction check below fires and we
+        // bail out, leaving the ops for downstream FIR-to-LLVM lowering.
+        auto isComplexComponentProjection = [&](fir::EmboxOp embox) -> bool {
+          if (!hasProjectedSlice(getSliceOp(embox.getSlice())))
+            return false;
+          Type memTy = fir::unwrapRefType(embox.getMemref().getType());
+          if (auto seqTy = dyn_cast<fir::SequenceType>(memTy))
+            memTy = seqTy.getEleTy();
+          return mlir::isa<mlir::ComplexType>(memTy);
+        };
+        bool projectedSlice = isComplexComponentProjection(embox);
+        if (!projectedSlice &&
+            !sameBaseBoxTypes(embox.getType(), embox.getMemref().getType())) {
           LLVM_DEBUG(llvm::dbgs()
                      << "FIRToMemRef: embox base type and memref type are not "
                         "the same, bailing out of conversion\n");
           return failure();
         }
-        if (embox.getSlice() &&
+        // Keep `box_addr` on the projected box so the descriptor remains the
+        // source of truth for projected element type and stride.
+        if (!projectedSlice && embox.getSlice() &&
             embox.getSlice().getDefiningOp<fir::SliceOp>()) {
           Type originalType = embox.getMemref().getType();
           basePtr = embox.getMemref();
diff --git a/flang/test/Transforms/FIRToMemRef/slice-projected.mlir b/flang/test/Transforms/FIRToMemRef/slice-projected.mlir
new file mode 100644
index 0000000..7b0fbdf
--- /dev/null
+++ b/flang/test/Transforms/FIRToMemRef/slice-projected.mlir
@@ -0,0 +1,99 @@
+// RUN: fir-opt %s --fir-to-memref --allow-unregistered-dialect | FileCheck %s
+
+// Tests for fir.slice with a path component (projected component slice).
+// A projected slice changes the element type of the boxed view, e.g.
+// z%re projects complex<f32> -> f32.  The layout (strides / base address)
+// must come from the projected box descriptor, NOT from reconstructing the
+// triplets, because memref.reinterpret_cast requires the same element type
+// on both sides and the triplet strides are in storage-element units
+// (complex<f32>) while the MemRef strides must be in projected-element units
+// (f32).
+//
+// Derived from:
+//   complex, target :: z(4) = 0.
+//   real, pointer  :: r(:)
+//   r => z%re
+//   r = r + z(4:1:-1)%re
+
+// ----------------------------------------------------------------------------
+// Forward projected slice: z(1:4:1)%re
+// The slice path %c0 projects complex<f32> -> f32 (real part).
+// Expected lowering:
+//   - fir.box_addr on the projected box (!fir.box<!fir.array<4xf32>>)
+//   - fir.convert to memref<4xf32>   (NOT to memref<4xcomplex<f32>>)
+//   - index = i - 1  (1-based, no triplet arithmetic)
+//   - strides from fir.box_dims / fir.box_elesize on the projected box
+// ----------------------------------------------------------------------------
+// CHECK-LABEL: func.func @projected_slice_fwd
+// CHECK:       [[C1:%.*]] = arith.constant 1 : index
+// CHECK:       [[C4:%.*]] = arith.constant 4 : index
+// CHECK:       [[C0:%.*]] = arith.constant 0 : index
+// CHECK:       [[SHAPE:%.*]] = fir.shape [[C4]] : (index) -> !fir.shape<1>
+// CHECK:       [[SLICE:%.*]] = fir.slice [[C1]], [[C4]], [[C1]] path [[C0]] : (index, index, index, index) -> !fir.slice<1>
+// CHECK:       [[EMBOX:%.*]] = fir.embox %arg0([[SHAPE]]) {{\[}}[[SLICE]]{{\]}} : (!fir.ref<!fir.array<4xcomplex<f32>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf32>>
+// CHECK:       fir.do_loop [[I:%.*]] = [[C1]] to [[C4]] step [[C1]] unordered {
+// Projected box_addr gives f32 pointer, not complex<f32>.
+// CHECK:         [[BOXADDR:%.*]] = fir.box_addr [[EMBOX]] : (!fir.box<!fir.array<4xf32>>) -> !fir.ref<!fir.array<4xf32>>
+// CHECK:         [[CONVERT:%.*]] = fir.convert [[BOXADDR]] : (!fir.ref<!fir.array<4xf32>>) -> memref<4xf32>
+// Index: i-1 (1-based). The lowering emits: delta=i-1, scaled=delta*1,
+// offset=1-1=0, finalIdx=scaled+offset.  The addi result is what feeds the load.
+// CHECK:         [[C1_0:%.*]] = arith.constant 1 : index
+// CHECK:         [[DELTA:%.*]] = arith.subi [[I]], [[C1_0]] : index
+// CHECK:         [[SCALED:%.*]] = arith.muli [[DELTA]], [[C1_0]] : index
+// CHECK:         [[OFFSET:%.*]] = arith.subi [[C1_0]], [[C1_0]] : index
+// CHECK:         [[IDX:%.*]] = arith.addi [[SCALED]], [[OFFSET]] : index
+// Layout: extent and stride come from the projected box descriptor.
+// CHECK:         [[ELE:%.*]] = fir.box_elesize [[EMBOX]] : (!fir.box<!fir.array<4xf32>>) -> index
+// CHECK:         [[C0_0:%.*]] = arith.constant 0 : index
+// CHECK:         [[DIMS:%.*]]:3 = fir.box_dims [[EMBOX]], [[C0_0]] : (!fir.box<!fir.array<4xf32>>, index) -> (index, index, index)
+// CHECK:         [[STRIDE:%.*]] = arith.divsi [[DIMS]]#2, [[ELE]] : index
+// CHECK:         [[C0_1:%.*]] = arith.constant 0 : index
+// CHECK:         [[VIEW:%.*]] = memref.reinterpret_cast [[CONVERT]] to offset: {{\[}}[[C0_1]]{{\]}}, sizes: {{\[}}[[DIMS]]#1{{\]}}, strides: {{\[}}[[STRIDE]]{{\]}} : memref<4xf32> to memref<?xf32, strided<[?], offset: ?>>
+// CHECK:         memref.load [[VIEW]]{{\[}}[[IDX]]{{\]}} : memref<?xf32, strided<[?], offset: ?>>
+func.func @projected_slice_fwd(%arg0: !fir.ref<!fir.array<4xcomplex<f32>>>) {
+  %c1 = arith.constant 1 : index
+  %c4 = arith.constant 4 : index
+  %c0 = arith.constant 0 : index
+  %shape = fir.shape %c4 : (index) -> !fir.shape<1>
+  %slice = fir.slice %c1, %c4, %c1 path %c0 : (index, index, index, index) -> !fir.slice<1>
+  %embox = fir.embox %arg0(%shape) [%slice] : (!fir.ref<!fir.array<4xcomplex<f32>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf32>>
+  fir.do_loop %i = %c1 to %c4 step %c1 unordered {
+    %coor = fir.array_coor %embox %i : (!fir.box<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+    %val = fir.load %coor : !fir.ref<f32>
+  }
+  return
+}
+
+// ----------------------------------------------------------------------------
+// Derived-type component projection: a%x where a : TYPE{x:f64, y:complex<f64>}
+//
+// This is NOT a complex projection — the storage element is the derived type T,
+// not complex<T>.  FIRToMemRef cannot safely compute element-unit strides via
+// divsi(byte_stride, elesize) because sizeof(T)/sizeof(component) may not be an
+// integer (e.g. sizeof(T)=24, sizeof(complex<f64>)=16 -> 1.5, truncated to 1).
+//
+// The pass must leave fir.array_coor and fir.store/fir.load unconverted;
+// downstream FIR-to-LLVM lowering handles them correctly via the descriptor.
+//
+// CHECK-LABEL: func.func @derived_component_not_projected
+// The fir.array_coor must survive (not be erased).
+// CHECK:       fir.array_coor
+// The store must remain as fir.store, not memref.store.
+// CHECK:       fir.store
+// CHECK-NOT:   memref.store
+// ----------------------------------------------------------------------------
+func.func @derived_component_not_projected(
+    %arg0: !fir.ref<!fir.array<4x!fir.type<T{x:f64,y:complex<f64>}>>>) {
+  %c1 = arith.constant 1 : index
+  %c4 = arith.constant 4 : index
+  %cst = arith.constant 9.9e+01 : f64
+  %field = fir.field_index x, !fir.type<T{x:f64,y:complex<f64>}>
+  %shape = fir.shape %c4 : (index) -> !fir.shape<1>
+  %slice = fir.slice %c1, %c4, %c1 path %field : (index, index, index, !fir.field) -> !fir.slice<1>
+  %embox = fir.embox %arg0(%shape) [%slice] : (!fir.ref<!fir.array<4x!fir.type<T{x:f64,y:complex<f64>}>>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<4xf64>>
+  fir.do_loop %i = %c1 to %c4 step %c1 unordered {
+    %coor = fir.array_coor %embox %i : (!fir.box<!fir.array<4xf64>>, index) -> !fir.ref<f64>
+    fir.store %cst to %coor : !fir.ref<f64>
+  }
+  return
+}