From 06fbf69204f4081ba2a7f3b97507e97dc58c2b12 Mon Sep 17 00:00:00 2001 From: Robert Usher <266585+dcchut@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:09:24 -0400 Subject: [PATCH 1/4] Update to syn v3.0 --- Cargo.toml | 2 +- src/expand.rs | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b424d4..6ca7671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ proc-macro = true [dependencies] proc-macro2 = { version = "1.0", default-features = false } quote = { version = "1.0", default-features = false } -syn = { version = "2.0", features = ["full", "visit-mut", "parsing", "printing", "proc-macro", "clone-impls"], default-features = false } +syn = { version = "3.0", features = ["full", "visit-mut", "parsing", "printing", "proc-macro", "clone-impls"], default-features = false } [dev-dependencies] futures-executor = "0.3" diff --git a/src/expand.rs b/src/expand.rs index dbf5cf7..3718359 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse_quote, punctuated::Punctuated, visit_mut::VisitMut, Block, Lifetime, Receiver, - ReturnType, Signature, TypeReference, WhereClause, + ReceiverKind, ReturnType, Signature, TypeReference, WhereClause, }; use crate::parse::{AsyncItem, RecursionArgs}; @@ -50,22 +50,21 @@ struct ReferenceVisitor { impl VisitMut for ReferenceVisitor { fn visit_receiver_mut(&mut self, receiver: &mut Receiver) { - self.self_lifetime = Some(if let Some((_, lt)) = &mut receiver.reference { - self.self_receiver = true; - - if let Some(lt) = lt { - lt.clone() - } else { - // Use 'life_self to avoid collisions with 'life lifetimes. - let new_lifetime: Lifetime = parse_quote!('life_self); - lt.replace(new_lifetime.clone()); - - self.self_receiver_new_lifetime = true; + // TODO: Support ReceiverKind::Typed as well + let ReceiverKind::Reference(_, lt, _) = &mut receiver.kind else { + return; + }; - new_lifetime - } + self.self_receiver = true; + self.self_lifetime = Some(if let Some(lt) = lt { + lt.clone() } else { - return; + // Use 'life_self to avoid collisions with 'life lifetimes. + let new_lifetime: Lifetime = parse_quote!('life_self); + lt.replace(new_lifetime.clone()); + + self.self_receiver_new_lifetime = true; + new_lifetime }); } From c133d10d02f5197972555ad2b9912109c524adb0 Mon Sep 17 00:00:00 2001 From: Robert Usher <266585+dcchut@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:15:10 -0400 Subject: [PATCH 2/4] Update MSRV to 1.71 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6ca7671..ce7c5d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "async-recursion" version = "1.1.1" authors = ["Robert Usher <266585+dcchut@users.noreply.github.com>"] edition = "2018" +rust-version = "1.71" license = "MIT OR Apache-2.0" description = "Recursion for async functions" repository = "https://github.com/dcchut/async-recursion" From 5fe7de8370ab99008614475cb2fef27fc491190b Mon Sep 17 00:00:00 2001 From: Robert Usher <266585+dcchut@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:30:40 -0400 Subject: [PATCH 3/4] Add MSRV check build --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ed4e47..e010ca6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,15 @@ jobs: - run: cargo install cargo-expand - run: cargo test --verbose + msrv: + name: MSRV + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@1.71.0 + # Only the library is covered by rust-version; dev-dependencies need a newer toolchain. + - run: cargo check --lib + fmt: name: Fmt runs-on: ubuntu-latest From 7aaedde9874f145c5d5e6615fe00774fdb66eb2e Mon Sep 17 00:00:00 2001 From: Robert Usher <266585+dcchut@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:42:31 -0400 Subject: [PATCH 4/4] Resolve clippy warnings in tests --- .github/workflows/ci.yml | 2 +- src/expand.rs | 3 ++- tests/generic_parameters.rs | 2 ++ tests/lifetimes.rs | 26 +++++++++++++------------- tests/struct_methods.rs | 3 ++- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e010ca6..95c0bcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly with: components: clippy - - run: cargo clippy -- -Dwarnings + - run: cargo clippy --all-targets -- -Dwarnings outdated: name: Outdated diff --git a/src/expand.rs b/src/expand.rs index 3718359..312c221 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -126,7 +126,8 @@ fn transform_sig(sig: &mut Signature, args: &RecursionArgs) { // Add an S : 'async_recursion bound to any generic parameter for param in sig.generics.type_params() { - let ident = param.ident.clone(); + let mut ident = param.ident.clone(); + ident.set_span(Span::call_site()); where_clause_generics.push(ident); requires_lifetime = true; } diff --git a/tests/generic_parameters.rs b/tests/generic_parameters.rs index 66c9e1c..a57b5b7 100644 --- a/tests/generic_parameters.rs +++ b/tests/generic_parameters.rs @@ -33,6 +33,8 @@ pub async fn generic_parameter(mut x: S) -> u64 { } } +// x is only used in the recursive call, but we need it to test a non-Send T under ?Send +#[allow(clippy::only_used_in_recursion)] #[async_recursion(?Send)] pub async fn generic_parameter_no_send(x: T, y: u64) -> u64 { if y > 0 { diff --git a/tests/lifetimes.rs b/tests/lifetimes.rs index d705dad..77fb07d 100644 --- a/tests/lifetimes.rs +++ b/tests/lifetimes.rs @@ -34,10 +34,10 @@ async fn contains_value_2<'a, 'b, T: PartialEq>(value: &'b T, node: &'b Node<'a, contains_value(value, node).await } -// The reference inside foo needs a `async_recursion bound +// The reference inside name needs a `async_recursion bound #[async_recursion] -async fn count_down(foo: Option<&str>) -> i32 { - let _ = foo; +async fn count_down(name: Option<&str>) -> i32 { + let _ = name; 0 } @@ -63,17 +63,17 @@ fn lifetime_expansion_works() { node.left = Some(&left); node.right = Some(&right); - assert_eq!(contains_value(&3, &node).await, true); - assert_eq!(contains_value(&4, &node).await, false); - assert_eq!(contains_value(&17, &node).await, true); - assert_eq!(contains_value(&13, &node).await, true); - assert_eq!(contains_value(&12, &node).await, false); + assert!(contains_value(&3, &node).await); + assert!(!contains_value(&4, &node).await); + assert!(contains_value(&17, &node).await); + assert!(contains_value(&13, &node).await); + assert!(!contains_value(&12, &node).await); - assert_eq!(contains_value_2(&3, &node).await, true); - assert_eq!(contains_value_2(&4, &node).await, false); - assert_eq!(contains_value_2(&17, &node).await, true); - assert_eq!(contains_value_2(&13, &node).await, true); - assert_eq!(contains_value_2(&12, &node).await, false); + assert!(contains_value_2(&3, &node).await); + assert!(!contains_value_2(&4, &node).await); + assert!(contains_value_2(&17, &node).await); + assert!(contains_value_2(&13, &node).await); + assert!(!contains_value_2(&12, &node).await); count_down(None).await; explicit_async_recursion_bound(None).await; diff --git a/tests/struct_methods.rs b/tests/struct_methods.rs index 1592d6f..6ce96ca 100644 --- a/tests/struct_methods.rs +++ b/tests/struct_methods.rs @@ -69,7 +69,8 @@ fn struct_method_with_generic_parameter_works() { block_on(async move { let e = Empty {}; assert_eq!( - e.generic_parameter::<*const u64>(&(0 as *const u64)).await, + e.generic_parameter::<*const u64>(&std::ptr::null::()) + .await, 0 ); })