Skip to content

Commit 17cc19c

Browse files
its-the-shrimpgeoffjay
authored andcommitted
Allow Self in prop fields (yewstack#3569)
* added replacement of `Self` in fields and attrs * added more tests * todo -> unimplemented
1 parent 56e3ce2 commit 17cc19c

File tree

5 files changed

+119
-16
lines changed

5 files changed

+119
-16
lines changed

packages/yew-macro/src/derive_props/field.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::derive_props::generics::push_type_param;
1212

1313
#[allow(clippy::large_enum_variant)]
1414
#[derive(PartialEq, Eq)]
15-
enum PropAttr {
15+
pub enum PropAttr {
1616
Required { wrapped_name: Ident },
1717
PropOr(Expr),
1818
PropOrElse(Expr),
@@ -21,9 +21,9 @@ enum PropAttr {
2121

2222
#[derive(Eq)]
2323
pub struct PropField {
24-
ty: Type,
24+
pub ty: Type,
2525
name: Ident,
26-
attr: PropAttr,
26+
pub attr: PropAttr,
2727
extra_attrs: Vec<Attribute>,
2828
}
2929

packages/yew-macro/src/derive_props/mod.rs

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@ use field::PropField;
1010
use proc_macro2::{Ident, Span};
1111
use quote::{format_ident, quote, ToTokens};
1212
use syn::parse::{Parse, ParseStream, Result};
13-
use syn::{Attribute, DeriveInput, Generics, Visibility};
13+
use syn::punctuated::Pair;
14+
use syn::visit_mut::VisitMut;
15+
use syn::{
16+
AngleBracketedGenericArguments, Attribute, ConstParam, DeriveInput, GenericArgument,
17+
GenericParam, Generics, Path, PathArguments, PathSegment, Type, TypeParam, TypePath,
18+
Visibility,
19+
};
1420
use wrapper::PropsWrapper;
1521

22+
use self::field::PropAttr;
1623
use self::generics::to_arguments;
1724

1825
pub struct DerivePropsInput {
@@ -23,6 +30,76 @@ pub struct DerivePropsInput {
2330
preserved_attrs: Vec<Attribute>,
2431
}
2532

33+
/// AST visitor that replaces all occurences of the keyword `Self` with `new_self`
34+
struct Normaliser<'ast> {
35+
new_self: &'ast Ident,
36+
generics: &'ast Generics,
37+
/// `Option` for one-time initialisation
38+
new_self_full: Option<PathSegment>,
39+
}
40+
41+
impl<'ast> Normaliser<'ast> {
42+
pub fn new(new_self: &'ast Ident, generics: &'ast Generics) -> Self {
43+
Self {
44+
new_self,
45+
generics,
46+
new_self_full: None,
47+
}
48+
}
49+
50+
fn get_new_self(&mut self) -> PathSegment {
51+
self.new_self_full
52+
.get_or_insert_with(|| {
53+
PathSegment {
54+
ident: self.new_self.clone(),
55+
arguments: if self.generics.lt_token.is_some() {
56+
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
57+
colon2_token: Some(Default::default()),
58+
lt_token: Default::default(),
59+
args: self
60+
.generics
61+
.params
62+
.pairs()
63+
.map(|pair| {
64+
let (value, punct) = pair.cloned().into_tuple();
65+
let value = match value {
66+
GenericParam::Lifetime(param) => {
67+
GenericArgument::Lifetime(param.lifetime)
68+
}
69+
GenericParam::Type(TypeParam { ident, .. })
70+
| GenericParam::Const(ConstParam { ident, .. }) => {
71+
GenericArgument::Type(Type::Path(TypePath {
72+
qself: None,
73+
path: ident.into(),
74+
}))
75+
}
76+
};
77+
Pair::new(value, punct)
78+
})
79+
.collect(),
80+
gt_token: Default::default(),
81+
})
82+
} else {
83+
// if no generics were defined for the struct
84+
PathArguments::None
85+
},
86+
}
87+
})
88+
.clone()
89+
}
90+
}
91+
92+
impl VisitMut for Normaliser<'_> {
93+
fn visit_path_mut(&mut self, path: &mut Path) {
94+
if let Some(first) = path.segments.first_mut() {
95+
if first.ident == "Self" {
96+
*first = self.get_new_self();
97+
}
98+
syn::visit_mut::visit_path_mut(self, path)
99+
}
100+
}
101+
}
102+
26103
/// Some attributes on the original struct are to be preserved and added to the builder struct,
27104
/// in order to avoid warnings (sometimes reported as errors) in the output.
28105
fn should_preserve_attr(attr: &Attribute) -> bool {
@@ -74,22 +151,33 @@ impl Parse for DerivePropsInput {
74151
}
75152
}
76153

154+
impl DerivePropsInput {
155+
/// Replaces all occurences of `Self` in the struct with the actual name of the struct.
156+
/// Must be called before tokenising the struct.
157+
pub fn normalise(&mut self) {
158+
let mut normaliser = Normaliser::new(&self.props_name, &self.generics);
159+
for field in &mut self.prop_fields {
160+
normaliser.visit_type_mut(&mut field.ty);
161+
if let PropAttr::PropOr(expr) | PropAttr::PropOrElse(expr) = &mut field.attr {
162+
normaliser.visit_expr_mut(expr)
163+
}
164+
}
165+
}
166+
}
167+
77168
impl ToTokens for DerivePropsInput {
78169
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
79170
let Self {
80171
generics,
81172
props_name,
173+
prop_fields,
174+
preserved_attrs,
82175
..
83176
} = self;
84177

85178
// The wrapper is a new struct which wraps required props in `Option`
86179
let wrapper_name = format_ident!("{}Wrapper", props_name, span = Span::mixed_site());
87-
let wrapper = PropsWrapper::new(
88-
&wrapper_name,
89-
generics,
90-
&self.prop_fields,
91-
&self.preserved_attrs,
92-
);
180+
let wrapper = PropsWrapper::new(&wrapper_name, generics, prop_fields, preserved_attrs);
93181
tokens.extend(wrapper.into_token_stream());
94182

95183
// The builder will only build if all required props have been set
@@ -101,7 +189,7 @@ impl ToTokens for DerivePropsInput {
101189
self,
102190
&wrapper_name,
103191
&check_all_props_name,
104-
&self.preserved_attrs,
192+
preserved_attrs,
105193
);
106194
let generic_args = to_arguments(generics);
107195
tokens.extend(builder.into_token_stream());

packages/yew-macro/src/derive_props/wrapper.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,21 @@ impl ToTokens for PropsWrapper<'_> {
4848
}
4949
}
5050

