From 1cc7c4383b4a7b913113cdfaf7544e36ff6982fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Sun, 11 Aug 2024 02:12:33 +0200 Subject: [PATCH 1/5] Use #[doc(fake_variadic)] --- crates/bevy_app/src/lib.rs | 4 +- crates/bevy_dylib/src/lib.rs | 4 +- crates/bevy_ecs/src/bundle.rs | 11 +- crates/bevy_ecs/src/lib.rs | 4 +- crates/bevy_ecs/src/query/fetch.rs | 12 +- crates/bevy_ecs/src/query/filter.rs | 29 +++- crates/bevy_ecs/src/query/world_query.rs | 12 +- crates/bevy_ecs/src/schedule/config.rs | 23 ++- .../src/system/exclusive_system_param.rs | 11 +- crates/bevy_ecs/src/system/system_param.rs | 12 +- crates/bevy_internal/src/lib.rs | 4 +- crates/bevy_reflect/src/lib.rs | 4 +- crates/bevy_reflect/src/tuple.rs | 17 ++- crates/bevy_render/src/lib.rs | 4 +- crates/bevy_render/src/render_phase/draw.rs | 13 +- crates/bevy_state/src/lib.rs | 4 + crates/bevy_state/src/state/state_set.rs | 18 ++- crates/bevy_utils/macros/src/lib.rs | 133 +++++++++++++++++- src/lib.rs | 4 +- 19 files changed, 281 insertions(+), 42 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index d22669a634241..078bacf5321eb 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -1,4 +1,6 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index c37cff70c36a1..bf5c66967d9de 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -1,4 +1,6 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 563cb0bf7962e..f4964ee012634 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -225,13 +225,14 @@ impl DynamicBundle for C { } macro_rules! tuple_impl { - ($($name: ident),*) => { + ($(#[$meta:meta])* $($name: ident),*) => { // SAFETY: // - `Bundle::component_ids` calls `ids` for each component type in the // bundle, in the exact order that `DynamicBundle::get_components` is called. // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. + $(#[$meta])* unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { #[allow(unused_variables)] fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){ @@ -270,7 +271,13 @@ macro_rules! tuple_impl { } } -all_tuples!(tuple_impl, 0, 15, B); +all_tuples!( + #[doc(fake_variadic)] + tuple_impl, + 0, + 15, + B +); /// For a specific [`World`], this stores a unique value identifying a type of a registered [`Bundle`]. /// diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 9a9a533da09b5..0c0314314df2e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,7 +1,9 @@ // FIXME(11590): remove this once the lint is fixed #![allow(unsafe_op_in_unsafe_fn)] #![doc = include_str!("../README.md")] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![allow(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index fe085253f4a12..269b9dc9140e0 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1793,10 +1793,11 @@ unsafe impl ReadOnlyQueryData for Has {} pub struct AnyOf(PhantomData); macro_rules! impl_tuple_query_data { - ($(($name: ident, $state: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] + $(#[$meta])* // SAFETY: defers to soundness `$name: WorldQuery` impl unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) { type ReadOnly = ($($name::ReadOnly,)*); @@ -1938,7 +1939,14 @@ macro_rules! impl_anytuple_fetch { }; } -all_tuples!(impl_tuple_query_data, 0, 15, F, S); +all_tuples!( + #[doc(fake_variadic)] + impl_tuple_query_data, + 0, + 15, + F, + S +); all_tuples!(impl_anytuple_fetch, 0, 15, F, S); /// [`WorldQuery`] used to nullify queries by turning `Query` into `Query>` diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index f7f9617d51434..93036a5c5bf65 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -493,11 +493,11 @@ macro_rules! impl_or_query_filter { } macro_rules! impl_tuple_query_filter { - ($($name: ident),*) => { + ($(#[$meta:meta])* $($name: ident),*) => { #[allow(unused_variables)] #[allow(non_snake_case)] #[allow(clippy::unused_unit)] - + $(#[$meta])* impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; @@ -516,7 +516,13 @@ macro_rules! impl_tuple_query_filter { }; } -all_tuples!(impl_tuple_query_filter, 0, 15, F); +all_tuples!( + #[doc(fake_variadic)] + impl_tuple_query_filter, + 0, + 15, + F +); all_tuples!(impl_or_query_filter, 0, 15, F, S); /// A filter on a component that only retains results the first time after they have been added. @@ -958,11 +964,24 @@ impl ArchetypeFilter for With {} impl ArchetypeFilter for Without {} macro_rules! impl_archetype_filter_tuple { - ($($filter: ident),*) => { + ($(#[$meta:meta])* $($filter: ident),*) => { + $(#[$meta])* impl<$($filter: ArchetypeFilter),*> ArchetypeFilter for ($($filter,)*) {} + }; +} +macro_rules! impl_archetype_or_filter_tuple { + ($($filter: ident),*) => { impl<$($filter: ArchetypeFilter),*> ArchetypeFilter for Or<($($filter,)*)> {} }; } -all_tuples!(impl_archetype_filter_tuple, 0, 15, F); +all_tuples!( + #[doc(fake_variadic)] + impl_archetype_filter_tuple, + 0, + 15, + F +); + +all_tuples!(impl_archetype_or_filter_tuple, 0, 15, F); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 7c8283cbfe196..414b237d3c121 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -144,7 +144,7 @@ pub unsafe trait WorldQuery { } macro_rules! impl_tuple_world_query { - ($(($name: ident, $state: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] @@ -153,6 +153,7 @@ macro_rules! impl_tuple_world_query { /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. /// `update_component_access` adds all `With` and `Without` filters from the subqueries. /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. + $(#[$meta])* unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { type Fetch<'w> = ($($name::Fetch<'w>,)*); type Item<'w> = ($($name::Item<'w>,)*); @@ -229,4 +230,11 @@ macro_rules! impl_tuple_world_query { }; } -all_tuples!(impl_tuple_world_query, 0, 15, F, S); +all_tuples!( + #[doc(fake_variadic)] + impl_tuple_world_query, + 0, + 15, + F, + S +); diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index ddf725260141b..55f38ca19c3ea 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -521,7 +521,8 @@ impl IntoSystemConfigs<()> for SystemConfigs { pub struct SystemConfigTupleMarker; macro_rules! impl_system_collection { - ($(($param: ident, $sys: ident)),*) => { + ($(#[$meta:meta])* $(($param: ident, $sys: ident)),*) => { + $(#[$meta])* impl<$($param, $sys),*> IntoSystemConfigs<(SystemConfigTupleMarker, $($param,)*)> for ($($sys,)*) where $($sys: IntoSystemConfigs<$param>),* @@ -539,7 +540,14 @@ macro_rules! impl_system_collection { } } -all_tuples!(impl_system_collection, 1, 20, P, S); +all_tuples!( + #[doc(fake_variadic)] + impl_system_collection, + 1, + 20, + P, + S +); /// A [`SystemSet`] with scheduling metadata. pub type SystemSetConfig = NodeConfig; @@ -740,7 +748,8 @@ impl IntoSystemSetConfigs for SystemSetConfig { } macro_rules! impl_system_set_collection { - ($($set: ident),*) => { + ($(#[$meta:meta])* $($set: ident),*) => { + $(#[$meta])* impl<$($set: IntoSystemSetConfigs),*> IntoSystemSetConfigs for ($($set,)*) { #[allow(non_snake_case)] @@ -756,4 +765,10 @@ macro_rules! impl_system_set_collection { } } -all_tuples!(impl_system_set_collection, 1, 20, S); +all_tuples!( + #[doc(fake_variadic)] + impl_system_set_collection, + 1, + 20, + S +); diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index 93ad2e603a247..255da95fec682 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -87,9 +87,10 @@ impl ExclusiveSystemParam for PhantomData { } macro_rules! impl_exclusive_system_param_tuple { - ($($param: ident),*) => { + ($(#[$meta:meta])* $($param: ident),*) => { #[allow(unused_variables)] #[allow(non_snake_case)] + $(#[$meta])* impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { type State = ($($param::State,)*); type Item<'s> = ($($param::Item<'s>,)*); @@ -113,7 +114,13 @@ macro_rules! impl_exclusive_system_param_tuple { }; } -all_tuples!(impl_exclusive_system_param_tuple, 0, 16, P); +all_tuples!( + #[doc(fake_variadic)] + impl_exclusive_system_param_tuple, + 0, + 16, + P +); #[cfg(test)] mod tests { diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 14a258784b908..3a7fa1388a357 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1471,13 +1471,15 @@ unsafe impl SystemParam for SystemChangeTick { } macro_rules! impl_system_param_tuple { - ($($param: ident),*) => { + ($(#[$meta:meta])* $($param: ident),*) => { // SAFETY: tuple consists only of ReadOnlySystemParams + $(#[$meta])* unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} // SAFETY: implementors of each `SystemParam` in the tuple have validated their impls #[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy #[allow(non_snake_case)] + $(#[$meta])* unsafe impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { type State = ($($param::State,)*); type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); @@ -1520,7 +1522,13 @@ macro_rules! impl_system_param_tuple { }; } -all_tuples!(impl_system_param_tuple, 0, 16, P); +all_tuples!( + #[doc(fake_variadic)] + impl_system_param_tuple, + 0, + 16, + P +); /// Contains type aliases for built-in [`SystemParam`]s with `'static` lifetimes. /// This makes it more convenient to refer to these types in contexts where diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index bc71e57efa519..b215fb6833c0b 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -1,4 +1,6 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 37957c098ad9e..0b5e55b0f1585 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1,6 +1,8 @@ // FIXME(3492): remove once docs are ready #![allow(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 67a6896f263e1..4636eb149af6d 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -685,7 +685,7 @@ impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L} macro_rules! impl_type_path_tuple { - () => { + ($(#[$meta:meta])*) => { impl TypePath for () { fn type_path() -> &'static str { "()" @@ -697,7 +697,8 @@ macro_rules! impl_type_path_tuple { } }; - ($param:ident) => { + ($(#[$meta:meta])* $param:ident) => { + $(#[$meta])* impl <$param: TypePath> TypePath for ($param,) { fn type_path() -> &'static str { static CELL: GenericTypePathCell = GenericTypePathCell::new(); @@ -715,8 +716,8 @@ macro_rules! impl_type_path_tuple { } }; - ($last:ident $(,$param:ident)*) => { - + ($(#[$meta:meta])* $last:ident $(,$param:ident)*) => { + $(#[$meta])* impl <$($param: TypePath,)* $last: TypePath> TypePath for ($($param,)* $last) { fn type_path() -> &'static str { static CELL: GenericTypePathCell = GenericTypePathCell::new(); @@ -735,7 +736,13 @@ macro_rules! impl_type_path_tuple { }; } -all_tuples!(impl_type_path_tuple, 0, 12, P); +all_tuples!( + #[doc(fake_variadic)] + impl_type_path_tuple, + 0, + 12, + P +); #[cfg(feature = "functions")] const _: () = { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d03a7a6e010fe..e5649c844f82a 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,7 +1,9 @@ // FIXME(3492): remove once docs are ready #![allow(missing_docs)] #![allow(unsafe_code)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 439392c098eff..0880ae797efd3 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -228,7 +228,8 @@ pub enum RenderCommandResult { } macro_rules! render_command_tuple_impl { - ($(($name: ident, $view: ident, $entity: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $view: ident, $entity: ident)),*) => { + $(#[$meta])* impl),*> RenderCommand

