derive: another refactor, support generics

The two derive macros share common code now.
Furthermore, components derived from one of these
macros may use arbitrary generics.  Finally, the
uwui crate name (as in, crate or ::uwui) is being
resolved dynamically.
main
anna 7 months ago
parent e79902e911
commit b0feea938a
Signed by: fef
GPG Key ID: 2585C2DC6D79B485

64
Cargo.lock generated

@ -2,6 +2,43 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "hashbrown"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
[[package]]
name = "indexmap"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "proc-macro-crate"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
dependencies = [
"toml_edit",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.67" version = "1.0.67"
@ -31,6 +68,23 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
[[package]]
name = "toml_edit"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"
@ -48,7 +102,17 @@ dependencies = [
name = "uwui-dsl" name = "uwui-dsl"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "winnow"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [
"memchr",
]

@ -8,6 +8,7 @@ repository.workspace = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
proc-macro-crate = "2.0.0"
proc-macro2 = "1.0.67" proc-macro2 = "1.0.67"
quote = "1.0.33" quote = "1.0.33"
syn = "2.0.37" syn = "2.0.37"

@ -0,0 +1,179 @@
use crate::util::{define_ident, use_absolute};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, GenericParam};
pub fn custom(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = ComponentDef::new(parse_macro_input!(input as DeriveInput));
let name = input.name_with_generics();
let generics = input.generic_predicates();
let where_clause_predicates = input.where_clause_predicates();
use_absolute! {
uwui::backend::Backend as Backend,
uwui::view::GetTemplate as GetTemplate,
uwui::view::__internal::CustomComponent as CustomComponent,
uwui::view::__internal::IntoNode as IntoNode,
uwui::view::__internal::CustomComponentWrapper as CustomComponentWrapper,
}
define_ident! {
B
}
let expanded = quote! {
#[automatically_derived]
impl<#B: #Backend, #generics>
#CustomComponent<#B> for #name
where
#name : #GetTemplate<#B>,
#where_clause_predicates
{}
#[automatically_derived]
impl<#B: #Backend, #generics>
#IntoNode<#B> for #name
where
#name : #GetTemplate<#B>,
#where_clause_predicates
{
type Node = #CustomComponentWrapper<#B, Self>;
fn into_node(self) -> Self::Node {
#CustomComponentWrapper::new(self)
}
}
};
expanded.into()
}
pub fn native(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = ComponentDef::new(parse_macro_input!(input as DeriveInput));
let name = input.name_with_generics();
let generics = input.generic_predicates();
let where_clause_predicates = input.where_clause_predicates();
use_absolute! {
uwui::backend::Backend as Backend,
uwui::backend::Render as Render,
uwui::view::__internal::Node as Node,
uwui::view::__internal::IntoNode as IntoNode,
}
define_ident! {
B
}
let expanded = quote! {
#[automatically_derived]
impl<#B: #Backend, #generics>
#Node<#B> for #name
where
#name : #Render<#B>,
#where_clause_predicates
{}
#[automatically_derived]
impl<#B: #Backend, #generics>
#IntoNode<#B> for #name
where
#name : #Render<#B>,
#where_clause_predicates
{
type Node = Self;
fn into_node(self) -> Self::Node {
self
}
}
};
expanded.into()
}
struct ComponentDef {
derive_input: DeriveInput,
}
impl ComponentDef {
pub fn new(derive_input: DeriveInput) -> Self {
Self { derive_input }
}
/// `Name<T1, T2, T3>`
pub fn name_with_generics(&self) -> TokenStream {
let name = &self.derive_input.ident;
let generics = self.generic_names();
if generics.is_empty() {
name.to_token_stream()
} else {
quote! { #name < #generics > }
}
}
/// `T1, T2, T3`
pub fn generic_names(&self) -> TokenStream {
let mut out = Vec::new();
let generics = &self.derive_input.generics;
for param in generics.params.iter() {
match param {
GenericParam::Lifetime(lft) => {
out.push(lft.lifetime.to_token_stream());
}
GenericParam::Type(typ) => {
out.push(typ.ident.to_token_stream());
}
GenericParam::Const(cnst) => {
out.push(cnst.ident.to_token_stream());
}
}
}
quote! {
#(#out),*
}
}
/// `T1: Constraint1, T2: Constraint2<T1> + Constraint1, T3`
pub fn generic_predicates(&self) -> TokenStream {
let mut out = TokenStream::new();
let generics = &self.derive_input.generics;
for param in generics.params.iter() {
match param {
GenericParam::Lifetime(lft) => {
lft.lifetime.to_tokens(&mut out);
if let Some(colon) = lft.colon_token {
let bounds = &lft.bounds;
out.extend(quote! { #colon #bounds });
}
out.extend(quote! { , });
}
GenericParam::Type(typ) => {
typ.ident.to_tokens(&mut out);
if let Some(colon) = typ.colon_token {
let bounds = &typ.bounds;
out.extend(quote! { #colon #bounds });
}
out.extend(quote! { , });
}
GenericParam::Const(cnst) => {
let kw = &cnst.const_token;
let ident = &cnst.ident;
let colon = &cnst.colon_token;
let ty = &cnst.ty;
out.extend(quote! { #kw #ident #colon #ty , });
}
}
}
out
}
pub fn where_clause_predicates(&self) -> TokenStream {
self.derive_input
.generics
.where_clause
.as_ref()
.map(|clause| &clause.predicates)
.to_token_stream()
}
}

@ -1,31 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::__internal::CustomComponent<B> for #name
where
#name : crate::view::GetTemplate<B>
{}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::__internal::IntoNode<B> for #name
where
#name : crate::view::GetTemplate<B>
{
type Node = crate::view::__internal::CustomComponentWrapper<B, Self>;
fn into_node(self) -> Self::Node {
crate::view::__internal::CustomComponentWrapper::new(self)
}
}
};
expanded.into()
}

@ -1,2 +1 @@
pub mod custom_component; pub mod component;
pub mod native_component;

@ -1,31 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::__internal::Node<B> for #name
where
#name : crate::backend::Render<B>,
{}
#[automatically_derived]
impl<B: crate::backend::Backend>
crate::view::__internal::IntoNode<B> for #name
where
#name : crate::backend::Render<B>,
{
type Node = Self;
fn into_node(self) -> Self::Node {
self
}
}
};
expanded.into()
}

@ -4,6 +4,7 @@ use proc_macro::TokenStream;
mod derive; mod derive;
mod template; mod template;
mod util;
#[proc_macro] #[proc_macro]
pub fn template(input: TokenStream) -> TokenStream { pub fn template(input: TokenStream) -> TokenStream {
@ -12,10 +13,10 @@ pub fn template(input: TokenStream) -> TokenStream {
#[proc_macro_derive(NativeComponent)] #[proc_macro_derive(NativeComponent)]
pub fn native_component(input: TokenStream) -> TokenStream { pub fn native_component(input: TokenStream) -> TokenStream {
derive::native_component::invoke(input) derive::component::native(input)
} }
#[proc_macro_derive(CustomComponent)] #[proc_macro_derive(CustomComponent)]
pub fn custom_component(input: TokenStream) -> TokenStream { pub fn custom_component(input: TokenStream) -> TokenStream {
derive::custom_component::invoke(input) derive::component::custom(input)
} }

@ -1,4 +1,5 @@
use crate::template::Params; use crate::template::Params;
use crate::util::{define_ident, use_absolute};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
@ -17,49 +18,62 @@ impl ToTokens for Body {
impl ToTokens for Component { impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.name; let name = &self.name;
use_absolute! {
core::convert::Into as Into,
core::default::Default as Default,
core::iter::IntoIterator as IntoIterator,
uwui::backend::TargetBackend as TargetBackend,
uwui::view::Construct as Construct,
uwui::view::ConstructGroup as ConstructGroup,
uwui::view::View as View,
uwui::view::__internal::IntoNode as IntoNode,
}
define_ident! {
construct_params
}
let params = match self.params.as_ref() { let params = match self.params.as_ref() {
Some(p) => match p { Some(p) => match p {
#[allow(non_snake_case)]
Params::Anon(params) => quote! { Params::Anon(params) => quote! {
::core::convert::Into::<<#name as crate::view::Construct>::Params>::into( #Into::<<#name as #Construct>::Params>::into(
(#(::core::convert::Into::<_>::into(#params)),*,) (#(#Into::<_>::into(#params)),*,)
) )
}, },
Params::Named(params) => quote! { Params::Named(params) => quote! {
{ {
let mut __uwui_params = < let mut #construct_params = <
<#name as crate::view::Construct>::Params as ::core::default::Default <#name as #Construct>::Params as #Default
>::default(); >::default();
#(__uwui_params.#params)* #(#construct_params.#params)*
__uwui_params #construct_params
} }
}, },
}, },
None => quote! { None => quote! {
<<#name as crate::view::Construct>::Params as ::core::default::Default>::default() <<#name as #Construct>::Params as #Default>::default()
}, },
}; };
let initializer = if let Some(body) = self.body.as_ref() { let initializer = if let Some(body) = self.body.as_ref() {
let body = body.elements.as_slice(); let body = body.elements.as_slice();
quote! { quote! {
< < #name as #ConstructGroup::<#TargetBackend> >::construct_group(
#name as crate::view::ConstructGroup::<crate::backend::TargetBackend>
>::construct_group(
#params, #params,
::core::iter::IntoIterator::into_iter([ #IntoIterator::into_iter([
#(#body),* #(#body),*
]) ])
) )
} }
} else { } else {
quote! { quote! {
<#name as crate::view::Construct>::construct(#params) <#name as #Construct>::construct(#params)
} }
}; };
let initializer = quote! { let initializer = quote! {
crate::view::View::<crate::backend::TargetBackend>::new( #View::<#TargetBackend>::new(
crate::view::__internal::IntoNode::<crate::backend::TargetBackend>::into_node( #IntoNode::<#TargetBackend>::into_node(
#initializer #initializer
) )
) )

@ -1,3 +1,4 @@
use crate::util::use_absolute;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, token, Expr, Ident, Path}; use syn::{parse_macro_input, token, Expr, Ident, Path};
@ -7,12 +8,16 @@ mod parse;
pub fn invoke(input: TokenStream) -> TokenStream { pub fn invoke(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Body); let input = parse_macro_input!(input as Body);
use_absolute! {
core::iter::FromIterator as FromIterator,
uwui::view::Template as Template,
uwui::view::View as View,
}
let output = quote! { let output = quote! {
match (()) { match (()) {
(()) => { (()) => {
< < #Template as #FromIterator<#View<_>> >::from_iter([
crate::view::Template as ::core::iter::FromIterator<crate::view::View<_>>
>::from_iter([
#input #input
]) ])
} }

@ -1,30 +1,92 @@
use proc_macro::{Diagnostic, Level, Span}; use proc_macro2::{Ident, Span, TokenStream};
use proc_macro2::TokenStream; use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{parse::ParseStream, Error};
pub trait Compile: Sized { pub fn __make_absolute_path(name: &str, input: TokenStream) -> TokenStream {
fn compile(self) -> TokenStream; match name {
// core and std are special because they don't appear in Cargo.toml
"core" => quote! { ::core #input },
"std" => quote! { ::std #input },
name => {
let prefix = match crate_name(name) {
Ok(found) => match found {
FoundCrate::Itself => quote! { crate },
FoundCrate::Name(name) => {
let name = Ident::new(&name, Span::call_site());
quote! { :: #name }
}
},
Err(e) => {
// proc_macro_crate failed to do its thing, so we try our best to
// not break other people's builds. Assume we're being called
// from another crate and the crate's author didn't rename us.
eprintln!("uwui_dsl: proc_macro_crate failed to resolve crate {name}, expect macro bugs!");
eprintln!("uwui_dsl: proc_macro_crate has this to say: \"{e}\"");
let name = Ident::new(name, Span::call_site());
quote! { :: #name }
}
};
quote! { #prefix #input }
}
}
} }
pub fn emit_err(span: impl Into<Option<Span>>, msg: impl Into<String>) { macro_rules! absolute_path {
emit_diagnostic(span.into(), Level::Error, msg) ($crate_name:ident $(::$segment:ident)*) => {
$crate::util::__make_absolute_path(
stringify!($crate_name),
::quote::quote!($(::$segment)*)
)
};
} }
pub(crate) use absolute_path;
pub fn emit_warning(span: impl Into<Option<Span>>, msg: impl Into<String>) { macro_rules! use_absolute {
emit_diagnostic(span.into(), Level::Warning, msg) ($($crate_name:ident $(::$path:ident)* as $name:ident),* $(,)?) => {
$(
#[allow(non_snake_case)]
let $name = $crate::util::absolute_path!($crate_name $(::$path)*);
)*
}
} }
pub(crate) use use_absolute;
const RESERVED_IDENT_PREFIX: &str = "__uwui_";
pub fn emit_note(span: impl Into<Option<Span>>, msg: impl Into<String>) { pub fn make_reserved(name: &str, span: impl Into<Option<Span>>) -> Ident {
emit_diagnostic(span.into(), Level::Note, msg) let span = span.into().unwrap_or_else(Span::call_site);
if name.starts_with(RESERVED_IDENT_PREFIX) {
Ident::new(name, span)
} else {
Ident::new(&format!("{RESERVED_IDENT_PREFIX}{name}"), span)
}
} }
pub fn emit_help(span: impl Into<Option<Span>>, msg: impl Into<String>) { macro_rules! define_ident {
emit_diagnostic(span.into(), Level::Help, msg) ($($name:ident),* $(,)?) => {
$(
#[allow(non_snake_case)]
let $name = $crate::util::make_reserved(stringify!($name), None);
)*
}
} }
pub(crate) use define_ident;
fn emit_diagnostic(span: Option<Span>, level: Level, msg: impl Into<String>) { /// Recursively walk through a `ParseStream` and examine all identifiers.
let diagnostic = match span { /// Emit an error for any identifiers that start with [`RESERVED_IDENT_PREFIX`]
Some(span) => Diagnostic::spanned(span, level, msg), /// (`__uwui_`).
None => Diagnostic::new(level, msg), ///
}; /// TODO: See if we can wrap ParseStream in a monad that does this check when
diagnostic.emit(); /// actually consuming the tokens
pub fn scan_for_reserved_idents(stream: ParseStream) -> syn::Result<ParseStream> {
let fork = stream.fork();
match scan_buf_for_ident_prefix(&fork, RESERVED_IDENT_PREFIX) {
Some(error) => Err(error),
None => Ok(stream),
}
}
fn scan_buf_for_ident_prefix(stream: ParseStream, prefix: &str) -> Option<Error> {
todo!()
} }

@ -5,9 +5,8 @@ pub mod widget;
pub use uwui_dsl::{template, CustomComponent, NativeComponent}; pub use uwui_dsl::{template, CustomComponent, NativeComponent};
use crate::view::Construct;
use layout::Gravity; use layout::Gravity;
use view::{GetTemplate, Template}; use view::{Construct, GetTemplate, Template};
use widget::{Button, Text, VStack}; use widget::{Button, Text, VStack};
fn asdf() -> Template { fn asdf() -> Template {

Loading…
Cancel
Save