Skip to content

Commit 9af7f1b

Browse files
committed
internal: pin_data: perform AST lifetime replacement if possible
Currently lifetimes are replaced with `ForLt4` trait. This is very general approach as it uses generic associated type to replace lifetime, so it can even work when macros are involved. This does cause more generated code, and does not render in documentation nicely. Thus, just replace the lifetime in the AST if no macros are involved. Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent 095a0b1 commit 9af7f1b

1 file changed

Lines changed: 77 additions & 10 deletions

File tree

internal/src/util.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,35 @@ use proc_macro2::Span;
66
use quote::quote;
77
use syn::punctuated::Punctuated;
88
use syn::visit::Visit;
9-
use syn::GenericParam;
10-
use syn::{BoundLifetimes, Ident, LifetimeParam};
9+
use syn::visit_mut::VisitMut;
10+
use syn::{BoundLifetimes, GenericParam, Ident, LifetimeParam};
1111
use syn::{Lifetime, Type};
1212

1313
pub(crate) trait TypeExt {
14+
/// Check if the type includes macro invocations.
15+
///
16+
/// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes special handling is required.
17+
fn has_macro(&self) -> bool;
18+
1419
/// Get the list of unbound lifetimes referenced by the type.
1520
fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime>;
1621
}
1722

1823
impl TypeExt for Type {
24+
fn has_macro(&self) -> bool {
25+
struct HasMacro(bool);
26+
27+
impl<'ast> Visit<'ast> for HasMacro {
28+
fn visit_macro(&mut self, _: &'ast syn::Macro) {
29+
self.0 = true;
30+
}
31+
}
32+
33+
let mut visitor = HasMacro(false);
34+
visitor.visit_type(self);
35+
visitor.0
36+
}
37+
1938
fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime> {
2039
struct LifetimeVisitor<'a> {
2140
bound: BTreeSet<&'a Lifetime>,
@@ -117,14 +136,62 @@ impl Binder<Type> {
117136
return self.value.clone();
118137
}
119138

120-
let bound = self.for_bound_4();
121-
let bound_lt = bound.lifetimes.iter();
122-
let ty = &self.value;
123-
return syn::Type::Verbatim(quote!(
124-
<::pin_init::__internal::ForLtImpl<
125-
dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty>
126-
> as ::pin_init::__internal::ForLt4>::Of<#lifetime, #lifetime, #lifetime, #lifetime>
127-
));
139+
// If the type has macro, we cannot peek into it. Use some different approach to replace
140+
// the type using GAT.
141+
if self.value.has_macro() {
142+
let bound = self.for_bound_4();
143+
let bound_lt = bound.lifetimes.iter();
144+
let ty = &self.value;
145+
return syn::Type::Verbatim(quote!(
146+
<::pin_init::__internal::ForLtImpl<
147+
dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty>
148+
> as ::pin_init::__internal::ForLt4>::Of<#lifetime, #lifetime, #lifetime, #lifetime>
149+
));
150+
}
151+
152+
struct LifetimeReplacer<'a> {
153+
to_replace: BTreeSet<&'a Lifetime>,
154+
replacement: &'a Lifetime,
155+
}
156+
157+
impl VisitMut for LifetimeReplacer<'_> {
158+
fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) {
159+
if self.to_replace.contains(lt) {
160+
*lt = self.replacement.clone();
161+
}
162+
}
163+
164+
fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) {
165+
// In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`,
166+
// temporarily remove them from to_replace if they're.
167+
168+
let mut removed = Vec::new();
169+
if let Some(bound_lt) = &bound.lifetimes {
170+
for lt in &bound_lt.lifetimes {
171+
let GenericParam::Lifetime(lt) = lt else {
172+
continue;
173+
};
174+
if let Some(lt) = self.to_replace.take(&lt.lifetime) {
175+
removed.push(lt);
176+
}
177+
}
178+
}
179+
180+
self.visit_path_mut(&mut bound.path);
181+
182+
for lt in removed {
183+
self.to_replace.insert(lt);
184+
}
185+
}
186+
}
187+
188+
let mut ret = self.value.clone();
189+
LifetimeReplacer {
190+
to_replace: self.bound.iter().collect(),
191+
replacement: lifetime,
192+
}
193+
.visit_type_mut(&mut ret);
194+
ret
128195
}
129196
}
130197

0 commit comments

Comments
 (0)