Add lifetime support to derive props macro

This commit is contained in:
Justin Starry 2019-08-11 16:08:34 -04:00
parent c9d5c70af0
commit 74373e6164
10 changed files with 627 additions and 346 deletions

View File

@ -4,6 +4,8 @@
- #### ⚡️ Features
- The derive props macro now supports Properties with lifetimes [[@jstarry], [#580](https://github.com/yewstack/yew/pull/580)]
- #### 🛠 Fixes
- #### 🚨 Breaking changes

View File

@ -1,342 +0,0 @@
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use std::convert::{TryFrom, TryInto};
use std::iter;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated;
use syn::spanned::Spanned;
use syn::{
DeriveInput, Error, Field, GenericParam, Generics, Meta, MetaList, NestedMeta, Type, TypeParam,
Visibility,
};
struct PropField {
ty: Type,
name: Ident,
wrapped_name: Option<Ident>,
}
impl TryFrom<Field> for PropField {
type Error = Error;
fn try_from(field: Field) -> Result<Self> {
Ok(PropField {
wrapped_name: Self::required_wrapper(&field)?,
ty: field.ty,
name: field.ident.unwrap(),
})
}
}
pub struct DerivePropsInput {
vis: Visibility,
generics: Generics,
props_name: Ident,
prop_fields: Vec<PropField>,
}
impl Parse for DerivePropsInput {
fn parse(input: ParseStream) -> Result<Self> {
let input: DeriveInput = input.parse()?;
let named_fields = match input.data {
syn::Data::Struct(data) => match data.fields {
syn::Fields::Named(fields) => fields.named,
_ => unimplemented!("only structs are supported"),
},
_ => unimplemented!("only structs are supported"),
};
let mut prop_fields: Vec<PropField> = named_fields
.into_iter()
.map(|f| f.try_into())
.collect::<Result<Vec<PropField>>>()?;
// Alphabetize
prop_fields.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
Ok(Self {
vis: input.vis,
props_name: input.ident,
generics: input.generics,
prop_fields,
})
}
}
impl ToTokens for DerivePropsInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
vis,
generics,
props_name,
..
} = self;
let generic_params = &generics.params;
let generic_where = &generics.where_clause;
let generic_types = self.generic_types();
let wrapped_name = Ident::new(&format!("Wrapped{}", props_name), Span::call_site());
let wrapped_field_defs = self.wrapped_field_defs();
let wrapped_default_setters = self.wrapped_default_setters();
let builder_name = Ident::new(&format!("{}Builder", props_name), Span::call_site());
let builder_step = Ident::new(&format!("{}BuilderStep", props_name), Span::call_site());
let builder_step_names = self.builder_step_names();
let builder_start_step = &builder_step_names[0];
let builder_build_step = &builder_step_names[builder_step_names.len() - 1];
let builder_steps = &builder_step_names;
let builder_step_repeat = iter::repeat(&builder_step);
let impl_builder_for_steps = self.impl_builder_for_steps(&builder_name, &builder_steps);
let builder_set_fields = self.builder_set_fields();
let vis_repeat = iter::repeat(&vis);
let expanded = quote! {
struct #wrapped_name#generics {
#(#wrapped_field_defs)*
}
impl#generics ::std::default::Default for #wrapped_name<#generic_types> #generic_where {
fn default() -> Self {
#wrapped_name::<#generic_types> {
#(#wrapped_default_setters)*
}
}
}
#(
#[doc(hidden)]
#vis_repeat struct #builder_steps;
)*
#[doc(hidden)]
#vis trait #builder_step {}
#(impl #builder_step_repeat for #builder_steps {})*
#[doc(hidden)]
#vis struct #builder_name<YEW_PROPS_BUILDER_STEP: #builder_step, #generic_params> #generic_where {
wrapped: ::std::boxed::Box<#wrapped_name<#generic_types>>,
_marker: ::std::marker::PhantomData<YEW_PROPS_BUILDER_STEP>,
}
#(#impl_builder_for_steps)*
impl #generics #builder_name<#builder_build_step, #generic_types> #generic_where {
#[doc(hidden)]
#vis fn build(self) -> #props_name<#generic_types> {
#props_name::<#generic_types> {
#(#builder_set_fields)*
}
}
}
impl #generics ::yew::html::Properties for #props_name<#generic_types> #generic_where {
type Builder = #builder_name<#builder_start_step, #generic_types>;
fn builder() -> Self::Builder {
#builder_name {
wrapped: ::std::boxed::Box::new(::std::default::Default::default()),
_marker: ::std::marker::PhantomData,
}
}
}
};
tokens.extend(proc_macro2::TokenStream::from(expanded));
}
}
impl PropField {
fn required_wrapper(named_field: &syn::Field) -> Result<Option<Ident>> {
let meta_list = if let Some(meta_list) = Self::find_props_meta_list(named_field) {
meta_list
} else {
return Ok(None);
};
let expected_required = syn::Error::new(meta_list.span(), "expected `props(required)`");
let first_nested = if let Some(first_nested) = meta_list.nested.first() {
first_nested
} else {
return Err(expected_required);
};
let word_ident = match first_nested {
punctuated::Pair::End(NestedMeta::Meta(Meta::Word(ident))) => ident,
_ => return Err(expected_required),
};
if word_ident != "required" {
return Err(expected_required);
}
if let Some(ident) = &named_field.ident {
Ok(Some(Ident::new(
&format!("{}_wrapper", ident),
Span::call_site(),
)))
} else {
unreachable!()
}
}
fn find_props_meta_list(field: &syn::Field) -> Option<MetaList> {
let meta_list = field
.attrs
.iter()
.find_map(|attr| match attr.parse_meta().ok()? {
Meta::List(meta_list) => Some(meta_list),
_ => None,
})?;
if meta_list.ident == "props" {
Some(meta_list)
} else {
None
}
}
}
impl DerivePropsInput {
fn generic_types(&self) -> proc_macro2::TokenStream {
let generic_types = self.generics.params.iter().map(|param| match param {
GenericParam::Type(TypeParam { ident, .. }) => ident,
_ => unimplemented!("only generic types are supported"),
});
quote! {#(#generic_types),*}
}
fn builder_step_names(&self) -> Vec<Ident> {
let mut step_names: Vec<Ident> = self
.prop_fields
.iter()
.filter(|prop_field| prop_field.wrapped_name.is_some())
.map(|prop_field| {
Ident::new(
&format!("{}_{}_is_required", self.props_name, prop_field.name),
Span::call_site(),
)
})
.collect();
step_names.push(Ident::new(
&format!("{}BuildStep", self.props_name),
Span::call_site(),
));
step_names
}
fn wrapped_field_defs(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| {
let PropField { name, ty, .. } = &pf;
if let Some(wrapped_name) = &pf.wrapped_name {
quote! {
#wrapped_name: ::std::option::Option<#ty>,
}
} else {
quote! {
#name: #ty,
}
}
})
}
fn wrapped_default_setters(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| {
if let Some(wrapped_name) = &pf.wrapped_name {
quote! {
#wrapped_name: ::std::default::Default::default(),
}
} else {
let name = &pf.name;
quote! {
#name: ::std::default::Default::default(),
}
}
})
}
fn builder_set_fields(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| {
let name = &pf.name;
if let Some(wrapped_name) = &pf.wrapped_name {
quote! {
#name: self.wrapped.#wrapped_name.unwrap(),
}
} else {
quote! {
#name: self.wrapped.#name,
}
}
})
}
fn impl_builder_for_steps(
&self,
builder_name: &Ident,
builder_step_names: &[Ident],
) -> proc_macro2::TokenStream {
let Self { vis, generics, .. } = self;
let generic_types = self.generic_types();
let generic_where = &generics.where_clause;
let mut fields_index = 0;
let mut token_stream = proc_macro2::TokenStream::new();
for (step, step_name) in builder_step_names.iter().enumerate() {
let mut optional_fields = Vec::new();
let mut required_field = None;
if fields_index >= self.prop_fields.len() {
break;
}
while let Some(pf) = self.prop_fields.get(fields_index) {
fields_index += 1;
if pf.wrapped_name.is_some() {
required_field = Some(pf);
break;
} else {
optional_fields.push((&pf.name, &pf.ty));
}
}
let optional_prop_fn = optional_fields.into_iter().map(|(prop_name, prop_type)| {
quote! {
#[doc(hidden)]
#vis fn #prop_name(mut self, #prop_name: #prop_type) -> #builder_name<#step_name, #generic_types> {
self.wrapped.#prop_name = #prop_name;
self
}
}
});
let required_prop_fn = required_field.iter().map(|p| {
let prop_name = &p.name;
let prop_type = &p.ty;
let wrapped_name = p.wrapped_name.as_ref().unwrap();
let next_step_name = &builder_step_names[step + 1];
quote! {
#[doc(hidden)]
#vis fn #prop_name(mut self, #prop_name: #prop_type) -> #builder_name<#next_step_name, #generic_types> {
self.wrapped.#wrapped_name = ::std::option::Option::Some(#prop_name);
#builder_name {
wrapped: self.wrapped,
_marker: ::std::marker::PhantomData,
}
}
}
});
token_stream.extend(quote! {
impl #generics #builder_name<#step_name, #generic_types> #generic_where {
#(#optional_prop_fn)*
#(#required_prop_fn)*
}
});
}
token_stream
}
}

