Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation generation for custom types #847

Merged
merged 18 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions codegen/src/custom_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,30 @@ pub fn derive_custom_type_impl(input: DeriveInput) -> TokenStream {
}
};

let register = {
let method = {
quote! { builder.with_name(#display_name) }
};

#[cfg(feature = "metadata")]
{
let Ok(docs) = crate::attrs::doc_attributes(&input.attrs) else {
return syn::Error::new(Span::call_site(), "failed to parse doc comments")
.into_compile_error();
};
// Not sure how to make a Vec<String> a literal, using a string instead.
let docs = proc_macro2::Literal::string(&docs.join("\n"));
quote! { #method.with_comments(&#docs.lines().collect::<Vec<_>>()[..]); }
}
#[cfg(not(feature = "metadata"))]
quote! { #method; }
};

quote! {
impl CustomType for #type_name {
fn build(mut builder: TypeBuilder<Self>) {
#(#errors)*
builder.with_name(#display_name);
#register
#(#field_accessors)*
#(#extras(&mut builder);)*
}
Expand Down Expand Up @@ -220,6 +239,7 @@ fn scan_fields(fields: &[&Field], accessors: &mut Vec<TokenStream>, errors: &mut
errors.push(syn::Error::new(path.span(), msg).into_compile_error());
continue;
}

// Error
_ => {
errors.push(
Expand Down Expand Up @@ -264,10 +284,38 @@ fn scan_fields(fields: &[&Field], accessors: &mut Vec<TokenStream>, errors: &mut
let set = set_fn.unwrap_or_else(|| quote! { |obj: &mut Self, val| obj.#field_name = val });
let name = map_name.unwrap_or_else(|| quote! { stringify!(#field_name) });

accessors.push(if readonly {
quote! { builder.with_get(#name, #get); }
} else {
quote! { builder.with_get_set(#name, #get, #set); }
accessors.push({
let method = if readonly {
quote! { builder.with_get(#name, #get) }
} else {
quote! { builder.with_get_set(#name, #get, #set) }
};

#[cfg(feature = "metadata")]
{
match crate::attrs::doc_attributes(&field.attrs) {
Ok(docs) => {
// Not sure how to make a Vec<String> a literal, using a string instead.
let docs = proc_macro2::Literal::string(&docs.join("\n"));
quote! { #method.and_comments(&#docs.lines().collect::<Vec<_>>()[..]); }
}
Err(_) => {
errors.push(
syn::Error::new(
Span::call_site(),
format!(
"failed to parse doc comments for field {}",
quote! { #name }
),
)
.into_compile_error(),
);
continue;
}
}
}
#[cfg(not(feature = "metadata"))]
quote! { #method; }
});
}
}
97 changes: 97 additions & 0 deletions codegen/src/test/custom_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[cfg(not(feature = "metadata"))]
#[cfg(test)]
mod custom_type_tests {
use crate::test::assert_streams_eq;
Expand Down Expand Up @@ -85,3 +86,99 @@ mod custom_type_tests {
assert_streams_eq(result, expected);
}
}

#[cfg(feature = "metadata")]
#[cfg(test)]
mod custom_type_tests {
use crate::test::assert_streams_eq;
use quote::quote;

#[test]
fn test_custom_type_tuple_struct() {
let input = quote! {
/// Bar comments.
#[derive(Clone, CustomType)]
pub struct Bar(
#[rhai_type(skip)]
#[cfg(not(feature = "no_float"))]
rhai::FLOAT,
INT,
/// boo comments.
#[rhai_type(name = "boo", readonly)]
String,
/// This is a vector.
Vec<INT>
);
};

let result = crate::custom_type::derive_custom_type_impl(
syn::parse2::<syn::DeriveInput>(input).unwrap(),
);

let expected = quote! {
impl CustomType for Bar {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name(stringify!(Bar)).with_comments(&"/// Bar comments.".lines().collect::<Vec<_>>()[..]);
builder.with_get_set("field1",
|obj: &mut Self| obj.1.clone(),
|obj: &mut Self, val| obj.1 = val
).and_comments(&"".lines().collect::<Vec<_>>()[..]);
builder.with_get("boo", |obj: &mut Self| obj.2.clone())
.and_comments(&"/// boo comments.".lines().collect::<Vec<_>>()[..]);
builder.with_get_set("field3",
|obj: &mut Self| obj.3.clone(),
|obj: &mut Self, val| obj.3 = val
).and_comments(&"/// This is a vector.".lines().collect::<Vec<_>>()[..]);
}
}
};

assert_streams_eq(result, expected);
}

#[test]
fn test_custom_type_struct() {
let input = quote! {
/// Foo comments.
#[derive(CustomType)]
#[rhai_type(skip, name = "MyFoo", extra = Self::build_extra)]
pub struct Foo {
#[cfg(not(feature = "no_float"))]
#[rhai_type(skip)]
_dummy: rhai::FLOAT,
#[rhai_type(get = get_bar)]
pub bar: INT,
/// boo comments.
#[rhai_type(name = "boo", readonly)]
pub(crate) baz: String,
#[rhai_type(set = Self::set_qux)]
pub qux: Vec<INT>
}
};

let result = crate::custom_type::derive_custom_type_impl(
syn::parse2::<syn::DeriveInput>(input).unwrap(),
);

let expected = quote! {
impl CustomType for Foo {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("MyFoo").with_comments(&"/// Foo comments.".lines().collect::<Vec<_>>()[..]);
builder.with_get_set(stringify!(bar),
|obj: &mut Self| get_bar(&*obj),
|obj: &mut Self, val| obj.bar = val
).and_comments(&"".lines().collect::<Vec<_>>()[..]);
builder.with_get("boo", |obj: &mut Self| obj.baz.clone())
.and_comments(&"/// boo comments.".lines().collect::<Vec<_>>()[..]);
builder.with_get_set(stringify!(qux),
|obj: &mut Self| obj.qux.clone(),
Self::set_qux
).and_comments(&"".lines().collect::<Vec<_>>()[..]);
Self::build_extra(&mut builder);
}
}
};

assert_streams_eq(result, expected);
}
}
49 changes: 49 additions & 0 deletions examples/custom_types_and_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ use rhai::{CustomType, Engine, EvalAltResult, TypeBuilder};

#[cfg(not(feature = "no_object"))]
fn main() -> Result<(), Box<EvalAltResult>> {
/// This is a test structure. If the metadata feature
/// is enabled, this comment will be exported.
#[derive(Debug, Clone, CustomType)]
#[rhai_type(extra = Self::build_extra)]
struct TestStruct {
/// A number.
///
/// ```js
/// let t = new_ts();
/// print(t.x); // Get the value of x.
/// t.x = 42; // Set the value of x.
/// ```
x: i64,
}

Expand Down Expand Up @@ -48,6 +57,46 @@ fn main() -> Result<(), Box<EvalAltResult>> {
.for_each(|func| println!("{func}"));

println!();

let docs: serde_json::Value =
serde_json::from_str(&engine.gen_fn_metadata_to_json(false).unwrap()).unwrap();

// compare comments from the type.
assert_eq!(
docs["customTypes"][0]["docComments"],
serde_json::json!([
"/// This is a test structure. If the metadata feature",
"/// is enabled, this comment will be exported."
])
);

// compare comments from the getter.
assert_eq!(
docs["functions"][1]["docComments"],
serde_json::json!([
"/// A number.",
"///",
"/// ```js",
"/// let t = new_ts();",
"/// print(t.x); // Get the value of x.",
"/// t.x = 42; // Set the value of x.",
"/// ```"
])
);

// compare comments from the setter.
assert_eq!(
docs["functions"][3]["docComments"],
serde_json::json!([
"/// A number.",
"///",
"/// ```js",
"/// let t = new_ts();",
"/// print(t.x); // Get the value of x.",
"/// t.x = 42; // Set the value of x.",
"/// ```"
])
);
}

let result = engine.eval::<i64>(
Expand Down
Loading
Loading