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.
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]]
name = "proc-macro2"
version = "1.0.67"
@ -31,6 +68,23 @@ dependencies = [
"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]]
name = "unicode-ident"
version = "1.0.12"
@ -48,7 +102,17 @@ dependencies = [
name = "uwui-dsl"
version = "0.1.0"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"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
[dependencies]
proc-macro-crate = "2.0.0"
proc-macro2 = "1.0.67"
quote = "1.0.33"
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 native_component;
pub mod 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 template;
mod util;
#[proc_macro]
pub fn template(input: TokenStream) -> TokenStream {
@ -12,10 +13,10 @@ pub fn template(input: TokenStream) -> TokenStream {
#[proc_macro_derive(NativeComponent)]
pub fn native_component(input: TokenStream) -> TokenStream {
derive::native_component::invoke(input)
derive::component::native(input)
}
#[proc_macro_derive(CustomComponent)]
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::util::{define_ident, use_absolute};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
@ -17,49 +18,62 @@ impl ToTokens for Body {
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream) {
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() {
Some(p) => match p {
#[allow(non_snake_case)]
Params::Anon(params) => quote! {
::core::convert::Into::<<#name as crate::view::Construct>::Params>::into(
(#(::core::convert::Into::<_>::into(#params)),*,)
#Into::<<#name as #Construct>::Params>::into(
(#(#Into::<_>::into(#params)),*,)
)
},
Params::Named(params) => quote! {
{
let mut __uwui_params = <
<#name as crate::view::Construct>::Params as ::core::default::Default
let mut #construct_params = <
<#name as #Construct>::Params as #Default
>::default();
#(__uwui_params.#params)*
__uwui_params
#(#construct_params.#params)*
#construct_params
}
},
},
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 body = body.elements.as_slice();
quote! {
<
#name as crate::view::ConstructGroup::<crate::backend::TargetBackend>
>::construct_group(
< #name as #ConstructGroup::<#TargetBackend> >::construct_group(
#params,
::core::iter::IntoIterator::into_iter([
#IntoIterator::into_iter([
#(#body),*
])
)
}
} else {
quote! {
<#name as crate::view::Construct>::construct(#params)
<#name as #Construct>::construct(#params)
}
};
let initializer = quote! {
crate::view::View::<crate::backend::TargetBackend>::new(
crate::view::__internal::IntoNode::<crate::backend::TargetBackend>::into_node(
#View::<#TargetBackend>::new(
#IntoNode::<#TargetBackend>::into_node(
#initializer
)
)

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

@ -1,30 +1,92 @@
use proc_macro::{Diagnostic, Level, Span};
use proc_macro2::TokenStream;
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{parse::ParseStream, Error};
pub trait Compile: Sized {
fn compile(self) -> TokenStream;
pub fn __make_absolute_path(name: &str, input: TokenStream) -> 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>) {
emit_diagnostic(span.into(), Level::Error, msg)
macro_rules! absolute_path {
($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>) {
emit_diagnostic(span.into(), Level::Warning, msg)
macro_rules! use_absolute {
($($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>) {
emit_diagnostic(span.into(), Level::Note, msg)
pub fn make_reserved(name: &str, span: impl Into<Option<Span>>) -> Ident {
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>) {
emit_diagnostic(span.into(), Level::Help, msg)
macro_rules! define_ident {
($($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>) {
let diagnostic = match span {
Some(span) => Diagnostic::spanned(span, level, msg),
None => Diagnostic::new(level, msg),
};
diagnostic.emit();
/// Recursively walk through a `ParseStream` and examine all identifiers.
/// Emit an error for any identifiers that start with [`RESERVED_IDENT_PREFIX`]
/// (`__uwui_`).
///
/// TODO: See if we can wrap ParseStream in a monad that does this check when
/// 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};
use crate::view::Construct;
use layout::Gravity;
use view::{GetTemplate, Template};
use view::{Construct, GetTemplate, Template};
use widget::{Button, Text, VStack};
fn asdf() -> Template {

Loading…
Cancel
Save