/*! module for the Rust code generator */ use crate::diagnostics::{CompilerDiagnostic, Diagnostics}; use crate::expression_tree::{Expression, NamedReference, OperatorClass, Path}; use crate::object_tree::{Component, ElementRc}; use crate::parser::Spanned; use crate::typeregister::Type; use proc_macro2::TokenStream; use quote::quote; use std::rc::Rc; fn rust_type( ty: &Type, span: &crate::diagnostics::Span, ) -> Result { match ty { Type::Int32 => Ok(quote!(i32)), Type::Float32 => Ok(quote!(f32)), Type::String => Ok(quote!(sixtyfps::re_exports::SharedString)), Type::Color => Ok(quote!(sixtyfps::re_exports::Color)), Type::Bool => Ok(quote!(bool)), Type::Object(o) => { let elem = o.values().map(|v| rust_type(v, span)).collect::, _>>()?; // This will produce a tuple Ok(quote!((#(#elem,)*))) } _ => Err(CompilerDiagnostic { message: "Cannot map property type to Rust".into(), span: span.clone(), }), } } /// Generate the rust code for the given component. /// /// Fill the diagnostic in case of error. pub fn generate(component: &Rc, diag: &mut Diagnostics) -> Option { let mut extra_components = vec![]; let mut declared_property_vars = vec![]; let mut declared_property_types = vec![]; let mut declared_signals = vec![]; let mut property_and_signal_accessors: Vec = vec![]; for (prop_name, property_decl) in component.root_element.borrow().property_declarations.iter() { let prop_ident = quote::format_ident!("{}", prop_name); if property_decl.property_type == Type::Signal { declared_signals.push(prop_ident.clone()); if property_decl.expose_in_public_api { let emitter_ident = quote::format_ident!("emit_{}", prop_name); property_and_signal_accessors.push( quote!( #[allow(dead_code)] fn #emitter_ident(self: ::core::pin::Pin<&Self>) { let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component( sixtyfps::re_exports::ComponentRef::new_pin(self) ); Self::field_offsets().#prop_ident.apply_pin(self).emit(&eval_context, ()) } ) .into(), ); } } else { declared_property_vars.push(prop_ident.clone()); let rust_property_type = rust_type(&property_decl.property_type, &property_decl.type_location) .unwrap_or_else(|err| { diag.push_compiler_error(err); quote!().into() }); declared_property_types.push(rust_property_type.clone()); if property_decl.expose_in_public_api { let getter_ident = quote::format_ident!("get_{}", prop_name); let setter_ident = quote::format_ident!("set_{}", prop_name); property_and_signal_accessors.push( quote!( #[allow(dead_code)] fn #getter_ident(self: ::core::pin::Pin<&Self>) -> #rust_property_type { let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component( sixtyfps::re_exports::ComponentRef::new_pin(self) ); Self::field_offsets().#prop_ident.apply_pin(self).get(&eval_context) } ) .into(), ); property_and_signal_accessors.push( quote!( #[allow(dead_code)] fn #setter_ident(&self, value: #rust_property_type) { Self::field_offsets().#prop_ident.apply(self).set(value) } ) .into(), ); } } } if diag.has_error() { return None; } let component_id = component_id(component); let mut item_tree_array = Vec::new(); let mut item_names = Vec::new(); let mut item_types = Vec::new(); let mut repeated_element_names = Vec::new(); let mut repeated_element_components = Vec::new(); let mut repeated_dynmodel_names = Vec::new(); let mut repeated_visit_branch = Vec::new(); let mut init = Vec::new(); let mut init_repeaters = Vec::new(); super::build_array_helper(component, |item_rc, children_index| { let item = item_rc.borrow(); if let Some(repeated) = &item.repeated { let base_component = match &item.base_type { Type::Component(c) => c, _ => panic!("should be a component because of the repeater_component pass"), }; let repeater_index = repeated_element_names.len(); let repeater_id = quote::format_ident!("repeater_{}", repeater_index); let rep_component_id = self::component_id(&*base_component); extra_components.push(generate(&*base_component, diag).unwrap_or_else(|| { assert!(diag.has_error()); Default::default() })); extra_components.push(if repeated.is_conditional_element { quote! { impl sixtyfps::re_exports::RepeatedComponent for #rep_component_id { type Data = (); fn update(&self, _: usize, _: Self::Data) { } } } } else { let data_type = rust_type( &Expression::RepeaterModelReference { element: Rc::downgrade(item_rc) }.ty(), &item.node.as_ref().map_or_else(Default::default, |n| n.span()), ) .unwrap_or_else(|err| { diag.push_compiler_error(err); quote!().into() }); quote! { impl sixtyfps::re_exports::RepeatedComponent for #rep_component_id { type Data = #data_type; fn update(&self, index: usize, data: Self::Data) { self.index.set(index); self.model_data.set(data) } } } }); let mut model = compile_expression(&repeated.model, component); if repeated.is_conditional_element { model = quote!((if #model {Some(())} else {None}).iter().cloned()) } if repeated.model.is_constant() { init_repeaters.push(quote! { self_pinned.#repeater_id.update_model(#model, || { let mut new_comp = #rep_component_id::default(); new_comp.parent = self_pinned.self_weak.get().unwrap().clone(); new_comp }); }); repeated_visit_branch.push(quote!( #repeater_index => self_pinned.#repeater_id.visit(visitor), )); } else { let model_name = quote::format_ident!("model_{}", repeater_index); repeated_visit_branch.push(quote!( #repeater_index => { if self_pinned.#model_name.is_dirty() { #component_id::field_offsets().#model_name.apply_pin(self_pinned).evaluate(|| { let _self = self_pinned.clone(); // FIXME: this should not be the root_component (but that's fine as we no longer access the parent) let context = sixtyfps::re_exports::EvaluationContext::for_root_component( sixtyfps::re_exports::ComponentRef::new_pin(_self) ); let context = &context; self_pinned.#repeater_id.update_model(#model, || { let mut new_comp = #rep_component_id::default(); new_comp.parent = self_pinned.self_weak.get().unwrap().clone(); new_comp }); }); } self_pinned.#repeater_id.visit(visitor) } )); repeated_dynmodel_names.push(model_name); } item_tree_array.push(quote!( sixtyfps::re_exports::ItemTreeNode::DynamicTree { index: #repeater_index, } )); repeated_element_names.push(repeater_id); repeated_element_components.push(rep_component_id); } else { let field_name = quote::format_ident!("{}", item.id); let children_count = item.children.len() as u32; item_tree_array.push(quote!( sixtyfps::re_exports::ItemTreeNode::Item{ item: VOffset::new(#component_id::field_offsets().#field_name), chilren_count: #children_count, children_index: #children_index, } )); for (k, binding_expression) in &item.bindings { let rust_property_ident = quote::format_ident!("{}", k); let rust_property_accessor_prefix = if item.property_declarations.contains_key(k) { proc_macro2::TokenStream::new() } else { quote!(#field_name.) }; let rust_property = quote!(#rust_property_accessor_prefix#rust_property_ident); let tokens_for_expression = compile_expression(binding_expression, &component); if matches!(item.lookup_property(k.as_str()), Type::Signal) { init.push(quote!( self_.#rust_property.set_handler(|context, ()| { let _self = context.get_component::<#component_id>().unwrap(); #tokens_for_expression; }); )); } else { if binding_expression.is_constant() { let setter = property_set_value_tokens( component, &item_rc, k, quote!((#tokens_for_expression) as _), ); init.push(quote!( self_.#rust_property.#setter; )); } else { let setter = property_set_binding_tokens( component, &item_rc, k, quote!( |context| { let _self = context.get_component::<#component_id>().unwrap(); (#tokens_for_expression) as _ } ), ); init.push(quote!( self_.#rust_property.#setter; )); } } } item_names.push(field_name); item_types.push(quote::format_ident!("{}", item.base_type.as_builtin().class_name)); } }); let resource_symbols: Vec = component .embedded_file_resources .borrow() .iter() .map(|(path, id)| { let symbol = quote::format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id); quote!(const #symbol: &'static [u8] = ::core::include_bytes!(#path);) }) .collect(); let layouts = compute_layout(component); let mut parent_component_type = None; if let Some(parent_element) = component.parent_element.upgrade() { if !parent_element.borrow().repeated.as_ref().map_or(false, |r| r.is_conditional_element) { declared_property_vars.push(quote::format_ident!("index")); declared_property_types.push(quote!(usize)); declared_property_vars.push(quote::format_ident!("model_data")); declared_property_types.push( rust_type( &Expression::RepeaterModelReference { element: component.parent_element.clone(), } .ty(), &parent_element .borrow() .node .as_ref() .map_or_else(Default::default, |n| n.span()), ) .unwrap_or_else(|err| { diag.push_compiler_error(err); quote!().into() }), ); } parent_component_type = Some(self::component_id( &parent_element.borrow().enclosing_component.upgrade().unwrap(), )); } else { property_and_signal_accessors.push(quote! { fn run(self) { use sixtyfps::re_exports::*; let window = sixtyfps::create_window(); let self_pinned = Rc::pin(self); self_pinned.self_weak.set(WeakPin::downgrade(self_pinned.clone())) .map_err(|_|()) .expect("Can only be pinned once"); #(#init_repeaters)* window.run(VRef::new_pin(self_pinned.as_ref())); } }); }; // Trick so we can use `#()` as a `if let Some` in `quote!` let parent_component_type = parent_component_type.iter().collect::>(); if diag.has_error() { return None; } Some(quote!( #(#resource_symbols)* #[derive(sixtyfps::re_exports::FieldOffsets)] #[const_field_offset(sixtyfps::re_exports::const_field_offset)] #[repr(C)] #[pin] struct #component_id { #(#item_names : sixtyfps::re_exports::#item_types,)* #(#declared_property_vars : sixtyfps::re_exports::Property<#declared_property_types>,)* #(#declared_signals : sixtyfps::re_exports::Signal<()>,)* #(#repeated_element_names : sixtyfps::re_exports::Repeater<#repeated_element_components>,)* #(#repeated_dynmodel_names : sixtyfps::re_exports::PropertyListenerScope,)* self_weak: sixtyfps::re_exports::OnceCell>, #(parent : sixtyfps::re_exports::WeakPin<#parent_component_type>,)* } impl ::core::default::Default for #component_id { fn default() -> Self { #![allow(unused)] use sixtyfps::re_exports::*; ComponentVTable_static!(static VT for #component_id); let mut self_ = Self { #(#item_names : ::core::default::Default::default(),)* #(#declared_property_vars : ::core::default::Default::default(),)* #(#declared_signals : ::core::default::Default::default(),)* #(#repeated_element_names : ::core::default::Default::default(),)* #(#repeated_dynmodel_names : ::core::default::Default::default(),)* self_weak : ::core::default::Default::default(), #(parent : sixtyfps::re_exports::WeakPin::<#parent_component_type>::default(),)* }; #(#init)* self_ } } impl sixtyfps::re_exports::Component for #component_id { fn visit_children_item(self: ::core::pin::Pin<&Self>, index: isize, visitor: sixtyfps::re_exports::ItemVisitorRefMut) { use sixtyfps::re_exports::*; let tree = &[#(#item_tree_array),*]; sixtyfps::re_exports::visit_item_tree(self, VRef::new_pin(self), tree, index, visitor, visit_dynamic); #[allow(unused)] fn visit_dynamic(self_pinned: ::core::pin::Pin<&#component_id>, visitor: ItemVisitorRefMut, dyn_index: usize) { match dyn_index { #(#repeated_visit_branch)* _ => panic!("invalid dyn_index {}", dyn_index), } } } #layouts } impl #component_id{ #(#property_and_signal_accessors)* } #(#extra_components)* )) } /// Return an identifier suitable for this component fn component_id(component: &Component) -> proc_macro2::Ident { if component.id.is_empty() { let s = &component.root_element.borrow().id; // Capitalize first leter: let mut it = s.chars(); let id = it.next().map(|c| c.to_ascii_uppercase()).into_iter().chain(it).collect::(); quote::format_ident!("{}", id) } else { quote::format_ident!("{}", component.id) } } fn property_animation_tokens( component: &Rc, element: &ElementRc, property_name: &str, ) -> Option { if let Some(animation) = element.borrow().property_animations.get(property_name) { let bindings: Vec = animation .borrow() .bindings .iter() .map(|(prop, initializer)| { let prop_ident = quote::format_ident!("{}", prop); let initializer = compile_expression(initializer, component); quote!(#prop_ident: #initializer as _) }) .collect(); Some(quote!(&sixtyfps::re_exports::PropertyAnimation{ #(#bindings)*, ..::core::default::Default::default() })) } else { None } } fn property_set_value_tokens( component: &Rc, element: &ElementRc, property_name: &str, value_tokens: TokenStream, ) -> TokenStream { if let Some(animation_tokens) = property_animation_tokens(component, element, property_name) { quote!(set_animated_value(#value_tokens, #animation_tokens)) } else { quote!(set(#value_tokens)) } } fn property_set_binding_tokens( component: &Rc, element: &ElementRc, property_name: &str, binding_tokens: TokenStream, ) -> TokenStream { if let Some(animation_tokens) = property_animation_tokens(component, element, property_name) { quote!(set_animated_binding(#binding_tokens, #animation_tokens)) } else { quote!(set_binding(#binding_tokens)) } } /// Returns the code that can access the given property or signal from the context (but without the set or get) /// /// to be used like: /// ```ignore /// let (access, context) = access_member(...) /// quote!(#access.get(#context)) /// ``` fn access_member( element: &ElementRc, name: &str, component: &Rc, context: TokenStream, component_rust: TokenStream, ) -> (TokenStream, TokenStream) { let e = element.borrow(); let enclosing_component = e.enclosing_component.upgrade().unwrap(); if Rc::ptr_eq(component, &enclosing_component) { let component_id = component_id(&enclosing_component); let name_ident = quote::format_ident!("{}", name); if e.property_declarations.contains_key(name) { (quote!(#component_id::field_offsets().#name_ident.apply_pin(#component_rust)), context) } else { let elem_ident = quote::format_ident!("{}", e.id); let elem_ty = quote::format_ident!("{}", e.base_type.as_builtin().class_name); ( quote!((#component_id::field_offsets().#elem_ident + #elem_ty::field_offsets().#name_ident) .apply_pin(#component_rust) ), context, ) } } else { access_member( element, name, &component .parent_element .upgrade() .unwrap() .borrow() .enclosing_component .upgrade() .unwrap(), quote!((&sixtyfps::re_exports::EvaluationContext::for_root_component(#context.component))), quote!(#component_rust.parent.upgrade().unwrap().as_ref()), ) } } fn compile_expression(e: &Expression, component: &Rc) -> TokenStream { match e { Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)), Expression::NumberLiteral(n) => quote!(#n), Expression::BoolLiteral(b) => quote!(#b), Expression::Cast { from, to } => { let f = compile_expression(&*from, &component); match (from.ty(), to) { (Type::Float32, Type::String) | (Type::Int32, Type::String) => { quote!(sixtyfps::re_exports::SharedString::from(format!("{}", #f).as_str())) } (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => quote!((0..#f as i32)), (Type::Array(_), Type::Model) => quote!(#f.iter().cloned()), (Type::Float32, Type::Color) => { quote!(sixtyfps::re_exports::Color::from(#f as u32)) } _ => f, } } Expression::PropertyReference(NamedReference { element, name }) => { let (access, context) = access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(context), quote!(_self), ); quote!(#access.get(#context)) } Expression::RepeaterIndexReference { element } => { if element.upgrade().unwrap().borrow().base_type == Type::Component(component.clone()) { let component_id = component_id(&component); quote!({ #component_id::field_offsets().index.apply_pin(_self).get(context) }) } else { todo!(); } } Expression::RepeaterModelReference { element } => { if element.upgrade().unwrap().borrow().base_type == Type::Component(component.clone()) { let component_id = component_id(&component); quote!({ #component_id::field_offsets().model_data.apply_pin(_self).get(context) }) } else { todo!(); } } Expression::ObjectAccess { base, name } => { let index = if let Type::Object(ty) = base.ty() { ty.keys() .position(|k| k == name) .expect("Expression::ObjectAccess: Cannot find a key in an object") } else { panic!("Expression::ObjectAccess's base expression is not an Object type") }; let index = proc_macro2::Literal::usize_unsuffixed(index); let base_e = compile_expression(base, component); quote!((#base_e).#index ) } Expression::CodeBlock(sub) => { let map = sub.iter().map(|e| compile_expression(e, &component)); quote!({ #(#map);* }) } Expression::SignalReference(NamedReference { element, name, .. }) => { let (access, context) = access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(context), quote!(_self), ); quote!(#access.emit(#context, ())) } Expression::FunctionCall { function } => { if matches!(function.ty(), Type::Signal) { compile_expression(function, &component) } else { let error = format!("the function {:?} is not a signal", e); quote!(compile_error! {#error}) } } Expression::SelfAssignment { lhs, rhs, op } => match &**lhs { Expression::PropertyReference(NamedReference { element, name }) => { let (lhs, context) = access_member( &element.upgrade().unwrap(), name.as_str(), component, quote!(context), quote!(_self), ); let rhs = compile_expression(&*rhs, &component); let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); quote!( #lhs.set(#lhs.get(#context) #op &((#rhs) as _) )) } _ => panic!("typechecking should make sure this was a PropertyReference"), }, Expression::BinaryExpression { lhs, rhs, op } => { let conv = match crate::expression_tree::operator_class(*op) { OperatorClass::ArithmeticOp => Some(quote!(as f64)), OperatorClass::ComparisonOp if matches!(lhs.ty(), Type::Int32 | Type::Float32) => { Some(quote!(as f64)) } _ => None, }; let lhs = compile_expression(&*lhs, &component); let rhs = compile_expression(&*rhs, &component); let op = match op { '=' => quote!(==), '!' => quote!(!=), '≤' => quote!(<=), '≥' => quote!(>=), '&' => quote!(&&), '|' => quote!(||), _ => proc_macro2::TokenTree::Punct(proc_macro2::Punct::new( *op, proc_macro2::Spacing::Alone, )) .into(), }; quote!( ((#lhs #conv ) #op (#rhs #conv )) ) } Expression::UnaryOp { sub, op } => { let sub = compile_expression(&*sub, &component); let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); quote!( #op #sub ) } Expression::ResourceReference { absolute_source_path } => { if let Some(id) = component.embedded_file_resources.borrow().get(absolute_source_path) { let symbol = quote::format_ident!("SFPS_EMBEDDED_RESOURCE_{}", id); quote!(sixtyfps::re_exports::Resource::EmbeddedData(#symbol.into())) } else { quote!(sixtyfps::re_exports::Resource::AbsoluteFilePath(sixtyfps::re_exports::SharedString::from(#absolute_source_path))) } } Expression::Condition { condition, true_expr, false_expr } => { let condition_code = compile_expression(&*condition, component); let true_code = compile_expression(&*true_expr, component); let false_code = compile_expression(&*false_expr, component); quote!( if #condition_code { #true_code } else { (#false_code) as _ } ) } Expression::Invalid | Expression::Uncompiled(_) => { let error = format!("unsupported expression {:?}", e); quote!(compile_error! {#error}) } Expression::Array { values, .. } => { //let rust_element_ty = rust_type(&element_ty, &Default::default()); let val = values.iter().map(|e| compile_expression(e, component)); quote!([#(#val as _),*]) } Expression::Object { ty, values } => { if let Type::Object(ty) = ty { let elem = ty.iter().map(|(k, t)| { values.get(k).map(|e| { let ce = compile_expression(e, component); let t = rust_type(t, &Default::default()).unwrap_or_default(); quote!(#ce as #t) }) }); // This will produce a tuple quote!((#(#elem,)*)) } else { panic!("Expression::Object is not a Type::Object") } } Expression::PathElements { elements } => compile_path(elements, component), } } fn compute_layout(component: &Rc) -> TokenStream { let mut layouts = vec![]; for grid_layout in component.layout_constraints.borrow().grids.iter() { let within = quote::format_ident!("{}", grid_layout.within.borrow().id); let within_ty = quote::format_ident!( "{}", grid_layout.within.borrow().base_type.as_builtin().class_name ); let row_constraint = vec![quote!(Constraint::default()); grid_layout.row_count()]; let col_constraint = vec![quote!(Constraint::default()); grid_layout.col_count()]; let cells = grid_layout .elems .iter() .map(|x| { x.iter() .map(|y| { y.as_ref() .map(|elem| { let e = quote::format_ident!("{}", elem.borrow().id); let p = |n: &str| { if elem.borrow().lookup_property(n) == Type::Float32 { let n = quote::format_ident!("{}", n); quote! {Some(&self.#e.#n)} } else { quote! {None} } }; let width = p("width"); let height = p("height"); let x = p("x"); let y = p("y"); quote!(GridLayoutCellData { x: #x, y: #y, width: #width, height: #height, }) }) .unwrap_or_else(|| quote!(GridLayoutCellData::default())) }) .collect::>() }) .collect::>(); let x_pos = compile_expression(&*grid_layout.x_reference, &component); let y_pos = compile_expression(&*grid_layout.y_reference, &component); layouts.push(quote! { solve_grid_layout(&GridLayoutData { row_constraint: Slice::from_slice(&[#(#row_constraint),*]), col_constraint: Slice::from_slice(&[#(#col_constraint),*]), width: (Self::field_offsets().#within + #within_ty::field_offsets().width) .apply_pin(self).get(context), height: (Self::field_offsets().#within + #within_ty::field_offsets().height) .apply_pin(self).get(context), x: #x_pos, y: #y_pos, cells: Slice::from_slice(&[#( Slice::from_slice(&[#( #cells ),*])),*]), }); }); } for path_layout in component.layout_constraints.borrow().paths.iter() { let items = path_layout .elements .iter() .map(|elem| { let e = quote::format_ident!("{}", elem.borrow().id); let p = |n: &str| { if elem.borrow().lookup_property(n) == Type::Float32 { let n = quote::format_ident!("{}", n); quote! {Some(&self.#e.#n)} } else { quote! {None} } }; let x = p("x"); let y = p("y"); quote!(PathLayoutItemData { x: #x, y: #y, }) }) .collect::>(); let path = compile_path(&path_layout.path, &component); let x_pos = compile_expression(&*path_layout.x_reference, &component); let y_pos = compile_expression(&*path_layout.y_reference, &component); let width = compile_expression(&*path_layout.width_reference, &component); let height = compile_expression(&*path_layout.width_reference, &component); layouts.push(quote! { solve_path_layout(&PathLayoutData { items: Slice::from_slice(&[#( #items ),*]), elements: &#path, x: #x_pos, y: #y_pos, width: #width, height: #height, }); }); } quote! { fn layout_info(self: ::core::pin::Pin<&Self>) -> sixtyfps::re_exports::LayoutInfo { todo!("Implement in rust.rs") } fn compute_layout(self: ::core::pin::Pin<&Self>, context: &sixtyfps::re_exports::EvaluationContext) { #![allow(unused)] use sixtyfps::re_exports::*; let dummy = Property::::default(); let _self = self; #(#layouts)* } } } fn compile_path_events(events: &crate::expression_tree::PathEvents) -> TokenStream { use lyon::path::Event; let mut coordinates = Vec::new(); let converted_events: Vec = events .iter() .map(|event| match event { Event::Begin { at } => { coordinates.push(at); quote!(sixtyfps::re_exports::PathEvent::Begin) } Event::Line { from, to } => { coordinates.push(from); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Line) } Event::Quadratic { from, ctrl, to } => { coordinates.push(from); coordinates.push(ctrl); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Quadratic) } Event::Cubic { from, ctrl1, ctrl2, to } => { coordinates.push(from); coordinates.push(ctrl1); coordinates.push(ctrl2); coordinates.push(to); quote!(sixtyfps::re_exports::PathEvent::Cubic) } Event::End { last, first, close } => { debug_assert_eq!(coordinates.first(), Some(&first)); debug_assert_eq!(coordinates.last(), Some(&last)); if *close { quote!(sixtyfps::re_exports::PathEvent::EndClosed) } else { quote!(sixtyfps::re_exports::PathEvent::EndOpen) } } }) .collect(); let coordinates: Vec = coordinates .into_iter() .map(|pt| { let x = pt.x; let y = pt.y; quote!(sixtyfps::re_exports::Point::new(#x, #y)) }) .collect(); quote!(sixtyfps::re_exports::SharedArray::::from(&[#(#converted_events),*]), sixtyfps::re_exports::SharedArray::::from(&[#(#coordinates),*])) } fn compile_path(path: &Path, component: &Rc) -> TokenStream { match path { Path::Elements(elements) => { let converted_elements: Vec = elements .iter() .map(|element| { let mut bindings = element .bindings .iter() .map(|(property, expr)| { let prop_ident = quote::format_ident!("{}", property); let binding_expr = compile_expression(expr, component); quote!(#prop_ident: #binding_expr as _).to_string() }) .collect::>(); if bindings.len() < element.element_type.properties.len() { bindings.push("..Default::default()".into()) } let bindings = bindings.join(","); let ctor_format_string = element .element_type .rust_type_constructor .as_ref() .expect( "Unexpected error in type registry: path element is lacking rust type name", ); ctor_format_string .replace("{}", &bindings) .parse() .expect("Error parsing rust path element constructor") }) .collect(); quote!(sixtyfps::re_exports::PathData::Elements( sixtyfps::re_exports::SharedArray::::from(&[#(#converted_elements),*]) )) } Path::Events(events) => { let events = compile_path_events(events); quote!(sixtyfps::re_exports::PathData::Events(#events)) } } }