51-
impl<'a> PropsWrapper<'_> {
51+
impl<'a> PropsWrapper<'a> {
5252
pub fn new(
5353
name: &'a Ident,
5454
generics: &'a Generics,
5555
prop_fields: &'a [PropField],
5656
extra_attrs: &'a [Attribute],
57-
) -> PropsWrapper<'a> {
57+
) -> Self {
5858
PropsWrapper {
5959
wrapper_name: name,
6060
generics,
6161
prop_fields,
6262
extra_attrs,
6363
}
6464
}
65-
}
6665

67-
impl PropsWrapper<'_> {
6866
fn field_defs(&self) -> impl Iterator<Item = impl ToTokens + '_> {
6967
self.prop_fields.iter().map(|pf| pf.to_field_def())
7068
}

packages/yew-macro/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ fn is_ide_completion() -> bool {
108108

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

packages/yew-macro/tests/props_macro/props-pass.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ pub struct RawIdentProps {
5656
r#pointless_raw_name: ::std::primitive::usize,
5757
}
5858

59+
#[derive(::yew::Properties)]
60+
pub struct SelfRefProps<'a, T> {
61+
x: ::std::boxed::Box<T>,
62+
y: ::std::boxed::Box<Self>,
63+
z: &'a Self,
64+
a: ::std::marker::PhantomData<(&'a Self, Self)>,
65+
b: ::std::marker::PhantomData<::std::boxed::Box<Self>>,
66+
c: fn(&Self) -> Self,
67+
}
68+
69+
impl<T> ::std::cmp::PartialEq for SelfRefProps<'_, T> {
70+
fn eq(&self, _: &Self) -> ::std::primitive::bool {
71+
::std::unimplemented!()
72+
}
73+
}
74+
5975
fn pass_raw_idents() {
6076
::yew::props!(RawIdentProps { r#true: 5 });
6177
let (r#true, r#pointless_raw_name) = (3, 5);

0 commit comments

Comments
 (0)