From 776c9239e136e1e8dde795ad30f51f9168fb4a5b Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Thu, 2 May 2024 22:29:14 -0700 Subject: [PATCH] Generate docs for the field contained in a transparent value Most `#[turbo_tasks::value(transparent)]` types leave their inner value as private. I think this okay, but because rustdoc hides private fields by default, it makes it hard to understand at a glance the contained value, without scrolling down to the `impl` of `VcValueType`. This auto-generates documentation for these types. --- Cargo.lock | 1 + crates/turbo-tasks-macros/Cargo.toml | 1 + crates/turbo-tasks-macros/src/value_macro.rs | 68 ++++++++++++++------ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7927028539d9..94ea8d3e1ed46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10662,6 +10662,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", + "regex", "syn 1.0.109", "turbo-tasks-macros-shared", ] diff --git a/crates/turbo-tasks-macros/Cargo.toml b/crates/turbo-tasks-macros/Cargo.toml index 984ad25fbe8d3..9d6509310a716 100644 --- a/crates/turbo-tasks-macros/Cargo.toml +++ b/crates/turbo-tasks-macros/Cargo.toml @@ -17,5 +17,6 @@ anyhow = { workspace = true } proc-macro-error = "1.0.4" proc-macro2 = { workspace = true } quote = { workspace = true } +regex = { workspace = true } syn = { workspace = true, features = ["full", "extra-traits", "visit-mut"] } turbo-tasks-macros-shared = { workspace = true } diff --git a/crates/turbo-tasks-macros/src/value_macro.rs b/crates/turbo-tasks-macros/src/value_macro.rs index f7d57a7696f79..8b06032e0ba8b 100644 --- a/crates/turbo-tasks-macros/src/value_macro.rs +++ b/crates/turbo-tasks-macros/src/value_macro.rs @@ -1,9 +1,12 @@ +use std::sync::OnceLock; + use proc_macro::TokenStream; use proc_macro2::Ident; -use quote::quote; +use quote::{quote, ToTokens}; +use regex::Regex; use syn::{ parse::{Parse, ParseStream}, - parse_macro_input, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Error, Fields, FieldsUnnamed, Generics, Item, ItemEnum, ItemStruct, Lit, LitStr, Meta, @@ -189,7 +192,7 @@ impl Parse for ValueArguments { } pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { - let item = parse_macro_input!(input as Item); + let mut item = parse_macro_input!(input as Item); let ValueArguments { serialization_mode, into_mode, @@ -198,6 +201,49 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { transparent, } = parse_macro_input!(args as ValueArguments); + let mut inner_type = None; + if transparent { + if let Item::Struct(ItemStruct { + attrs, + fields: Fields::Unnamed(FieldsUnnamed { unnamed, .. }), + .. + }) = &mut item + { + if unnamed.len() == 1 { + let field = unnamed.iter().next().unwrap(); + inner_type = Some(field.ty.clone()); + + // generate a type string to add to the docs + let inner_type_string = inner_type.to_token_stream().to_string(); + + // HACK: proc_macro2 inserts whitespace between every token. It's ugly, so + // remove it, assuming these whitespace aren't syntatically important. Using + // prettyplease (or similar) would be more correct, but slower and add another + // dependency. + static WHITESPACE_RE: OnceLock = OnceLock::new(); + // Remove whitespace, as long as there is a non-word character (e.g. `>` or `,`) + // on either side. Try not to remove whitespace between `dyn Trait`. + let whitespace_re = WHITESPACE_RE.get_or_init(|| { + Regex::new(r"\b \B|\B \b|\B \B").expect("WHITESPACE_RE is valid") + }); + let inner_type_string = whitespace_re.replace_all(&inner_type_string, ""); + + // Add a couple blank lines in case there's already a doc comment we're + // effectively appending to. If there's not, rustdoc will strip + // the leading whitespace. + let doc_str = format!( + "\n\nThis is a [transparent value type][::turbo_tasks::value#transparent] \ + wrapping [`{}`].", + inner_type_string, + ); + + attrs.push(parse_quote! { + #[doc = #doc_str] + }); + } + } + } + let ident = match &item { Item::Enum(ItemEnum { ident, .. }) => ident, Item::Struct(ItemStruct { ident, .. }) => ident, @@ -211,20 +257,6 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { } }; - let mut inner_type = None; - if transparent { - if let Item::Struct(ItemStruct { - fields: Fields::Unnamed(FieldsUnnamed { unnamed, .. }), - .. - }) = &item - { - if unnamed.len() == 1 { - let field = unnamed.iter().next().unwrap(); - inner_type = Some(&field.ty); - } - } - } - let cell_mode = match cell_mode { CellMode::New => quote! { turbo_tasks::VcCellNewMode<#ident> @@ -234,7 +266,7 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { }, }; - let (cell_prefix, cell_access_content, read) = if let Some(inner_type) = inner_type { + let (cell_prefix, cell_access_content, read) = if let Some(inner_type) = &inner_type { ( quote! { pub }, quote! {