From a5cad080171d40ff99e47dbe7fa87020fe65c158 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 19 Dec 2023 22:30:35 -0600 Subject: [PATCH] Derive macro can handle generics and where clauses now --- Cargo.lock | 1 + execution-plan-macros/src/lib.rs | 16 +++++-- execution-plan-macros/tests/main.rs | 19 ++++++--- modeling-cmds/Cargo.toml | 1 + modeling-cmds/src/kcep_value.rs | 65 +---------------------------- modeling-cmds/src/shared.rs | 21 +++++----- 6 files changed, 40 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cb25799..bfb1e71a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1564,6 +1564,7 @@ dependencies = [ "enum-iterator-derive", "euler", "http 0.2.11", + "kittycad-execution-plan-macros", "kittycad-execution-plan-traits", "kittycad-unit-conversion-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "measurements", diff --git a/execution-plan-macros/src/lib.rs b/execution-plan-macros/src/lib.rs index 9bf622fe..288bd996 100644 --- a/execution-plan-macros/src/lib.rs +++ b/execution-plan-macros/src/lib.rs @@ -5,7 +5,7 @@ use proc_macro2::Span; use quote::{quote, quote_spanned}; use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Fields}; -#[proc_macro_derive(MyMacro)] +#[proc_macro_derive(ExecutionPlanValue)] pub fn impl_value(input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let input = parse_macro_input!(input as DeriveInput); @@ -16,7 +16,7 @@ pub fn impl_value(input: TokenStream) -> TokenStream { // Build the output, possibly using quasi-quotation let expanded = match input.data { - syn::Data::Struct(data) => impl_value_on_struct(span, name, data), + syn::Data::Struct(data) => impl_value_on_struct(span, name, data, input.generics), syn::Data::Enum(_) => todo!(), syn::Data::Union(_) => quote_spanned! {span => compile_error!("Value cannot be implemented on a union type") @@ -27,7 +27,12 @@ pub fn impl_value(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -fn impl_value_on_struct(span: Span, name: proc_macro2::Ident, data: syn::DataStruct) -> proc_macro2::TokenStream { +fn impl_value_on_struct( + span: Span, + name: proc_macro2::Ident, + data: syn::DataStruct, + generics: syn::Generics, +) -> proc_macro2::TokenStream { let Fields::Named(ref fields) = data.fields else { return quote_spanned! {span => compile_error!("Value cannot be implemented on a struct with unnamed fields") @@ -46,8 +51,11 @@ fn impl_value_on_struct(span: Span, name: proc_macro2::Ident, data: syn::DataStr #instantiate_each_field } } + let where_clause = &generics.where_clause; quote! { - impl kittycad_execution_plan_traits::Value for #name { + impl #generics kittycad_execution_plan_traits::Value for #name #generics + #where_clause + { fn into_parts(self) -> Vec { let mut parts = Vec::new(); #extend_per_field diff --git a/execution-plan-macros/tests/main.rs b/execution-plan-macros/tests/main.rs index ab65556a..795a57fd 100644 --- a/execution-plan-macros/tests/main.rs +++ b/execution-plan-macros/tests/main.rs @@ -1,10 +1,19 @@ -use kittycad_execution_plan_macros::MyMacro; +use kittycad_execution_plan_macros::ExecutionPlanValue; +use kittycad_execution_plan_traits::{Primitive, Value}; -#[derive(MyMacro)] -struct Foo { +#[derive(ExecutionPlanValue)] +struct FooGeneric +where + Primitive: From, + T: Value, +{ f: f64, i: usize, + t: T, } -#[test] -fn check_impl() {} +#[derive(ExecutionPlanValue)] +struct FooConcrete { + f: f64, + i: usize, +} diff --git a/modeling-cmds/Cargo.toml b/modeling-cmds/Cargo.toml index 4f1af8c8..44f90dd7 100644 --- a/modeling-cmds/Cargo.toml +++ b/modeling-cmds/Cargo.toml @@ -22,6 +22,7 @@ enum-iterator-derive = "1.2.1" euler = "0.4.1" http = "0.2.9" kittycad-execution-plan-traits = { path = "../execution-plan-traits" } +kittycad-execution-plan-macros = { path = "../execution-plan-macros" } kittycad-unit-conversion-derive = "0.1.0" measurements = "0.11.0" parse-display = "0.8.2" diff --git a/modeling-cmds/src/kcep_value.rs b/modeling-cmds/src/kcep_value.rs index 859ab8ec..6144dad5 100644 --- a/modeling-cmds/src/kcep_value.rs +++ b/modeling-cmds/src/kcep_value.rs @@ -3,72 +3,9 @@ use kittycad_execution_plan_traits::{MemoryError, Primitive, Value}; use crate::{ ok_response::OkModelingCmdResponse, output, - shared::{Angle, Color, ExportFile, PathSegment, Point2d, Point3d, Point4d}, + shared::{Angle, PathSegment, Point2d, Point3d}, }; -/// Macro to generate the methods of trait `Value` for the given fields. -/// Args: -/// `$field`: Repeated 0 or more times. Listing of each field in the struct. -/// The order in which these fields are given determines the order that fields are -/// written to and read from memory. -macro_rules! impl_value_on_struct_fields { - ($($field:ident),*) => { - fn into_parts(self) -> Vec { - let mut parts = Vec::new(); - $( - parts.extend(self.$field.into_parts()); - )* - parts - } - - fn from_parts(values: &mut I) -> Result - where - I: Iterator>, - { - $( - let $field = Value::from_parts(values)?; - )* - Ok(Self { - $( - $field, - )* - }) - } - }; -} - -impl Value for Point2d -where - Primitive: From, - T: Value, -{ - impl_value_on_struct_fields!(x, y); -} - -impl Value for Point3d -where - Primitive: From, - T: Value, -{ - impl_value_on_struct_fields!(x, y, z); -} - -impl Value for Point4d -where - Primitive: From, - T: Value, -{ - impl_value_on_struct_fields!(x, y, z, w); -} - -impl Value for Color { - impl_value_on_struct_fields!(r, g, b, a); -} - -impl Value for ExportFile { - impl_value_on_struct_fields!(name, contents); -} - pub(crate) const EMPTY: &str = "EMPTY"; pub(crate) const TAKE_SNAPSHOT: &str = "TAKE_SNAPSHOT"; pub(crate) const ARC: &str = "arc"; diff --git a/modeling-cmds/src/shared.rs b/modeling-cmds/src/shared.rs index 3ceabea6..4630d568 100644 --- a/modeling-cmds/src/shared.rs +++ b/modeling-cmds/src/shared.rs @@ -6,6 +6,7 @@ use diesel::{mysql::Mysql, serialize::ToSql, sql_types::Text}; #[cfg(feature = "diesel")] use diesel_derives::{AsExpression, FromSqlRow}; use enum_iterator::Sequence; +use kittycad_execution_plan_macros::ExecutionPlanValue; use parse_display_derive::{Display, FromStr}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -50,7 +51,7 @@ pub struct AnnotationOptions { /// Color to render the annotation pub color: Option, /// Position to put the annotation - pub position: Option, + pub position: Option>, } /// Options for annotation text @@ -93,7 +94,7 @@ pub enum DistanceType { } /// An RGBA color -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ExecutionPlanValue)] pub struct Color { /// Red pub r: f32, @@ -138,10 +139,10 @@ pub enum AnnotationTextAlignmentY { impl_string_enum_sql! {AnnotationTextAlignmentY} /// A point in 3D space -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default, ExecutionPlanValue)] #[serde(rename = "Point3d")] #[serde(rename_all = "snake_case")] -pub struct Point3d { +pub struct Point3d { #[allow(missing_docs)] pub x: T, #[allow(missing_docs)] @@ -256,10 +257,10 @@ pub enum PathSegment { } /// A point in homogeneous (4D) space -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, ExecutionPlanValue)] #[serde(rename = "Point4d")] #[serde(rename_all = "snake_case")] -pub struct Point4d { +pub struct Point4d { #[allow(missing_docs)] pub x: T, #[allow(missing_docs)] @@ -270,17 +271,17 @@ pub struct Point4d { pub w: T, } -impl From for Point3d { +impl From for Point3d { fn from(v: euler::Vec3) -> Self { Self { x: v.x, y: v.y, z: v.z } } } /// A point in 2D space -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default, ExecutionPlanValue)] #[serde(rename = "Point2d")] #[serde(rename_all = "snake_case")] -pub struct Point2d { +pub struct Point2d { #[allow(missing_docs)] pub x: T, #[allow(missing_docs)] @@ -472,7 +473,7 @@ pub enum CurveType { } /// A file to be exported to the client. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, JsonSchema, ExecutionPlanValue)] pub struct ExportFile { /// The name of the file. pub name: String,