for ($($name,)*) { type Param = ($($name::Param,)*); type ViewQuery = ($($name::ViewQuery,)*); @@ -268,7 +269,15 @@ macro_rules! render_command_tuple_impl { }; } -all_tuples!(render_command_tuple_impl, 0, 15, C, V, E); +all_tuples!( + #[doc(fake_variadic)] + render_command_tuple_impl, + 0, + 15, + C, + V, + E +); /// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function. /// diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 32f354cdbdaa0..5e39da3f821e0 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -27,6 +27,10 @@ //! - The [`in_state`](crate::condition::in_state) and [`state_changed`](crate::condition::state_changed) run conditions - which are used //! to determine whether a system should run based on the current state. +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(rustdoc_internals))] + #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods pub mod app; diff --git a/crates/bevy_state/src/state/state_set.rs b/crates/bevy_state/src/state/state_set.rs index 6b83b2d17cb50..348c1fec257e5 100644 --- a/crates/bevy_state/src/state/state_set.rs +++ b/crates/bevy_state/src/state/state_set.rs @@ -229,10 +229,12 @@ impl StateSet for S { } macro_rules! impl_state_set_sealed_tuples { - ($(($param: ident, $val: ident, $evt: ident)), *) => { - impl<$($param: InnerStateSet),*> StateSetSealed for ($($param,)*) {} + ($(#[$meta:meta])* $(($param: ident, $val: ident, $evt: ident)), *) => { + $(#[$meta])* + impl<$($param: InnerStateSet),*> StateSetSealed for ($($param,)*) {} - impl<$($param: InnerStateSet),*> StateSet for ($($param,)*) { + $(#[$meta])* + impl<$($param: InnerStateSet),*> StateSet for ($($param,)*) { const SET_DEPENDENCY_DEPTH : usize = $($param::DEPENDENCY_DEPTH +)* 0; @@ -338,4 +340,12 @@ macro_rules! impl_state_set_sealed_tuples { }; } -all_tuples!(impl_state_set_sealed_tuples, 1, 15, S, s, ereader); +all_tuples!( + #[doc(fake_variadic)] + impl_state_set_sealed_tuples, + 1, + 15, + S, + s, + ereader +); diff --git a/crates/bevy_utils/macros/src/lib.rs b/crates/bevy_utils/macros/src/lib.rs index 3fdd2997671b3..c580814ba929d 100644 --- a/crates/bevy_utils/macros/src/lib.rs +++ b/crates/bevy_utils/macros/src/lib.rs @@ -3,14 +3,17 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] use proc_macro::TokenStream; +use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, + spanned::Spanned as _, token::Comma, - Ident, LitInt, Result, + Attribute, Error, Ident, LitInt, LitStr, Result, }; struct AllTuples { + fake_variadic: bool, macro_ident: Ident, start: usize, end: usize, @@ -19,6 +22,7 @@ struct AllTuples { impl Parse for AllTuples { fn parse(input: ParseStream) -> Result { + let fake_variadic = input.call(parse_fake_variadic_attr)?; let macro_ident = input.parse::()?; input.parse::()?; let start = input.parse::()?.base10_parse()?; @@ -30,7 +34,15 @@ impl Parse for AllTuples { idents.push(input.parse::()?); } + if start > 1 && fake_variadic { + return Err(Error::new( + input.span(), + "#[doc(fake_variadic)] only works when the tuple with length one is included", + )); + } + Ok(AllTuples { + fake_variadic, macro_ident, start, end, @@ -105,7 +117,42 @@ impl Parse for AllTuples { /// // impl_append!((P0, p0), (P1, p1), (P2, p2)); /// // .. /// // impl_append!((P0, p0) .. (P14, p14)); -/// ```` +/// ``` +/// +/// **`#[doc(fake_variadic)]`** +/// +/// To improve the readability of your docs when implementing a trait for +/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker. +/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`. +/// +/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro +/// is that you have to accept attributes using `$(#[$meta:meta])*`. +/// +/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default. +/// Add the following to your lib.rs if not already present: +/// +/// ``` +/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +/// #![allow(internal_features)] +/// #![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +/// ``` +/// +/// ``` +/// use bevy_utils_proc_macros::all_tuples; +/// +/// trait Variadic {} +/// +/// impl Variadic for () {} +/// +/// macro_rules! impl_append { +/// ($(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { +/// $(#[$meta])* +/// impl<$($P),*> Variadic for ($($P,)*) {} +/// } +/// } +/// +/// all_tuples!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p); +/// ``` #[proc_macro] pub fn all_tuples(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as AllTuples); @@ -129,9 +176,14 @@ pub fn all_tuples(input: TokenStream) -> TokenStream { let macro_ident = &input.macro_ident; let invocations = (input.start..=input.end).map(|i| { - let ident_tuples = &ident_tuples[..i]; + let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i); + let attrs = if input.fake_variadic { + fake_variadic_attrs(len, i) + } else { + TokenStream2::default() + }; quote! { - #macro_ident!(#(#ident_tuples),*); + #macro_ident!(#attrs #ident_tuples); } }); TokenStream::from(quote! { @@ -161,7 +213,6 @@ pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { }); } } - let macro_ident = &input.macro_ident; let invocations = (input.start..=input.end).map(|i| { let ident_tuples = &ident_tuples[..i]; @@ -175,3 +226,75 @@ pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { )* }) } + +/// Parses the attribute `#[doc(fake_variadic)]` +fn parse_fake_variadic_attr(input: ParseStream) -> Result { + let attribute = match input.call(Attribute::parse_outer)? { + attributes if attributes.len() == 0 => return Ok(false), + attributes if attributes.len() == 1 => attributes[0].clone(), + attributes => { + return Err(Error::new( + input.span(), + format!("Expected exactly one attribute, got {}", attributes.len()), + )) + } + }; + + if attribute.path().is_ident("doc") { + let nested = attribute.parse_args::()?; + if nested == "fake_variadic" { + return Ok(true); + } + } + + Err(Error::new( + attribute.meta.span(), + format!("Unexpected attribute"), + )) +} + +fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStream2 { + if len < 2 { + quote! { #(#idents)* } + } else { + quote! { (#(#idents),*) } + } +} + +fn choose_ident_tuples<'a>( + input: &AllTuples, + ident_tuples: &'a [TokenStream2], + i: usize, +) -> TokenStream2 { + // `rustdoc` uses the first ident to generate nice + // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ). + // We don't want two numbers, so we use the + // original, unnumbered idents for this case. + if input.fake_variadic && i == 1 { + let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len()); + quote! { #ident_tuple } + } else { + let ident_tuples = &ident_tuples[..i]; + quote! { #(#ident_tuples),* } + } +} + +fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 { + match i { + // An empty tuple (i.e. the unit type) is still documented separately, + // so no `#[doc(hidden)]` here. + 0 => TokenStream2::default(), + // The `#[doc(fake_variadic)]` attr has to be on the first impl block. + 1 => { + let doc = LitStr::new( + &format!("This trait is implemented for tuples up to {len} items long."), + Span2::call_site(), + ); + quote! { + #[cfg_attr(docsrs, doc(fake_variadic))] + #[cfg_attr(docsrs, doc = #doc)] + } + } + _ => quote! { #[cfg_attr(docsrs, doc(hidden))] }, + } +} diff --git a/src/lib.rs b/src/lib.rs index 6cdd5f4f9d6ce..d207f855992ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![allow(clippy::single_component_path_imports)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +#![allow(internal_features)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] //! [![Bevy Logo](https://bevyengine.org/assets/bevy_logo_docs.svg)](https://bevyengine.org) //! From 7d8ba48e395500fbcf7d0f7ea94ea300adfd08b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 12 Aug 2024 16:33:34 +0200 Subject: [PATCH 2/5] Fix documentation of re-exports --- Cargo.toml | 6 ++++++ crates/bevy_app/src/lib.rs | 2 +- crates/bevy_dylib/src/lib.rs | 4 +--- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_internal/src/lib.rs | 4 +--- crates/bevy_reflect/src/lib.rs | 2 +- crates/bevy_render/src/lib.rs | 2 +- crates/bevy_state/src/lib.rs | 2 +- crates/bevy_utils/macros/src/lib.rs | 9 +++++---- src/lib.rs | 4 +--- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99e3ccff9199d..2e42dc62e40e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ ref_as_ptr = "warn" unsafe_op_in_unsafe_fn = "warn" missing_docs = "warn" unsafe_code = "deny" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } [lints] workspace = true @@ -3363,6 +3364,11 @@ lto = "fat" panic = "abort" [package.metadata.docs.rs] +# This cfg is needed so that #[doc(fake_variadic)] is correctly propagated for +# impls for re-exported traits. See https://github.com/rust-lang/cargo/issues/8811 +# for details on why this is needed. Since dependencies don't expect to be built +# with `--cfg docsrs` (and thus fail to compile) we use a different cfg. +rustc-args = ["--cfg docsrs_dep"] rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] all-features = true cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 078bacf5321eb..9d1ad251b977e 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -1,6 +1,6 @@ // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index bf5c66967d9de..c37cff70c36a1 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -1,6 +1,4 @@ -// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` -#![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 0c0314314df2e..07701de519fc5 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -3,7 +3,7 @@ #![doc = include_str!("../README.md")] // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] #![allow(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index b215fb6833c0b..bc71e57efa519 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -1,6 +1,4 @@ -// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` -#![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 0b5e55b0f1585..6c28df88fc9e3 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2,7 +2,7 @@ #![allow(missing_docs)] // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index e5649c844f82a..8bd49ed2d124b 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -3,7 +3,7 @@ #![allow(unsafe_code)] // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 5e39da3f821e0..5506221ce35cf 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -29,7 +29,7 @@ // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] -#![cfg_attr(docsrs, feature(rustdoc_internals))] +#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods diff --git a/crates/bevy_utils/macros/src/lib.rs b/crates/bevy_utils/macros/src/lib.rs index c580814ba929d..e6c70d443b592 100644 --- a/crates/bevy_utils/macros/src/lib.rs +++ b/crates/bevy_utils/macros/src/lib.rs @@ -134,7 +134,7 @@ impl Parse for AllTuples { /// ``` /// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` /// #![allow(internal_features)] -/// #![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] /// ``` /// /// ``` @@ -280,6 +280,7 @@ fn choose_ident_tuples<'a>( } fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 { + let cfg = quote! { any(docsrs, docsrs_dep) }; match i { // An empty tuple (i.e. the unit type) is still documented separately, // so no `#[doc(hidden)]` here. @@ -291,10 +292,10 @@ fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 { Span2::call_site(), ); quote! { - #[cfg_attr(docsrs, doc(fake_variadic))] - #[cfg_attr(docsrs, doc = #doc)] + #[cfg_attr(#cfg, doc(fake_variadic))] + #[cfg_attr(#cfg, doc = #doc)] } } - _ => quote! { #[cfg_attr(docsrs, doc(hidden))] }, + _ => quote! { #[cfg_attr(#cfg, doc(hidden))] }, } } diff --git a/src/lib.rs b/src/lib.rs index d207f855992ae..6cdd5f4f9d6ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ #![allow(clippy::single_component_path_imports)] -// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` -#![allow(internal_features)] -#![cfg_attr(docsrs, feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] //! [![Bevy Logo](https://bevyengine.org/assets/bevy_logo_docs.svg)](https://bevyengine.org) //! From a2b354ea3cea72c08ee55fc92b5bcef32365a6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 12 Aug 2024 16:37:11 +0200 Subject: [PATCH 3/5] Revert change to bevy_app --- crates/bevy_app/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 9d1ad251b977e..d22669a634241 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -1,6 +1,4 @@ -// `rustdoc_internals` is needed for `#[doc(fake_variadics)]` -#![allow(internal_features)] -#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", From 583eeede3d86fdd6c25ae017b12771071c601927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 12 Aug 2024 17:52:16 +0200 Subject: [PATCH 4/5] Fix lint errors --- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/query/world_query.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 4 ++-- crates/bevy_utils/macros/src/lib.rs | 12 ++++-------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f4964ee012634..aa622011e4769 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -226,13 +226,13 @@ impl DynamicBundle for C { macro_rules! tuple_impl { ($(#[$meta:meta])* $($name: ident),*) => { + $(#[$meta])* // SAFETY: // - `Bundle::component_ids` calls `ids` for each component type in the // bundle, in the exact order that `DynamicBundle::get_components` is called. // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. - $(#[$meta])* unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { #[allow(unused_variables)] fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){ diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 414b237d3c121..6e7d0479cf857 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -148,12 +148,12 @@ macro_rules! impl_tuple_world_query { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] + $(#[$meta])* /// SAFETY: /// `fetch` accesses are the conjunction of the subqueries' accesses /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. /// `update_component_access` adds all `With` and `Without` filters from the subqueries. /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. - $(#[$meta])* unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { type Fetch<'w> = ($($name::Fetch<'w>,)*); type Item<'w> = ($($name::Item<'w>,)*); diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 3a7fa1388a357..cc8ca0e19b8a1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -418,7 +418,7 @@ fn assert_component_access_compatibility( /// # let _event = event; /// } /// set.p1().send(MyEvent::new()); -/// +/// /// let entities = set.p2().entities(); /// // ... /// # let _entities = entities; @@ -1472,8 +1472,8 @@ unsafe impl SystemParam for SystemChangeTick { macro_rules! impl_system_param_tuple { ($(#[$meta:meta])* $($param: ident),*) => { - // SAFETY: tuple consists only of ReadOnlySystemParams $(#[$meta])* + // SAFETY: tuple consists only of ReadOnlySystemParams unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} // SAFETY: implementors of each `SystemParam` in the tuple have validated their impls diff --git a/crates/bevy_utils/macros/src/lib.rs b/crates/bevy_utils/macros/src/lib.rs index e6c70d443b592..a00475c57f998 100644 --- a/crates/bevy_utils/macros/src/lib.rs +++ b/crates/bevy_utils/macros/src/lib.rs @@ -144,7 +144,7 @@ impl Parse for AllTuples { /// /// impl Variadic for () {} /// -/// macro_rules! impl_append { +/// macro_rules! impl_variadic { /// ($(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { /// $(#[$meta])* /// impl<$($P),*> Variadic for ($($P,)*) {} @@ -230,7 +230,7 @@ pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { /// Parses the attribute `#[doc(fake_variadic)]` fn parse_fake_variadic_attr(input: ParseStream) -> Result { let attribute = match input.call(Attribute::parse_outer)? { - attributes if attributes.len() == 0 => return Ok(false), + attributes if attributes.is_empty() => return Ok(false), attributes if attributes.len() == 1 => attributes[0].clone(), attributes => { return Err(Error::new( @@ -249,7 +249,7 @@ fn parse_fake_variadic_attr(input: ParseStream) -> Result { Err(Error::new( attribute.meta.span(), - format!("Unexpected attribute"), + "Unexpected attribute".to_string(), )) } @@ -261,11 +261,7 @@ fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStrea } } -fn choose_ident_tuples<'a>( - input: &AllTuples, - ident_tuples: &'a [TokenStream2], - i: usize, -) -> TokenStream2 { +fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usize) -> TokenStream2 { // `rustdoc` uses the first ident to generate nice // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ). // We don't want two numbers, so we use the From 491c283c9adc0c048e938d0ca1f68a3bd72aa9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 12 Aug 2024 19:50:22 +0200 Subject: [PATCH 5/5] Move to_ident_tuple and use it everywhere --- crates/bevy_utils/macros/src/lib.rs | 36 ++++++++--------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/crates/bevy_utils/macros/src/lib.rs b/crates/bevy_utils/macros/src/lib.rs index a00475c57f998..199be4755b571 100644 --- a/crates/bevy_utils/macros/src/lib.rs +++ b/crates/bevy_utils/macros/src/lib.rs @@ -163,15 +163,7 @@ pub fn all_tuples(input: TokenStream) -> TokenStream { .idents .iter() .map(|ident| format_ident!("{}{}", ident, i)); - if input.idents.len() < 2 { - ident_tuples.push(quote! { - #(#idents)* - }); - } else { - ident_tuples.push(quote! { - (#(#idents),*) - }); - } + ident_tuples.push(to_ident_tuple(idents, input.idents.len())); } let macro_ident = &input.macro_ident; @@ -203,15 +195,7 @@ pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { .idents .iter() .map(|ident| format_ident!("{}{}", ident, i)); - if input.idents.len() < 2 { - ident_tuples.push(quote! { - #(#idents)* - }); - } else { - ident_tuples.push(quote! { - (#(#idents),*) - }); - } + ident_tuples.push(to_ident_tuple(idents, input.idents.len())); } let macro_ident = &input.macro_ident; let invocations = (input.start..=input.end).map(|i| { @@ -253,14 +237,6 @@ fn parse_fake_variadic_attr(input: ParseStream) -> Result { )) } -fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStream2 { - if len < 2 { - quote! { #(#idents)* } - } else { - quote! { (#(#idents),*) } - } -} - fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usize) -> TokenStream2 { // `rustdoc` uses the first ident to generate nice // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ). @@ -275,6 +251,14 @@ fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usiz } } +fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStream2 { + if len < 2 { + quote! { #(#idents)* } + } else { + quote! { (#(#idents),*) } + } +} + fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 { let cfg = quote! { any(docsrs, docsrs_dep) }; match i {