View File

@ -0,0 +1,194 @@
//! The `PropsBuilder` constructs props in alphabetical order and enforces that required props have
//! been set before allowing the build to complete. Each property has a corresponding method in the
//! builder. Required property builder methods advance the builder to the next step, optional
//! properties can be added or skipped with no effect on the build step. Once all of required
//! properties have been set, the builder moves to the final build step which implements the
//! `build()` method.
use super::generics::{to_arguments, with_param_bounds, GenericArguments};
use super::{DerivePropsInput, PropField};
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use std::iter;
pub struct PropsBuilder<'a> {
builder_name: &'a Ident,
step_trait: &'a Ident,
step_names: Vec<Ident>,
props: &'a DerivePropsInput,
wrapper_name: &'a Ident,
}
impl ToTokens for PropsBuilder<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
builder_name,
step_trait,
step_names,
props,
wrapper_name,
} = self;
let DerivePropsInput {
vis,
generics,
props_name,
..
} = props;
let step_trait_repeat = iter::repeat(step_trait);
let vis_repeat = iter::repeat(&vis);
let build_step = self.build_step();
let impl_steps = self.impl_steps();
let set_fields = self.set_fields();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let turbofish_generics = ty_generics.as_turbofish();
let generic_args = to_arguments(&generics, build_step.clone());
// Each builder step implements the `BuilderStep` trait and `step_generics` is used to
// enforce that.
let step_generic_param = Ident::new("YEW_PROPS_BUILDER_STEP", Span::call_site());
let step_generics = with_param_bounds(
&generics,
step_generic_param.clone(),
step_trait.clone().to_owned(),
);
let builder = quote! {
#(
#[doc(hidden)]
#vis_repeat struct #step_names;
)*
#[doc(hidden)]
#vis trait #step_trait {}
#(impl #step_trait_repeat for #step_names {})*
#[doc(hidden)]
#vis struct #builder_name#step_generics {
wrapped: ::std::boxed::Box<#wrapper_name#ty_generics>,
_marker: ::std::marker::PhantomData<#step_generic_param>,
}
#(#impl_steps)*
impl#impl_generics #builder_name<#generic_args> #where_clause {
#[doc(hidden)]
#vis fn build(self) -> #props_name#ty_generics {
#props_name#turbofish_generics {
#(#set_fields)*
}
}
}
};
tokens.extend(builder);
}
}
impl<'a> PropsBuilder<'_> {
pub fn new(
name: &'a Ident,
step_trait: &'a Ident,
props: &'a DerivePropsInput,
wrapper_name: &'a Ident,
) -> PropsBuilder<'a> {
PropsBuilder {
builder_name: name,
step_trait,
step_names: Self::build_step_names(step_trait, &props.prop_fields),
props,
wrapper_name,
}
}
}
impl PropsBuilder<'_> {
pub fn first_step_generic_args(&self) -> GenericArguments {
to_arguments(&self.props.generics, self.first_step().clone())
}
fn first_step(&self) -> &Ident {
&self.step_names[0]
}
fn build_step(&self) -> &Ident {
&self.step_names[self.step_names.len() - 1]
}
fn build_step_names(prefix: &Ident, prop_fields: &[PropField]) -> Vec<Ident> {
let mut step_names: Vec<Ident> = prop_fields
.iter()
.filter(|pf| pf.is_required())
.map(|pf| pf.to_step_name(prefix))
.collect();
step_names.push(Ident::new(&format!("{}_build", prefix), Span::call_site()));
step_names
}
fn set_fields(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.props.prop_fields.iter().map(|pf| pf.to_field_setter())
}
fn impl_steps(&self) -> proc_macro2::TokenStream {
let Self {
builder_name,
props,
step_names,
..
} = self;
let DerivePropsInput {
vis,
generics,
prop_fields,
..
} = props;
let (impl_generics, _, where_clause) = generics.split_for_impl();
let mut fields_index = 0;
let mut token_stream = proc_macro2::TokenStream::new();
for (step, step_name) in step_names.iter().enumerate() {
let mut optional_fields = Vec::new();
let mut required_field = None;
if fields_index >= prop_fields.len() {
break;
}
while let Some(pf) = prop_fields.get(fields_index) {
fields_index += 1;
if pf.is_required() {
required_field = Some(pf);
break;
} else {
optional_fields.push(pf);
}
}
// Optional properties keep the builder on the current step
let current_step_arguments = to_arguments(generics, step_name.clone());
let optional_prop_fn = optional_fields
.iter()
.map(|pf| pf.to_build_step_fn(builder_name, &current_step_arguments, vis));
// Required properties will advance the builder to the next step
let required_prop_fn = required_field.iter().map(|pf| {
let next_step_name = &step_names[step + 1];
let next_step_arguments = to_arguments(generics, next_step_name.clone());
pf.to_build_step_fn(builder_name, &next_step_arguments, vis)
});
token_stream.extend(quote! {
impl#impl_generics #builder_name<#current_step_arguments> #where_clause {
#(#optional_prop_fn)*
#(#required_prop_fn)*
}
});
}
token_stream
}
}

