Skip to content

Commit 8735d1e

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 15cc12f commit 8735d1e

13 files changed

+477
-60
lines changed

tests/tuple_struct.rs

Lines changed: 204 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,224 @@
22
#![cfg_attr(USE_RUSTC_FEATURES, feature(raw_ref_op))]
33
#![cfg_attr(feature = "alloc", feature(allocator_api))]
44

5-
use core::ptr;
6-
5+
use core::{
6+
pin::Pin,
7+
sync::atomic::{AtomicUsize, Ordering},
8+
};
79
use pin_init::*;
810

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

12-
fn init_i32(value: i32) -> impl PinInit<i32> {
13-
// SAFETY: The closure always initializes `slot` with a valid `i32` value.
14-
unsafe {
15-
pin_init_from_closure(move |slot| {
16-
// SAFETY: `slot` is provided by the initialization framework and valid for write.
17-
ptr::write(slot, value);
18-
Ok(())
19-
})
20-
}
21-
}
19+
fn assert_pinned_mutex<T>(_: &Pin<&mut CMutex<T>>) {}
2220

2321
#[test]
2422
fn tuple_struct_values() {
25-
stack_pin_init!(let foo = pin_init!(TupleStruct { 0: 42, 1: 24 }));
26-
assert_eq!(foo.as_ref().get_ref().0, 42);
27-
assert_eq!(foo.as_ref().get_ref().1, 24);
23+
// Baseline tuple-field syntax with index-based struct initializer.
24+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize> { 0 <- CMutex::new(42), 1: 24 }));
25+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 42);
26+
assert_eq!(tuple.as_ref().get_ref().1, 24);
2827
}
2928

3029
#[test]
31-
#[allow(clippy::redundant_locals)]
3230
fn tuple_struct_init_arrow_and_projection() {
33-
stack_pin_init!(let foo = pin_init!(TupleStruct { 0 <- init_i32(7), 1: 13 }));
34-
let mut foo = foo;
35-
let projected = foo.as_mut().project();
36-
assert_eq!(*projected._0.as_ref().get_ref(), 7);
31+
// Checks projection types and that `<-` correctly initializes the pinned tuple field.
32+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize> { 0 <- CMutex::new(7), 1: 13 }));
33+
34+
let projected = tuple.as_mut().project();
35+
assert_pinned_mutex(&projected._0);
36+
let projected = tuple.as_mut().project();
37+
assert_eq!(*projected._0.as_ref().get_ref().lock(), 7);
3738
assert_eq!(*projected._1, 13);
3839
}
3940

