diff options
Diffstat (limited to 'third_party/rust/inherent/src')
-rw-r--r-- | third_party/rust/inherent/src/expand.rs | 128 | ||||
-rw-r--r-- | third_party/rust/inherent/src/lib.rs | 97 | ||||
-rw-r--r-- | third_party/rust/inherent/src/parse.rs | 69 |
3 files changed, 294 insertions, 0 deletions
diff --git a/third_party/rust/inherent/src/expand.rs b/third_party/rust/inherent/src/expand.rs new file mode 100644 index 0000000000..1e3ce2142f --- /dev/null +++ b/third_party/rust/inherent/src/expand.rs @@ -0,0 +1,128 @@ +use crate::parse::TraitImpl; +use proc_macro2::{Span, TokenStream, TokenTree}; +use quote::{quote, quote_spanned}; +use syn::{FnArg, Ident, ImplItem, ImplItemMethod, Item, Pat, Path, Stmt, Visibility}; + +pub fn inherent(mut input: TraitImpl) -> TokenStream { + let impl_token = &input.impl_token; + let generics = &input.generics; + let where_clause = &input.generics.where_clause; + let trait_ = &input.trait_; + let ty = &input.self_ty; + + let fwd_methods: Vec<_> = input + .items + .iter() + .filter_map(|item| match item { + ImplItem::Method(method) => Some(fwd_method(trait_, method)), + _ => None, + }) + .collect(); + + input.items = input + .items + .into_iter() + .filter_map(|item| match item { + ImplItem::Method(mut method) => { + if inherit_default_implementation(&method) { + None + } else { + method.vis = Visibility::Inherited; + Some(ImplItem::Method(method)) + } + } + item => Some(item), + }) + .collect(); + + let body = quote_spanned!(input.brace_token.span=> { #(#fwd_methods)* }); + + quote! { + #impl_token #generics #ty #where_clause #body + + #input + } +} + +fn fwd_method(trait_: &Path, method: &ImplItemMethod) -> TokenStream { + let attrs = &method.attrs; + let vis = &method.vis; + let constness = &method.sig.constness; + let asyncness = &method.sig.asyncness; + let unsafety = &method.sig.unsafety; + let abi = &method.sig.abi; + let fn_token = method.sig.fn_token; + let ident = &method.sig.ident; + let generics = &method.sig.generics; + let output = &method.sig.output; + let where_clause = &method.sig.generics.where_clause; + + let (arg_pat, arg_val): (Vec<_>, Vec<_>) = method + .sig + .inputs + .pairs() + .enumerate() + .map(|(i, pair)| { + let (input, comma_token) = pair.into_tuple(); + match input { + FnArg::Receiver(receiver) => { + let self_token = receiver.self_token; + if receiver.reference.is_some() { + (quote!(#receiver #comma_token), quote!(#self_token)) + } else { + (quote!(#self_token #comma_token), quote!(#self_token)) + } + } + FnArg::Typed(arg) => { + let var = match arg.pat.as_ref() { + Pat::Ident(pat) => pat.ident.clone(), + _ => Ident::new(&format!("__arg{}", i), Span::call_site()), + }; + let colon_token = arg.colon_token; + let ty = &arg.ty; + (quote!(#var #colon_token #ty #comma_token), quote!(#var)) + } + } + }) + .unzip(); + + let types = generics.type_params().map(|param| ¶m.ident); + let body = quote!(<Self as #trait_>::#ident::<#(#types,)*>(#(#arg_val,)*)); + let block = quote_spanned!(method.block.brace_token.span=> { #body }); + let args = quote_spanned!(method.sig.paren_token.span=> (#(#arg_pat)*)); + + let has_doc = attrs.iter().any(|attr| attr.path.is_ident("doc")); + let default_doc = if has_doc { + None + } else { + let mut link = String::new(); + for segment in &trait_.segments { + link += &segment.ident.to_string(); + link += "::"; + } + let msg = format!("See [`{}{}`]", link, ident); + Some(quote!(#[doc = #msg])) + }; + + quote! { + #(#attrs)* + #default_doc + #vis #constness #asyncness #unsafety #abi #fn_token #ident #generics #args #output #where_clause #block + } +} + +fn inherit_default_implementation(method: &ImplItemMethod) -> bool { + method.block.stmts.len() == 1 + && match &method.block.stmts[0] { + Stmt::Item(Item::Verbatim(verbatim)) => { + let mut iter = verbatim.clone().into_iter(); + match iter.next() { + Some(TokenTree::Punct(punct)) => { + punct.as_char() == ';' && iter.next().is_none() + } + _ => false, + } + } + _ => false, + } +} diff --git a/third_party/rust/inherent/src/lib.rs b/third_party/rust/inherent/src/lib.rs new file mode 100644 index 0000000000..a04f8f6690 --- /dev/null +++ b/third_party/rust/inherent/src/lib.rs @@ -0,0 +1,97 @@ +//! [![github]](https://github.com/dtolnay/inherent) [![crates-io]](https://crates.io/crates/inherent) [![docs-rs]](https://docs.rs/inherent) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//! <br> +//! +//! ##### An attribute macro to make trait methods callable without the trait in scope. +//! +//! # Example +//! +//! ```rust +//! mod types { +//! use inherent::inherent; +//! +//! trait Trait { +//! fn f(self); +//! } +//! +//! pub struct Struct; +//! +//! #[inherent] +//! impl Trait for Struct { +//! pub fn f(self) {} +//! } +//! } +//! +//! fn main() { +//! // types::Trait is not in scope, but method can be called. +//! types::Struct.f(); +//! } +//! ``` +//! +//! Without the `inherent` macro on the trait impl, this would have failed with the +//! following error: +//! +//! ```console +//! error[E0599]: no method named `f` found for type `types::Struct` in the current scope +//! --> src/main.rs:18:19 +//! | +//! 8 | pub struct Struct; +//! | ------------------ method `f` not found for this +//! ... +//! 18 | types::Struct.f(); +//! | ^ +//! | +//! = help: items from traits can only be used if the trait is implemented and in scope +//! = note: the following trait defines an item `f`, perhaps you need to implement it: +//! candidate #1: `types::Trait` +//! ``` +//! +//! The `inherent` macro expands to inherent methods on the `Self` type of the trait +//! impl that forward to the trait methods. In the case above, the generated code +//! would be: +//! +//! ```rust +//! # trait Trait { +//! # fn f(self); +//! # } +//! # +//! # pub struct Struct; +//! # +//! # impl Trait for Struct { +//! # fn f(self) {} +//! # } +//! # +//! impl Struct { +//! pub fn f(self) { +//! <Self as Trait>::f(self) +//! } +//! } +//! ``` + +#![allow( + clippy::default_trait_access, + clippy::needless_doctest_main, + clippy::needless_pass_by_value +)] + +extern crate proc_macro; + +mod expand; +mod parse; + +use proc_macro::TokenStream; +use syn::parse::Nothing; +use syn::parse_macro_input; + +use crate::parse::TraitImpl; + +#[proc_macro_attribute] +pub fn inherent(args: TokenStream, input: TokenStream) -> TokenStream { + parse_macro_input!(args as Nothing); + let input = parse_macro_input!(input as TraitImpl); + expand::inherent(input).into() +} diff --git a/third_party/rust/inherent/src/parse.rs b/third_party/rust/inherent/src/parse.rs new file mode 100644 index 0000000000..92a91f624b --- /dev/null +++ b/third_party/rust/inherent/src/parse.rs @@ -0,0 +1,69 @@ +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::token::Brace; +use syn::{Attribute, Generics, ImplItem, ItemImpl, Path, Token, Type}; + +#[derive(Clone)] +pub struct TraitImpl { + pub attrs: Vec<Attribute>, + pub defaultness: Option<Token![default]>, + pub unsafety: Option<Token![unsafe]>, + pub impl_token: Token![impl], + pub generics: Generics, + pub trait_: Path, + pub for_token: Token![for], + pub self_ty: Type, + pub brace_token: Brace, + pub items: Vec<ImplItem>, +} + +impl Parse for TraitImpl { + fn parse(input: ParseStream) -> Result<Self> { + let imp: ItemImpl = input.parse()?; + + let (trait_, for_token) = match imp.trait_ { + Some((_bang_token, trait_, for_token)) => (trait_, for_token), + None => { + return Err(Error::new( + Span::call_site(), + "must be placed on a trait impl", + )) + } + }; + + Ok(TraitImpl { + attrs: imp.attrs, + defaultness: imp.defaultness, + unsafety: imp.unsafety, + impl_token: imp.impl_token, + generics: imp.generics, + trait_, + for_token, + self_ty: *imp.self_ty, + brace_token: imp.brace_token, + items: imp.items, + }) + } +} + +impl ToTokens for TraitImpl { + fn to_tokens(&self, tokens: &mut TokenStream) { + let imp = self.clone(); + + ItemImpl::to_tokens( + &ItemImpl { + attrs: imp.attrs, + defaultness: imp.defaultness, + unsafety: imp.unsafety, + impl_token: imp.impl_token, + generics: imp.generics, + trait_: Some((None, imp.trait_, imp.for_token)), + self_ty: Box::new(imp.self_ty), + brace_token: imp.brace_token, + items: imp.items, + }, + tokens, + ); + } +} |