From 88d4f6242d1f7cde8c5a5e5678a1d7fb3e13fc61 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 26 Jan 2020 23:00:28 +0900 Subject: [PATCH] Utility ClassItemMeta for class item meta parsing --- derive/src/pyclass.rs | 302 +++++++++++++++++++++++++++--------------- 1 file changed, 198 insertions(+), 104 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index d755fc2e0..830afd311 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -42,6 +42,182 @@ enum ClassItem { }, } +struct ClassItemMeta<'a> { + sig: &'a Signature, + parent_type: &'static str, + meta: HashMap>, +} + +impl<'a> ClassItemMeta<'a> { + const METHOD_NAMES: &'static [&'static str] = &["name", "magic"]; + const PROPERTY_NAMES: &'static [&'static str] = &["name", "magic", "setter"]; + + fn from_nested_meta( + parent_type: &'static str, + sig: &'a Signature, + nested_meta: &[NestedMeta], + names: &[&'static str], + ) -> Result { + let mut extracted = Self { + sig, + parent_type, + meta: HashMap::new(), + }; + + let validate_name = |name: &str, extracted: &Self| -> Result<(), Diagnostic> { + if names.contains(&name) { + if extracted.meta.contains_key(name) { + bail_span!( + &sig.ident, + "#[{}] must have only one '{}'", + parent_type, + name + ); + } else { + Ok(()) + } + } else { + bail_span!( + &sig.ident, + "#[{}({})] is not one of allowed attributes {}", + parent_type, + name, + names.join(", ") + ); + } + }; + + for meta in nested_meta { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + NestedMeta::Lit(_) => continue, + }; + + match meta { + Meta::NameValue(name_value) => { + if let Some(ident) = name_value.path.get_ident() { + let name = ident.to_string(); + validate_name(&name, &extracted)?; + extracted.meta.insert(name, Some(name_value.lit.clone())); + } + } + Meta::Path(path) => { + if let Some(ident) = path.get_ident() { + let name = ident.to_string(); + validate_name(&name, &extracted)?; + extracted.meta.insert(name, None); + } else { + continue; + } + } + _ => (), + } + } + + Ok(extracted) + } + + fn _str(&self, key: &str) -> Result, Diagnostic> { + Ok(match self.meta.get(key) { + Some(Some(lit)) => { + if let Lit::Str(s) = lit { + Some(s.value()) + } else { + bail_span!( + &self.sig.ident, + "#[{}({} = ...)] must be a string", + self.parent_type, + key + ); + } + } + Some(None) => { + bail_span!( + &self.sig.ident, + "#[{}({} = ...)] is expected", + self.parent_type, + key, + ); + } + None => None, + }) + } + + fn _bool(&self, key: &str) -> Result { + Ok(match self.meta.get(key) { + Some(Some(_)) => { + bail_span!( + &self.sig.ident, + "#[{}({})] is expected", + self.parent_type, + key, + ); + } + Some(None) => true, + None => false, + }) + } + + fn method_name(&self) -> Result { + let name = self._str("name")?; + let magic = self._bool("magic")?; + Ok(if let Some(name) = name { + name + } else { + let name = self.sig.ident.to_string(); + if magic { + format!("__{}__", name) + } else { + name + } + }) + } + + fn setter(&self) -> Result { + self._bool("setter") + } + + fn property_name(&self) -> Result { + let magic = self._bool("magic")?; + let setter = self._bool("setter")?; + let name = self._str("name")?; + + Ok(if let Some(name) = name { + name + } else { + let sig_name = self.sig.ident.to_string(); + let name = if setter { + if sig_name.starts_with("set_") { + let name = &sig_name["set_".len()..]; + if name.is_empty() { + bail_span!( + &self.sig.ident, + "A #[{}(setter)] fn with a set_* name must \ + have something after \"set_\"", + self.parent_type + ) + } + name.to_string() + } else { + bail_span!( + &self.sig.ident, + "A #[{}(setter)] fn must either have a `name` \ + parameter or a fn name along the lines of \"set_*\"", + self.parent_type + ) + } + } else { + sig_name + }; + if magic { + format!("__{}__", name) + } else { + name + } + }) + } +} + impl Class { fn add_item(&mut self, item: ClassItem, span: Span) -> Result<(), Diagnostic> { if self.items.insert(item) { @@ -62,40 +238,16 @@ impl Class { #[pymethod(name = \"...\")]", ) })?; - let mut py_name = None; - for meta in nesteds { - let meta = match meta { - NestedMeta::Meta(meta) => meta, - NestedMeta::Lit(_) => continue, - }; - - match meta { - Meta::NameValue(name_value) => { - if path_eq(&name_value.path, "name") { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - bail_span!(&sig.ident, "#[pymethod(name = ...)] must be a string"); - } - } - } - Meta::Path(path) => { - if path.get_ident().map_or(false, |v| v == "magic") { - py_name = Some(format!("__{}__", sig.ident.to_string())); - } else { - bail_span!( - &sig.ident, - "#[pymethod(magic)] or #[pymethod(name = ...)] is expected" - ); - } - } - _ => (), - } - } + let item_meta = ClassItemMeta::from_nested_meta( + "pymethod", + sig, + &nesteds, + ClassItemMeta::METHOD_NAMES, + )?; Ok(ClassItem::Method { item_ident: sig.ident.clone(), - py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), + py_name: item_meta.method_name()?, }) } @@ -107,25 +259,15 @@ impl Class { #[pyclassmethod(name = \"...\")]", ) })?; - let mut py_name = None; - for meta in nesteds { - let meta = match meta { - NestedMeta::Meta(meta) => meta, - NestedMeta::Lit(_) => continue, - }; - if let Meta::NameValue(name_value) = meta { - if path_eq(&name_value.path, "name") { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - bail_span!(&sig.ident, "#[pyclassmethod(name = ...)] must be a string"); - } - } - } - } + let item_meta = ClassItemMeta::from_nested_meta( + "pyclassmethod", + sig, + &nesteds, + ClassItemMeta::METHOD_NAMES, + )?; Ok(ClassItem::ClassMethod { item_ident: sig.ident.clone(), - py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), + py_name: item_meta.method_name()?, }) } @@ -137,64 +279,16 @@ impl Class { #[pyproperty(name = \"...\")]" ) })?; - let mut setter = false; - let mut py_name = None; - for meta in nesteds { - let meta = match meta { - NestedMeta::Meta(meta) => meta, - NestedMeta::Lit(_) => continue, - }; - match meta { - Meta::NameValue(name_value) => { - if path_eq(&name_value.path, "name") { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - bail_span!(&sig.ident, "#[pyproperty(name = ...)] must be a string"); - } - } - } - Meta::Path(path) => { - if path_eq(&path, "setter") { - setter = true; - } - } - _ => {} - } - } - let py_name = match py_name { - Some(py_name) => py_name, - None => { - let item_ident = sig.ident.to_string(); - if setter { - if item_ident.starts_with("set_") { - let name = &item_ident["set_".len()..]; - if name.is_empty() { - bail_span!( - &sig.ident, - "A #[pyproperty(setter)] fn with a set_* name must \ - have something after \"set_\"" - ) - } else { - name.to_string() - } - } else { - bail_span!( - &sig.ident, - "A #[pyproperty(setter)] fn must either have a `name` \ - parameter or a fn name along the lines of \"set_*\"" - ) - } - } else { - item_ident - } - } - }; - + let item_meta = ClassItemMeta::from_nested_meta( + "pyproperty", + sig, + &nesteds, + ClassItemMeta::PROPERTY_NAMES, + )?; Ok(ClassItem::Property { - py_name, + py_name: item_meta.property_name()?, item_ident: sig.ident.clone(), - setter, + setter: item_meta.setter()?, }) }