1 | use proc_macro2::TokenStream; |
2 | use quote::{quote, ToTokens}; |
3 | use syn::parse::Parser; |
4 | use syn::{ |
5 | ext::IdentExt, |
6 | parse::{Parse, ParseStream}, |
7 | punctuated::Punctuated, |
8 | spanned::Spanned, |
9 | token::Comma, |
10 | Attribute, Expr, ExprPath, Ident, Index, LitBool, LitStr, Member, Path, Result, Token, |
11 | }; |
12 | |
13 | pub mod kw { |
14 | syn::custom_keyword!(annotation); |
15 | syn::custom_keyword!(attribute); |
16 | syn::custom_keyword!(cancel_handle); |
17 | syn::custom_keyword!(constructor); |
18 | syn::custom_keyword!(dict); |
19 | syn::custom_keyword!(eq); |
20 | syn::custom_keyword!(eq_int); |
21 | syn::custom_keyword!(extends); |
22 | syn::custom_keyword!(freelist); |
23 | syn::custom_keyword!(from_py_with); |
24 | syn::custom_keyword!(frozen); |
25 | syn::custom_keyword!(get); |
26 | syn::custom_keyword!(get_all); |
27 | syn::custom_keyword!(hash); |
28 | syn::custom_keyword!(into_py_with); |
29 | syn::custom_keyword!(item); |
30 | syn::custom_keyword!(from_item_all); |
31 | syn::custom_keyword!(mapping); |
32 | syn::custom_keyword!(module); |
33 | syn::custom_keyword!(name); |
34 | syn::custom_keyword!(ord); |
35 | syn::custom_keyword!(pass_module); |
36 | syn::custom_keyword!(rename_all); |
37 | syn::custom_keyword!(sequence); |
38 | syn::custom_keyword!(set); |
39 | syn::custom_keyword!(set_all); |
40 | syn::custom_keyword!(signature); |
41 | syn::custom_keyword!(str); |
42 | syn::custom_keyword!(subclass); |
43 | syn::custom_keyword!(submodule); |
44 | syn::custom_keyword!(text_signature); |
45 | syn::custom_keyword!(transparent); |
46 | syn::custom_keyword!(unsendable); |
47 | syn::custom_keyword!(weakref); |
48 | syn::custom_keyword!(gil_used); |
49 | } |
50 | |
51 | fn take_int(read: &mut &str, tracker: &mut usize) -> String { |
52 | let mut int: String = String::new(); |
53 | for (i: usize, ch: char) in read.char_indices() { |
54 | match ch { |
55 | '0' ..='9' => { |
56 | *tracker += 1; |
57 | int.push(ch) |
58 | } |
59 | _ => { |
60 | *read = &read[i..]; |
61 | break; |
62 | } |
63 | } |
64 | } |
65 | int |
66 | } |
67 | |
68 | fn take_ident(read: &mut &str, tracker: &mut usize) -> Ident { |
69 | let mut ident: String = String::new(); |
70 | if read.starts_with("r#" ) { |
71 | ident.push_str(string:"r#" ); |
72 | *tracker += 2; |
73 | *read = &read[2..]; |
74 | } |
75 | for (i: usize, ch: char) in read.char_indices() { |
76 | match ch { |
77 | 'a' ..='z' | 'A' ..='Z' | '0' ..='9' | '_' => { |
78 | *tracker += 1; |
79 | ident.push(ch) |
80 | } |
81 | _ => { |
82 | *read = &read[i..]; |
83 | break; |
84 | } |
85 | } |
86 | } |
87 | Ident::parse_any.parse_str(&ident).unwrap() |
88 | } |
89 | |
90 | // shorthand parsing logic inspiration taken from https://github.com/dtolnay/thiserror/blob/master/impl/src/fmt.rs |
91 | fn parse_shorthand_format(fmt: LitStr) -> Result<(LitStr, Vec<Member>)> { |
92 | let span = fmt.span(); |
93 | let token = fmt.token(); |
94 | let value = fmt.value(); |
95 | let mut read = value.as_str(); |
96 | let mut out = String::new(); |
97 | let mut members = Vec::new(); |
98 | let mut tracker = 1; |
99 | while let Some(brace) = read.find('{' ) { |
100 | tracker += brace; |
101 | out += &read[..brace + 1]; |
102 | read = &read[brace + 1..]; |
103 | if read.starts_with('{' ) { |
104 | out.push('{' ); |
105 | read = &read[1..]; |
106 | tracker += 2; |
107 | continue; |
108 | } |
109 | let next = match read.chars().next() { |
110 | Some(next) => next, |
111 | None => break, |
112 | }; |
113 | tracker += 1; |
114 | let member = match next { |
115 | '0' ..='9' => { |
116 | let start = tracker; |
117 | let index = take_int(&mut read, &mut tracker).parse::<u32>().unwrap(); |
118 | let end = tracker; |
119 | let subspan = token.subspan(start..end).unwrap_or(span); |
120 | let idx = Index { |
121 | index, |
122 | span: subspan, |
123 | }; |
124 | Member::Unnamed(idx) |
125 | } |
126 | 'a' ..='z' | 'A' ..='Z' | '_' => { |
127 | let start = tracker; |
128 | let mut ident = take_ident(&mut read, &mut tracker); |
129 | let end = tracker; |
130 | let subspan = token.subspan(start..end).unwrap_or(span); |
131 | ident.set_span(subspan); |
132 | Member::Named(ident) |
133 | } |
134 | '}' | ':' => { |
135 | let start = tracker; |
136 | tracker += 1; |
137 | let end = tracker; |
138 | let subspan = token.subspan(start..end).unwrap_or(span); |
139 | // we found a closing bracket or formatting ':' without finding a member, we assume the user wants the instance formatted here |
140 | bail_spanned!(subspan.span() => "No member found, you must provide a named or positionally specified member." ) |
141 | } |
142 | _ => continue, |
143 | }; |
144 | members.push(member); |
145 | } |
146 | out += read; |
147 | Ok((LitStr::new(&out, span), members)) |
148 | } |
149 | |
150 | #[derive (Clone, Debug)] |
151 | pub struct StringFormatter { |
152 | pub fmt: LitStr, |
153 | pub args: Vec<Member>, |
154 | } |
155 | |
156 | impl Parse for crate::attributes::StringFormatter { |
157 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
158 | let (fmt: LitStr, args: Vec) = parse_shorthand_format(fmt:input.parse()?)?; |
159 | Ok(Self { fmt, args }) |
160 | } |
161 | } |
162 | |
163 | impl ToTokens for crate::attributes::StringFormatter { |
164 | fn to_tokens(&self, tokens: &mut TokenStream) { |
165 | self.fmt.to_tokens(tokens); |
166 | tokens.extend(iter:quote! {self.args}) |
167 | } |
168 | } |
169 | |
170 | #[derive (Clone, Debug)] |
171 | pub struct KeywordAttribute<K, V> { |
172 | pub kw: K, |
173 | pub value: V, |
174 | } |
175 | |
176 | #[derive (Clone, Debug)] |
177 | pub struct OptionalKeywordAttribute<K, V> { |
178 | pub kw: K, |
179 | pub value: Option<V>, |
180 | } |
181 | |
182 | /// A helper type which parses the inner type via a literal string |
183 | /// e.g. `LitStrValue<Path>` -> parses "some::path" in quotes. |
184 | #[derive (Clone, Debug, PartialEq, Eq)] |
185 | pub struct LitStrValue<T>(pub T); |
186 | |
187 | impl<T: Parse> Parse for LitStrValue<T> { |
188 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
189 | let lit_str: LitStr = input.parse()?; |
190 | lit_str.parse().map(op:LitStrValue) |
191 | } |
192 | } |
193 | |
194 | impl<T: ToTokens> ToTokens for LitStrValue<T> { |
195 | fn to_tokens(&self, tokens: &mut TokenStream) { |
196 | self.0.to_tokens(tokens) |
197 | } |
198 | } |
199 | |
200 | /// A helper type which parses a name via a literal string |
201 | #[derive (Clone, Debug, PartialEq, Eq)] |
202 | pub struct NameLitStr(pub Ident); |
203 | |
204 | impl Parse for NameLitStr { |
205 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
206 | let string_literal: LitStr = input.parse()?; |
207 | if let Ok(ident: Ident) = string_literal.parse_with(parser:Ident::parse_any) { |
208 | Ok(NameLitStr(ident)) |
209 | } else { |
210 | bail_spanned!(string_literal.span() => "expected a single identifier in double quotes" ) |
211 | } |
212 | } |
213 | } |
214 | |
215 | impl ToTokens for NameLitStr { |
216 | fn to_tokens(&self, tokens: &mut TokenStream) { |
217 | self.0.to_tokens(tokens) |
218 | } |
219 | } |
220 | |
221 | /// Available renaming rules |
222 | #[derive (Clone, Copy, Debug, PartialEq, Eq)] |
223 | pub enum RenamingRule { |
224 | CamelCase, |
225 | KebabCase, |
226 | Lowercase, |
227 | PascalCase, |
228 | ScreamingKebabCase, |
229 | ScreamingSnakeCase, |
230 | SnakeCase, |
231 | Uppercase, |
232 | } |
233 | |
234 | /// A helper type which parses a renaming rule via a literal string |
235 | #[derive (Clone, Debug, PartialEq, Eq)] |
236 | pub struct RenamingRuleLitStr { |
237 | pub lit: LitStr, |
238 | pub rule: RenamingRule, |
239 | } |
240 | |
241 | impl Parse for RenamingRuleLitStr { |
242 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
243 | let string_literal: LitStr = input.parse()?; |
244 | let rule: RenamingRule = match string_literal.value().as_ref() { |
245 | "camelCase" => RenamingRule::CamelCase, |
246 | "kebab-case" => RenamingRule::KebabCase, |
247 | "lowercase" => RenamingRule::Lowercase, |
248 | "PascalCase" => RenamingRule::PascalCase, |
249 | "SCREAMING-KEBAB-CASE" => RenamingRule::ScreamingKebabCase, |
250 | "SCREAMING_SNAKE_CASE" => RenamingRule::ScreamingSnakeCase, |
251 | "snake_case" => RenamingRule::SnakeCase, |
252 | "UPPERCASE" => RenamingRule::Uppercase, |
253 | _ => { |
254 | bail_spanned!(string_literal.span() => "expected a valid renaming rule, possible values are: \"camelCase \", \"kebab-case \", \"lowercase \", \"PascalCase \", \"SCREAMING-KEBAB-CASE \", \"SCREAMING_SNAKE_CASE \", \"snake_case \", \"UPPERCASE \"" ) |
255 | } |
256 | }; |
257 | Ok(Self { |
258 | lit: string_literal, |
259 | rule, |
260 | }) |
261 | } |
262 | } |
263 | |
264 | impl ToTokens for RenamingRuleLitStr { |
265 | fn to_tokens(&self, tokens: &mut TokenStream) { |
266 | self.lit.to_tokens(tokens) |
267 | } |
268 | } |
269 | |
270 | /// Text signatue can be either a literal string or opt-in/out |
271 | #[derive (Clone, Debug, PartialEq, Eq)] |
272 | pub enum TextSignatureAttributeValue { |
273 | Str(LitStr), |
274 | // `None` ident to disable automatic text signature generation |
275 | Disabled(Ident), |
276 | } |
277 | |
278 | impl Parse for TextSignatureAttributeValue { |
279 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
280 | if let Ok(lit_str: LitStr) = input.parse::<LitStr>() { |
281 | return Ok(TextSignatureAttributeValue::Str(lit_str)); |
282 | } |
283 | |
284 | let err_span: Span = match input.parse::<Ident>() { |
285 | Ok(ident: Ident) if ident == "None" => { |
286 | return Ok(TextSignatureAttributeValue::Disabled(ident)); |
287 | } |
288 | Ok(other_ident: Ident) => other_ident.span(), |
289 | Err(e: Error) => e.span(), |
290 | }; |
291 | |
292 | Err(err_spanned!(err_span => "expected a string literal or `None`" )) |
293 | } |
294 | } |
295 | |
296 | impl ToTokens for TextSignatureAttributeValue { |
297 | fn to_tokens(&self, tokens: &mut TokenStream) { |
298 | match self { |
299 | TextSignatureAttributeValue::Str(s: &LitStr) => s.to_tokens(tokens), |
300 | TextSignatureAttributeValue::Disabled(b: &Ident) => b.to_tokens(tokens), |
301 | } |
302 | } |
303 | } |
304 | |
305 | pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>; |
306 | pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>; |
307 | pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>; |
308 | pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>; |
309 | pub type RenameAllAttribute = KeywordAttribute<kw::rename_all, RenamingRuleLitStr>; |
310 | pub type StrFormatterAttribute = OptionalKeywordAttribute<kw::str, StringFormatter>; |
311 | pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, TextSignatureAttributeValue>; |
312 | pub type SubmoduleAttribute = kw::submodule; |
313 | pub type GILUsedAttribute = KeywordAttribute<kw::gil_used, LitBool>; |
314 | |
315 | impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> { |
316 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
317 | let kw: K = input.parse()?; |
318 | let _: Token![=] = input.parse()?; |
319 | let value: V = input.parse()?; |
320 | Ok(KeywordAttribute { kw, value }) |
321 | } |
322 | } |
323 | |
324 | impl<K: ToTokens, V: ToTokens> ToTokens for KeywordAttribute<K, V> { |
325 | fn to_tokens(&self, tokens: &mut TokenStream) { |
326 | self.kw.to_tokens(tokens); |
327 | Token).to_tokens(tokens); |
328 | self.value.to_tokens(tokens); |
329 | } |
330 | } |
331 | |
332 | impl<K: Parse + std::fmt::Debug, V: Parse> Parse for OptionalKeywordAttribute<K, V> { |
333 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
334 | let kw: K = input.parse()?; |
335 | let value: Option = match input.parse::<Token![=]>() { |
336 | Ok(_) => Some(input.parse()?), |
337 | Err(_) => None, |
338 | }; |
339 | Ok(OptionalKeywordAttribute { kw, value }) |
340 | } |
341 | } |
342 | |
343 | impl<K: ToTokens, V: ToTokens> ToTokens for OptionalKeywordAttribute<K, V> { |
344 | fn to_tokens(&self, tokens: &mut TokenStream) { |
345 | self.kw.to_tokens(tokens); |
346 | if self.value.is_some() { |
347 | Token).to_tokens(tokens); |
348 | self.value.to_tokens(tokens); |
349 | } |
350 | } |
351 | } |
352 | |
353 | #[derive (Debug, Clone)] |
354 | pub struct ExprPathWrap { |
355 | pub from_lit_str: bool, |
356 | pub expr_path: ExprPath, |
357 | } |
358 | |
359 | impl Parse for ExprPathWrap { |
360 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
361 | match input.parse::<ExprPath>() { |
362 | Ok(expr_path: ExprPath) => Ok(ExprPathWrap { |
363 | from_lit_str: false, |
364 | expr_path, |
365 | }), |
366 | Err(e: Error) => match input.parse::<LitStrValue<ExprPath>>() { |
367 | Ok(LitStrValue(expr_path: ExprPath)) => Ok(ExprPathWrap { |
368 | from_lit_str: true, |
369 | expr_path, |
370 | }), |
371 | Err(_) => Err(e), |
372 | }, |
373 | } |
374 | } |
375 | } |
376 | |
377 | impl ToTokens for ExprPathWrap { |
378 | fn to_tokens(&self, tokens: &mut TokenStream) { |
379 | self.expr_path.to_tokens(tokens) |
380 | } |
381 | } |
382 | |
383 | pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, ExprPathWrap>; |
384 | pub type IntoPyWithAttribute = KeywordAttribute<kw::into_py_with, ExprPath>; |
385 | |
386 | pub type DefaultAttribute = OptionalKeywordAttribute<Token![default], Expr>; |
387 | |
388 | /// For specifying the path to the pyo3 crate. |
389 | pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>; |
390 | |
391 | pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> { |
392 | if attr.path().is_ident("pyo3" ) { |
393 | attr.parse_args_with(Punctuated::parse_terminated).map(op:Some) |
394 | } else { |
395 | Ok(None) |
396 | } |
397 | } |
398 | |
399 | /// Takes attributes from an attribute vector. |
400 | /// |
401 | /// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then |
402 | /// the attribute will be removed from the vector. |
403 | /// |
404 | /// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed. |
405 | /// (In `retain`, returning `true` keeps the element, here it removes it.) |
406 | pub fn take_attributes( |
407 | attrs: &mut Vec<Attribute>, |
408 | mut extractor: impl FnMut(&Attribute) -> Result<bool>, |
409 | ) -> Result<()> { |
410 | *attrs = attrsimpl Iterator- >
|
411 | .drain(..) |
412 | .filter_map(|attr: Attribute| { |
413 | extractorResult(&attr) |
414 | .map(op:move |attribute_handled: bool| if attribute_handled { None } else { Some(attr) }) |
415 | .transpose() |
416 | }) |
417 | .collect::<Result<_>>()?; |
418 | Ok(()) |
419 | } |
420 | |
421 | pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<T>> { |
422 | let mut out: Vec = Vec::new(); |
423 | let mut all_errors: ErrorCombiner = ErrorCombiner(None); |
424 | take_attributes(attrs, |attr: &Attribute| match get_pyo3_options(attr) { |
425 | Ok(result: Option>) => { |
426 | if let Some(options: Punctuated) = result { |
427 | out.extend(iter:options); |
428 | Ok(true) |
429 | } else { |
430 | Ok(false) |
431 | } |
432 | } |
433 | Err(err: Error) => { |
434 | all_errors.combine(error:err); |
435 | Ok(true) |
436 | } |
437 | })?; |
438 | all_errors.ensure_empty()?; |
439 | Ok(out) |
440 | } |
441 | |
442 | pub struct ErrorCombiner(pub Option<syn::Error>); |
443 | |
444 | impl ErrorCombiner { |
445 | pub fn combine(&mut self, error: syn::Error) { |
446 | if let Some(existing: &mut Error) = &mut self.0 { |
447 | existing.combine(another:error); |
448 | } else { |
449 | self.0 = Some(error); |
450 | } |
451 | } |
452 | |
453 | pub fn ensure_empty(self) -> Result<()> { |
454 | if let Some(error: Error) = self.0 { |
455 | Err(error) |
456 | } else { |
457 | Ok(()) |
458 | } |
459 | } |
460 | } |
461 | |