|
| 1 | +use crate::attributes::{ |
| 2 | + self, get_pyo3_options, CrateAttribute, DefaultAttribute, FromPyWithAttribute, |
| 3 | + IntoPyWithAttribute, RenameAllAttribute, |
| 4 | +}; |
| 5 | +use proc_macro2::Span; |
| 6 | +use syn::parse::{Parse, ParseStream}; |
| 7 | +use syn::spanned::Spanned; |
| 8 | +use syn::{parenthesized, Attribute, LitStr, Result, Token}; |
| 9 | + |
| 10 | +/// Attributes for deriving `FromPyObject`/`IntoPyObject` scoped on containers. |
| 11 | +pub enum ContainerAttribute { |
| 12 | + /// Treat the Container as a Wrapper, operate directly on its field |
| 13 | + Transparent(attributes::kw::transparent), |
| 14 | + /// Force every field to be extracted from item of source Python object. |
| 15 | + ItemAll(attributes::kw::from_item_all), |
| 16 | + /// Change the name of an enum variant in the generated error message. |
| 17 | + ErrorAnnotation(LitStr), |
| 18 | + /// Change the path for the pyo3 crate |
| 19 | + Crate(CrateAttribute), |
| 20 | + /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction |
| 21 | + RenameAll(RenameAllAttribute), |
| 22 | +} |
| 23 | + |
| 24 | +impl Parse for ContainerAttribute { |
| 25 | + fn parse(input: ParseStream<'_>) -> Result<Self> { |
| 26 | + let lookahead = input.lookahead1(); |
| 27 | + if lookahead.peek(attributes::kw::transparent) { |
| 28 | + let kw: attributes::kw::transparent = input.parse()?; |
| 29 | + Ok(ContainerAttribute::Transparent(kw)) |
| 30 | + } else if lookahead.peek(attributes::kw::from_item_all) { |
| 31 | + let kw: attributes::kw::from_item_all = input.parse()?; |
| 32 | + Ok(ContainerAttribute::ItemAll(kw)) |
| 33 | + } else if lookahead.peek(attributes::kw::annotation) { |
| 34 | + let _: attributes::kw::annotation = input.parse()?; |
| 35 | + let _: Token![=] = input.parse()?; |
| 36 | + input.parse().map(ContainerAttribute::ErrorAnnotation) |
| 37 | + } else if lookahead.peek(Token![crate]) { |
| 38 | + input.parse().map(ContainerAttribute::Crate) |
| 39 | + } else if lookahead.peek(attributes::kw::rename_all) { |
| 40 | + input.parse().map(ContainerAttribute::RenameAll) |
| 41 | + } else { |
| 42 | + Err(lookahead.error()) |
| 43 | + } |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +#[derive(Default)] |
| 48 | +pub struct ContainerAttributes { |
| 49 | + /// Treat the Container as a Wrapper, operate directly on its field |
| 50 | + pub transparent: Option<attributes::kw::transparent>, |
| 51 | + /// Force every field to be extracted from item of source Python object. |
| 52 | + pub from_item_all: Option<attributes::kw::from_item_all>, |
| 53 | + /// Change the name of an enum variant in the generated error message. |
| 54 | + pub annotation: Option<syn::LitStr>, |
| 55 | + /// Change the path for the pyo3 crate |
| 56 | + pub krate: Option<CrateAttribute>, |
| 57 | + /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction |
| 58 | + pub rename_all: Option<RenameAllAttribute>, |
| 59 | +} |
| 60 | + |
| 61 | +impl ContainerAttributes { |
| 62 | + pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> { |
| 63 | + let mut options = ContainerAttributes::default(); |
| 64 | + |
| 65 | + for attr in attrs { |
| 66 | + if let Some(pyo3_attrs) = get_pyo3_options(attr)? { |
| 67 | + pyo3_attrs |
| 68 | + .into_iter() |
| 69 | + .try_for_each(|opt| options.set_option(opt))?; |
| 70 | + } |
| 71 | + } |
| 72 | + Ok(options) |
| 73 | + } |
| 74 | + |
| 75 | + fn set_option(&mut self, option: ContainerAttribute) -> syn::Result<()> { |
| 76 | + macro_rules! set_option { |
| 77 | + ($key:ident) => { |
| 78 | + { |
| 79 | + ensure_spanned!( |
| 80 | + self.$key.is_none(), |
| 81 | + $key.span() => concat!("`", stringify!($key), "` may only be specified once") |
| 82 | + ); |
| 83 | + self.$key = Some($key); |
| 84 | + } |
| 85 | + }; |
| 86 | + } |
| 87 | + |
| 88 | + match option { |
| 89 | + ContainerAttribute::Transparent(transparent) => set_option!(transparent), |
| 90 | + ContainerAttribute::ItemAll(from_item_all) => set_option!(from_item_all), |
| 91 | + ContainerAttribute::ErrorAnnotation(annotation) => set_option!(annotation), |
| 92 | + ContainerAttribute::Crate(krate) => set_option!(krate), |
| 93 | + ContainerAttribute::RenameAll(rename_all) => set_option!(rename_all), |
| 94 | + } |
| 95 | + Ok(()) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +#[derive(Clone, Debug)] |
| 100 | +pub enum FieldGetter { |
| 101 | + GetItem(attributes::kw::item, Option<syn::Lit>), |
| 102 | + GetAttr(attributes::kw::attribute, Option<syn::LitStr>), |
| 103 | +} |
| 104 | + |
| 105 | +impl FieldGetter { |
| 106 | + pub fn span(&self) -> Span { |
| 107 | + match self { |
| 108 | + FieldGetter::GetItem(item, _) => item.span, |
| 109 | + FieldGetter::GetAttr(attribute, _) => attribute.span, |
| 110 | + } |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +pub enum FieldAttribute { |
| 115 | + Getter(FieldGetter), |
| 116 | + FromPyWith(FromPyWithAttribute), |
| 117 | + IntoPyWith(IntoPyWithAttribute), |
| 118 | + Default(DefaultAttribute), |
| 119 | +} |
| 120 | + |
| 121 | +impl Parse for FieldAttribute { |
| 122 | + fn parse(input: ParseStream<'_>) -> Result<Self> { |
| 123 | + let lookahead = input.lookahead1(); |
| 124 | + if lookahead.peek(attributes::kw::attribute) { |
| 125 | + let attr_kw: attributes::kw::attribute = input.parse()?; |
| 126 | + if input.peek(syn::token::Paren) { |
| 127 | + let content; |
| 128 | + let _ = parenthesized!(content in input); |
| 129 | + let attr_name: LitStr = content.parse()?; |
| 130 | + if !content.is_empty() { |
| 131 | + return Err(content.error( |
| 132 | + "expected at most one argument: `attribute` or `attribute(\"name\")`", |
| 133 | + )); |
| 134 | + } |
| 135 | + ensure_spanned!( |
| 136 | + !attr_name.value().is_empty(), |
| 137 | + attr_name.span() => "attribute name cannot be empty" |
| 138 | + ); |
| 139 | + Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, Some(attr_name)))) |
| 140 | + } else { |
| 141 | + Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, None))) |
| 142 | + } |
| 143 | + } else if lookahead.peek(attributes::kw::item) { |
| 144 | + let item_kw: attributes::kw::item = input.parse()?; |
| 145 | + if input.peek(syn::token::Paren) { |
| 146 | + let content; |
| 147 | + let _ = parenthesized!(content in input); |
| 148 | + let key = content.parse()?; |
| 149 | + if !content.is_empty() { |
| 150 | + return Err( |
| 151 | + content.error("expected at most one argument: `item` or `item(key)`") |
| 152 | + ); |
| 153 | + } |
| 154 | + Ok(Self::Getter(FieldGetter::GetItem(item_kw, Some(key)))) |
| 155 | + } else { |
| 156 | + Ok(Self::Getter(FieldGetter::GetItem(item_kw, None))) |
| 157 | + } |
| 158 | + } else if lookahead.peek(attributes::kw::from_py_with) { |
| 159 | + input.parse().map(Self::FromPyWith) |
| 160 | + } else if lookahead.peek(attributes::kw::into_py_with) { |
| 161 | + input.parse().map(FieldAttribute::IntoPyWith) |
| 162 | + } else if lookahead.peek(Token![default]) { |
| 163 | + input.parse().map(Self::Default) |
| 164 | + } else { |
| 165 | + Err(lookahead.error()) |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +#[derive(Clone, Debug, Default)] |
| 171 | +pub struct FieldAttributes { |
| 172 | + pub getter: Option<FieldGetter>, |
| 173 | + pub from_py_with: Option<FromPyWithAttribute>, |
| 174 | + pub into_py_with: Option<IntoPyWithAttribute>, |
| 175 | + pub default: Option<DefaultAttribute>, |
| 176 | +} |
| 177 | + |
| 178 | +impl FieldAttributes { |
| 179 | + /// Extract the field attributes. |
| 180 | + pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> { |
| 181 | + let mut options = FieldAttributes::default(); |
| 182 | + |
| 183 | + for attr in attrs { |
| 184 | + if let Some(pyo3_attrs) = get_pyo3_options(attr)? { |
| 185 | + pyo3_attrs |
| 186 | + .into_iter() |
| 187 | + .try_for_each(|opt| options.set_option(opt))?; |
| 188 | + } |
| 189 | + } |
| 190 | + Ok(options) |
| 191 | + } |
| 192 | + |
| 193 | + fn set_option(&mut self, option: FieldAttribute) -> syn::Result<()> { |
| 194 | + macro_rules! set_option { |
| 195 | + ($key:ident) => { |
| 196 | + set_option!($key, concat!("`", stringify!($key), "` may only be specified once")) |
| 197 | + }; |
| 198 | + ($key:ident, $msg: expr) => {{ |
| 199 | + ensure_spanned!( |
| 200 | + self.$key.is_none(), |
| 201 | + $key.span() => $msg |
| 202 | + ); |
| 203 | + self.$key = Some($key); |
| 204 | + }} |
| 205 | + } |
| 206 | + |
| 207 | + match option { |
| 208 | + FieldAttribute::Getter(getter) => { |
| 209 | + set_option!(getter, "only one of `attribute` or `item` can be provided") |
| 210 | + } |
| 211 | + FieldAttribute::FromPyWith(from_py_with) => set_option!(from_py_with), |
| 212 | + FieldAttribute::IntoPyWith(into_py_with) => set_option!(into_py_with), |
| 213 | + FieldAttribute::Default(default) => set_option!(default), |
| 214 | + } |
| 215 | + Ok(()) |
| 216 | + } |
| 217 | +} |
0 commit comments