Port example 04

This commit is contained in:
2026-03-22 00:33:43 -04:00
parent 0d8bc38113
commit ed4c216f96
5 changed files with 615 additions and 17 deletions

View File

@@ -4,8 +4,8 @@ use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{
Error, Expr, FnArg, Ident, ItemFn, Pat, PatIdent, Path, Result, ReturnType, Token, Type,
braced, parenthesized, parse_macro_input,
Error, Expr, FnArg, Ident, ItemFn, ItemStruct, Pat, PatIdent, Path, Result, ReturnType, Token,
Type, braced, parenthesized, parse_macro_input,
};
#[proc_macro_attribute]
@@ -29,6 +29,16 @@ pub fn view(input: TokenStream) -> TokenStream {
expand_node(&root.root).into()
}
#[proc_macro_attribute]
pub fn context_provider(attr: TokenStream, item: TokenStream) -> TokenStream {
let value_ty = parse_macro_input!(attr as Type);
let item_struct = parse_macro_input!(item as ItemStruct);
match expand_context_provider(item_struct, value_ty) {
Ok(tokens) => tokens.into(),
Err(error) => error.to_compile_error().into(),
}
}
fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
validate_component_function(&function)?;
@@ -256,6 +266,34 @@ fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
})
}
fn expand_context_provider(
item_struct: ItemStruct,
value_ty: Type,
) -> Result<proc_macro2::TokenStream> {
if !item_struct.generics.params.is_empty() || item_struct.generics.where_clause.is_some() {
return Err(Error::new_spanned(
&item_struct.generics,
"context providers cannot be generic",
));
}
if !matches!(item_struct.fields, syn::Fields::Unit) {
return Err(Error::new_spanned(
&item_struct.fields,
"context providers must be unit structs",
));
}
let ident = &item_struct.ident;
Ok(quote! {
#item_struct
impl ::ruin_app::ContextKey for #ident {
type Value = #value_ty;
}
})
}
fn validate_component_function(function: &ItemFn) -> Result<()> {
let signature = &function.sig;
@@ -443,6 +481,9 @@ fn looks_like_node(input: ParseStream<'_>) -> Result<bool> {
fn expand_node(node: &Node) -> proc_macro2::TokenStream {
let path = &node.path;
if is_provider_path(path) {
return expand_provider_node(node);
}
let prop_calls = node.props.iter().map(|property| {
let name = &property.name;
let value = &property.value;
@@ -465,6 +506,34 @@ fn expand_node(node: &Node) -> proc_macro2::TokenStream {
}
}
fn expand_provider_node(node: &Node) -> proc_macro2::TokenStream {
let path = &node.path;
let value = match node.props.as_slice() {
[Property { name, value }] if name == "value" => value,
_ => {
return Error::new_spanned(
path,
"provide::<Context>(...) expects exactly one `value = ...` property",
)
.to_compile_error();
}
};
let child = match node.children.as_slice() {
[child] => expand_child(child),
_ => {
return Error::new_spanned(path, "provide::<Context>(...) requires exactly one child")
.to_compile_error();
}
};
quote! {
::ruin_app::#path(
#value,
|| ::ruin_app::IntoView::into_view(#child),
)
}
}
fn expand_children(children: &[Child]) -> proc_macro2::TokenStream {
match children {
[] => quote! { () },
@@ -491,3 +560,12 @@ fn is_component_path(path: &Path) -> bool {
.map(|ch| ch.is_ascii_uppercase())
.unwrap_or(false)
}
fn is_provider_path(path: &Path) -> bool {
!is_component_path(path)
&& path
.segments
.last()
.map(|segment| segment.ident == "provide")
.unwrap_or(false)
}