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
parent
e79902e911
commit
b0feea938a
@ -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()
|
||||
}
|
@ -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!()
|
||||
}
|
||||
|
Loading…
Reference in New Issue