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

Allow Self in prop fields #3569

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions packages/yew-macro/src/derive_props/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::derive_props::generics::push_type_param;

#[allow(clippy::large_enum_variant)]
#[derive(PartialEq, Eq)]
enum PropAttr {
pub enum PropAttr {
Required { wrapped_name: Ident },
PropOr(Expr),
PropOrElse(Expr),
Expand All @@ -21,9 +21,9 @@ enum PropAttr {

#[derive(Eq)]
pub struct PropField {
ty: Type,
pub ty: Type,
name: Ident,
attr: PropAttr,
pub attr: PropAttr,
extra_attrs: Vec<Attribute>,
}

Expand Down
104 changes: 96 additions & 8 deletions packages/yew-macro/src/derive_props/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ use field::PropField;
use proc_macro2::{Ident, Span};
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};
use syn::{Attribute, DeriveInput, Generics, Visibility};
use syn::punctuated::Pair;
use syn::visit_mut::VisitMut;
use syn::{
AngleBracketedGenericArguments, Attribute, ConstParam, DeriveInput, GenericArgument,
GenericParam, Generics, Path, PathArguments, PathSegment, Type, TypeParam, TypePath,
Visibility,
};
use wrapper::PropsWrapper;

use self::field::PropAttr;
use self::generics::to_arguments;

pub struct DerivePropsInput {
Expand All @@ -23,6 +30,76 @@ pub struct DerivePropsInput {
preserved_attrs: Vec<Attribute>,
}

/// AST visitor that replaces all occurences of the keyword `Self` with `new_self`
struct Normaliser<'ast> {
new_self: &'ast Ident,
generics: &'ast Generics,
/// `Option` for one-time initialisation
new_self_full: Option<PathSegment>,
}

impl<'ast> Normaliser<'ast> {
pub fn new(new_self: &'ast Ident, generics: &'ast Generics) -> Self {
Self {
new_self,
generics,
new_self_full: None,
}
}

fn get_new_self(&mut self) -> PathSegment {
self.new_self_full
.get_or_insert_with(|| {
PathSegment {
ident: self.new_self.clone(),
arguments: if self.generics.lt_token.is_some() {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: Some(Default::default()),
lt_token: Default::default(),
args: self
.generics
.params
.pairs()
.map(|pair| {
let (value, punct) = pair.cloned().into_tuple();
let value = match value {
GenericParam::Lifetime(param) => {
GenericArgument::Lifetime(param.lifetime)
}
GenericParam::Type(TypeParam { ident, .. })
| GenericParam::Const(ConstParam { ident, .. }) => {
GenericArgument::Type(Type::Path(TypePath {
qself: None,
path: ident.into(),
}))
}
};
Pair::new(value, punct)
})
.collect(),
gt_token: Default::default(),
})
} else {
// if no generics were defined for the struct
PathArguments::None
},
}
})
.clone()
}
}

impl VisitMut for Normaliser<'_> {
fn visit_path_mut(&mut self, path: &mut Path) {
if let Some(first) = path.segments.first_mut() {
if first.ident == "Self" {
*first = self.get_new_self();
}
syn::visit_mut::visit_path_mut(self, path)
}
}
}

/// Some attributes on the original struct are to be preserved and added to the builder struct,
/// in order to avoid warnings (sometimes reported as errors) in the output.
fn should_preserve_attr(attr: &Attribute) -> bool {
Expand Down Expand Up @@ -74,22 +151,33 @@ impl Parse for DerivePropsInput {
}
}

impl DerivePropsInput {
/// Replaces all occurences of `Self` in the struct with the actual name of the struct.
/// Must be called before tokenising the struct.
pub fn normalise(&mut self) {
let mut normaliser = Normaliser::new(&self.props_name, &self.generics);
for field in &mut self.prop_fields {
normaliser.visit_type_mut(&mut field.ty);
if let PropAttr::PropOr(expr) | PropAttr::PropOrElse(expr) = &mut field.attr {
normaliser.visit_expr_mut(expr)
}
}
}
}

impl ToTokens for DerivePropsInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
generics,
props_name,
prop_fields,
preserved_attrs,
..
} = self;

// The wrapper is a new struct which wraps required props in `Option`
let wrapper_name = format_ident!("{}Wrapper", props_name, span = Span::mixed_site());
let wrapper = PropsWrapper::new(
&wrapper_name,
generics,
&self.prop_fields,
&self.preserved_attrs,
);
let wrapper = PropsWrapper::new(&wrapper_name, generics, prop_fields, preserved_attrs);
tokens.extend(wrapper.into_token_stream());

// The builder will only build if all required props have been set
Expand All @@ -101,7 +189,7 @@ impl ToTokens for DerivePropsInput {
self,
&wrapper_name,
&check_all_props_name,
&self.preserved_attrs,
preserved_attrs,
);
let generic_args = to_arguments(generics);
tokens.extend(builder.into_token_stream());
Expand Down
6 changes: 2 additions & 4 deletions packages/yew-macro/src/derive_props/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,21 @@ impl ToTokens for PropsWrapper<'_> {
}
}

impl<'a> PropsWrapper<'_> {
impl<'a> PropsWrapper<'a> {
pub fn new(
name: &'a Ident,
generics: &'a Generics,
prop_fields: &'a [PropField],
extra_attrs: &'a [Attribute],
) -> PropsWrapper<'a> {
) -> Self {
PropsWrapper {
wrapper_name: name,
generics,
prop_fields,
extra_attrs,
}
}
}

impl PropsWrapper<'_> {
fn field_defs(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| pf.to_field_def())
}
Expand Down
3 changes: 2 additions & 1 deletion packages/yew-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ fn is_ide_completion() -> bool {

#[proc_macro_derive(Properties, attributes(prop_or, prop_or_else, prop_or_default))]
pub fn derive_props(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DerivePropsInput);
let mut input = parse_macro_input!(input as DerivePropsInput);
input.normalise();
TokenStream::from(input.into_token_stream())
}

Expand Down
16 changes: 16 additions & 0 deletions packages/yew-macro/tests/props_macro/props-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ pub struct RawIdentProps {
r#pointless_raw_name: ::std::primitive::usize,
}

#[derive(::yew::Properties)]
pub struct SelfRefProps<'a, T> {
x: ::std::boxed::Box<T>,
y: ::std::boxed::Box<Self>,
z: &'a Self,
a: ::std::marker::PhantomData<(&'a Self, Self)>,
b: ::std::marker::PhantomData<::std::boxed::Box<Self>>,
c: fn(&Self) -> Self,
}

impl<T> ::std::cmp::PartialEq for SelfRefProps<'_, T> {
fn eq(&self, _: &Self) -> ::std::primitive::bool {
::std::unimplemented!()
}
}

fn pass_raw_idents() {
::yew::props!(RawIdentProps { r#true: 5 });
let (r#true, r#pointless_raw_name) = (3, 5);
Expand Down
Loading