Skip to content

Commit cc23cf8

Browse files
committed
Fix drop order and ref mut handling in proc-macro
1 parent a301a05 commit cc23cf8

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

macros/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ syn = { version = "2.0", features = ["full"] }
2020

2121
[dev-dependencies]
2222
async-ffi = { version = "0.4", path = ".." }
23+
futures-task = "0.3"

macros/src/lib.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ fn expand(
234234
}
235235
FnArg::Typed(pat_ty) => pat_ty,
236236
};
237+
238+
let attributes = &pat_ty.attrs;
237239
let param_ident = match &*pat_ty.pat {
238240
Pat::Ident(pat_ident) => {
239241
if pat_ident.ident == "self" {
@@ -244,6 +246,12 @@ fn expand(
244246
}
245247
_ => Ident::new(&format!("__param{}", i), pat_ty.span()),
246248
};
249+
250+
// If this is a declaration, only check but not transform.
251+
if body.is_none() {
252+
continue;
253+
}
254+
247255
let old_pat = mem::replace(
248256
&mut *pat_ty.pat,
249257
Pat::Ident(PatIdent {
@@ -254,10 +262,18 @@ fn expand(
254262
subpat: None,
255263
}),
256264
);
257-
let attributes = &pat_ty.attrs;
258-
// NB. Re-bindings use external (macro) spans, so they won't trigger lints.
265+
266+
// NB.
267+
// - Rebind the parameter once, to ensure their drop order not broken
268+
// by non-moving patterns containing `_`.
269+
// - `mut` is required when the old pattern has `ref mut` inside.
270+
// - Use external (macro) spans, so they won't trigger lints.
259271
param_bindings.extend(quote! {
260272
#(#attributes)*
273+
#[allow(clippy::used_underscore_binding)]
274+
let mut #param_ident = #param_ident;
275+
#(#attributes)*
276+
#[allow(clippy::used_underscore_binding)]
261277
let #old_pat = #param_ident;
262278
});
263279
}
@@ -306,4 +322,12 @@ mod tests {
306322
/// }
307323
/// ```
308324
fn typed_receiver() {}
325+
326+
/// ```compile_fail
327+
/// extern "C" {
328+
/// #[async_ffi_macros::async_ffi]
329+
/// async fn foo(ref mut x: i32);
330+
/// }
331+
/// ```
332+
fn declaration_pattern() {}
309333
}

macros/tests/test.rs

+53
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,56 @@ pub async fn pat_param(_: i32, (a, _, _b): (i32, i32, i32), x: i32, mut y: i32)
4949
y += 1;
5050
a + x + y
5151
}
52+
53+
#[test]
54+
#[allow(clippy::toplevel_ref_arg, clippy::unused_async)]
55+
fn drop_order() {
56+
use std::cell::RefCell;
57+
use std::future::Future;
58+
use std::pin::Pin;
59+
use std::rc::Rc;
60+
use std::task::{Context, Poll};
61+
62+
struct Dropper(&'static str, Rc<RefCell<Vec<&'static str>>>);
63+
64+
impl Drop for Dropper {
65+
fn drop(&mut self) {
66+
self.1.borrow_mut().push(self.0);
67+
}
68+
}
69+
70+
fn check_order<F>(
71+
f: fn(Dropper, Dropper, Dropper, (Dropper, Dropper)) -> F,
72+
) -> Vec<&'static str>
73+
where
74+
F: Future<Output = ()>,
75+
{
76+
let order = Rc::new(RefCell::new(Vec::new()));
77+
let mut fut = f(
78+
Dropper("a", order.clone()),
79+
Dropper("b", order.clone()),
80+
Dropper("_arg", order.clone()),
81+
(Dropper("c", order.clone()), Dropper("_pat", order.clone())),
82+
);
83+
Dropper("ret", order.clone());
84+
let fut_pinned = unsafe { Pin::new_unchecked(&mut fut) };
85+
let mut cx = Context::from_waker(futures_task::noop_waker_ref());
86+
assert_eq!(fut_pinned.poll(&mut cx), Poll::Ready(()));
87+
Dropper("done", order.clone());
88+
drop(fut);
89+
let ret = order.borrow().to_vec();
90+
ret
91+
}
92+
93+
#[async_ffi(?Send)]
94+
async fn func1(a: Dropper, ref mut _b: Dropper, _: Dropper, (_c, _): (Dropper, Dropper)) {
95+
a.1.borrow_mut().push("run");
96+
}
97+
async fn func2(a: Dropper, ref mut _b: Dropper, _: Dropper, (_c, _): (Dropper, Dropper)) {
98+
a.1.borrow_mut().push("run");
99+
}
100+
101+
let ord1 = check_order(func1);
102+
let ord2 = check_order(func2);
103+
assert_eq!(ord1, ord2);
104+
}

0 commit comments

Comments
 (0)