Skip to content

Commit cc01981

Browse files
committed
tests: strengthen tuple-struct coverage
Increase test coverage for new tuple struct feature with more comprehensive cases focusing on generics and !Unpin types. Extend `tests/tuple_struct.rs` cases - add runtime checks for generic payloads (type, lifetime, const generics) - cover multi-pinned tuple fields and `PinnedDrop` delegation - verify partial-init failure cleanup/rollback semantics Expand tuple struct UI test coverage - `tests/ui/compile-fail/init/` (wrong generics, invalid index, tuple arrow/syntax error cases) - `tests/ui/compile-fail/pin_data/` (missing #[pin]) Update tuple expand expectations - `tests/ui/expand/tuple_struct.rs` Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
1 parent 9b187c6 commit cc01981

15 files changed

Lines changed: 476 additions & 73 deletions

tests/tuple_struct.rs

Lines changed: 190 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,217 @@
11
#![cfg_attr(USE_RUSTC_FEATURES, feature(lint_reasons))]
2+
#![cfg_attr(feature = "alloc", feature(allocator_api))]
23

3-
use core::ptr;
4-
4+
use core::{
5+
pin::Pin,
6+
sync::atomic::{AtomicUsize, Ordering},
7+
};
58
use pin_init::*;
69

10+
#[allow(unused_attributes)]
11+
#[path = "../examples/mutex.rs"]
12+
mod mutex;
13+
use mutex::*;
14+
715
#[pin_data]
8-
struct TupleStruct(#[pin] i32, i32);
16+
struct TupleStruct<T>(#[pin] CMutex<T>, i32);
917

10-
fn init_i32(value: i32) -> impl PinInit<i32> {
11-
// SAFETY: The closure always initializes `slot` with a valid `i32` value.
12-
unsafe {
13-
pin_init_from_closure(move |slot| {
14-
// SAFETY: `slot` is provided by the initialization framework and valid for write.
15-
ptr::write(slot, value);
16-
Ok(())
17-
})
18+
fn assert_pinned_mutex<T>(_: &Pin<&mut CMutex<T>>) {}
19+
20+
#[test]
21+
fn tuple_struct_values() {
22+
// Baseline tuple-field syntax with index-based struct initializer.
23+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize> { 0 <- CMutex::new(42), 1: 24 }));
24+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 42);
25+
assert_eq!(tuple.as_ref().get_ref().1, 24);
26+
}
27+
28+
#[test]
29+
fn tuple_struct_init_arrow_and_projection() {
30+
// Checks projection types and that `<-` correctly initializes the pinned tuple field.
31+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize> { 0 <- CMutex::new(7), 1: 13 }));
32+
33+
let projected = tuple.as_mut().project();
34+
assert_pinned_mutex(&projected._0);
35+
let projected = tuple.as_mut().project();
36+
assert_eq!(*projected._0.as_ref().get_ref().lock(), 7);
37+
assert_eq!(*projected._1, 13);
38+
}
39+
40+
#[test]
41+
fn tuple_struct_constructor_form() {
42+
// Same semantics as `tuple_struct_values`, but using tuple constructor syntax.
43+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize>(<- CMutex::new(11), 29)));
44+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 11);
45+
assert_eq!(tuple.as_ref().get_ref().1, 29);
46+
}
47+
48+
#[pin_data]
49+
struct DualPinned<T>(#[pin] CMutex<T>, #[pin] CMutex<T>, usize);
50+
51+
#[test]
52+
fn tuple_struct_multi_pinned_fields_projection() {
53+
// Both pinned tuple fields should project to `Pin<&mut CMutex<T>>` and stay usable.
54+
stack_pin_init!(let tuple = pin_init!(DualPinned::<usize>(<- CMutex::new(1), <- CMutex::new(2), 3)));
55+
let projected = tuple.as_mut().project();
56+
assert_pinned_mutex(&projected._0);
57+
assert_pinned_mutex(&projected._1);
58+
59+
*projected._0.as_ref().get_ref().lock() = 10;
60+
*projected._1.as_ref().get_ref().lock() = 20;
61+
*projected._2 = 30;
62+
63+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10);
64+
assert_eq!(*tuple.as_ref().get_ref().1.lock(), 20);
65+
assert_eq!(tuple.as_ref().get_ref().2, 30);
66+
}
67+
68+
#[test]
69+
fn tuple_struct_generic_type_param_behavior() {
70+
// Keep this focused on explicit generic-arg syntax (`::<u16>`) with struct-style init.
71+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<u16> { 0 <- CMutex::new(123u16), 1: 7 }));
72+
let projected = tuple.as_mut().project();
73+
assert_pinned_mutex(&projected._0);
74+
assert_eq!(*projected._0.as_ref().get_ref().lock(), 123u16);
75+
assert_eq!(*projected._1, 7);
76+
}
77+
78+
#[pin_data]
79+
struct RefTuple<'a>(#[pin] CMutex<&'a usize>, usize);
80+
81+
#[test]
82+
fn tuple_struct_lifetime_reference_behavior() {
83+
// Verifies tuple init/projection with borrowed data (`'a`) through the pinned field.
84+
let first = 111usize;
85+
let first_ref = &first;
86+
stack_pin_init!(let tuple = pin_init!(RefTuple { 0 <- CMutex::new(first_ref), 1: 3 }));
87+
assert_eq!(**tuple.as_ref().get_ref().0.lock(), 111usize);
88+
assert_eq!(tuple.as_ref().get_ref().1, 3);
89+
90+
let second = 222usize;
91+
let second_ref = &second;
92+
stack_pin_init!(let tuple = pin_init!(RefTuple(<- CMutex::new(second_ref), 4)));
93+
let projected = tuple.as_mut().project();
94+
assert_pinned_mutex(&projected._0);
95+
assert_eq!(**projected._0.as_ref().get_ref().lock(), 222usize);
96+
assert_eq!(*projected._1, 4);
97+
}
98+
99+
#[test]
100+
fn tuple_struct_projection_mutation_behavior() {
101+
// Confirms both projected fields can be mutated through their projected references.
102+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize>(<- CMutex::new(1usize), 2)));
103+
104+
let projected = tuple.as_mut().project();
105+
*projected._0.as_ref().get_ref().lock() = 10usize;
106+
*projected._1 = 20;
107+
108+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10usize);
109+
assert_eq!(tuple.as_ref().get_ref().1, 20);
110+
}
111+
112+
struct DropCounter;
113+
114+
static FALLIBLE_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0);
115+
116+
impl Drop for DropCounter {
117+
fn drop(&mut self) {
118+
FALLIBLE_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst);
18119
}
19120
}
20121