View File

@ -0,0 +1,188 @@
use super::generics::GenericArguments;
use proc_macro2::{Ident, Span};
use quote::quote;
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::convert::TryFrom;
use syn::parse::Result;
use syn::punctuated;
use syn::spanned::Spanned;
use syn::{Error, Field, Meta, MetaList, NestedMeta, Type, Visibility};
#[derive(Eq)]
pub struct PropField {
ty: Type,
name: Ident,
wrapped_name: Option<Ident>,
}
impl PropField {
/// All required property fields are wrapped in an `Option`
pub fn is_required(&self) -> bool {
self.wrapped_name.is_some()
}
/// This step name is descriptive to help a developer realize they missed a required prop
pub fn to_step_name(&self, props_name: &Ident) -> Ident {
Ident::new(
&format!("{}_missing_required_prop_{}", props_name, self.name),
Span::call_site(),
)
}
/// Used to transform the `PropWrapper` struct into `Properties`
pub fn to_field_setter(&self) -> proc_macro2::TokenStream {
let name = &self.name;
if let Some(wrapped_name) = &self.wrapped_name {
quote! {
#name: self.wrapped.#wrapped_name.unwrap(),
}
} else {
quote! {
#name: self.wrapped.#name,
}
}
}
/// Wrap all required props in `Option`
pub fn to_field_def(&self) -> proc_macro2::TokenStream {
let ty = &self.ty;
if let Some(wrapped_name) = &self.wrapped_name {
quote! {
#wrapped_name: ::std::option::Option<#ty>,
}
} else {
let name = &self.name;
quote! {
#name: #ty,
}
}
}
/// All optional props must implement the `Default` trait
pub fn to_default_setter(&self) -> proc_macro2::TokenStream {
if let Some(wrapped_name) = &self.wrapped_name {
quote! {
#wrapped_name: ::std::default::Default::default(),
}
} else {
let name = &self.name;
quote! {
#name: ::std::default::Default::default(),
}
}
}
/// Each field is set using a builder method
pub fn to_build_step_fn(
&self,
builder_name: &Ident,
generic_arguments: &GenericArguments,
vis: &Visibility,
) -> proc_macro2::TokenStream {
let Self {
name,
ty,
wrapped_name,
} = self;
if let Some(wrapped_name) = wrapped_name {
quote! {
#[doc(hidden)]
#vis fn #name(mut self, #name: #ty) -> #builder_name<#generic_arguments> {
self.wrapped.#wrapped_name = ::std::option::Option::Some(#name);
#builder_name {
wrapped: self.wrapped,
_marker: ::std::marker::PhantomData,
}
}
}
} else {
quote! {
#[doc(hidden)]
#vis fn #name(mut self, #name: #ty) -> #builder_name<#generic_arguments> {
self.wrapped.#name = #name;
self
}
}
}
}
// Detect the `#[props(required)]` attribute which denotes required fields
fn required_wrapper(named_field: &syn::Field) -> Result<Option<Ident>> {
let meta_list = if let Some(meta_list) = Self::find_props_meta_list(named_field) {
meta_list
} else {
return Ok(None);
};
let expected_required = syn::Error::new(meta_list.span(), "expected `props(required)`");
let first_nested = if let Some(first_nested) = meta_list.nested.first() {
first_nested
} else {
return Err(expected_required);
};
let word_ident = match first_nested {
punctuated::Pair::End(NestedMeta::Meta(Meta::Word(ident))) => ident,
_ => return Err(expected_required),
};
if word_ident != "required" {
return Err(expected_required);
}
if let Some(ident) = &named_field.ident {
Ok(Some(Ident::new(
&format!("{}_wrapper", ident),
Span::call_site(),
)))
} else {
unreachable!()
}
}
fn find_props_meta_list(field: &syn::Field) -> Option<MetaList> {
let meta_list = field
.attrs
.iter()
.find_map(|attr| match attr.parse_meta().ok()? {
Meta::List(meta_list) => Some(meta_list),
_ => None,
})?;
if meta_list.ident == "props" {
Some(meta_list)
} else {
None
}
}
}
impl TryFrom<Field> for PropField {
type Error = Error;
fn try_from(field: Field) -> Result<Self> {
Ok(PropField {
wrapped_name: Self::required_wrapper(&field)?,
ty: field.ty,
name: field.ident.unwrap(),
})
}
}
impl PartialOrd for PropField {
fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl Ord for PropField {
fn cmp(&self, other: &PropField) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialEq for PropField {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}

View File

@ -0,0 +1,69 @@
use proc_macro2::{Ident, Span};
use syn::{
punctuated::Punctuated, token::Colon2, GenericArgument, GenericParam, Generics, Path,
PathArguments, PathSegment, Token, TraitBound, TraitBoundModifier, Type, TypeParam,
TypeParamBound, TypePath,
};
/// Alias for a comma-separated list of `GenericArgument`
pub type GenericArguments = Punctuated<GenericArgument, Token![,]>;
/// Converts `GenericParams` into `GenericArguments` and adds `type_ident` as a type arg
pub fn to_arguments(generics: &Generics, type_ident: Ident) -> GenericArguments {
let mut args: GenericArguments = Punctuated::new();
args.extend(generics.params.iter().map(|param| match param {
GenericParam::Type(type_param) => new_generic_type_arg(type_param.ident.clone()),
GenericParam::Lifetime(lifetime_param) => {
GenericArgument::Lifetime(lifetime_param.lifetime.clone())
}
_ => unimplemented!("const params are not supported in the derive macro"),
}));
args.push(new_generic_type_arg(type_ident));
args
}
/// Adds a new bounded `GenericParam` to a `Generics`
pub fn with_param_bounds(generics: &Generics, param_ident: Ident, param_bounds: Ident) -> Generics {
let mut new_generics = generics.clone();
new_generics
.params
.push(new_param_bounds(param_ident, param_bounds));
new_generics
}
// Creates a `GenericArgument` from an `Ident`
fn new_generic_type_arg(ident: Ident) -> GenericArgument {
GenericArgument::Type(Type::Path(TypePath {
path: Path::from(ident),
qself: None,
}))
}
// Creates a bounded `GenericParam` from two `Ident`
fn new_param_bounds(param_ident: Ident, param_bounds: Ident) -> GenericParam {
let mut path_segments: Punctuated<PathSegment, Colon2> = Punctuated::new();
path_segments.push(PathSegment {
ident: param_bounds,
arguments: PathArguments::None,
});
let mut param_bounds: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
param_bounds.push(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: TraitBoundModifier::None,
lifetimes: None,
path: Path {
leading_colon: None,
segments: path_segments,
},
}));
GenericParam::Type(TypeParam {
attrs: Vec::new(),
ident: param_ident,
colon_token: Some(Token![:](Span::call_site())),
bounds: param_bounds,
eq_token: None,
default: None,
})
}

