Skip to main content

pin_init_internal/
util.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use proc_macro2::{Ident, TokenStream};
6use quote::{format_ident, ToTokens};
7use syn::{
8    parse_quote, visit::Visit, visit_mut::VisitMut, Attribute, BoundLifetimes, GenericParam,
9    Generics, Index, Lifetime, Member, Token, Type, TypePath,
10};
11
12use crate::DiagCtxt;
13
14pub(crate) trait AttrListExt {
15    fn extract_cfg_attrs(&mut self) -> Vec<TokenStream>;
16
17    /// Extract attribute with identifier `path`.
18    ///
19    /// Report error if the attribute appears multiple times.
20    fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option<Attribute>;
21}
22
23impl AttrListExt for Vec<Attribute> {
24    fn extract_cfg_attrs(&mut self) -> Vec<TokenStream> {
25        let cfg: Vec<_> = self
26            .iter()
27            .filter(|a| a.path().is_ident("cfg"))
28            .map(|a| {
29                a.parse_args::<TokenStream>()
30                    .expect("parse as token stream cannot fail")
31            })
32            .collect();
33
34        if !cfg.is_empty() {
35            self.retain(|a| !a.path().is_ident("cfg"));
36        }
37
38        cfg
39    }
40
41    fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option<Attribute> {
42        // FIXME: Replace with `extract_if` when MSRV >= 1.85.
43        let attr_pos = self.iter().position(|attr| attr.path().is_ident(path))?;
44        let attr = self.remove(attr_pos);
45        self.retain(|attr| {
46            if !attr.path().is_ident(path) {
47                return true;
48            }
49
50            dcx.error(
51                attr,
52                format!("`#[{path}]` attribute specified more than once"),
53            );
54            false
55        });
56        Some(attr)
57    }
58}
59
60pub(crate) trait MemberExt {
61    /// Returns an identifier for the member.
62    ///
63    /// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead.
64    fn as_ident(&self) -> Ident;
65
66    /// Obtain a display name for the member in diagnostics.
67    fn display_name(&self) -> String;
68}
69
70impl MemberExt for Member {
71    fn as_ident(&self) -> Ident {
72        match self {
73            Member::Named(ident) => ident.clone(),
74            Member::Unnamed(Index { index, span }) => format_ident!("_{index}", span = *span),
75        }
76    }
77
78    fn display_name(&self) -> String {
79        match self {
80            Member::Named(ident) => format!("`{ident}`"),
81            Member::Unnamed(Index { index, .. }) => format!("index `{index}`"),
82        }
83    }
84}
85
86pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>);
87pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>);
88pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>);
89pub(crate) struct CombinedWhereClauses<'a>(&'a CombinedGenerics<'a>);
90
91impl CombinedGenerics<'_> {
92    pub(crate) fn split_for_impl(
93        &self,
94    ) -> (
95        CombinedImplGenerics<'_>,
96        CombinedTypeGenerics<'_>,
97        CombinedWhereClauses<'_>,
98    ) {
99        (
100            CombinedImplGenerics(self),
101            CombinedTypeGenerics(self),
102            CombinedWhereClauses(self),
103        )
104    }
105}
106
107impl ToTokens for CombinedGenerics<'_> {
108    fn to_tokens(&self, tokens: &mut TokenStream) {
109        // Most of the time we are only adding lifetimes, so we prefer to place
110        // the user provided generics last.
111        self.0
112            .last()
113            .and_then(|x| x.lt_token)
114            .unwrap_or_default()
115            .to_tokens(tokens);
116
117        let comma: Token![,] = Default::default();
118
119        // Output lifetimes first.
120        for generics in self.0.iter() {
121            for param in generics.params.pairs() {
122                if let GenericParam::Lifetime(lt) = param.value() {
123                    lt.to_tokens(tokens);
124                    param.punct().unwrap_or(&&comma).to_tokens(tokens);
125                }
126            }
127        }
128
129        for generics in self.0.iter() {
130            for param in generics.params.pairs() {
131                if let GenericParam::Lifetime(_) = param.value() {
132                    continue;
133                };
134                param.value().to_tokens(tokens);
135                param.punct().unwrap_or(&&comma).to_tokens(tokens);
136            }
137        }
138
139        self.0
140            .last()
141            .and_then(|x| x.gt_token)
142            .unwrap_or_default()
143            .to_tokens(tokens);
144    }
145}
146
147impl ToTokens for CombinedImplGenerics<'_> {
148    fn to_tokens(&self, tokens: &mut TokenStream) {
149        self.0
150             .0
151            .last()
152            .and_then(|x| x.lt_token)
153            .unwrap_or_default()
154            .to_tokens(tokens);
155
156        let comma: Token![,] = Default::default();
157
158        // Output lifetimes first.
159        for generics in self.0 .0.iter() {
160            for param in generics.params.pairs() {
161                if let GenericParam::Lifetime(lt) = param.value() {
162                    lt.to_tokens(tokens);
163                    param.punct().unwrap_or(&&comma).to_tokens(tokens);
164                }
165            }
166        }
167
168        for generics in self.0 .0.iter() {
169            for param in generics.params.pairs() {
170                // Leave out defaults.
171                match param.value() {
172                    GenericParam::Lifetime(_) => continue,
173                    GenericParam::Type(param) => {
174                        param.ident.to_tokens(tokens);
175                        if !param.bounds.is_empty() {
176                            param
177                                .colon_token
178                                .unwrap_or_else(Default::default)
179                                .to_tokens(tokens);
180                            param.bounds.to_tokens(tokens);
181                        }
182                    }
183                    GenericParam::Const(param) => {
184                        param.const_token.to_tokens(tokens);
185                        param.ident.to_tokens(tokens);
186                        param.colon_token.to_tokens(tokens);
187                        param.ty.to_tokens(tokens);
188                    }
189                }
190                param.punct().unwrap_or(&&comma).to_tokens(tokens);
191            }
192        }
193
194        self.0
195             .0
196            .last()
197            .and_then(|x| x.gt_token)
198            .unwrap_or_default()
199            .to_tokens(tokens);
200    }
201}
202
203impl ToTokens for CombinedTypeGenerics<'_> {
204    fn to_tokens(&self, tokens: &mut TokenStream) {
205        self.0
206             .0
207            .last()
208            .and_then(|x| x.lt_token)
209            .unwrap_or_default()
210            .to_tokens(tokens);
211
212        let comma: Token![,] = Default::default();
213
214        // Output lifetimes first.
215        for generics in self.0 .0.iter() {
216            for param in generics.params.pairs() {
217                if let GenericParam::Lifetime(lt) = param.value() {
218                    // Leave out bounds
219                    lt.lifetime.to_tokens(tokens);
220                    param.punct().unwrap_or(&&comma).to_tokens(tokens);
221                }
222            }
223        }
224
225        for generics in self.0 .0.iter() {
226            for param in generics.params.pairs() {
227                // Leave out bounds and defaults.
228                match param.value() {
229                    GenericParam::Lifetime(_) => continue,
230                    GenericParam::Type(param) => {
231                        param.ident.to_tokens(tokens);
232                    }
233                    GenericParam::Const(param) => {
234                        param.ident.to_tokens(tokens);
235                    }
236                }
237                param.punct().unwrap_or(&&comma).to_tokens(tokens);
238            }
239        }
240
241        self.0
242             .0
243            .last()
244            .and_then(|x| x.gt_token)
245            .unwrap_or_default()
246            .to_tokens(tokens);
247    }
248}
249
250impl ToTokens for CombinedWhereClauses<'_> {
251    fn to_tokens(&self, tokens: &mut TokenStream) {
252        self.0
253             .0
254            .iter()
255            .filter_map(|x| Some(x.where_clause.as_ref()?.where_token))
256            .next_back()
257            .unwrap_or_default()
258            .to_tokens(tokens);
259
260        let comma: Token![,] = Default::default();
261
262        for generics in self.0 .0.iter() {
263            let Some(where_clause) = &generics.where_clause else {
264                continue;
265            };
266
267            where_clause.predicates.to_tokens(tokens);
268            if !where_clause.predicates.empty_or_trailing() {
269                comma.to_tokens(tokens);
270            }
271        }
272    }
273}
274
275pub(crate) trait LifetimeExt {
276    /// Get a visitor that call the provided function for all unbound lifetimes.
277    fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>;
278
279    /// Obtain a lifetime from a identifier.
280    ///
281    /// The created lifetime has the same span.
282    fn from_ident(ident: &Ident) -> Self;
283}
284
285impl LifetimeExt for Lifetime {
286    fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a> {
287        LifetimeVisitor {
288            bound: BTreeSet::new(),
289            visit: f,
290        }
291    }
292
293    fn from_ident(ident: &Ident) -> Self {
294        Lifetime {
295            apostrophe: ident.span(),
296            ident: ident.clone(),
297        }
298    }
299}
300
301struct LifetimeVisitor<'a, F> {
302    bound: BTreeSet<&'a Lifetime>,
303    visit: F,
304}
305
306impl<'a, F> LifetimeVisitor<'a, F> {
307    fn with_bound_lifetimes(
308        &mut self,
309        bound: Option<&'a BoundLifetimes>,
310        f: impl FnOnce(&mut Self),
311    ) {
312        // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`,
313        // the lifetimes in the binder are bound and should not be visited.
314
315        let mut to_remove = Vec::new();
316        if let Some(bound) = bound {
317            for lt in &bound.lifetimes {
318                let GenericParam::Lifetime(lt) = lt else {
319                    continue;
320                };
321                if !self.bound.contains(&&lt.lifetime) {
322                    self.bound.insert(&lt.lifetime);
323                    to_remove.push(&lt.lifetime);
324                }
325            }
326        }
327
328        f(self);
329
330        for lt in to_remove {
331            self.bound.remove(lt);
332        }
333    }
334}
335
336impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> {
337    fn visit_lifetime(&mut self, lt: &'a Lifetime) {
338        if lt.ident == "static" {
339            return;
340        }
341
342        if !self.bound.contains(lt) {
343            (self.visit)(lt);
344        }
345    }
346
347    fn visit_trait_bound(&mut self, bound: &'a syn::TraitBound) {
348        self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| {
349            this.visit_path(&bound.path)
350        });
351    }
352
353    fn visit_type_bare_fn(&mut self, bare_fn: &'a syn::TypeBareFn) {
354        self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| {
355            for input in bare_fn.inputs.iter() {
356                this.visit_bare_fn_arg(input);
357            }
358            this.visit_return_type(&bare_fn.output);
359        });
360    }
361}
362
363pub(crate) trait GenericParamExt {
364    fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a>;
365}
366
367impl GenericParamExt for GenericParam {
368    fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a> {
369        struct TypeParamVisitor<F>(F);
370
371        impl<'a, F> Visit<'a> for TypeParamVisitor<F>
372        where
373            F: FnMut(&'a Ident),
374        {
375            fn visit_type_path(&mut self, ty: &'a TypePath) {
376                if ty.qself.is_none()
377                    && ty.path.leading_colon.is_none()
378                    && ty.path.segments[0].arguments.is_none()
379                {
380                    (self.0)(&ty.path.segments[0].ident);
381                }
382                syn::visit::visit_type_path(self, ty);
383            }
384        }
385
386        TypeParamVisitor(f)
387    }
388}
389
390pub(crate) trait TypeExt {
391    /// Check if the type includes macro invocations.
392    ///
393    /// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes
394    /// special handling is required.
395    fn has_macro(&self) -> bool;
396
397    fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type;
398}
399
400impl TypeExt for Type {
401    fn has_macro(&self) -> bool {
402        struct HasMacro(bool);
403
404        impl<'ast> Visit<'ast> for HasMacro {
405            fn visit_macro(&mut self, _: &'ast syn::Macro) {
406                self.0 = true;
407            }
408        }
409
410        let mut visitor = HasMacro(false);
411        visitor.visit_type(self);
412        visitor.0
413    }
414
415    fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type {
416        if needle.is_empty() {
417            return self.clone();
418        }
419
420        // If the type has macro, we cannot peek into it. Use some different approach to replace
421        // the type using GAT.
422        if self.has_macro() {
423            return parse_quote!(
424                <
425                    for<#(#needle,)*> fn(#(&#needle (),)*) -> #self
426                        as
427                    ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)>
428                >::Output
429            );
430        }
431
432        struct LifetimeReplacer<'a> {
433            to_replace: BTreeMap<&'a Lifetime, &'a Lifetime>,
434        }
435
436        impl<'a> LifetimeReplacer<'a> {
437            fn with_bound_lifetimes(
438                &mut self,
439                bound: Option<&BoundLifetimes>,
440                f: impl FnOnce(&mut Self),
441            ) {
442                // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`,
443                // temporarily remove them from to_replace if they're.
444
445                let mut removed = Vec::new();
446                if let Some(bound) = bound {
447                    for lt in &bound.lifetimes {
448                        let GenericParam::Lifetime(lt) = lt else {
449                            continue;
450                        };
451                        if let Some(entry) = self.to_replace.remove_entry(&lt.lifetime) {
452                            removed.push(entry);
453                        }
454                    }
455                }
456
457                f(self);
458
459                for (key, val) in removed {
460                    self.to_replace.insert(key, val);
461                }
462            }
463        }
464
465        impl VisitMut for LifetimeReplacer<'_> {
466            fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) {
467                if let Some(&replacement) = self.to_replace.get(lt) {
468                    *lt = replacement.clone();
469                }
470            }
471
472            fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) {
473                self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| {
474                    this.visit_path_mut(&mut bound.path)
475                });
476            }
477
478            fn visit_type_bare_fn_mut(&mut self, bare_fn: &mut syn::TypeBareFn) {
479                self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| {
480                    for input in bare_fn.inputs.iter_mut() {
481                        this.visit_bare_fn_arg_mut(input);
482                    }
483
484                    this.visit_return_type_mut(&mut bare_fn.output);
485                });
486            }
487        }
488
489        let mut ret = self.clone();
490        LifetimeReplacer {
491            to_replace: needle
492                .iter()
493                .copied()
494                .zip(replacement.iter().copied())
495                .collect(),
496        }
497        .visit_type_mut(&mut ret);
498        ret
499    }
500}