[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <DFK2IZSN3G5Y.2ESC7WN213SYD@garyguo.net>
Date: Fri, 09 Jan 2026 12:47:36 +0000
From: "Gary Guo" <gary@...yguo.net>
To: "Benno Lossin" <lossin@...nel.org>, "Gary Guo" <gary@...yguo.net>,
"Miguel Ojeda" <ojeda@...nel.org>, "Boqun Feng" <boqun.feng@...il.com>,
Björn Roy Baron <bjorn3_gh@...tonmail.com>, "Andreas
Hindborg" <a.hindborg@...nel.org>, "Alice Ryhl" <aliceryhl@...gle.com>,
"Trevor Gross" <tmgross@...ch.edu>, "Danilo Krummrich" <dakr@...nel.org>,
"Fiona Behrens" <me@...enk.dev>, "Greg Kroah-Hartman"
<gregkh@...uxfoundation.org>, "Alban Kurti" <kurti@...icto.ai>
Cc: <linux-kernel@...r.kernel.org>, <rust-for-linux@...r.kernel.org>
Subject: Re: [PATCH 06/12] rust: pin-init: rewrite `#[pin_data]` using `syn`
On Thu Jan 8, 2026 at 1:50 PM GMT, Benno Lossin wrote:
> Rewrite the attribute macro `#[pin_data]` using `syn`. No functional
> changes intended aside from improved error messages on syntactic and
> semantical errors. For example if one forgets a comma at the end of a
> field:
>
> #[pin_data]
> struct Foo {
> a: Box<Foo>
> b: Box<Foo>
> }
>
> The declarative macro reports the following errors:
>
> error: expected `,`, or `}`, found `b`
> --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16
> |
> 5 | a: Box<Foo>
> | ^ help: try adding a comma: `,`
>
> error: recursion limit reached while expanding `$crate::__pin_data!`
> --> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1
> |
> 3 | #[pin_data]
> | ^^^^^^^^^^^
> |
> = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`)
> = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)
>
> The new `syn` version reports:
>
> error: expected `,`, or `}`, found `b`
> --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16
> |
> 5 | a: Box<Foo>
> | ^ help: try adding a comma: `,`
>
> error: expected `,`
> --> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5
> |
> 6 | b: Box<Foo>
> | ^
>
> Signed-off-by: Benno Lossin <lossin@...nel.org>
> ---
> rust/pin-init/internal/src/helpers.rs | 149 ------
> rust/pin-init/internal/src/lib.rs | 12 +-
> rust/pin-init/internal/src/pin_data.rs | 645 ++++++++++++++++++++-----
> rust/pin-init/src/macros.rs | 574 ----------------------
> 4 files changed, 543 insertions(+), 837 deletions(-)
> delete mode 100644 rust/pin-init/internal/src/helpers.rs
>
> diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
> index 86a53b37cc66..d1e7ed121860 100644
> --- a/rust/pin-init/internal/src/pin_data.rs
> +++ b/rust/pin-init/internal/src/pin_data.rs
> @@ -1,126 +1,549 @@
> // SPDX-License-Identifier: Apache-2.0 OR MIT
>
> -use crate::helpers::{parse_generics, Generics};
> -use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree};
> -use quote::quote;
> +use proc_macro2::TokenStream;
> +use quote::{format_ident, quote};
> +use syn::{
> + parse::{End, Nothing, Parse},
> + parse_quote, parse_quote_spanned,
> + spanned::Spanned,
> + visit_mut::VisitMut,
> + Error, Field, Ident, Item, ItemStruct, PathSegment, Result, Type, TypePath, WhereClause,
> +};
>
> -pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
> - // This proc-macro only does some pre-parsing and then delegates the actual parsing to
> - // `pin_init::__pin_data!`.
> +pub(crate) mod kw {
> + syn::custom_keyword!(PinnedDrop);
> +}
> +
> +pub(crate) enum Args {
> + Nothing(Nothing),
> + #[allow(dead_code)]
> + PinnedDrop(kw::PinnedDrop),
> +}
> +
> +impl Parse for Args {
> + fn parse(input: syn::parse::ParseStream) -> Result<Self> {
> + let lh = input.lookahead1();
> + if lh.peek(End) {
> + input.parse().map(Self::Nothing)
How about make this `impl Parse for Option<Args>` and remove the nothing
variant? It looks a bit weird.
> + } else if lh.peek(kw::PinnedDrop) {
> + let res = input.parse().map(Self::PinnedDrop)?;
> + let lh = input.lookahead1();
> + if lh.peek(End) {
> + Ok(res)
> + } else {
> + Err(lh.error())
> + }
> + } else {
> + Err(lh.error())
> + }
> + }
> +}
> +
> +pub(crate) fn pin_data(args: Args, input: Item) -> Result<TokenStream> {
> + let mut struct_ = match input {
> + Item::Struct(struct_) => struct_,
> + Item::Enum(enum_) => {
> + return Err(Error::new_spanned(
> + enum_.enum_token,
> + "`#[pin_data]` only supports structs for now",
> + ));
> + }
> + Item::Union(union) => {
> + return Err(Error::new_spanned(
> + union.union_token,
> + "`#[pin_data]` only supports structs for now",
> + ));
> + }
> + rest => {
> + return Err(Error::new_spanned(
> + rest,
> + "`#[pin_data]` can only be applied to struct, enum and union defintions",
> + ));
> + }
> + };
> +
> + // The generics might contain the `Self` type. Since this macro will define a new type with the
> + // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed
> + // to this struct definition. Therefore we have to replace `Self` with the concrete name.
> + let mut replacer = {
> + let name = &struct_.ident;
> + let (_, ty_generics, _) = struct_.generics.split_for_impl();
> + SelfReplacer(parse_quote!(#name #ty_generics))
> + };
> + replacer.visit_generics_mut(&mut struct_.generics);
> + replacer.visit_fields_mut(&mut struct_.fields);
> +
> + let mut error: Option<Error> = None;
I would probably just collect into a `Vec` and merge at the end.
> + for field in &struct_.fields {
> + if !is_field_structurally_pinned(field) && is_phantom_pinned(&field.ty) {
> + let mut err = Error::new_spanned(
> + field,
> + format!(
> + "The field `{}` of type `PhantomData` only has an effect \
You mean PhantomPinned?
> + if it has the `#[pin]` attribute",
> + field.ident.as_ref().expect(""),
> + ),
> + );
> + if let Some(mut error) = error.take() {
> + error.combine(err);
> + err = error;
> + }
> + error = Some(err);
> + }
> + }
> +
> + let unpin_impl = generate_unpin_impl(&struct_);
> + let drop_impl = generate_drop_impl(&struct_, args);
> + let projections = generate_projections(&struct_);
> + let the_pin_data = generate_the_pin_data(&struct_);
> +
> + strip_pin_annotations(&mut struct_);
> +
> + let error = error.map(|e| e.into_compile_error());
> +
> + Ok(quote! {
> + #struct_
> + #projections
> + // We put the rest into this const item, because it then will not be accessible to anything
> + // outside.
> + const _: () = {
> + #the_pin_data
> + #unpin_impl
> + #drop_impl
> + };
> + #error
> + })
> +}
> +
> +fn is_phantom_pinned(ty: &Type) -> bool {
> + match ty {
> + Type::Path(TypePath { qself: None, path }) => {
> + // Cannot possibly refer to `PhantomPinned` (except alias, but that's on the user).
> + if path.segments.len() > 3 {
> + return false;
> + }
> + // If there is a `::`, then the path needs to be `::core::marker::PhantomPinned` or
> + // `::std::marker::PhantomPinned`.
> + if path.leading_colon.is_some() && path.segments.len() != 3 {
> + return false;
> + }
> + let expected: Vec<&[&str]> = vec![&["PhantomPinned"], &["marker"], &["core", "std"]];
> + for (actual, expected) in path.segments.iter().rev().zip(expected) {
> + if !actual.arguments.is_empty() || expected.iter().all(|e| actual.ident != e) {
> + return false;
> + }
> + }
> + true
> + }
> + _ => false,
> + }
> +}
> +
> +fn is_field_structurally_pinned(field: &Field) -> bool {
> + field.attrs.iter().any(|a| a.path().is_ident("pin"))
> +}
>
> +fn generate_unpin_impl(
> + ItemStruct {
> + ident,
> + generics,
> + fields,
> + ..
> + }: &ItemStruct,
> +) -> TokenStream {
> + let generics_with_pinlt = {
Can you name this `generics_with_pin_lt`? Otherwise I read it as a misspelled
`pinit` :)
> + let mut g = generics.clone();
> + g.params.insert(0, parse_quote!('__pin));
> + let _ = g.make_where_clause();
> + g
> + };
> let (
> + impl_generics_with_pinlt,
> + ty_generics_with_pinlt,
> + Some(WhereClause {
> + where_token,
> + predicates,
> + }),
> + ) = generics_with_pinlt.split_for_impl()
> + else {
> + unreachable!()
> + };
> + let (_, ty_generics, _) = generics.split_for_impl();
> + let mut pinned_fields = fields
> .iter()
> + .filter(|f| is_field_structurally_pinned(f))
> + .cloned()
> + .collect::<Vec<_>>();
> + for field in &mut pinned_fields {
> + field.attrs.retain(|a| !a.path().is_ident("pin"));
> + }
> + quote! {
> + // This struct will be used for the unpin analysis. It is needed, because only structurally
> + // pinned fields are relevant whether the struct should implement `Unpin`.
> + #[allow(dead_code)] // The fields below are never used.
> + struct __Unpin #generics_with_pinlt
> + #where_token
> + #predicates
> + {
> + __phantom_pin: ::core::marker::PhantomData<fn(&'__pin ()) -> &'__pin ()>,
> + __phantom: ::core::marker::PhantomData<
> + fn(#ident #ty_generics) -> #ident #ty_generics
> + >,
> + #(#pinned_fields),*
> + }
> +
> + #[doc(hidden)]
> + impl #impl_generics_with_pinlt ::core::marker::Unpin for #ident #ty_generics
> + #where_token
> + __Unpin #ty_generics_with_pinlt: ::core::marker::Unpin,
> + #predicates
> + {}
> + }
> +}
> +
> +fn generate_drop_impl(
> + ItemStruct {
> + ident, generics, ..
> + }: &syn::ItemStruct,
> + args: Args,
> +) -> TokenStream {
> + let (impl_generics, ty_generics, whr) = generics.split_for_impl();
> + let has_pinned_drop = matches!(args, Args::PinnedDrop(_));
> + // We need to disallow normal `Drop` implementation, the exact behavior depends on whether
> + // `PinnedDrop` was specified in `args`.
> + if has_pinned_drop {
> + // When `PinnedDrop` was specified we just implement `Drop` and delegate.
> + quote! {
> + impl #impl_generics ::core::ops::Drop for #ident #ty_generics
> + #whr
> + {
> + fn drop(&mut self) {
> + // SAFETY: Since this is a destructor, `self` will not move after this function
> + // terminates, since it is inaccessible.
> + let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) };
> + // SAFETY: Since this is a drop function, we can create this token to call the
> + // pinned destructor of this type.
> + let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() };
> + ::pin_init::PinnedDrop::drop(pinned, token);
> }
> - Some(res)
> }
> - _ => None,
> - })
> - .unwrap_or_else(|| {
> - // If we did not find the name of the struct then we will use `Self` as the replacement
> - // and add a compile error to ensure it does not compile.
> - errs.extend(
> - "::core::compile_error!(\"Could not locate type name.\");"
> - .parse::<TokenStream>()
> - .unwrap(),
> - );
> - "Self".parse::<TokenStream>().unwrap().into_iter().collect()
> - });
> - let impl_generics = impl_generics
> - .into_iter()
> - .flat_map(|tt| replace_self_and_deny_type_defs(&struct_name, tt, &mut errs))
> - .collect::<Vec<_>>();
> - let mut rest = rest
> - .into_iter()
> - .flat_map(|tt| {
> - // We ignore top level `struct` tokens, since they would emit a compile error.
> - if matches!(&tt, TokenTree::Ident(i) if i == "struct") {
> - vec![tt]
> + }
> + } else {
> + // When no `PinnedDrop` was specified, then we have to prevent implementing drop.
> + quote! {
> + // We prevent this by creating a trait that will be implemented for all types implementing
> + // `Drop`. Additionally we will implement this trait for the struct leading to a conflict,
> + // if it also implements `Drop`
> + trait MustNotImplDrop {}
> + #[expect(drop_bounds)]
> + impl<T: ::core::ops::Drop> MustNotImplDrop for T {}
> + impl #impl_generics MustNotImplDrop for #ident #ty_generics
> + #whr
> + {}
> + // We also take care to prevent users from writing a useless `PinnedDrop` implementation.
> + // They might implement `PinnedDrop` correctly for the struct, but forget to give
> + // `PinnedDrop` as the parameter to `#[pin_data]`.
> + #[expect(non_camel_case_types)]
> + trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {}
> + impl<T: ::pin_init::PinnedDrop>
> + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {}
> + impl #impl_generics
> + UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics
> + #whr
> + {}
> + }
> + }
> +}
> +
> +fn generate_projections(
> + ItemStruct {
> + vis,
> + ident,
> + generics,
> + fields,
> + ..
> + }: &ItemStruct,
> +) -> TokenStream {
> + let (og_impl_gen, og_ty_gen, _) = generics.split_for_impl();
Please make sure the names match the early generate_ function, so impl_generics,
ty_generics for the original ones, and _with_pin_lt for the ones with lifetime
added.
> + let mut generics = generics.clone();
> + generics.params.insert(0, parse_quote!('__pin));
> + let (_, ty_gen, whr) = generics.split_for_impl();
> + let projection = format_ident!("{ident}Projection");
> + let this = format_ident!("this");
> +
> + let (fields_decl, fields_proj) = collect_tuple(fields.iter().map(
> + |f @ Field {
> + vis,
> + ident,
> + ty,
> + attrs,
> + ..
> + }| {
> + let mut attrs = attrs.clone();
> + attrs.retain(|a| !a.path().is_ident("pin"));
> + let mut no_doc_attrs = attrs.clone();
> + no_doc_attrs.retain(|a| !a.path().is_ident("doc"));
> + let ident = ident
> + .as_ref()
> + .expect("only structs with named fields are supported");
> + if is_field_structurally_pinned(f) {
> + (
> + quote!(
> + #(#attrs)*
> + #vis #ident: ::core::pin::Pin<&'__pin mut #ty>,
> + ),
> + quote!(
> + #(#no_doc_attrs)*
> + // SAFETY: this field is structurally pinned.
> + #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) },
> + ),
> + )
> } else {
> - replace_self_and_deny_type_defs(&struct_name, tt, &mut errs)
> + (
> + quote!(
> + #(#attrs)*
> + #vis #ident: &'__pin mut #ty,
> + ),
> + quote!(
> + #(#no_doc_attrs)*
> + #ident: &mut #this.#ident,
> + ),
> + )
> }
> - })
> - .collect::<Vec<_>>();
> - // This should be the body of the struct `{...}`.
> - let last = rest.pop();
> - let mut quoted = quote!(::pin_init::__pin_data! {
> - parse_input:
> - @args(#args),
> - @sig(#(#rest)*),
> - @impl_generics(#(#impl_generics)*),
> - @ty_generics(#(#ty_generics)*),
> - @decl_generics(#(#decl_generics)*),
> - @body(#last),
> - });
> - quoted.extend(errs);
> - quoted
> + },
> + ));
> + let structurally_pinned_fields_docs = fields
> + .iter()
> + .filter(|f| is_field_structurally_pinned(f))
> + .map(|Field { ident, .. }| {
> + let doc = format!(" - `{}`", ident.as_ref().expect(""));
I'd just use `unwrap` over `expect("")`.
> + quote!(#[doc = #doc])
> + });
> + let not_structurally_pinned_fields_docs = fields
> + .iter()
> + .filter(|f| !is_field_structurally_pinned(f))
> + .map(|Field { ident, .. }| {
> + let doc = format!(" - `{}`", ident.as_ref().expect(""));
> + quote!(#[doc = #doc])
> + });
Instead of building the `#[doc = ...]` streams for each field, I think we should just build the
docs entirely and insert in one go.
> + let docs = format!(" Pin-projections of [`{ident}`]");
> + quote! {
> + #[doc = #docs]
> + #[allow(dead_code)]
> + #[doc(hidden)]
> + #vis struct #projection #generics {
> + #(#fields_decl)*
> + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>,
> + }
> +
> + impl #og_impl_gen #ident #og_ty_gen
> + #whr
> + {
> + /// Pin-projects all fields of `Self`.
> + ///
> + /// These fields are structurally pinned:
> + #(#structurally_pinned_fields_docs)*
> + ///
> + /// These fields are **not** structurally pinned:
> + #(#not_structurally_pinned_fields_docs)*
> + #[inline]
> + #vis fn project<'__pin>(
> + self: ::core::pin::Pin<&'__pin mut Self>,
> + ) -> #projection #ty_gen {
> + // SAFETY: we only give access to `&mut` for fields not structurally pinned.
> + let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
> + #projection {
> + #(#fields_proj)*
> + ___pin_phantom_data: ::core::marker::PhantomData,
> + }
> + }
> + }
> + }
> }
>
> +fn generate_the_pin_data(
> + ItemStruct {
> + generics,
> + fields,
> + ident,
> + vis,
> + ..
> + }: &syn::ItemStruct,
> +) -> TokenStream {
> + let (impl_generics, ty_generics, whr) = generics.split_for_impl();
> +
> + // For every field, we create an initializing projection function according to its projection
> + // type. If a field is structurally pinned, then it must be initialized via `PinInit`, if it is
> + // not structurally pinned, then it can be initialized via `Init`.
> + //
> + // The functions are `unsafe` to prevent accidentally calling them.
> + fn handle_field(
> + Field {
> + vis,
> + ident,
> + ty,
> + attrs,
> + ..
> + }: &Field,
> + struct_ident: &Ident,
> + pinned: bool,
> + ) -> TokenStream {
> + let mut attrs = attrs.clone();
> + attrs.retain(|a| !a.path().is_ident("pin"));
> + let ident = ident
> + .as_ref()
> + .expect("only structs with named fields are supported");
> + let project_ident = format_ident!("__project_{ident}");
> + let (init_ty, init_fn, project_ty, project_body, pin_safety) = if pinned {
> + (
> + quote!(PinInit),
> + quote!(__pinned_init),
> + quote!(::core::pin::Pin<&'__slot mut #ty>),
> + // SAFETY: this field is structurally pinned.
> + quote!(unsafe { ::core::pin::Pin::new_unchecked(slot) }),
> + quote!(
> + #[doc = " - `slot` will not move until it is dropped, i.e. it will be pinned."]
This can just be /// ...
> + ),
> + )
> + } else {
> + (
> + quote!(Init),
> + quote!(__init),
> + quote!(&'__slot mut #ty),
> + quote!(slot),
> + quote!(),
> + )
> + };
> + let slot_safety = format!(
> + " `slot` points at the field `{ident}` inside of `{struct_ident}`, which is pinned.",
> + );
> + quote! {
> + /// # Safety
> + ///
> + /// - `slot` is a valid pointer to uninitialized memory.
> + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted
> + /// to deallocate.
> + #pin_safety
> + #(#attrs)*
> + #vis unsafe fn #ident<E>(
> + self,
> + slot: *mut #ty,
> + init: impl ::pin_init::#init_ty<#ty, E>,
> + ) -> ::core::result::Result<(), E> {
> + // SAFETY: this function has the same safety requirements as the __init function
> + // called below.
> + unsafe { ::pin_init::#init_ty::#init_fn(init, slot) }
> + }
> +
> + /// # Safety
> + ///
> + #[doc = #slot_safety]
> + #(#attrs)*
> + #vis unsafe fn #project_ident<'__slot>(
> + self,
> + slot: &'__slot mut #ty,
> + ) -> #project_ty {
> + #project_body
> + }
> + }
> + }
> +
> + let field_accessors = fields
> + .iter()
> + .map(|f| handle_field(f, ident, is_field_structurally_pinned(f)))
> + .collect::<TokenStream>();
> + quote! {
> + // We declare this struct which will host all of the projection function for our type. It
> + // will be invariant over all generic parameters which are inherited from the struct.
> + #[doc(hidden)]
> + #vis struct __ThePinData #generics
> + #whr
> {
> - errs.extend(
> - format!(
> - "::core::compile_error!(\"Cannot use `{i}` inside of struct definition with \
> - `#[pin_data]`.\");"
> - )
> - .parse::<TokenStream>()
> - .unwrap()
> - .into_iter()
> - .map(|mut tok| {
> - tok.set_span(tt.span());
> - tok
> - }),
> - );
> - vec![tt]
> - }
> - TokenTree::Ident(i) if i == "Self" => struct_name.clone(),
> - TokenTree::Literal(_) | TokenTree::Punct(_) | TokenTree::Ident(_) => vec![tt],
> - TokenTree::Group(g) => vec![TokenTree::Group(Group::new(
> - g.delimiter(),
> - g.stream()
> - .into_iter()
> - .flat_map(|tt| replace_self_and_deny_type_defs(struct_name, tt, errs))
> - .collect(),
> - ))],
> + __phantom: ::core::marker::PhantomData<
> + fn(#ident #ty_generics) -> #ident #ty_generics
> + >,
> + }
> +
> + impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics
> + #whr
> + {
> + fn clone(&self) -> Self { *self }
> + }
> +
> + impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics
> + #whr
> + {}
> +
> + #[allow(dead_code)] // Some functions might never be used and private.
> + #[expect(clippy::missing_safety_doc)]
> + impl #impl_generics __ThePinData #ty_generics
> + #whr
> + {
> + #field_accessors
> + }
> +
> + // SAFETY: We have added the correct projection functions above to `__ThePinData` and
> + // we also use the least restrictive generics possible.
> + unsafe impl #impl_generics ::pin_init::__internal::HasPinData for #ident #ty_generics
> + #whr
> + {
> + type PinData = __ThePinData #ty_generics;
> +
> + unsafe fn __pin_data() -> Self::PinData {
> + __ThePinData { __phantom: ::core::marker::PhantomData }
> + }
> + }
> +
> + // SAFETY: TODO
> + unsafe impl #impl_generics ::pin_init::__internal::PinData for __ThePinData #ty_generics
> + #whr
> + {
> + type Datee = #ident #ty_generics;
> + }
> + }
> +}
> +
> +fn strip_pin_annotations(struct_: &mut syn::ItemStruct) {
> + for field in &mut struct_.fields {
> + field.attrs.retain(|a| !a.path().is_ident("pin"));
> + }
> +}
Multiple places have similar things for stripping annotations and checking if
structurally pinned. Would it make sense to do this at the very beginning, and
build a `HashSet` of structurally pinned fields, and use that as canonical
source for all generate_ functions?
Best,
Gary
> +
> +struct SelfReplacer(PathSegment);
> +
> +impl VisitMut for SelfReplacer {
> + fn visit_path_mut(&mut self, i: &mut syn::Path) {
> + if i.is_ident("Self") {
> + let span = i.span();
> + let seg = &self.0;
> + *i = parse_quote_spanned!(span=> #seg);
> + } else {
> + syn::visit_mut::visit_path_mut(self, i);
> + }
> + }
> +
> + fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) {
> + if seg.ident == "Self" {
> + let span = seg.span();
> + let this = &self.0;
> + *seg = parse_quote_spanned!(span=> #this);
> + } else {
> + syn::visit_mut::visit_path_segment_mut(self, seg);
> + }
> + }
> +
> + fn visit_item_mut(&mut self, _: &mut Item) {
> + // Do not descend into items, since items reset/change what `Self` refers to.
> + }
> +}
> +
> +// replace with `.collect()` once MSRV is above 1.79
> +fn collect_tuple<A, B>(iter: impl Iterator<Item = (A, B)>) -> (Vec<A>, Vec<B>) {
> + let mut res_a = vec![];
> + let mut res_b = vec![];
> + for (a, b) in iter {
> + res_a.push(a);
> + res_b.push(b);
> }
> + (res_a, res_b)
> }
Powered by blists - more mailing lists