View File

@ -0,0 +1,86 @@
mod builder;
mod field;
mod generics;
mod wrapper;
use builder::PropsBuilder;
use field::PropField;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use std::convert::TryInto;
use syn::parse::{Parse, ParseStream, Result};
use syn::{DeriveInput, Generics, Visibility};
use wrapper::PropsWrapper;
pub struct DerivePropsInput {
vis: Visibility,
generics: Generics,
props_name: Ident,
prop_fields: Vec<PropField>,
}
impl Parse for DerivePropsInput {
fn parse(input: ParseStream) -> Result<Self> {
let input: DeriveInput = input.parse()?;
let named_fields = match input.data {
syn::Data::Struct(data) => match data.fields {
syn::Fields::Named(fields) => fields.named,
_ => unimplemented!("only structs are supported"),
},
_ => unimplemented!("only structs are supported"),
};
let mut prop_fields: Vec<PropField> = named_fields
.into_iter()
.map(|f| f.try_into())
.collect::<Result<Vec<PropField>>>()?;
// Alphabetize
prop_fields.sort();
Ok(Self {
vis: input.vis,
props_name: input.ident,
generics: input.generics,
prop_fields,
})
}
}
impl ToTokens for DerivePropsInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
generics,
props_name,
..
} = self;
// The wrapper is a new struct which wraps required props in `Option`
let wrapper_name = Ident::new(&format!("{}Wrapper", props_name), Span::call_site());
let wrapper = PropsWrapper::new(&wrapper_name, &generics, &self.prop_fields);
tokens.extend(wrapper.into_token_stream());
// The builder will only build if all required props have been set
let builder_name = Ident::new(&format!("{}Builder", props_name), Span::call_site());
let builder_step = Ident::new(&format!("{}BuilderStep", props_name), Span::call_site());
let builder = PropsBuilder::new(&builder_name, &builder_step, &self, &wrapper_name);
let builder_generic_args = builder.first_step_generic_args();
tokens.extend(builder.into_token_stream());
// The properties trait has a `builder` method which creates the props builder
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let properties = quote! {
impl#impl_generics ::yew::html::Properties for #props_name#ty_generics #where_clause {
type Builder = #builder_name<#builder_generic_args>;
fn builder() -> Self::Builder {
#builder_name {
wrapped: ::std::boxed::Box::new(::std::default::Default::default()),
_marker: ::std::marker::PhantomData,
}
}
}
};
tokens.extend(properties);
}
}

