Skip to content

Commit

Permalink
Merge pull request #847 from ltabis/main
Browse files Browse the repository at this point in the history
Documentation generation for custom types
  • Loading branch information
schungx committed Mar 25, 2024
2 parents 4ea9b74 + 208f4cb commit 6619595
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 37 deletions.
58 changes: 53 additions & 5 deletions codegen/src/custom_type.rs
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
@@ -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
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

0 comments on commit 6619595

Please sign in to comment.