41+
#[pin_data]
42+
struct Triple(i32, i32, i32);
43+
4044
#[test]
4145
fn tuple_struct_constructor_form() {
42-
stack_pin_init!(let foo = pin_init!(TupleStruct(11, 29)));
43-
assert_eq!(foo.as_ref().get_ref().0, 11);
44-
assert_eq!(foo.as_ref().get_ref().1, 29);
46+
// Constructor form remains value-only syntax.
47+
stack_pin_init!(let triple = pin_init!(Triple(11, 29, 31)));
48+
assert_eq!(triple.as_ref().get_ref().0, 11);
49+
assert_eq!(triple.as_ref().get_ref().1, 29);
50+
assert_eq!(triple.as_ref().get_ref().2, 31);
51+
}
52+
53+
#[pin_data]
54+
struct DualPinned<T>(#[pin] CMutex<T>, #[pin] CMutex<T>, usize);
55+
56+
#[test]
57+
fn tuple_struct_multi_pinned_fields_projection() {
58+
// Both pinned tuple fields should project to `Pin<&mut CMutex<T>>` and stay usable.
59+
stack_pin_init!(let tuple = pin_init!(DualPinned::<usize> { 0 <- CMutex::new(1), 1 <- CMutex::new(2), 2: 3 }));
60+
let projected = tuple.as_mut().project();
61+
assert_pinned_mutex(&projected._0);
62+
assert_pinned_mutex(&projected._1);
63+
64+
*projected._0.as_ref().get_ref().lock() = 10;
65+
*projected._1.as_ref().get_ref().lock() = 20;
66+
*projected._2 = 30;
67+
68+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10);
69+
assert_eq!(*tuple.as_ref().get_ref().1.lock(), 20);
70+
assert_eq!(tuple.as_ref().get_ref().2, 30);
71+
}
72+
73+
#[test]
74+
fn tuple_struct_generic_type_param_behavior() {
75+
// Keep this focused on explicit generic-arg syntax (`::<u16>`) with struct-style init.
76+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<u16> { 0 <- CMutex::new(123u16), 1: 7 }));
77+
let projected = tuple.as_mut().project();
78+
assert_pinned_mutex(&projected._0);
79+
assert_eq!(*projected._0.as_ref().get_ref().lock(), 123u16);
80+
assert_eq!(*projected._1, 7);
81+
}
82+
83+
#[pin_data]
84+
struct ValueTuple<T>(T, i32);
85+
86+
#[test]
87+
fn tuple_struct_generic_inference_constructor_form() {
88+
// Constructor form infers `T` from positional values.
89+
stack_pin_init!(let tuple = pin_init!(ValueTuple(9u32, 6)));
90+
assert_eq!(tuple.as_ref().get_ref().0, 9u32);
91+
assert_eq!(tuple.as_ref().get_ref().1, 6);
92+
}
93+
94+
#[pin_data]
95+
struct RefTuple<'a>(#[pin] CMutex<&'a usize>, usize);
96+
97+
#[test]
98+
fn tuple_struct_lifetime_reference_behavior() {
99+
// Verifies tuple init/projection with borrowed data (`'a`) through the pinned field.
100+
let first = 111usize;
101+
let first_ref = &first;
102+
stack_pin_init!(let tuple = pin_init!(RefTuple { 0 <- CMutex::new(first_ref), 1: 3 }));
103+
assert_eq!(**tuple.as_ref().get_ref().0.lock(), 111usize);
104+
assert_eq!(tuple.as_ref().get_ref().1, 3);
105+
106+
let second = 222usize;
107+
let second_ref = &second;
108+
stack_pin_init!(let tuple = pin_init!(RefTuple { 0 <- CMutex::new(second_ref), 1: 4 }));
109+
let projected = tuple.as_mut().project();
110+
assert_pinned_mutex(&projected._0);
111+
assert_eq!(**projected._0.as_ref().get_ref().lock(), 222usize);
112+
assert_eq!(*projected._1, 4);
113+
}
114+
115+
#[test]
116+
fn tuple_struct_projection_mutation_behavior() {
117+
// Confirms both projected fields can be mutated through their projected references.
118+
stack_pin_init!(let tuple = pin_init!(TupleStruct::<usize> { 0 <- CMutex::new(1usize), 1: 2 }));
119+
120+
let projected = tuple.as_mut().project();
121+
*projected._0.as_ref().get_ref().lock() = 10usize;
122+
*projected._1 = 20;
123+
124+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10usize);
125+
assert_eq!(tuple.as_ref().get_ref().1, 20);
126+
}
127+
128+
struct DropCounter;
129+
130+
static FALLIBLE_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0);
131+
132+
impl Drop for DropCounter {
133+
fn drop(&mut self) {
134+
FALLIBLE_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst);
135+
}
136+
}
137+
138+
fn tuple_failing_init() -> impl PinInit<TupleStruct<DropCounter>, ()> {
139+
// SAFETY: We emulate "initialized first field, then fail" and ensure rollback leaves no
140+
// partially initialized value in `slot`.
141+
unsafe {
142+
pin_init_from_closure(|slot: *mut TupleStruct<DropCounter>| {
143+
// Manually initialize only field 0 to model a mid-initialization failure.
144+
let field0 = core::ptr::addr_of_mut!((*slot).0);
145+
let init0 = CMutex::new(DropCounter);
146+
// SAFETY: `field0` points into `slot`, which is valid uninitialized memory.
147+
match init0.__pinned_init(field0) {
148+
Ok(()) => {}
149+
Err(infallible) => match infallible {},
150+
}
151+
// Explicit rollback is required before returning `Err` to avoid leaking initialized state.
152+
core::ptr::drop_in_place(field0);
153+
Err(())
154+
})
155+
}
156+
}
157+
158+
#[test]
159+
fn tuple_struct_fallible_init_drops_initialized_fields() {
160+
// A failure after partial initialization must still drop the already-initialized field.
161+
FALLIBLE_TUPLE_DROPS.store(0, Ordering::SeqCst);
162+
stack_try_pin_init!(let tuple: TupleStruct<DropCounter> = tuple_failing_init());
163+
assert!(matches!(tuple, Err(())));
164+
assert_eq!(FALLIBLE_TUPLE_DROPS.load(Ordering::SeqCst), 1);
165+
}
166+
167+
#[pin_data]
168+
struct TupleConst<T, const N: usize>(#[pin] CMutex<[T; N]>, usize);
169+
170+
#[test]
171+
fn tuple_struct_const_generic_behavior() {
172+
// Covers tuple-field init/projection when the pinned field contains a const-generic array.
173+
stack_pin_init!(let tuple = pin_init!(TupleConst::<u8, 3> { 0 <- CMutex::new([1, 2, 3]), 1: 9 }));
174+
let projected = tuple.as_mut().project();
175+
assert_pinned_mutex(&projected._0);
176+
assert_eq!(*projected._0.as_ref().get_ref().lock(), [1, 2, 3]);
177+
assert_eq!(*projected._1, 9);
178+
179+
stack_pin_init!(let tuple = pin_init!(TupleConst::<u8, 2> { 0 <- CMutex::new([7, 8]), 1: 5 }));
180+
assert_eq!(*tuple.as_ref().get_ref().0.lock(), [7, 8]);
181+
assert_eq!(tuple.as_ref().get_ref().1, 5);
182+
}
183+
184+
#[pin_data]
185+
struct MixedTuple<'a, T, const N: usize>(#[pin] CMutex<MixedPayload<'a, T, N>>, usize);
186+
187+
type MixedPayload<'a, T, const N: usize> = (&'a T, [u8; N]);
188+
189+
#[test]
190+
fn tuple_struct_mixed_lifetime_type_const_generics() {
191+
// Stress case combining lifetime + type + const generics in one tuple pinned field.
192+
let value = 77u16;
193+
let pair = (&value, [1, 2, 3, 4]);
194+
stack_pin_init!(let tuple = pin_init!(MixedTuple { 0 <- CMutex::new(pair), 1: 12 }));
195+
196+
let projected = tuple.as_mut().project();
197+
assert_pinned_mutex(&projected._0);
198+
let locked = projected._0.as_ref().get_ref().lock();
199+
assert_eq!(*locked.0, 77u16);
200+
assert_eq!(locked.1, [1, 2, 3, 4]);
201+
assert_eq!(*projected._1, 12);
202+
}
203+
204+
static PINNED_DROP_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0);
205+
206+
#[pin_data(PinnedDrop)]
207+
struct DropTuple(#[pin] CMutex<usize>, usize);
208+
209+
#[pinned_drop]
210+
impl PinnedDrop for DropTuple {
211+
fn drop(self: Pin<&mut Self>) {
212+
let _ = self;
213+
PINNED_DROP_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst);
214+
}
215+
}
216+
217+
#[test]
218+
fn tuple_struct_pinned_drop_delegates_from_drop() {
219+
// `#[pin_data(PinnedDrop)]` should call our `PinnedDrop::drop` exactly once.
220+
PINNED_DROP_TUPLE_DROPS.store(0, Ordering::SeqCst);
221+
{
222+
stack_pin_init!(let _tuple = pin_init!(DropTuple { 0 <- CMutex::new(5usize), 1: 1 }));
223+
}
224+
assert_eq!(PINNED_DROP_TUPLE_DROPS.load(Ordering::SeqCst), 1);
45225
}
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(<- 1, 2));
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }`
2+
--> tests/ui/compile-fail/init/no_tuple_paren_arrow.rs:7:29
3+
|
4+
7 | let _ = pin_init!(Tuple(<- 1, 2));
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(#[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: 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+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: comparison operators cannot be chained
2+
--> tests/ui/compile-fail/init/tuple_wrong_generics.rs:6:22
3+
|
4+
6 | let _ = init!(Foo<()> {
5+
| ^ ^
6+
|
7+
help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
8+
|
9+
6 | let _ = init!(Foo::<()> {
10+
| ++
11+
12+
error: comparison operators cannot be chained
13+
--> tests/ui/compile-fail/init/tuple_wrong_generics.rs:6:22
14+
|
15+
6 | let _ = init!(Foo<()> {
16+
| ^ ^
17+
|
18+
= help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
19+
= help: or use `(...)` if you meant to specify fn arguments
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use pin_init::*;
2+
3+
#[pin_data]
4+
struct Tuple<T>(T, core::marker::PhantomPinned);
5+
6+
fn main() {}

0 commit comments

Comments
 (0)