View File

@ -0,0 +1,65 @@
use super::PropField;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::Generics;
pub struct PropsWrapper<'a> {
wrapper_name: &'a Ident,
generics: &'a Generics,
prop_fields: &'a [PropField],
}
impl ToTokens for PropsWrapper<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
generics,
wrapper_name,
..
} = self;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let turbofish_generics = ty_generics.as_turbofish();
let wrapper_field_defs = self.field_defs();
let wrapper_default_setters = self.default_setters();
let wrapper = quote! {
struct #wrapper_name#generics {
#(#wrapper_field_defs)*
}
impl#impl_generics ::std::default::Default for #wrapper_name#ty_generics #where_clause {
fn default() -> Self {
#wrapper_name#turbofish_generics {
#(#wrapper_default_setters)*
}
}
}
};
wrapper.to_tokens(tokens);
}
}
impl<'a> PropsWrapper<'_> {
pub fn new(
name: &'a Ident,
generics: &'a Generics,
prop_fields: &'a [PropField],
) -> PropsWrapper<'a> {
PropsWrapper {
wrapper_name: name,
generics,
prop_fields,
}
}
}
impl PropsWrapper<'_> {
fn field_defs(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| pf.to_field_def())
}
fn default_setters(&self) -> impl Iterator<Item = impl ToTokens + '_> {
self.prop_fields.iter().map(|pf| pf.to_default_setter())
}
}

