Port example 07
This commit is contained in:
@@ -50,22 +50,57 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
.attrs
|
||||
.push(syn::parse_quote!(#[allow(non_snake_case)]));
|
||||
|
||||
let props = function
|
||||
let mut inputs = function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(parse_prop)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let child_contract = match inputs.last() {
|
||||
Some(prop) if prop.ident == "children" => {
|
||||
let prop = inputs.pop().expect("last input should exist");
|
||||
let kind = parse_children_contract_kind(&prop.ty)?;
|
||||
Some(ChildContract {
|
||||
ty: prop.ty,
|
||||
kind,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let props = inputs;
|
||||
|
||||
let builder_name = format_ident!("__{}Builder", component_name);
|
||||
let child_field_ident = child_contract.as_ref().map(|_| format_ident!("children"));
|
||||
|
||||
let builder_tokens = if props.is_empty() {
|
||||
quote! {
|
||||
#vis struct #builder_name;
|
||||
match child_contract.as_ref() {
|
||||
None => {
|
||||
quote! {
|
||||
#vis struct #builder_name;
|
||||
|
||||
impl #builder_name {
|
||||
#vis fn children(self, _children: ()) -> #component_name {
|
||||
#component_name
|
||||
impl #builder_name {
|
||||
#vis fn children(self, _children: ()) -> #component_name {
|
||||
#component_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(contract) => {
|
||||
let child_arg_ty = child_builder_arg_type_tokens(contract.kind);
|
||||
let child_value = wrap_special_value_tokens(contract.kind, &format_ident!("children"));
|
||||
let child_field_ident = child_field_ident
|
||||
.as_ref()
|
||||
.expect("child field ident should exist");
|
||||
quote! {
|
||||
#vis struct #builder_name;
|
||||
|
||||
impl #builder_name {
|
||||
#vis fn children(self, children: #child_arg_ty) -> #component_name {
|
||||
#component_name {
|
||||
#child_field_ident: #child_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +133,10 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
|
||||
let builder_methods = props.iter().enumerate().map(|(index, prop)| {
|
||||
let value_ident = &prop.ident;
|
||||
let value_ty = &prop.ty;
|
||||
let missing_at_index = &missing_names[index];
|
||||
let present_at_index = &present_names[index];
|
||||
let setter_arg_ty = prop_builder_arg_type_tokens(prop);
|
||||
let stored_value = wrap_special_value_tokens(prop.kind, value_ident);
|
||||
|
||||
let impl_generics = state_idents
|
||||
.iter()
|
||||
@@ -138,7 +174,7 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
.enumerate()
|
||||
.map(|(current, field_ident)| {
|
||||
if current == index {
|
||||
quote! { #field_ident: #present_at_index(#value_ident) }
|
||||
quote! { #field_ident: #present_at_index(#stored_value) }
|
||||
} else {
|
||||
quote! { #field_ident: self.#field_ident }
|
||||
}
|
||||
@@ -148,7 +184,7 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
if impl_generics.is_empty() {
|
||||
quote! {
|
||||
impl #builder_name<#(#self_builder_args),*> {
|
||||
#vis fn #value_ident(self, #value_ident: #value_ty) -> #builder_name<#(#next_builder_args),*> {
|
||||
#vis fn #value_ident(self, #value_ident: #setter_arg_ty) -> #builder_name<#(#next_builder_args),*> {
|
||||
#builder_name {
|
||||
#(#field_initializers),*
|
||||
}
|
||||
@@ -158,7 +194,7 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
} else {
|
||||
quote! {
|
||||
impl<#(#impl_generics),*> #builder_name<#(#self_builder_args),*> {
|
||||
#vis fn #value_ident(self, #value_ident: #value_ty) -> #builder_name<#(#next_builder_args),*> {
|
||||
#vis fn #value_ident(self, #value_ident: #setter_arg_ty) -> #builder_name<#(#next_builder_args),*> {
|
||||
#builder_name {
|
||||
#(#field_initializers),*
|
||||
}
|
||||
@@ -179,6 +215,59 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
let field_extractors = field_idents
|
||||
.iter()
|
||||
.map(|field_ident| quote! { #field_ident: builder.#field_ident.0 });
|
||||
let component_render_args = props
|
||||
.iter()
|
||||
.map(|prop| {
|
||||
let ident = &prop.ident;
|
||||
quote! { ::core::clone::Clone::clone(&self.#ident) }
|
||||
})
|
||||
.chain(child_field_ident.iter().map(|ident| {
|
||||
quote! { ::core::clone::Clone::clone(&self.#ident) }
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
let children_method = match child_contract.as_ref() {
|
||||
None => quote! {
|
||||
#vis fn children(self, _children: ()) -> #component_name {
|
||||
#component_name::from_builder(self)
|
||||
}
|
||||
},
|
||||
Some(contract) => {
|
||||
let child_arg_ty = child_builder_arg_type_tokens(contract.kind);
|
||||
let child_value =
|
||||
wrap_special_value_tokens(contract.kind, &format_ident!("children"));
|
||||
quote! {
|
||||
#vis fn children(self, children: #child_arg_ty) -> #component_name {
|
||||
#component_name::from_builder(self, #child_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let from_builder = match child_contract.as_ref() {
|
||||
None => quote! {
|
||||
fn from_builder(builder: #builder_name<#(#ready_builder_args),*>) -> Self {
|
||||
Self {
|
||||
#(#field_extractors),*
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(contract) => {
|
||||
let child_ty = &contract.ty;
|
||||
let child_field_ident = child_field_ident
|
||||
.as_ref()
|
||||
.expect("child field ident should exist");
|
||||
quote! {
|
||||
fn from_builder(
|
||||
builder: #builder_name<#(#ready_builder_args),*>,
|
||||
#child_field_ident: #child_ty,
|
||||
) -> Self {
|
||||
Self {
|
||||
#(#field_extractors),*,
|
||||
#child_field_ident,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
#(#marker_structs)*
|
||||
@@ -190,17 +279,11 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
#(#builder_methods)*
|
||||
|
||||
impl #builder_name<#(#ready_builder_args),*> {
|
||||
#vis fn children(self, _children: ()) -> #component_name {
|
||||
#component_name::from_builder(self)
|
||||
}
|
||||
#children_method
|
||||
}
|
||||
|
||||
impl #component_name {
|
||||
fn from_builder(builder: #builder_name<#(#ready_builder_args),*>) -> Self {
|
||||
Self {
|
||||
#(#field_extractors),*
|
||||
}
|
||||
}
|
||||
#from_builder
|
||||
}
|
||||
|
||||
impl ::ruin_app::Component for #component_name {
|
||||
@@ -214,47 +297,82 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
|
||||
|
||||
fn render(&self) -> ::ruin_app::View {
|
||||
::ruin_app::IntoView::into_view(#render_name(
|
||||
#(::core::clone::Clone::clone(&self.#field_idents)),*
|
||||
#(#component_render_args),*
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let render_call = if props.is_empty() {
|
||||
quote! { #render_name() }
|
||||
} else {
|
||||
let field_idents = props.iter().map(|prop| &prop.ident);
|
||||
quote! { #render_name(#(::core::clone::Clone::clone(&self.#field_idents)),*) }
|
||||
};
|
||||
let render_args = props
|
||||
.iter()
|
||||
.map(|prop| {
|
||||
let ident = &prop.ident;
|
||||
quote! { ::core::clone::Clone::clone(&self.#ident) }
|
||||
})
|
||||
.chain(child_field_ident.iter().map(|ident| {
|
||||
quote! { ::core::clone::Clone::clone(&self.#ident) }
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
let render_call = quote! { #render_name(#(#render_args),*) };
|
||||
|
||||
let component_tokens = if props.is_empty() {
|
||||
quote! {
|
||||
#vis struct #component_name;
|
||||
let component_tokens = match (props.is_empty(), child_contract.as_ref()) {
|
||||
(true, None) => {
|
||||
quote! {
|
||||
#vis struct #component_name;
|
||||
|
||||
#builder_tokens
|
||||
#builder_tokens
|
||||
|
||||
impl ::ruin_app::Component for #component_name {
|
||||
type Builder = #builder_name;
|
||||
impl ::ruin_app::Component for #component_name {
|
||||
type Builder = #builder_name;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
#builder_name
|
||||
}
|
||||
fn builder() -> Self::Builder {
|
||||
#builder_name
|
||||
}
|
||||
|
||||
fn render(&self) -> ::ruin_app::View {
|
||||
::ruin_app::IntoView::into_view(#render_call)
|
||||
fn render(&self) -> ::ruin_app::View {
|
||||
::ruin_app::IntoView::into_view(#render_call)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let field_idents = props.iter().map(|prop| &prop.ident);
|
||||
let field_types = props.iter().map(|prop| &prop.ty);
|
||||
quote! {
|
||||
#vis struct #component_name {
|
||||
#( #field_idents: #field_types ),*
|
||||
}
|
||||
_ => {
|
||||
let field_idents = props
|
||||
.iter()
|
||||
.map(|prop| prop.ident.clone())
|
||||
.chain(child_field_ident.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
let field_types = props
|
||||
.iter()
|
||||
.map(|prop| prop.ty.clone())
|
||||
.chain(child_contract.iter().map(|contract| contract.ty.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let component_impl = if props.is_empty() {
|
||||
quote! {
|
||||
impl ::ruin_app::Component for #component_name {
|
||||
type Builder = #builder_name;
|
||||
|
||||
#builder_tokens
|
||||
fn builder() -> Self::Builder {
|
||||
#builder_name
|
||||
}
|
||||
|
||||
fn render(&self) -> ::ruin_app::View {
|
||||
::ruin_app::IntoView::into_view(#render_call)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
quote! {
|
||||
#vis struct #component_name {
|
||||
#( #field_idents: #field_types ),*
|
||||
}
|
||||
|
||||
#builder_tokens
|
||||
|
||||
#component_impl
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -333,7 +451,8 @@ fn validate_component_function(function: &ItemFn) -> Result<()> {
|
||||
"components cannot be variadic",
|
||||
));
|
||||
}
|
||||
for input in &signature.inputs {
|
||||
let input_count = signature.inputs.len();
|
||||
for (index, input) in signature.inputs.iter().enumerate() {
|
||||
let FnArg::Typed(typed) = input else {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
@@ -347,10 +466,13 @@ fn validate_component_function(function: &ItemFn) -> Result<()> {
|
||||
));
|
||||
};
|
||||
if ident == "children" {
|
||||
return Err(Error::new_spanned(
|
||||
ident,
|
||||
"`children` is reserved for the generated builder finalizer",
|
||||
));
|
||||
if index + 1 != input_count {
|
||||
return Err(Error::new_spanned(
|
||||
ident,
|
||||
"component `children` contracts must be the final parameter",
|
||||
));
|
||||
}
|
||||
parse_children_contract_kind(typed.ty.as_ref())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +488,7 @@ fn validate_component_function(function: &ItemFn) -> Result<()> {
|
||||
struct Prop {
|
||||
ident: Ident,
|
||||
ty: Type,
|
||||
kind: SpecialValueKind,
|
||||
}
|
||||
|
||||
fn parse_prop(input: &FnArg) -> Result<Prop> {
|
||||
@@ -385,9 +508,83 @@ fn parse_prop(input: &FnArg) -> Result<Prop> {
|
||||
Ok(Prop {
|
||||
ident: ident.clone(),
|
||||
ty: (*typed.ty).clone(),
|
||||
kind: parse_special_value_kind(typed.ty.as_ref()),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ChildContract {
|
||||
ty: Type,
|
||||
kind: SpecialValueKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum SpecialValueKind {
|
||||
Plain,
|
||||
ChildViews,
|
||||
TextValue,
|
||||
}
|
||||
|
||||
fn parse_special_value_kind(ty: &Type) -> SpecialValueKind {
|
||||
match terminal_type_ident(ty).as_deref() {
|
||||
Some("ChildViews") => SpecialValueKind::ChildViews,
|
||||
Some("TextValue") => SpecialValueKind::TextValue,
|
||||
_ => SpecialValueKind::Plain,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_children_contract_kind(ty: &Type) -> Result<SpecialValueKind> {
|
||||
match parse_special_value_kind(ty) {
|
||||
SpecialValueKind::ChildViews => Ok(SpecialValueKind::ChildViews),
|
||||
SpecialValueKind::TextValue => Ok(SpecialValueKind::TextValue),
|
||||
SpecialValueKind::Plain => Err(Error::new_spanned(
|
||||
ty,
|
||||
"component `children` contracts must use `ruin_app::ChildViews` or `ruin_app::TextValue`",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal_type_ident(ty: &Type) -> Option<String> {
|
||||
let Type::Path(type_path) = ty else {
|
||||
return None;
|
||||
};
|
||||
type_path
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|segment| segment.ident.to_string())
|
||||
}
|
||||
|
||||
fn prop_builder_arg_type_tokens(prop: &Prop) -> proc_macro2::TokenStream {
|
||||
match prop.kind {
|
||||
SpecialValueKind::Plain => {
|
||||
let ty = &prop.ty;
|
||||
quote! { #ty }
|
||||
}
|
||||
SpecialValueKind::ChildViews => quote! { impl ::ruin_app::Children },
|
||||
SpecialValueKind::TextValue => quote! { impl ::ruin_app::TextChildren },
|
||||
}
|
||||
}
|
||||
|
||||
fn child_builder_arg_type_tokens(kind: SpecialValueKind) -> proc_macro2::TokenStream {
|
||||
match kind {
|
||||
SpecialValueKind::Plain => quote! { () },
|
||||
SpecialValueKind::ChildViews => quote! { impl ::ruin_app::Children },
|
||||
SpecialValueKind::TextValue => quote! { impl ::ruin_app::TextChildren },
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_special_value_tokens(
|
||||
kind: SpecialValueKind,
|
||||
ident: &Ident,
|
||||
) -> proc_macro2::TokenStream {
|
||||
match kind {
|
||||
SpecialValueKind::Plain => quote! { #ident },
|
||||
SpecialValueKind::ChildViews => quote! { ::ruin_app::ChildViews::from_children(#ident) },
|
||||
SpecialValueKind::TextValue => quote! { ::ruin_app::TextValue::from_text(#ident) },
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewRoot {
|
||||
root: Node,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user