From 11457e378640b91d3cae6d0e617ea96794f156f8 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Apr 2019 09:56:51 -0700 Subject: [PATCH] Support showing readonly fields as pub in rustdoc --- src/args.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/expand.rs | 19 ++++++++++++++++++- src/lib.rs | 7 +++++-- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/args.rs diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..62894d3 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,39 @@ +// Syntax: +// +// #[readonly::make(doc = mycratename_doc_cfg)] +// pub struct MyStruct {...} +// +// If provided, the `doc` attribute will make readonly fields fully pub, meaning +// writeable, if the given cfg is set. This way the readonly fields end up +// visible in rustdoc. The caller is responsible for providing a doc comment +// that explains that the field is readonly. +// +// Intended usage is by placing the following in Cargo.toml: +// +// [package.metadata.docs.rs] +// rustdoc-args = ["--cfg", "mycratename_doc_cfg"] + +use syn::parse::{Parse, ParseStream, Result}; +use syn::{Ident, Token}; + +mod kw { + syn::custom_keyword!(doc); +} + +pub struct Args { + pub doc_cfg: Option, +} + +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + let doc_cfg = if input.is_empty() { + None + } else { + input.parse::()?; + input.parse::()?; + Some(input.parse()?) + }; + + Ok(Args { doc_cfg }) + } +} diff --git a/src/expand.rs b/src/expand.rs index 3a18d7a..5a79f49 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,3 +1,4 @@ +use crate::args::Args; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::visit_mut::{self, VisitMut}; @@ -8,7 +9,7 @@ use syn::{ type Punctuated = syn::punctuated::Punctuated; -pub fn readonly(input: DeriveInput) -> Result { +pub fn readonly(args: Args, input: DeriveInput) -> Result { let call_site = Span::call_site(); match &input.data { @@ -23,8 +24,16 @@ pub fn readonly(input: DeriveInput) -> Result { } let mut input = input; + let indices = find_and_strip_readonly_attrs(&mut input); + let original_input = args.doc_cfg.as_ref().map(|doc_cfg| { + quote! { + #[cfg(all(#doc_cfg, rustdoc))] + #input + } + }); + if !has_defined_repr(&input) { input.attrs.push(parse_quote!(#[repr(C)])); } @@ -58,11 +67,19 @@ pub fn readonly(input: DeriveInput) -> Result { readonly.ident = Ident::new(&format!("ReadOnly{}", input.ident), call_site); let readonly_ident = &readonly.ident; + if let Some(doc_cfg) = args.doc_cfg { + let not_doc_cfg = parse_quote!(#[cfg(not(all(#doc_cfg, rustdoc)))]); + input.attrs.insert(0, not_doc_cfg); + } + Ok(quote! { + #original_input + #input #readonly + #[doc(hidden)] impl #impl_generics core::ops::Deref for #ident #ty_generics #where_clause { type Target = #readonly_ident #ty_generics; diff --git a/src/lib.rs b/src/lib.rs index bcf5014..b213dd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,18 +62,21 @@ extern crate proc_macro; +mod args; mod expand; +use crate::args::Args; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; #[proc_macro_attribute] -pub fn make(_args: TokenStream, tokens: TokenStream) -> TokenStream { +pub fn make(args: TokenStream, tokens: TokenStream) -> TokenStream { let original = tokens.clone(); + let args = parse_macro_input!(args as Args); let input = parse_macro_input!(tokens as DeriveInput); - expand::readonly(input) + expand::readonly(args, input) .unwrap_or_else(|e| { let original = proc_macro2::TokenStream::from(original); let compile_error = e.to_compile_error();