21-
fn init_i32_unpinned(value: i32) -> impl Init<i32> {
22-
// SAFETY: The closure always initializes `slot` with a valid `i32` value.
122+
fn tuple_failing_init() -> impl PinInit<TupleStruct<DropCounter>, ()> {
123+
// SAFETY: We emulate "initialized first field, then fail" and ensure rollback leaves no
124+
// partially initialized value in `slot`.
23125
unsafe {
24-
init_from_closure(move |slot| {
25-
// SAFETY: `slot` is provided by the initialization framework and valid for write.
26-
ptr::write(slot, value);
27-
Ok(())
126+
pin_init_from_closure(|slot: *mut TupleStruct<DropCounter>| {
127+
// Manually initialize only field 0 to model a mid-initialization failure.
128+
let field0 = core::ptr::addr_of_mut!((*slot).0);
129+
let init0 = CMutex::new(DropCounter);
130+
// SAFETY: `field0` points into `slot`, which is valid uninitialized memory.
131+
match init0.__pinned_init(field0) {
132+
Ok(()) => {}
133+
Err(infallible) => match infallible {},
134+
}
135+
// Explicit rollback is required before returning `Err` to avoid leaking initialized state.
136+
core::ptr::drop_in_place(field0);
137+
Err(())
28138
})
29139
}
30140
}
31141

32142
#[test]
33-
fn tuple_struct_values() {
34-
stack_pin_init!(let foo = pin_init!(TupleStruct { 0: 42, 1: 24 }));
35-
assert_eq!(foo.as_ref().get_ref().0, 42);
36-
assert_eq!(foo.as_ref().get_ref().1, 24);
143+
fn tuple_struct_fallible_init_drops_initialized_fields() {
144+
// A failure after partial initialization must still drop the already-initialized field.
145+
FALLIBLE_TUPLE_DROPS.store(0, Ordering::SeqCst);
146+
stack_try_pin_init!(let tuple: TupleStruct<DropCounter> = tuple_failing_init());
147+
assert!(matches!(tuple, Err(())));
148+
assert_eq!(FALLIBLE_TUPLE_DROPS.load(Ordering::SeqCst), 1);
37149
}
38150

151+
#[pin_data]
152+
struct TupleConst<T, const N: usize>(#[pin] CMutex<[T; N]>, usize);
153+
39154
#[test]
40-
#[allow(clippy::redundant_locals)]
41-
fn tuple_struct_init_arrow_and_projection() {
42-
stack_pin_init!(let foo = pin_init!(TupleStruct { 0 <- init_i32(7), 1: 13 }));
43-
let mut foo = foo;
44-
let projected = foo.as_mut().project();
45-
assert_eq!(*projected._0.as_ref().get_ref(), 7);
46-
assert_eq!(*projected._1, 13);
155+
fn tuple_struct_const_generic_behavior() {
156+
// Covers tuple-field init/projection when the pinned field contains a const-generic array.
157+
stack_pin_init!(let tuple = pin_init!(TupleConst::<u8, 3> { 0 <- CMutex::new([1, 2, 3]), 1: 9 }));
158+
let projected = tuple.as_mut().project();
159+
assert_pinned_mutex(&projected._0);
160+
assert_eq!(*projected._0.as_ref().get_ref().lock(), [1, 2, 3]);
161+
assert_eq!(*projected._1, 9);
162+
163+
stack_pin_init!(let tuple = pin_init!(TupleConst::<u8, 2>(<- CMutex::new([7, 8]), 5)));
164+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), [7, 8]);
165+
assert_eq!(tuple.as_ref().get_ref().1, 5);
47166
}
48167

49168
#[test]
50-
fn tuple_struct_constructor_form() {
51-
stack_pin_init!(let foo = pin_init!(TupleStruct(<- init_i32(11), 29)));
52-
assert_eq!(foo.as_ref().get_ref().0, 11);
53-
assert_eq!(foo.as_ref().get_ref().1, 29);
169+
fn tuple_struct_generic_inference_constructor_form() {
170+
// Ensures tuple constructor form can infer `T` from the pinned field initializer.
171+
stack_pin_init!(let tuple = pin_init!(TupleStruct(<- CMutex::new(9u32), 6)));
172+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 9u32);
173+
assert_eq!(tuple.as_ref().get_ref().1, 6);
54174
}
55175

56176
#[pin_data]
57-
struct Triple(i32, i32, i32);
177+
struct MixedTuple<'a, T, const N: usize>(#[pin] CMutex<MixedPayload<'a, T, N>>, usize);
178+
179+
type MixedPayload<'a, T, const N: usize> = (&'a T, [u8; N]);
58180

59181
#[test]
60-
fn tuple_struct_constructor_form_mixed_middle_init() {
61-
stack_pin_init!(let triple = pin_init!(Triple(1, <- init_i32_unpinned(2), 3)));
62-
assert_eq!(triple.as_ref().get_ref().0, 1);
63-
assert_eq!(triple.as_ref().get_ref().1, 2);
64-
assert_eq!(triple.as_ref().get_ref().2, 3);
182+
fn tuple_struct_mixed_lifetime_type_const_generics() {
183+
// Stress case combining lifetime + type + const generics in one tuple pinned field.
184+
let value = 77u16;
185+
let pair = (&value, [1, 2, 3, 4]);
186+
stack_pin_init!(let tuple = pin_init!(MixedTuple(<- CMutex::new(pair), 12)));
187+
188+
let projected = tuple.as_mut().project();
189+
assert_pinned_mutex(&projected._0);
190+
let locked = projected._0.as_ref().get_ref().lock();
191+
assert_eq!(*locked.0, 77u16);
192+
assert_eq!(locked.1, [1, 2, 3, 4]);
193+
assert_eq!(*projected._1, 12);
194+
}
195+
196+
static PINNED_DROP_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0);
197+
198+
#[pin_data(PinnedDrop)]
199+
struct DropTuple(#[pin] CMutex<usize>, usize);
200+
201+
#[pinned_drop]
202+
impl PinnedDrop for DropTuple {
203+
fn drop(self: Pin<&mut Self>) {
204+
let _ = self;
205+
PINNED_DROP_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst);
206+
}
207+
}
208+
209+
#[test]
210+
fn tuple_struct_pinned_drop_delegates_from_drop() {
211+
// `#[pin_data(PinnedDrop)]` should call our `PinnedDrop::drop` exactly once.
212+
PINNED_DROP_TUPLE_DROPS.store(0, Ordering::SeqCst);
213+
{
214+
stack_pin_init!(let _tuple = pin_init!(DropTuple(<- CMutex::new(5usize), 1)));
215+
}
216+
assert_eq!(PINNED_DROP_TUPLE_DROPS.load(Ordering::SeqCst), 1);
65217
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple(#[pin] i32, i32);
5+
6+
fn main() {
7+
let _ = pin_init!(Tuple (0, 1: 24));
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: expected `,`
2+
--> tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs:7:34
3+
|
4+
7 | let _ = pin_init!(Tuple (0, 1: 24));
5+
| ^
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple(#[pin] i32, i32);
5+
6+
fn main() {
7+
let _ = pin_init!(Tuple (0 <-, 1: 24));
8+
}
9+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: expected an expression
2+
--> tests/ui/compile-fail/init/tuple_arrow_wrong_way.rs:7:34
3+
|
4+
7 | let _ = pin_init!(Tuple (0 <-, 1: 24));
5+
| ^
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple(#[pin] i32, i32);
5+
6+
fn main() {
7+
let _ = pin_init!(Tuple (<-,1:24));
8+
}
9+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: expected an expression
2+
--> tests/ui/compile-fail/init/tuple_empty_arrow.rs:7:32
3+
|
4+
7 | let _ = pin_init!(Tuple (<-,1:24));
5+
| ^
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple<T, const N: usize>(#[pin] [T; N], i32);
5+
6+
fn main() {
7+
let _ = pin_init!(Tuple::<u8, 3> { 0: [1, 2, 3], 1: 2, 2: 3 });
8+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
error[E0609]: no field `2` on type `Tuple<u8, 3>`
2+
--> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:60
3+
|
4+
7 | let _ = pin_init!(Tuple::<u8, 3> { 0: [1, 2, 3], 1: 2, 2: 3 });
5+
| ^ unknown field
6+
|
7+
= note: available fields are: `0`, `1`
8+
9+
error[E0599]: no method named `__project_2` found for struct `__ThePinData<T, N>` in the current scope
10+
--> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:13
11+
|
12+
3 | #[pin_data]
13+
| ----------- method `__project_2` not found for this struct
14+
...
15+
7 | let _ = pin_init!(Tuple::<u8, 3> { 0: [1, 2, 3], 1: 2, 2: 3 });
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
|
18+
= note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info)
19+
20+
error[E0560]: struct `Tuple<u8, 3>` has no field named `2`
21+
--> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:60
22+
|
23+
4 | struct Tuple<T, const N: usize>(#[pin] [T; N], i32);
24+
| ----- `Tuple<u8, 3>` defined here
25+
...
26+
7 | let _ = pin_init!(Tuple::<u8, 3> { 0: [1, 2, 3], 1: 2, 2: 3 });
27+
| ^ field does not exist
28+
|
29+
help: `Tuple<u8, 3>` is a tuple struct, use the appropriate syntax
30+
|
31+
7 - let _ = pin_init!(Tuple::<u8, 3> { 0: [1, 2, 3], 1: 2, 2: 3 });
32+
7 + let _ = Tuple<u8, 3>(/* [T; N] */, /* i32 */);
33+
|
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use pin_init::*;
2+
3+
struct Foo<T>(T);
4+
5+
fn main() {
6+
let _ = init!(Foo<()> {
7+
0 <- (),
8+
});
9+
}

0 commit comments

Comments
 (0)