From 6bf6f58ecf75192aa6ffbfd839e04e5300c92d99 Mon Sep 17 00:00:00 2001 From: Moreal Date: Sat, 15 Oct 2022 14:16:38 +0900 Subject: [PATCH] Provide setter in `pymember` --- derive/src/pyclass.rs | 167 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 23 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index c02a6d4f4e..2f431918b1 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -5,13 +5,13 @@ use crate::util::{ }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use std::collections::HashMap; use std::str::FromStr; -use std::{borrow::Borrow, collections::HashMap}; use syn::{ parse::{Parse, ParseStream, Result as ParsingResult}, parse_quote, spanned::Spanned, - Attribute, AttributeArgs, Ident, Item, LitStr, Meta, NestedMeta, Result, Token, Type, + Attribute, AttributeArgs, Ident, Item, LitStr, Meta, NestedMeta, Result, Token, }; use syn_ext::ext::*; @@ -66,6 +66,7 @@ impl FromStr for AttrName { struct ImplContext { impl_extend_items: ItemNursery, getset_items: GetSetNursery, + member_items: MemberNursery, extend_slots_items: ItemNursery, class_extensions: Vec, errors: Vec, @@ -94,6 +95,7 @@ fn extract_items_into_context<'a, Item>( context.errors.ok_or_push(r); } context.errors.ok_or_push(context.getset_items.validate()); + context.errors.ok_or_push(context.member_items.validate()); } pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result { @@ -110,6 +112,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result Result, ) { #getset_impl + #member_impl #extend_impl #with_impl #(#class_extensions)* @@ -146,6 +150,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result Result, ) { #getset_impl + #member_impl #extend_impl #with_impl #(#class_extensions)* @@ -689,30 +695,20 @@ where let item_attr = args.attrs.remove(self.index()); let item_meta = MemberItemMeta::from_attr(ident.clone(), &item_attr)?; - let py_name = item_meta.member_name()?; + let (py_name, member_item_kind) = item_meta.member_name()?; let member_kind = match item_meta.member_kind()? { Some(s) => match s.as_str() { - "bool" => quote!(::rustpython_vm::builtins::descriptor::MemberKind::Bool), + "bool" => MemberKind::Bool, _ => unreachable!(), }, - _ => quote!(::rustpython_vm::builtins::descriptor::MemberKind::ObjectEx), - }; - let tokens = { - quote_spanned! { ident.span() => - class.set_str_attr( - #py_name, - ctx.new_member(#py_name, #member_kind, Self::#ident, None, class), - ctx, - ); - } + _ => MemberKind::ObjectEx, }; - args.context.impl_extend_items.add_item( + args.context.member_items.add_item( + py_name, + member_item_kind, + member_kind, ident.clone(), - vec![py_name], - args.cfgs.to_vec(), - tokens, - 5, )?; Ok(()) } @@ -812,6 +808,95 @@ impl ToTokens for GetSetNursery { } } +#[derive(Default)] +#[allow(clippy::type_complexity)] +struct MemberNursery { + map: HashMap<(String, MemberKind), (Option, Option)>, + validated: bool, +} + +enum MemberItemKind { + Get, + Set, +} + +#[derive(Eq, PartialEq, Hash)] +enum MemberKind { + Bool, + ObjectEx, +} + +impl MemberNursery { + fn add_item( + &mut self, + name: String, + kind: MemberItemKind, + member_kind: MemberKind, + item_ident: Ident, + ) -> Result<()> { + assert!(!self.validated, "new item is not allowed after validation"); + let entry = self.map.entry((name.clone(), member_kind)).or_default(); + let func = match kind { + MemberItemKind::Get => &mut entry.0, + MemberItemKind::Set => &mut entry.1, + }; + if func.is_some() { + return Err(syn::Error::new_spanned( + item_ident, + format!("Multiple member accessors with name '{}'", name), + )); + } + *func = Some(item_ident); + Ok(()) + } + + fn validate(&mut self) -> Result<()> { + let mut errors = Vec::new(); + for ((name, _), (getter, setter)) in &self.map { + if getter.is_none() { + errors.push(syn::Error::new_spanned( + setter.as_ref().unwrap(), + format!("Member '{}' is missing a getter", name), + )); + }; + } + errors.into_result()?; + self.validated = true; + Ok(()) + } +} + +impl ToTokens for MemberNursery { + fn to_tokens(&self, tokens: &mut TokenStream) { + assert!(self.validated, "Call `validate()` before token generation"); + let properties = self + .map + .iter() + .map(|((name, member_kind), (getter, setter))| { + let setter = match setter { + Some(setter) => quote_spanned! { setter.span() => Some(Self::#setter)}, + None => quote! { None }, + }; + let member_kind = match member_kind { + MemberKind::Bool => { + quote!(::rustpython_vm::builtins::descriptor::MemberKind::Bool) + } + MemberKind::ObjectEx => { + quote!(::rustpython_vm::builtins::descriptor::MemberKind::ObjectEx) + } + }; + quote_spanned! { getter.span() => + class.set_str_attr( + #name, + ctx.new_member(#name, #member_kind, Self::#getter, #setter, class), + ctx, + ); + } + }); + tokens.extend(properties); + } +} + struct MethodItemMeta(ItemMetaInner); impl ItemMeta for MethodItemMeta { @@ -990,7 +1075,7 @@ impl SlotItemMeta { struct MemberItemMeta(ItemMetaInner); impl ItemMeta for MemberItemMeta { - const ALLOWED_NAMES: &'static [&'static str] = &["magic", "type"]; + const ALLOWED_NAMES: &'static [&'static str] = &["magic", "type", "setter"]; fn from_inner(inner: ItemMetaInner) -> Self { Self(inner) @@ -1001,11 +1086,47 @@ impl ItemMeta for MemberItemMeta { } impl MemberItemMeta { - fn member_name(&self) -> Result { + fn member_name(&self) -> Result<(String, MemberItemKind)> { let inner = self.inner(); + let sig_name = inner.item_name(); + let extract_prefix_name = |prefix, item_typ| { + if let Some(name) = sig_name.strip_prefix(prefix) { + if name.is_empty() { + Err(syn::Error::new_spanned( + &inner.meta_ident, + format!( + "A #[{}({typ})] fn with a {prefix}* name must \ + have something after \"{prefix}\"", + inner.meta_name(), + typ = item_typ, + prefix = prefix + ), + )) + } else { + Ok(name.to_owned()) + } + } else { + Err(syn::Error::new_spanned( + &inner.meta_ident, + format!( + "A #[{}(setter)] fn must either have a `name` \ + parameter or a fn name along the lines of \"set_*\"", + inner.meta_name() + ), + )) + } + }; let magic = inner._bool("magic")?; - let name = inner.item_name(); - Ok(if magic { format!("__{}__", name) } else { name }) + let kind = if inner._bool("setter")? { + MemberItemKind::Set + } else { + MemberItemKind::Get + }; + let name = match kind { + MemberItemKind::Get => sig_name, + MemberItemKind::Set => extract_prefix_name("set_", "setter")?, + }; + Ok((if magic { format!("__{}__", name) } else { name }, kind)) } fn member_kind(&self) -> Result> {