Skip to content

Commit

Permalink
Merge pull request #867 from Michael-F-Bryan/typecheck-model-signatures
Browse files Browse the repository at this point in the history
Add typechecking to model signatures
  • Loading branch information
hannobraun authored Jul 24, 2022
2 parents 8cb928b + 3601d60 commit d2a7c0b
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 18 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/fj-proc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ optional = true
[dependencies.syn]
version = "1.0.98"
features = ["full", "extra-traits"]

[dev-dependencies]
fj = { path = "../fj" }
114 changes: 96 additions & 18 deletions crates/fj-proc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,77 @@ 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);
let inputs = item.clone().sig.inputs;
let inputs = &item.sig.inputs;

let args: Vec<Argument> =
inputs.iter().map(|inp| parse_quote!(#inp)).collect();
Expand All @@ -16,10 +83,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! {
Expand Down Expand Up @@ -59,7 +125,6 @@ pub fn model(_: TokenStream, input: TokenStream) -> TokenStream {
});
}
}
let block = item.block;

let function_boilerplate = quote! {
#[no_mangle]
Expand All @@ -68,19 +133,32 @@ 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 {
#(
#parameter_extraction
)*
#(
#min_checks
)*
#(
#max_checks
)*
#block
}
#function_boilerplate {
#(
#parameter_extraction
)*
#(
#min_checks
)*
#(
#max_checks
)*

fn #function_name(
#( #arg_names : #arg_types ),*
) #return_type {
#body
}

#function_name(#( #arg_names),*).into()
}
}
.into()
}
Expand Down

0 comments on commit d2a7c0b

Please sign in to comment.