Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port yew-autoprops to yew-macro #3505

Merged
merged 22 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions packages/yew-macro/src/autoprops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use quote::quote;

use crate::function_component::FunctionComponentName;

#[derive(Clone)]
pub struct Autoprops {
item_fn: syn::ItemFn,
properties_name: syn::Ident,
}

impl syn::parse::Parse for Autoprops {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let parsed: syn::Item = input.parse()?;

let item_fn = match parsed {
syn::Item::Fn(m) => m,
item => {
return Err(syn::Error::new_spanned(
item,
"`autoprops` attribute can only be applied to functions",
))
}
};

let syn::ItemFn { attrs, sig, .. } = &item_fn;

let mut component_name = item_fn.sig.ident.clone();

attrs
.iter()
.find(|attr| {
match &attr.meta {
syn::Meta::Path(path) => {
if let Some(last_segment) = path.segments.last() {
if last_segment.ident == "function_component" {
return true;
}
}
}
syn::Meta::List(syn::MetaList { path, tokens, .. }) => {
if let Some(last_segment) = path.segments.last() {
if last_segment.ident == "function_component" {
if let Ok(attr) =
syn::parse2::<FunctionComponentName>(tokens.clone())
{
if let Some(name) = attr.component_name {
component_name = name;
}
}
return true;
}
}
}
_ => {}
}
false
})
.ok_or_else(|| {
syn::Error::new_spanned(
sig,
"could not find #[function_component] attribute in function declaration \
(#[autoprops] must be place *before* #[function_component])",
)
})?;

for input in &sig.inputs {
if let syn::FnArg::Typed(syn::PatType { pat, .. }) = input {
if let syn::Pat::Wild(wild) = pat.as_ref() {
return Err(syn::Error::new_spanned(
wild,
"cannot use `_` as field name",
));
}
}
}

let properties_name = syn::Ident::new(
&format!("{}Props", component_name),
proc_macro2::Span::call_site(),
);

Ok(Self {
properties_name,
item_fn,
})
}
}

impl Autoprops {
pub fn apply_args(&mut self, args: AutopropsArgs) {
if let Some(name) = args.properties_name {
self.properties_name = name;
}
}

fn print_function_component(&self) -> proc_macro2::TokenStream {
let properties_name = &self.properties_name;
let syn::ItemFn {
attrs,
vis,
sig,
block,
} = &self.item_fn;

let fn_name = &sig.ident;
let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl();
let inputs = if sig.inputs.is_empty() {
quote! { (): &() }
} else {
// NOTE: function components currently don't accept receivers, we're just passing the
// information to the next macro to fail and give its own error message
let receivers = sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(receiver) => Some(receiver),
_ => None,
})
.collect::<Vec<_>>();
let args = sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { #pat }),
_ => None,
})
.collect::<Vec<_>>();
quote! { #(#receivers,)* #properties_name { #(#args),* }: &#properties_name #type_generics }
};
let clones = sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(syn::PatType { pat, ty, .. })
if !matches!(**ty, syn::Type::Reference(_)) =>
{
Some(quote! { let #pat = ::std::clone::Clone::clone(#pat); })
}
_ => None,
})
.collect::<Vec<_>>();

quote! {
#(#attrs)*
#vis fn #fn_name #impl_generics (#inputs) -> ::yew::Html #where_clause {
#(#clones)*
#block
}
}
}

fn print_properties_struct(&self) -> proc_macro2::TokenStream {
let properties_name = &self.properties_name;
let syn::ItemFn { vis, sig, .. } = &self.item_fn;

if sig.inputs.is_empty() {
return quote! {};
}

let (impl_generics, _type_generics, where_clause) = sig.generics.split_for_impl();
let fields = sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => match ty.as_ref() {
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
Some(quote! { #(#attrs)* #pat: #elem, })
}
_ => Some(quote! { #(#attrs)* #pat: #ty, }),
},
_ => None,
})
.collect::<Vec<_>>();

quote! {
#[derive(::yew::Properties, ::std::cmp::PartialEq)]
#vis struct #properties_name #impl_generics #where_clause {
#(#fields)*
}
}
}
}

impl quote::ToTokens for Autoprops {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let function_component = self.print_function_component();
let properties_struct = self.print_properties_struct();

tokens.extend(quote! {
#function_component
#properties_struct
})
}
}

pub struct AutopropsArgs {
pub properties_name: Option<syn::Ident>,
}