View File

@ -12,7 +12,7 @@ error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfie
|
= note: required by `std::default::Default::default`
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::Props_value_is_required>` in the current scope
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` in the current scope
--> $DIR/fail.rs:34:26
|
27 | #[derive(Properties)]
@ -21,7 +21,7 @@ error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::Props
34 | Props::builder().build();
| ^^^^^
error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::Props_a_is_required>` in the current scope
error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope
--> $DIR/fail.rs:48:26
|
40 | #[derive(Properties)]

View File

@ -64,4 +64,23 @@ mod t4 {
}
}
mod t5 {
use super::*;
#[derive(Properties)]
pub struct Props<'a, T: Default + 'a> {
static_value: &'static str,
#[props(required)]
value: &'a T,
}
fn optional_prop_generics_with_lifetime_should_work() {
Props::<String>::builder().value(&String::from("")).build();
Props::<String>::builder()
.static_value("")
.value(&String::from(""))
.build();
}
}
fn main() {}

View File

@ -66,7 +66,7 @@ error[E0609]: no field `unknown` on type `ChildProperties`
|
= note: available fields are: `string`, `int`
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildProperties_int_is_required>` in the current scope
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:42:29
|
5 | #[derive(Properties, PartialEq)]
@ -114,7 +114,7 @@ error[E0308]: mismatched types
47 | html! { <ChildComponent int=0u32 /> };
| ^^^^ expected i32, found u32
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildProperties_int_is_required>` in the current scope
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
--> $DIR/html-component-fail.rs:48:29
|
5 | #[derive(Properties, PartialEq)]