@@ -28,6 +28,35 @@ def test_transition_slices_from_group_1d_object_a(self) -> None:
2828 [(0 , 2 , None ), (2 , 4 , None ), (4 , None , None )],
2929 )
3030
31+ def test_transition_slices_from_group_1d_unaligned (self ) -> None :
32+ # A contiguous (stride == itemsize) but *unaligned* buffer must not take
33+ # the typed fast-path, whose pointer-cast dereferences are UB on
34+ # misaligned data; it should fall through to the alignment-safe scan and
35+ # still produce results identical to an aligned copy.
36+ def make_unaligned (values , dtype ):
37+ dt = np .dtype (dtype )
38+ n = len (values )
39+ raw = np .empty (n * dt .itemsize + dt .itemsize , dtype = np .uint8 )
40+ # view starting one byte in -> contiguous but misaligned
41+ a = raw [1 : 1 + n * dt .itemsize ].view (dt )
42+ a [:] = values
43+ return a
44+
45+ for dtype , values in (
46+ (np .float64 , [1.0 , 1.0 , 2.0 , 2.0 , 2.0 , 3.0 ]),
47+ (np .int64 , [10 , 10 , 10 , 20 , 20 , 30 ]),
48+ (np .int32 , [5 , 5 , 6 , 7 , 7 ]),
49+ ):
50+ a = make_unaligned (values , dtype )
51+ self .assertTrue (a .flags .c_contiguous )
52+ self .assertFalse (a .flags .aligned )
53+ ref = np .array (values , dtype = dtype ) # fresh, aligned
54+
55+ slices , group_to_tuple = transition_slices_from_group (a )
56+ ref_slices , _ = transition_slices_from_group (ref )
57+ self .assertFalse (group_to_tuple )
58+ self .assertEqual (slices_to_pairs (slices ), slices_to_pairs (ref_slices ))
59+
3160 def test_transition_slices_from_group_2d_a (self ) -> None :
3261 group = np .array (
3362 [
0 commit comments