@@ -775,6 +775,165 @@ def test_compose_execute_equivalence_with_flags(self, flags, data, pipeline):
775775 self .assertEqual (expected , actual )
776776
777777
778+ class TestNestedComposeMapItems (unittest .TestCase ):
779+ """Tests for nested Compose respecting child map_items (issues #7932, #7565)."""
780+
781+ def test_child_map_items_false_receives_list (self ):
782+ """Parent map_items=True, child map_items=False: child receives list as-is."""
783+
784+ def split (x ):
785+ return [x + 1 , x + 2 ]
786+
787+ def sum_list (items ):
788+ return sum (items )
789+
790+ # The child Compose(map_items=False) should receive the list from split()
791+ # and pass it as-is to sum_list, rather than the parent expanding the list.
792+ pipeline = mt .Compose ([split , mt .Compose ([sum_list ], map_items = False )])
793+ result = pipeline (10 )
794+ self .assertEqual (result , 23 ) # (10+1) + (10+2) = 23
795+
796+ def test_inverse_respects_child_map_items (self ):
797+ """Inverse path should delegate to child Compose.inverse directly."""
798+ pipeline = mt .Compose ([mt .Flip (0 ), mt .Compose ([mt .Flip (1 )], map_items = False )])
799+ data = torch .randn (1 , 4 , 4 )
800+ result = pipeline (data )
801+ restored = pipeline .inverse (result )
802+ torch .testing .assert_close (data , restored )
803+
804+ def test_parent_no_map_child_map (self ):
805+ """Parent map_items=False, child map_items=True: child maps over items."""
806+
807+ def double (x ):
808+ return x * 2
809+
810+ # Parent treats the list as a single value; child maps double() over each item.
811+ pipeline = mt .Compose ([mt .Compose ([double ], map_items = True )], map_items = False )
812+ result = pipeline ([1 , 2 , 3 ])
813+ self .assertEqual (result , [2 , 4 , 6 ])
814+
815+ def test_flatten_preserves_different_map_items (self ):
816+ """flatten() should not merge a child Compose with different map_items."""
817+
818+ def noop (x ):
819+ return x
820+
821+ parent = mt .Compose ([noop , mt .Compose ([noop , noop ], map_items = False ), noop ])
822+ flat = parent .flatten ()
823+ # The inner Compose(map_items=False) should NOT be flattened
824+ self .assertEqual (len (flat .transforms ), 3 )
825+ self .assertIsInstance (flat .transforms [1 ], mt .Compose )
826+
827+ def test_multiple_children_with_mixed_map_items (self ):
828+ """Multiple internal Composes with different map_items should be handled correctly."""
829+
830+ def add_one (items ):
831+ if isinstance (items , list ):
832+ return [x + 1 for x in items ]
833+ return items + 1
834+
835+ def multiply_two (items ):
836+ if isinstance (items , list ):
837+ return [x * 2 for x in items ]
838+ return items * 2
839+
840+ # Parent with map_items=False processes the entire input as one unit
841+ # Child 1 (map_items=True) will map over each item in what it receives
842+ # Child 2 (map_items=False) will process the entire thing
843+ pipeline = mt .Compose (
844+ [mt .Compose ([add_one ], map_items = True ), mt .Compose ([multiply_two ], map_items = False )], map_items = False
845+ )
846+
847+ # Input [1, 2, 3]
848+ # First child with map_items=True maps add_one over [1,2,3]: [2, 3, 4]
849+ # Second child with map_items=False receives [2,3,4] and applies multiply_two: [4, 6, 8]
850+ result = pipeline ([1 , 2 , 3 ])
851+ self .assertEqual (result , [4 , 6 , 8 ])
852+
853+ def test_flatten_with_multiple_children_preserves_both (self ):
854+ """flatten() should preserve child with different map_items but flatten child with same."""
855+
856+ def noop (x ):
857+ return x
858+
859+ parent = mt .Compose (
860+ [
861+ noop ,
862+ mt .Compose ([noop , noop ], map_items = True ), # Same as parent, will be flattened
863+ mt .Compose ([noop , noop ], map_items = False ), # Different, will be preserved
864+ noop ,
865+ ]
866+ )
867+ flat = parent .flatten ()
868+ # First nested Compose(map_items=True) will be flattened into parent
869+ # Second nested Compose(map_items=False) will be preserved
870+ # Result: noop + noop + noop + Compose([noop, noop]) + noop = 5 transforms
871+ self .assertEqual (len (flat .transforms ), 5 )
872+ # Check that the preserved one is at the correct position
873+ self .assertIsInstance (flat .transforms [3 ], mt .Compose )
874+ self .assertEqual (flat .transforms [3 ].map_items , False )
875+
876+ def test_three_level_nesting_respects_different_map_items (self ):
877+ """Three-level nesting with different map_items at each level."""
878+
879+ def add_one (x ):
880+ return x + 1
881+
882+ # Level 1 (outermost): map_items=True (default)
883+ # Level 2: map_items=False
884+ # Level 3: map_items=True (same as level 2, so will be flattened into level 2)
885+ innermost = mt .Compose ([add_one ], map_items = True )
886+ middle = mt .Compose ([add_one , innermost ], map_items = False )
887+ outer = mt .Compose ([middle ])
888+
889+ # Test with a simple value
890+ # outer has map_items=True (default), middle has map_items=False
891+ # So middle should be preserved and receive the input as-is
892+ result = outer (5 )
893+ # outer(5) -> maps to middle -> middle(5) with map_items=False
894+ # middle(5) -> add_one(5) = 6, then innermost(6) with map_items=True
895+ # innermost(6) -> add_one(6) = 7
896+ self .assertEqual (result , 7 )
897+
898+ def test_inverse_with_multiple_children_different_map_items (self ):
899+ """Inverse should work correctly with multiple children having different map_items."""
900+ pipeline = mt .Compose (
901+ [mt .Flip (0 ), mt .Compose ([mt .Flip (1 )], map_items = False ), mt .Compose ([mt .Flip (0 )], map_items = True )]
902+ )
903+ data = torch .randn (2 , 4 , 4 )
904+ result = pipeline (data )
905+ restored = pipeline .inverse (result )
906+ torch .testing .assert_close (data , restored )
907+
908+ def test_flatten_with_mixed_same_and_different_map_items (self ):
909+ """flatten() should merge children with same map_items but preserve those with different."""
910+
911+ def noop (x ):
912+ return x
913+
914+ # Parent has map_items=True (default)
915+ # Child 1 has map_items=True (same as parent) -> should be flattened
916+ # Child 2 has map_items=False (different from parent) -> should NOT be flattened
917+ parent = mt .Compose (
918+ [
919+ noop ,
920+ mt .Compose ([noop , noop ], map_items = True ), # Same as parent, will be flattened
921+ mt .Compose ([noop , noop ], map_items = False ), # Different from parent, will be preserved
922+ noop ,
923+ ]
924+ )
925+ flat = parent .flatten ()
926+ # After flatten:
927+ # - noop (preserved)
928+ # - 2 noops from first Compose (flattened because map_items=True matches parent)
929+ # - Compose([noop, noop], map_items=False) (preserved because different)
930+ # - noop (preserved)
931+ # Total: 5 transforms
932+ self .assertEqual (len (flat .transforms ), 5 )
933+ self .assertIsInstance (flat .transforms [3 ], mt .Compose )
934+ self .assertEqual (flat .transforms [3 ].map_items , False )
935+
936+
778937class TestComposeCallableInput (unittest .TestCase ):
779938
780939 def test_value_error_when_not_sequence (self ):
0 commit comments