From a658bfef19992c3b0e7bbbfde24bc88cbb098e27 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Tue, 18 Oct 2022 13:49:57 +0000 Subject: [PATCH] bevy_reflect: Reflect doc comments (#6234) # Objective Resolves #6197 Make it so that doc comments can be retrieved via reflection. ## Solution Adds the new `documentation` feature to `bevy_reflect` (disabled by default). When enabled, documentation can be found using `TypeInfo::doc` for reflected types: ```rust /// Some struct. /// /// # Example /// /// ```ignore /// let some_struct = SomeStruct; /// ``` #[derive(Reflect)] struct SomeStruct; let info = ::type_info(); assert_eq!( Some(" Some struct.\n\n # Example\n\n ```ignore\n let some_struct = SomeStruct;\n ```"), info.docs() ); ``` ### Notes for Reviewers The bulk of the files simply added the same 16 lines of code (with slightly different documentation). Most of the real changes occur in the `bevy_reflect_derive` files as well as in the added tests. --- ## Changelog * Added `documentation` feature to `bevy_reflect` * Added `TypeInfo::docs` method (and similar methods for all info types) --- crates/bevy_reflect/Cargo.toml | 9 + .../bevy_reflect_derive/Cargo.toml | 5 + .../bevy_reflect_derive/src/derive_data.rs | 49 +++++- .../bevy_reflect_derive/src/documentation.rs | 77 +++++++++ .../bevy_reflect_derive/src/impls/enums.rs | 124 ++++++++++---- .../bevy_reflect_derive/src/impls/structs.rs | 39 ++++- .../src/impls/tuple_structs.rs | 39 ++++- .../bevy_reflect_derive/src/impls/values.rs | 10 +- .../bevy_reflect_derive/src/lib.rs | 11 +- .../bevy_reflect_derive/src/reflect_value.rs | 6 +- crates/bevy_reflect/examples/reflect_docs.rs | 50 ++++++ crates/bevy_reflect/src/array.rs | 16 ++ crates/bevy_reflect/src/enums/enum_trait.rs | 16 ++ crates/bevy_reflect/src/enums/variants.rs | 62 ++++++- crates/bevy_reflect/src/fields.rs | 32 ++++ crates/bevy_reflect/src/lib.rs | 154 ++++++++++++++++++ crates/bevy_reflect/src/list.rs | 16 ++ crates/bevy_reflect/src/map.rs | 16 ++ crates/bevy_reflect/src/struct_trait.rs | 16 ++ crates/bevy_reflect/src/tuple.rs | 16 ++ crates/bevy_reflect/src/tuple_struct.rs | 16 ++ crates/bevy_reflect/src/type_info.rs | 48 ++++++ 22 files changed, 777 insertions(+), 50 deletions(-) create mode 100644 crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs create mode 100644 crates/bevy_reflect/examples/reflect_docs.rs diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 2495e45b51..f8a4fb638f 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -10,7 +10,11 @@ keywords = ["bevy"] readme = "README.md" [features] +default = [] +# Provides Bevy-related reflection implementations bevy = ["glam", "smallvec", "bevy_math"] +# When enabled, allows documentation comments to be accessed via reflection +documentation = ["bevy_reflect_derive/documentation"] [dependencies] # bevy @@ -31,3 +35,8 @@ glam = { version = "0.21", features = ["serde"], optional = true } [dev-dependencies] ron = "0.8.0" + +[[example]] +name = "reflect_docs" +path = "examples/reflect_docs.rs" +required-features = ["documentation"] diff --git a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml index c7c947537f..669bf56135 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml +++ b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml @@ -11,6 +11,11 @@ keywords = ["bevy"] [lib] proc-macro = true +[features] +default = [] +# When enabled, allows documentation comments to be processed by the reflection macros +documentation = [] + [dependencies] bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.9.0-dev" } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index 7114613649..92b2035e19 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -39,6 +39,9 @@ pub(crate) struct ReflectMeta<'a> { generics: &'a Generics, /// A cached instance of the path to the `bevy_reflect` crate. bevy_reflect_path: Path, + /// The documentation for this type, if any + #[cfg(feature = "documentation")] + docs: crate::documentation::Documentation, } /// Struct data used by derive macros for `Reflect` and `FromReflect`. @@ -86,6 +89,9 @@ pub(crate) struct StructField<'a> { pub attrs: ReflectFieldAttr, /// The index of this field within the struct. pub index: usize, + /// The documentation for this field, if any + #[cfg(feature = "documentation")] + pub doc: crate::documentation::Documentation, } /// Represents a variant on an enum. @@ -100,6 +106,9 @@ pub(crate) struct EnumVariant<'a> { /// The index of this variant within the enum. #[allow(dead_code)] pub index: usize, + /// The documentation for this variant, if any + #[cfg(feature = "documentation")] + pub doc: crate::documentation::Documentation, } pub(crate) enum EnumVariantFields<'a> { @@ -123,6 +132,9 @@ impl<'a> ReflectDerive<'a> { // Should indicate whether `#[reflect_value]` was used let mut reflect_mode = None; + #[cfg(feature = "documentation")] + let mut doc = crate::documentation::Documentation::default(); + for attribute in input.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { match attribute { Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => { @@ -159,25 +171,31 @@ impl<'a> ReflectDerive<'a> { reflect_mode = Some(ReflectMode::Value); } + #[cfg(feature = "documentation")] + Meta::NameValue(pair) if pair.path.is_ident("doc") => { + if let syn::Lit::Str(lit) = pair.lit { + doc.push(lit.value()); + } + } _ => continue, } } + let meta = ReflectMeta::new(&input.ident, &input.generics, traits); + + #[cfg(feature = "documentation")] + let meta = meta.with_docs(doc); + // Use normal reflection if unspecified let reflect_mode = reflect_mode.unwrap_or(ReflectMode::Normal); if reflect_mode == ReflectMode::Value { - return Ok(Self::Value(ReflectMeta::new( - &input.ident, - &input.generics, - traits, - ))); + return Ok(Self::Value(meta)); } return match &input.data { Data::Struct(data) => { let fields = Self::collect_struct_fields(&data.fields)?; - let meta = ReflectMeta::new(&input.ident, &input.generics, traits); let reflect_struct = ReflectStruct { meta, serialization_denylist: members_to_serialization_denylist( @@ -194,7 +212,6 @@ impl<'a> ReflectDerive<'a> { } Data::Enum(data) => { let variants = Self::collect_enum_variants(&data.variants)?; - let meta = ReflectMeta::new(&input.ident, &input.generics, traits); let reflect_enum = ReflectEnum { meta, variants }; Ok(Self::Enum(reflect_enum)) @@ -216,6 +233,8 @@ impl<'a> ReflectDerive<'a> { index, attrs, data: field, + #[cfg(feature = "documentation")] + doc: crate::documentation::Documentation::from_attributes(&field.attrs), }) }) .fold( @@ -245,6 +264,8 @@ impl<'a> ReflectDerive<'a> { attrs: parse_field_attrs(&variant.attrs)?, data: variant, index, + #[cfg(feature = "documentation")] + doc: crate::documentation::Documentation::from_attributes(&variant.attrs), }) }) .fold( @@ -263,9 +284,17 @@ impl<'a> ReflectMeta<'a> { type_name, generics, bevy_reflect_path: utility::get_bevy_reflect_path(), + #[cfg(feature = "documentation")] + docs: Default::default(), } } + /// Sets the documentation for this type. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: crate::documentation::Documentation) -> Self { + Self { docs, ..self } + } + /// The registered reflect traits on this struct. pub fn traits(&self) -> &ReflectTraits { &self.traits @@ -296,6 +325,12 @@ impl<'a> ReflectMeta<'a> { None, ) } + + /// The collection of docstrings for this type, if any. + #[cfg(feature = "documentation")] + pub fn doc(&self) -> &crate::documentation::Documentation { + &self.docs + } } impl<'a> ReflectStruct<'a> { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs new file mode 100644 index 0000000000..1ce2c2efb7 --- /dev/null +++ b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs @@ -0,0 +1,77 @@ +//! Contains code related to documentation reflection (requires the `documentation` feature). + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{Attribute, Lit, Meta}; + +/// A struct used to represent a type's documentation, if any. +/// +/// When converted to a [`TokenStream`], this will output an `Option` +/// containing the collection of doc comments. +#[derive(Default)] +pub(crate) struct Documentation { + docs: Vec, +} + +impl Documentation { + /// Create a new [`Documentation`] from a type's attributes. + /// + /// This will collect all `#[doc = "..."]` attributes, including the ones generated via `///` and `//!`. + pub fn from_attributes<'a>(attributes: impl IntoIterator) -> Self { + let docs = attributes + .into_iter() + .filter_map(|attr| { + let meta = attr.parse_meta().ok()?; + match meta { + Meta::NameValue(pair) if pair.path.is_ident("doc") => { + if let Lit::Str(lit) = pair.lit { + Some(lit.value()) + } else { + None + } + } + _ => None, + } + }) + .collect(); + + Self { docs } + } + + /// The full docstring, if any. + pub fn doc_string(&self) -> Option { + if self.docs.is_empty() { + return None; + } + + let len = self.docs.len(); + Some( + self.docs + .iter() + .enumerate() + .map(|(index, doc)| { + if index < len - 1 { + format!("{}\n", doc) + } else { + doc.to_owned() + } + }) + .collect(), + ) + } + + /// Push a new docstring to the collection + pub fn push(&mut self, doc: String) { + self.docs.push(doc); + } +} + +impl ToTokens for Documentation { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(doc) = self.doc_string() { + quote!(Some(#doc)).to_tokens(tokens); + } else { + quote!(None).to_tokens(tokens); + } + } +} diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 8a7afb792e..83e8e18ff3 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -1,9 +1,10 @@ -use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField}; +use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; use crate::impls::impl_typed; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::quote; +use syn::Fields; pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); @@ -55,12 +56,28 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { }); let string_name = enum_name.to_string(); + + #[cfg(feature = "documentation")] + let info_generator = { + let doc = reflect_enum.meta().doc(); + quote! { + #bevy_reflect_path::EnumInfo::new::(#string_name, &variants).with_docs(#doc) + } + }; + + #[cfg(not(feature = "documentation"))] + let info_generator = { + quote! { + #bevy_reflect_path::EnumInfo::new::(#string_name, &variants) + } + }; + let typed_impl = impl_typed( enum_name, reflect_enum.meta().generics(), quote! { let variants = [#(#variant_info),*]; - let info = #bevy_reflect_path::EnumInfo::new::(#string_name, &variants); + let info = #info_generator; #bevy_reflect_path::TypeInfo::Enum(info) }, bevy_reflect_path, @@ -286,6 +303,18 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let name = ident.to_string(); let unit = reflect_enum.get_unit(ident); + let variant_type_ident = match variant.data.fields { + Fields::Unit => Ident::new("Unit", Span::call_site()), + Fields::Unnamed(..) => Ident::new("Tuple", Span::call_site()), + Fields::Named(..) => Ident::new("Struct", Span::call_site()), + }; + + let variant_info_ident = match variant.data.fields { + Fields::Unit => Ident::new("UnitVariantInfo", Span::call_site()), + Fields::Unnamed(..) => Ident::new("TupleVariantInfo", Span::call_site()), + Fields::Named(..) => Ident::new("StructVariantInfo", Span::call_site()), + }; + enum_variant_name.push(quote! { #unit{..} => #name }); @@ -293,10 +322,10 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden #unit{..} => #variant_index }); - fn for_fields( + fn get_field_args( fields: &[StructField], mut generate_for_field: impl FnMut(usize, usize, &StructField) -> proc_macro2::TokenStream, - ) -> (usize, Vec) { + ) -> Vec { let mut constructor_argument = Vec::new(); let mut reflect_idx = 0; for field in fields.iter() { @@ -307,41 +336,64 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden constructor_argument.push(generate_for_field(reflect_idx, field.index, field)); reflect_idx += 1; } - (reflect_idx, constructor_argument) + constructor_argument } - let mut add_fields_branch = |variant, info_type, arguments, field_len| { - let variant = Ident::new(variant, Span::call_site()); - let info_type = Ident::new(info_type, Span::call_site()); - variant_info.push(quote! { - #bevy_reflect_path::VariantInfo::#variant( - #bevy_reflect_path::#info_type::new(#arguments) - ) - }); - enum_field_len.push(quote! { - #unit{..} => #field_len - }); - enum_variant_type.push(quote! { - #unit{..} => #bevy_reflect_path::VariantType::#variant - }); - }; + + let mut push_variant = + |_variant: &EnumVariant, arguments: proc_macro2::TokenStream, field_len: usize| { + #[cfg(feature = "documentation")] + let with_docs = { + let doc = quote::ToTokens::to_token_stream(&_variant.doc); + Some(quote!(.with_docs(#doc))) + }; + #[cfg(not(feature = "documentation"))] + let with_docs: Option = None; + + variant_info.push(quote! { + #bevy_reflect_path::VariantInfo::#variant_type_ident( + #bevy_reflect_path::#variant_info_ident::new(#arguments) + #with_docs + ) + }); + enum_field_len.push(quote! { + #unit{..} => #field_len + }); + enum_variant_type.push(quote! { + #unit{..} => #bevy_reflect_path::VariantType::#variant_type_ident + }); + }; + match &variant.fields { EnumVariantFields::Unit => { - add_fields_branch("Unit", "UnitVariantInfo", quote!(#name), 0usize); + push_variant(variant, quote!(#name), 0); } EnumVariantFields::Unnamed(fields) => { - let (field_len, argument) = for_fields(fields, |reflect_idx, declar, field| { - let declar_field = syn::Index::from(declar); + let args = get_field_args(fields, |reflect_idx, declaration_index, field| { + let declar_field = syn::Index::from(declaration_index); enum_field_at.push(quote! { #unit { #declar_field : value, .. } if #ref_index == #reflect_idx => Some(value) }); + + #[cfg(feature = "documentation")] + let with_docs = { + let doc = quote::ToTokens::to_token_stream(&field.doc); + Some(quote!(.with_docs(#doc))) + }; + #[cfg(not(feature = "documentation"))] + let with_docs: Option = None; + let field_ty = &field.data.ty; - quote! { #bevy_reflect_path::UnnamedField::new::<#field_ty>(#reflect_idx) } + quote! { + #bevy_reflect_path::UnnamedField::new::<#field_ty>(#reflect_idx) + #with_docs + } }); - let arguments = quote!(#name, &[ #(#argument),* ]); - add_fields_branch("Tuple", "TupleVariantInfo", arguments, field_len); + + let field_len = args.len(); + push_variant(variant, quote!(#name, &[ #(#args),* ]), field_len); } EnumVariantFields::Named(fields) => { - let (field_len, argument) = for_fields(fields, |reflect_idx, _, field| { + let args = get_field_args(fields, |reflect_idx, _, field| { let field_ident = field.data.ident.as_ref().unwrap(); let field_name = field_ident.to_string(); enum_field.push(quote! { @@ -357,11 +409,23 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden #unit{ .. } if #ref_index == #reflect_idx => Some(#field_name) }); + #[cfg(feature = "documentation")] + let with_docs = { + let doc = quote::ToTokens::to_token_stream(&field.doc); + Some(quote!(.with_docs(#doc))) + }; + #[cfg(not(feature = "documentation"))] + let with_docs: Option = None; + let field_ty = &field.data.ty; - quote! { #bevy_reflect_path::NamedField::new::<#field_ty>(#field_name) } + quote! { + #bevy_reflect_path::NamedField::new::<#field_ty>(#field_name) + #with_docs + } }); - let arguments = quote!(#name, &[ #(#argument),* ]); - add_fields_branch("Struct", "StructVariantInfo", arguments, field_len); + + let field_len = args.len(); + push_variant(variant, quote!(#name, &[ #(#args),* ]), field_len); } }; } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index c68aab2e2d..2c35930703 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -51,15 +51,46 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { } }); + #[cfg(feature = "documentation")] + let field_generator = { + let docs = reflect_struct + .active_fields() + .map(|field| quote::ToTokens::to_token_stream(&field.doc)); + quote! { + #(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names).with_docs(#docs) ,)* + } + }; + + #[cfg(not(feature = "documentation"))] + let field_generator = { + quote! { + #(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names) ,)* + } + }; + let string_name = struct_name.to_string(); + + #[cfg(feature = "documentation")] + let info_generator = { + let doc = reflect_struct.meta().doc(); + quote! { + #bevy_reflect_path::StructInfo::new::(#string_name, &fields).with_docs(#doc) + } + }; + + #[cfg(not(feature = "documentation"))] + let info_generator = { + quote! { + #bevy_reflect_path::StructInfo::new::(#string_name, &fields) + } + }; + let typed_impl = impl_typed( struct_name, reflect_struct.meta().generics(), quote! { - let fields = [ - #(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names),)* - ]; - let info = #bevy_reflect_path::StructInfo::new::(#string_name, &fields); + let fields = [#field_generator]; + let info = #info_generator; #bevy_reflect_path::TypeInfo::Struct(info) }, bevy_reflect_path, diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index bb44fc5f23..df2e8661f7 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -35,15 +35,46 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { } }); + #[cfg(feature = "documentation")] + let field_generator = { + let docs = reflect_struct + .active_fields() + .map(|field| quote::ToTokens::to_token_stream(&field.doc)); + quote! { + #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents).with_docs(#docs) ,)* + } + }; + + #[cfg(not(feature = "documentation"))] + let field_generator = { + quote! { + #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents) ,)* + } + }; + let string_name = struct_name.to_string(); + + #[cfg(feature = "documentation")] + let info_generator = { + let doc = reflect_struct.meta().doc(); + quote! { + #bevy_reflect_path::TupleStructInfo::new::(#string_name, &fields).with_docs(#doc) + } + }; + + #[cfg(not(feature = "documentation"))] + let info_generator = { + quote! { + #bevy_reflect_path::TupleStructInfo::new::(#string_name, &fields) + } + }; + let typed_impl = impl_typed( struct_name, reflect_struct.meta().generics(), quote! { - let fields = [ - #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents),)* - ]; - let info = #bevy_reflect_path::TupleStructInfo::new::(#string_name, &fields); + let fields = [#field_generator]; + let info = #info_generator; #bevy_reflect_path::TypeInfo::TupleStruct(info) }, bevy_reflect_path, diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index 5de5991795..91f230876d 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -12,11 +12,19 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { let partial_eq_fn = meta.traits().get_partial_eq_impl(bevy_reflect_path); let debug_fn = meta.traits().get_debug_impl(); + #[cfg(feature = "documentation")] + let with_docs = { + let doc = quote::ToTokens::to_token_stream(meta.doc()); + Some(quote!(.with_docs(#doc))) + }; + #[cfg(not(feature = "documentation"))] + let with_docs: Option = None; + let typed_impl = impl_typed( type_name, meta.generics(), quote! { - let info = #bevy_reflect_path::ValueInfo::new::(); + let info = #bevy_reflect_path::ValueInfo::new::() #with_docs; #bevy_reflect_path::TypeInfo::Value(info) }, bevy_reflect_path, diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index d7a7515683..33d9ce3b75 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -16,6 +16,8 @@ extern crate proc_macro; mod container_attributes; mod derive_data; +#[cfg(feature = "documentation")] +mod documentation; mod enum_utility; mod field_attributes; mod from_reflect; @@ -95,11 +97,16 @@ pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro] pub fn impl_reflect_value(input: TokenStream) -> TokenStream { let def = parse_macro_input!(input as ReflectValueDef); - impls::impl_value(&ReflectMeta::new( + let meta = ReflectMeta::new( &def.type_name, &def.generics, def.traits.unwrap_or_default(), - )) + ); + + #[cfg(feature = "documentation")] + let meta = meta.with_docs(documentation::Documentation::from_attributes(&def.attrs)); + + impls::impl_value(&meta) } /// A replacement for `#[derive(Reflect)]` to be used with foreign types which diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs index ec54b99a6f..0e71413758 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_value.rs @@ -2,7 +2,7 @@ use crate::container_attributes::ReflectTraits; use proc_macro2::Ident; use syn::parse::{Parse, ParseStream}; use syn::token::{Paren, Where}; -use syn::{parenthesized, Generics}; +use syn::{parenthesized, Attribute, Generics}; /// A struct used to define a simple reflected value type (such as primitives). /// @@ -19,6 +19,8 @@ use syn::{parenthesized, Generics}; /// foo where T1: Bar (TraitA, TraitB) /// ``` pub(crate) struct ReflectValueDef { + #[allow(dead_code)] + pub attrs: Vec, pub type_name: Ident, pub generics: Generics, pub traits: Option, @@ -26,6 +28,7 @@ pub(crate) struct ReflectValueDef { impl Parse for ReflectValueDef { fn parse(input: ParseStream) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; let type_ident = input.parse::()?; let generics = input.parse::()?; let mut lookahead = input.lookahead1(); @@ -43,6 +46,7 @@ impl Parse for ReflectValueDef { } Ok(ReflectValueDef { + attrs, type_name: type_ident, generics: Generics { where_clause, diff --git a/crates/bevy_reflect/examples/reflect_docs.rs b/crates/bevy_reflect/examples/reflect_docs.rs new file mode 100644 index 0000000000..62a3882f97 --- /dev/null +++ b/crates/bevy_reflect/examples/reflect_docs.rs @@ -0,0 +1,50 @@ +//! This example illustrates how you can reflect doc comments. +//! +//! There may be cases where you may want to collect a reflected item's documentation. +//! For example, you may want to generate schemas or other external documentation for scripting. +//! Or perhaps you want your custom editor to display tooltips for certain properties that match the documentation. +//! +//! These scenarios can readily be achieved by using `bevy_reflect` with the `documentation` feature. + +use bevy_reflect::{Reflect, TypeInfo, Typed}; + +fn main() { + //! This function will simply demonstrate how you can access a type's documentation. + //! + //! Please note that the code below uses a standard struct with named fields; however, this isn't + //! exclusive to them. It can work for all kinds of data types including tuple structs and enums too! + + /// The struct that defines our player. + /// + /// # Example + /// + /// ``` + /// let player = Player::new("Urist McPlayer"); + /// ``` + #[derive(Reflect)] + struct Player { + /// The player's name. + name: String, + /// The player's current health points. + hp: u8, + // Some regular comment (i.e. not a doc comment) + max_hp: u8, + } + + // Using `TypeInfo` we can access all of the doc comments on the `Player` struct above: + let player_info = ::type_info(); + + // From here, we already have access to the struct's docs: + let player_docs = player_info.docs().unwrap(); + assert_eq!(" The struct that defines our player.\n\n # Example\n\n ```\n let player = Player::new(\"Urist McPlayer\");\n ```", player_docs); + println!("=====[ Player ]=====\n{}", player_docs); + + // We can then iterate through our struct's fields to get their documentation as well: + if let TypeInfo::Struct(struct_info) = player_info { + for field in struct_info.iter() { + let field_name = field.name(); + let field_docs = field.docs().unwrap_or(""); + println!("-----[ Player::{} ]-----\n{}", field_name, field_docs); + } + } +} diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index e8bdff2ad3..6571fdf337 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -49,6 +49,8 @@ pub struct ArrayInfo { item_type_name: &'static str, item_type_id: TypeId, capacity: usize, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl ArrayInfo { @@ -65,9 +67,17 @@ impl ArrayInfo { item_type_name: std::any::type_name::(), item_type_id: TypeId::of::(), capacity, + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this array. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The compile-time capacity of the array. pub fn capacity(&self) -> usize { self.capacity @@ -106,6 +116,12 @@ impl ArrayInfo { pub fn item_is(&self) -> bool { TypeId::of::() == self.item_type_id } + + /// The docstring of this array, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// A fixed-size list of reflected values. diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index ab3fd03dd0..799f44d4be 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -137,6 +137,8 @@ pub struct EnumInfo { type_id: TypeId, variants: Box<[VariantInfo]>, variant_indices: HashMap<&'static str, usize>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl EnumInfo { @@ -160,9 +162,17 @@ impl EnumInfo { type_id: TypeId::of::(), variants: variants.to_vec().into_boxed_slice(), variant_indices, + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this enum. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// Get a variant with the given name. pub fn variant(&self, name: &str) -> Option<&VariantInfo> { self.variant_indices @@ -227,6 +237,12 @@ impl EnumInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this enum, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// An iterator over the fields in the current enum variant. diff --git a/crates/bevy_reflect/src/enums/variants.rs b/crates/bevy_reflect/src/enums/variants.rs index d00c0a9c49..a67e9b4d62 100644 --- a/crates/bevy_reflect/src/enums/variants.rs +++ b/crates/bevy_reflect/src/enums/variants.rs @@ -72,6 +72,16 @@ impl VariantInfo { Self::Unit(info) => info.name(), } } + + /// The docstring of the underlying variant, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&str> { + match self { + Self::Struct(info) => info.docs(), + Self::Tuple(info) => info.docs(), + Self::Unit(info) => info.docs(), + } + } } /// Type info for struct variants. @@ -80,6 +90,8 @@ pub struct StructVariantInfo { name: &'static str, fields: Box<[NamedField]>, field_indices: HashMap<&'static str, usize>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl StructVariantInfo { @@ -90,9 +102,17 @@ impl StructVariantInfo { name, fields: fields.to_vec().into_boxed_slice(), field_indices, + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this variant. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The name of this variant. pub fn name(&self) -> &'static str { self.name @@ -132,6 +152,12 @@ impl StructVariantInfo { .map(|(index, field)| (field.name(), index)) .collect() } + + /// The docstring of this variant, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// Type info for tuple variants. @@ -139,6 +165,8 @@ impl StructVariantInfo { pub struct TupleVariantInfo { name: &'static str, fields: Box<[UnnamedField]>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl TupleVariantInfo { @@ -147,9 +175,17 @@ impl TupleVariantInfo { Self { name, fields: fields.to_vec().into_boxed_slice(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this variant. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The name of this variant. pub fn name(&self) -> &'static str { self.name @@ -169,22 +205,46 @@ impl TupleVariantInfo { pub fn field_len(&self) -> usize { self.fields.len() } + + /// The docstring of this variant, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// Type info for unit variants. #[derive(Clone, Debug)] pub struct UnitVariantInfo { name: &'static str, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl UnitVariantInfo { /// Create a new [`UnitVariantInfo`]. pub fn new(name: &'static str) -> Self { - Self { name } + Self { + name, + #[cfg(feature = "documentation")] + docs: None, + } + } + + /// Sets the docstring for this variant. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } } /// The name of this variant. pub fn name(&self) -> &'static str { self.name } + + /// The docstring of this variant, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index f70e5c4c68..62c5c332ad 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -7,6 +7,8 @@ pub struct NamedField { name: &'static str, type_name: &'static str, type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl NamedField { @@ -16,9 +18,17 @@ impl NamedField { name, type_name: std::any::type_name::(), type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this field. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The name of the field. pub fn name(&self) -> &'static str { self.name @@ -40,6 +50,12 @@ impl NamedField { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this field, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// The unnamed field of a reflected tuple or tuple struct. @@ -48,6 +64,8 @@ pub struct UnnamedField { index: usize, type_name: &'static str, type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl UnnamedField { @@ -56,9 +74,17 @@ impl UnnamedField { index, type_name: std::any::type_name::(), type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this field. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// Returns the index of the field. pub fn index(&self) -> usize { self.index @@ -80,4 +106,10 @@ impl UnnamedField { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this field, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index fb47aeb6c6..4c09c17332 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -835,6 +835,160 @@ mod tests { assert!(info.is::()); } + #[cfg(feature = "documentation")] + mod docstrings { + use super::*; + + #[test] + fn should_not_contain_docs() { + // Regular comments do not count as doc comments, + // and are therefore not reflected. + #[derive(Reflect)] + struct SomeStruct; + + let info = ::type_info(); + assert_eq!(None, info.docs()); + + /* + * Block comments do not count as doc comments, + * and are therefore not reflected. + */ + #[derive(Reflect)] + struct SomeOtherStruct; + + let info = ::type_info(); + assert_eq!(None, info.docs()); + } + + #[test] + fn should_contain_docs() { + /// Some struct. + /// + /// # Example + /// + /// ```ignore + /// let some_struct = SomeStruct; + /// ``` + #[derive(Reflect)] + struct SomeStruct; + + let info = ::type_info(); + assert_eq!( + Some(" Some struct.\n\n # Example\n\n ```ignore\n let some_struct = SomeStruct;\n ```"), + info.docs() + ); + + #[doc = "The compiler automatically converts `///`-style comments into `#[doc]` attributes."] + #[doc = "Of course, you _could_ use the attribute directly if you wanted to."] + #[doc = "Both will be reflected."] + #[derive(Reflect)] + struct SomeOtherStruct; + + let info = ::type_info(); + assert_eq!( + Some("The compiler automatically converts `///`-style comments into `#[doc]` attributes.\nOf course, you _could_ use the attribute directly if you wanted to.\nBoth will be reflected."), + info.docs() + ); + + /// Some tuple struct. + #[derive(Reflect)] + struct SomeTupleStruct(usize); + + let info = ::type_info(); + assert_eq!(Some(" Some tuple struct."), info.docs()); + + /// Some enum. + #[derive(Reflect)] + enum SomeEnum { + Foo, + } + + let info = ::type_info(); + assert_eq!(Some(" Some enum."), info.docs()); + + #[derive(Clone)] + struct SomePrimitive; + impl_reflect_value!( + /// Some primitive for which we have attributed custom documentation. + SomePrimitive + ); + + let info = ::type_info(); + assert_eq!( + Some(" Some primitive for which we have attributed custom documentation."), + info.docs() + ); + } + + #[test] + fn fields_should_contain_docs() { + #[derive(Reflect)] + struct SomeStruct { + /// The name + name: String, + /// The index + index: usize, + // Not documented... + data: Vec, + } + + let info = ::type_info(); + if let TypeInfo::Struct(info) = info { + let mut fields = info.iter(); + assert_eq!(Some(" The name"), fields.next().unwrap().docs()); + assert_eq!(Some(" The index"), fields.next().unwrap().docs()); + assert_eq!(None, fields.next().unwrap().docs()); + } else { + panic!("expected struct info"); + } + } + + #[test] + fn variants_should_contain_docs() { + #[derive(Reflect)] + enum SomeEnum { + // Not documented... + Nothing, + /// Option A + A( + /// Index + usize, + ), + /// Option B + B { + /// Name + name: String, + }, + } + + let info = ::type_info(); + if let TypeInfo::Enum(info) = info { + let mut variants = info.iter(); + assert_eq!(None, variants.next().unwrap().docs()); + + let variant = variants.next().unwrap(); + assert_eq!(Some(" Option A"), variant.docs()); + if let VariantInfo::Tuple(variant) = variant { + let field = variant.field_at(0).unwrap(); + assert_eq!(Some(" Index"), field.docs()); + } else { + panic!("expected tuple variant") + } + + let variant = variants.next().unwrap(); + assert_eq!(Some(" Option B"), variant.docs()); + if let VariantInfo::Struct(variant) = variant { + let field = variant.field_at(0).unwrap(); + assert_eq!(Some(" Name"), field.docs()); + } else { + panic!("expected struct variant") + } + } else { + panic!("expected enum info"); + } + } + } + #[test] fn as_reflect() { trait TestTrait: Reflect {} diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index b832e80c37..f98ac0f4be 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -34,6 +34,8 @@ pub struct ListInfo { type_id: TypeId, item_type_name: &'static str, item_type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl ListInfo { @@ -44,9 +46,17 @@ impl ListInfo { type_id: TypeId::of::(), item_type_name: std::any::type_name::(), item_type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this list. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The [type name] of the list. /// /// [type name]: std::any::type_name @@ -80,6 +90,12 @@ impl ListInfo { pub fn item_is(&self) -> bool { TypeId::of::() == self.item_type_id } + + /// The docstring of this list, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// A list of reflected values. diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 9bdeea87f6..c2b89d2bee 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -68,6 +68,8 @@ pub struct MapInfo { key_type_id: TypeId, value_type_name: &'static str, value_type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl MapInfo { @@ -80,9 +82,17 @@ impl MapInfo { key_type_id: TypeId::of::(), value_type_name: std::any::type_name::(), value_type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this map. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The [type name] of the map. /// /// [type name]: std::any::type_name @@ -133,6 +143,12 @@ impl MapInfo { pub fn value_is(&self) -> bool { TypeId::of::() == self.value_type_id } + + /// The docstring of this map, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } const HASH_ERROR: &str = "the given key does not support hashing"; diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index f246b7128c..78002085a6 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -74,6 +74,8 @@ pub struct StructInfo { type_id: TypeId, fields: Box<[NamedField]>, field_indices: HashMap<&'static str, usize>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl StructInfo { @@ -97,9 +99,17 @@ impl StructInfo { type_id: TypeId::of::(), fields: fields.to_vec().into_boxed_slice(), field_indices, + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this struct. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// Get the field with the given name. pub fn field(&self, name: &str) -> Option<&NamedField> { self.field_indices @@ -152,6 +162,12 @@ impl StructInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this struct, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// An iterator over the field values of a struct. diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index b31b5f78e8..785dab118b 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -134,6 +134,8 @@ pub struct TupleInfo { type_name: &'static str, type_id: TypeId, fields: Box<[UnnamedField]>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl TupleInfo { @@ -148,9 +150,17 @@ impl TupleInfo { type_name: std::any::type_name::(), type_id: TypeId::of::(), fields: fields.to_vec().into_boxed_slice(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this tuple. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// Get the field at the given index. pub fn field_at(&self, index: usize) -> Option<&UnnamedField> { self.fields.get(index) @@ -182,6 +192,12 @@ impl TupleInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this tuple, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// A tuple which allows fields to be added at runtime. diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index e7bb89cebb..d27d4934fc 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -53,6 +53,8 @@ pub struct TupleStructInfo { type_name: &'static str, type_id: TypeId, fields: Box<[UnnamedField]>, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl TupleStructInfo { @@ -69,9 +71,17 @@ impl TupleStructInfo { type_name: std::any::type_name::(), type_id: TypeId::of::(), fields: fields.to_vec().into_boxed_slice(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this struct. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// Get the field at the given index. pub fn field_at(&self, index: usize) -> Option<&UnnamedField> { self.fields.get(index) @@ -112,6 +122,12 @@ impl TupleStructInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this struct, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// An iterator over the field values of a tuple struct. diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 9e5519d500..37fe9f9cbe 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -146,6 +146,22 @@ impl TypeInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id() } + + /// The docstring of the underlying type, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&str> { + match self { + Self::Struct(info) => info.docs(), + Self::TupleStruct(info) => info.docs(), + Self::Tuple(info) => info.docs(), + Self::List(info) => info.docs(), + Self::Array(info) => info.docs(), + Self::Map(info) => info.docs(), + Self::Enum(info) => info.docs(), + Self::Value(info) => info.docs(), + Self::Dynamic(info) => info.docs(), + } + } } /// A container for compile-time info related to general value types, including primitives. @@ -160,6 +176,8 @@ impl TypeInfo { pub struct ValueInfo { type_name: &'static str, type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl ValueInfo { @@ -167,9 +185,17 @@ impl ValueInfo { Self { type_name: std::any::type_name::(), type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this value. + #[cfg(feature = "documentation")] + pub fn with_docs(self, doc: Option<&'static str>) -> Self { + Self { docs: doc, ..self } + } + /// The [type name] of the value. /// /// [type name]: std::any::type_name @@ -186,6 +212,12 @@ impl ValueInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this dynamic value, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } } /// A container for compile-time info related to Bevy's _dynamic_ types, including primitives. @@ -200,6 +232,8 @@ impl ValueInfo { pub struct DynamicInfo { type_name: &'static str, type_id: TypeId, + #[cfg(feature = "documentation")] + docs: Option<&'static str>, } impl DynamicInfo { @@ -207,9 +241,17 @@ impl DynamicInfo { Self { type_name: std::any::type_name::(), type_id: TypeId::of::(), + #[cfg(feature = "documentation")] + docs: None, } } + /// Sets the docstring for this dynamic value. + #[cfg(feature = "documentation")] + pub fn with_docs(self, docs: Option<&'static str>) -> Self { + Self { docs, ..self } + } + /// The [type name] of the dynamic value. /// /// [type name]: std::any::type_name @@ -226,4 +268,10 @@ impl DynamicInfo { pub fn is(&self) -> bool { TypeId::of::() == self.type_id } + + /// The docstring of this value, if any. + #[cfg(feature = "documentation")] + pub fn docs(&self) -> Option<&'static str> { + self.docs + } }