impl syn::parse::Parse for AutopropsArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.is_empty() {
return Ok(Self {
properties_name: None,
});
}

let properties_name = input.parse()?;

Ok(Self {
properties_name: Some(properties_name),
})
}
}
34 changes: 31 additions & 3 deletions packages/yew-macro/src/function_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,32 @@ impl Parse for FunctionComponent {
block,
} = func;

if let Some(_attr) = attrs.iter().find(|attr| {
match &attr.meta {
syn::Meta::Path(path) => {
if let Some(last_segment) = path.segments.last() {
if last_segment.ident == "autoprops" {
return true;
}
}
}
syn::Meta::List(syn::MetaList { path, .. }) => {
if let Some(last_segment) = path.segments.last() {
if last_segment.ident == "autoprops" {
return true;
}
}
}
_ => {}
}
false
}) {
return Err(syn::Error::new_spanned(
sig,
"#[autoprops] must be placed *before* #[function_component]",
));
}

if sig.generics.lifetimes().next().is_some() {
return Err(syn::Error::new_spanned(
sig.generics,
Expand Down Expand Up @@ -111,7 +137,8 @@ impl Parse for FunctionComponent {
}
ty => {
let msg = format!(
"expected a reference to a `Properties` type (try: `&{}`)",
"expected a reference to a `Properties` type \
(try: `&{}` or use #[autoprops])",
ty.to_token_stream()
);
return Err(syn::Error::new_spanned(ty, msg));
Expand All @@ -133,7 +160,8 @@ impl Parse for FunctionComponent {
let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect();
return Err(syn::Error::new_spanned(
params,
"function components can accept at most one parameter for the props",
"function components can accept at most one parameter for the props, \
maybe you wanted to use #[autoprops]?",
));
}

Expand Down Expand Up @@ -393,7 +421,7 @@ impl FunctionComponent {
}

pub struct FunctionComponentName {
component_name: Option<Ident>,
pub component_name: Option<Ident>,
}

impl Parse for FunctionComponentName {
Expand Down
12 changes: 12 additions & 0 deletions packages/yew-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
//!
//! Please refer to [https://github.com/yewstack/yew](https://github.com/yewstack/yew) for how to set this up.

mod autoprops;
mod classes;
mod derive_props;
mod function_component;
Expand All @@ -58,6 +59,7 @@ mod stringify;
mod use_prepared_state;
mod use_transitive_state;

use autoprops::{Autoprops, AutopropsArgs};
use derive_props::DerivePropsInput;
use function_component::{function_component_impl, FunctionComponent, FunctionComponentName};
use hook::{hook_impl, HookFn};
Expand Down Expand Up @@ -148,6 +150,16 @@ pub fn function_component(attr: TokenStream, item: TokenStream) -> proc_macro::T
.into()
}

#[proc_macro_error::proc_macro_error]
#[proc_macro_attribute]
pub fn autoprops(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
let mut autoprops = parse_macro_input!(item as Autoprops);
let args = parse_macro_input!(attr as AutopropsArgs);
autoprops.apply_args(args);

TokenStream::from(autoprops.into_token_stream())
}

#[proc_macro_error::proc_macro_error]
#[proc_macro_attribute]
pub fn hook(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
Expand Down
66 changes: 66 additions & 0 deletions packages/yew-macro/tests/function_component_attr/autoprops-fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use yew::prelude::*;

#[autoprops]
#[function_component]
fn CantAcceptReceiver(&self, b: bool) -> Html {
html! {
<p>{b}</p>
}
}

#[autoprops]
fn not_a_function_component(b: bool) -> Html {
html! {
<p>{b}</p>
}
}

#[function_component(WrongAttrsOrder)]
#[autoprops]
fn wrong_attrs_order(b: bool) -> Html {
html! {
<p>{b}</p>
}
}

#[autoprops]
#[function_component(let)]
fn BadFunctionComponent(b: bool) -> Html {
html! {
<p>{b}</p>
}
}

#[derive(PartialEq)]
struct NotClonable(u32);

#[autoprops]
#[function_component]
fn TypeIsNotClone(stuff: NotClonable) -> Html {
drop(stuff);
html! {
<p></p>
}
}

#[derive(Clone)]
struct NotPartialEq(u32);

#[autoprops]
#[function_component]
fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html {
drop(stuff);
html! {
<p></p>
}
}

#[autoprops]
#[function_component]
fn InvalidFieldName(_: u32) -> Html {
html! {
<p></p>
}
}

fn main() {}
Loading
Loading