From f0b8f995238572dd7603a11326b23f1791a0d63e Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Sat, 23 Jul 2022 21:28:14 +0800 Subject: [PATCH 1/2] Assert that models have the right signature at compile time --- crates/fj-proc/src/lib.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/fj-proc/src/lib.rs b/crates/fj-proc/src/lib.rs index e398d8563..0598d7bd5 100644 --- a/crates/fj-proc/src/lib.rs +++ b/crates/fj-proc/src/lib.rs @@ -7,7 +7,7 @@ use syn::{ #[proc_macro_attribute] pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as syn::ItemFn); - let inputs = item.clone().sig.inputs; + let inputs = &item.sig.inputs; let args: Vec = inputs.iter().map(|inp| parse_quote!(#inp)).collect(); @@ -16,10 +16,9 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { let mut min_checks = Vec::new(); let mut max_checks = Vec::new(); - for arg in args { - let ident = arg.ident; - let ty = arg.ty; - if let Some(attr) = arg.attr { + + for Argument { attr, ident, ty } in &args { + if let Some(attr) = attr { if let Some(default) = attr.get_default() { let def = default.val; parameter_extraction.push(quote! { @@ -59,7 +58,6 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { }); } } - let block = item.block; let function_boilerplate = quote! { #[no_mangle] @@ -68,6 +66,12 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { ) -> fj::Shape }; + let function_name = &item.sig.ident; + let body = &item.block; + let arg_names: Vec<_> = args.iter().map(|a| &a.ident).collect(); + let arg_types: Vec<_> = args.iter().map(|a| &a.ty).collect(); + let return_type = &item.sig.output; + quote! { #function_boilerplate { #( @@ -79,7 +83,14 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { #( #max_checks )* - #block + + fn #function_name( + #( #arg_names : #arg_types ),* + ) #return_type { + #body + } + + #function_name(#( #arg_names),*).into() } } .into() From 3601d6049acf394b0921ede0842a07f0964e1573 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Sat, 23 Jul 2022 21:28:49 +0800 Subject: [PATCH 2/2] Add documentation and examples to the #[model] attribute --- Cargo.lock | 1 + crates/fj-proc/Cargo.toml | 3 ++ crates/fj-proc/src/lib.rs | 89 ++++++++++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72a9179e9..46ce67ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -929,6 +929,7 @@ dependencies = [ name = "fj-proc" version = "0.8.0" dependencies = [ + "fj", "proc-macro2", "quote", "serde", diff --git a/crates/fj-proc/Cargo.toml b/crates/fj-proc/Cargo.toml index d186c07b7..3be56b509 100644 --- a/crates/fj-proc/Cargo.toml +++ b/crates/fj-proc/Cargo.toml @@ -26,3 +26,6 @@ optional = true [dependencies.syn] version = "1.0.98" features = ["full", "extra-traits"] + +[dev-dependencies] +fj = { path = "../fj" } diff --git a/crates/fj-proc/src/lib.rs b/crates/fj-proc/src/lib.rs index 0598d7bd5..7ad3179b3 100644 --- a/crates/fj-proc/src/lib.rs +++ b/crates/fj-proc/src/lib.rs @@ -4,6 +4,73 @@ use syn::{ bracketed, parenthesized, parse::Parse, parse_macro_input, parse_quote, }; +/// Define a function-based model. +/// +/// The simplest model function takes no parameters and returns a hard-coded +/// `fj::Shape`. +/// +/// ```rust +/// # use fj_proc::model; +/// use fj::{Circle, Sketch, Shape}; +/// #[model] +/// fn model() -> Shape { +/// let circle = Circle::from_radius(10.0); +/// Sketch::from_circle(circle).into() +/// } +/// ``` +/// +/// For convenience, you can also return anything that could be converted into +/// a `fj::Shape` (e.g. a `fj::Sketch`). +/// +/// ```rust +/// # use fj_proc::model; +/// use fj::{Circle, Sketch}; +/// #[model] +/// fn model() -> Sketch { +/// let circle = Circle::from_radius(10.0); +/// Sketch::from_circle(circle) +/// } +/// ``` +/// +/// The return type is checked at compile time. That means something like this +/// won't work because `()` can't be converted into a `fj::Shape`. +/// +/// ```rust,compile_fail +/// # use fj_proc::model; +/// #[model] +/// fn model() { todo!() } +/// ``` +/// +/// The model function's arguments can be anything that implement +/// [`std::str::FromStr`]. +/// +/// ```rust +/// # use fj_proc::model; +/// #[model] +/// fn cylinder(height: f64, label: String, is_horizontal: bool) -> fj::Shape { todo!() } +/// ``` +/// +/// Constraints and default values can be added to an argument using the +/// `#[param]` attribute. +/// +/// ```rust +/// use fj::syntax::*; +/// +/// #[fj::model] +/// pub fn spacer( +/// #[param(default = 1.0, min = inner * 1.01)] outer: f64, +/// #[param(default = 0.5, max = outer * 0.99)] inner: f64, +/// #[param(default = 1.0)] height: f64, +/// ) -> fj::Shape { +/// let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer)); +/// let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner)); +/// +/// let footprint = outer_edge.difference(&inner_edge); +/// let spacer = footprint.sweep([0., 0., height]); +/// +/// spacer.into() +/// } +/// ``` #[proc_macro_attribute] pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as syn::ItemFn); @@ -73,16 +140,16 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { let return_type = &item.sig.output; quote! { - #function_boilerplate { - #( - #parameter_extraction - )* - #( - #min_checks - )* - #( - #max_checks - )* + #function_boilerplate { + #( + #parameter_extraction + )* + #( + #min_checks + )* + #( + #max_checks + )* fn #function_name( #( #arg_names : #arg_types ),* @@ -91,7 +158,7 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { } #function_name(#( #arg_names),*).into() - } + } } .into() }