//! Proc macros for rust-analyzer. use quote::{ToTokens, quote}; use syn::parse_quote; use synstructure::decl_derive; decl_derive!( [TypeFoldable, attributes(type_foldable)] => /// Derives `TypeFoldable` for the annotated `struct` or `enum` (`union` is not supported). /// /// The fold will produce a value of the same struct or enum variant as the input, with /// each field respectively folded using the `TypeFoldable` implementation for its type. /// However, if a field of a struct or an enum variant is annotated with /// `#[type_foldable(identity)]` then that field will retain its incumbent value (and its /// type is not required to implement `TypeFoldable`). type_foldable_derive ); decl_derive!( [TypeVisitable, attributes(type_visitable)] => /// Derives `TypeVisitable` for the annotated `struct` or `enum` (`union` is not supported). /// /// Each field of the struct or enum variant will be visited in definition order, using the /// `TypeVisitable` implementation for its type. However, if a field of a struct or an enum /// variant is annotated with `#[type_visitable(ignore)]` then that field will not be /// visited (and its type is not required to implement `TypeVisitable`). type_visitable_derive ); decl_derive!( [GenericTypeVisitable] => generic_type_visitable_derive ); fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { panic!("cannot derive on union") } // ignore fields with #[type_visitable(ignore)] s.filter(|bi| { let mut ignored = false; bi.ast().attrs.iter().for_each(|attr| { if !attr.path().is_ident("type_visitable") { return; } let _ = attr.parse_nested_meta(|nested| { if nested.path.is_ident("ignore") { ignored = true; } Ok(()) }); }); !ignored }); if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "db") { s.add_impl_generic(parse_quote! { 'db }); } s.add_bounds(synstructure::AddBounds::Generics); let body_visit = s.each(|bind| { quote! { match ::rustc_type_ir::VisitorResult::branch( ::rustc_type_ir::TypeVisitable::visit_with(#bind, __visitor) ) { ::core::ops::ControlFlow::Continue(()) => {}, ::core::ops::ControlFlow::Break(r) => { return ::rustc_type_ir::VisitorResult::from_residual(r); }, } } }); s.bind_with(|_| synstructure::BindStyle::Move); s.bound_impl( quote!(::rustc_type_ir::TypeVisitable<::hir_ty::next_solver::DbInterner<'db>>), quote! { fn visit_with<__V: ::rustc_type_ir::TypeVisitor<::hir_ty::next_solver::DbInterner<'db>>>( &self, __visitor: &mut __V ) -> __V::Result { match *self { #body_visit } <__V::Result as ::rustc_type_ir::VisitorResult>::output() } }, ) } fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { panic!("cannot derive on union") } if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "db") { s.add_impl_generic(parse_quote! { 'db }); } s.add_bounds(synstructure::AddBounds::Generics); s.bind_with(|_| synstructure::BindStyle::Move); let try_body_fold = s.each_variant(|vi| { let bindings = vi.bindings(); vi.construct(|_, index| { let bind = &bindings[index]; // retain value of fields with #[type_foldable(identity)] if has_ignore_attr(&bind.ast().attrs, "type_foldable", "identity") { bind.to_token_stream() } else { quote! { ::rustc_type_ir::TypeFoldable::try_fold_with(#bind, __folder)? } } }) }); let body_fold = s.each_variant(|vi| { let bindings = vi.bindings(); vi.construct(|_, index| { let bind = &bindings[index]; // retain value of fields with #[type_foldable(identity)] if has_ignore_attr(&bind.ast().attrs, "type_foldable", "identity") { bind.to_token_stream() } else { quote! { ::rustc_type_ir::TypeFoldable::fold_with(#bind, __folder) } } }) }); s.bound_impl( quote!(::rustc_type_ir::TypeFoldable<::hir_ty::next_solver::DbInterner<'db>>), quote! { fn try_fold_with<__F: ::rustc_type_ir::FallibleTypeFolder<::hir_ty::next_solver::DbInterner<'db>>>( self, __folder: &mut __F ) -> Result { Ok(match self { #try_body_fold }) } fn fold_with<__F: ::rustc_type_ir::TypeFolder<::hir_ty::next_solver::DbInterner<'db>>>( self, __folder: &mut __F ) -> Self { match self { #body_fold } } }, ) } fn has_ignore_attr(attrs: &[syn::Attribute], name: &'static str, meta: &'static str) -> bool { let mut ignored = false; attrs.iter().for_each(|attr| { if !attr.path().is_ident(name) { return; } let _ = attr.parse_nested_meta(|nested| { if nested.path.is_ident(meta) { ignored = true; } Ok(()) }); }); ignored } fn generic_type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { panic!("cannot derive on union") } s.add_bounds(synstructure::AddBounds::Fields); s.bind_with(|_| synstructure::BindStyle::Move); s.add_impl_generic(parse_quote!(__V: hir_ty::next_solver::interner::WorldExposer)); let body_visit = s.each(|bind| { quote! { ::rustc_type_ir::GenericTypeVisitable::<__V>::generic_visit_with(#bind, __visitor); } }); s.bound_impl( quote!(::rustc_type_ir::GenericTypeVisitable<__V>), quote! { fn generic_visit_with( &self, __visitor: &mut __V ) { match self { #body_visit } } }, ) } decl_derive!( [UpmapFromRaFixture] => upmap_from_ra_fixture ); fn upmap_from_ra_fixture(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { panic!("cannot derive on union") } s.add_bounds(synstructure::AddBounds::Generics); s.bind_with(|_| synstructure::BindStyle::Move); let body = s.each_variant(|vi| { let bindings = vi.bindings(); vi.construct(|_, index| { let bind = &bindings[index]; quote! { ::ide_db::ra_fixture::UpmapFromRaFixture::upmap_from_ra_fixture( #bind, __analysis, __virtual_file_id, __real_file_id, )? } }) }); s.bound_impl( quote!(::ide_db::ra_fixture::UpmapFromRaFixture), quote! { fn upmap_from_ra_fixture( self, __analysis: &::ide_db::ra_fixture::RaFixtureAnalysis, __virtual_file_id: ::ide_db::ra_fixture::FileId, __real_file_id: ::ide_db::ra_fixture::FileId, ) -> Result { Ok(match self { #body }) } }, ) }