Skip to content

Commit

Permalink
Allow Self in prop fields (#3569)
Browse files Browse the repository at this point in the history
* added replacement of `Self` in fields and attrs

* added more tests

* todo -> unimplemented
  • Loading branch information
its-the-shrimp committed Jul 25, 2024
1 parent 4b3c223 commit 89c9dfe
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 16 deletions.
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

0 comments on commit 89c9dfe

Please sign in to comment.