1use crate::{
2 attributes::{kw, KeywordAttribute},
3 method::{FnArg, RegularArg},
4};
5use proc_macro2::{Span, TokenStream};
6use quote::ToTokens;
7use syn::{
8 ext::IdentExt,
9 parse::{Parse, ParseStream},
10 punctuated::Punctuated,
11 spanned::Spanned,
12 Token,
13};
14
15#[derive(Clone)]
16pub struct Signature {
17 paren_token: syn::token::Paren,
18 pub items: Punctuated<SignatureItem, Token![,]>,
19 pub returns: Option<(Token![->], PyTypeAnnotation)>,
20}
21
22impl Parse for Signature {
23 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
24 let content;
25 let paren_token = syn::parenthesized!(content in input);
26 let items = content.parse_terminated(SignatureItem::parse, Token![,])?;
27 let returns = if input.peek(Token![->]) {
28 Some((input.parse()?, input.parse()?))
29 } else {
30 None
31 };
32 Ok(Signature {
33 paren_token,
34 items,
35 returns,
36 })
37 }
38}
39
40impl ToTokens for Signature {
41 fn to_tokens(&self, tokens: &mut TokenStream) {
42 self.paren_token
43 .surround(tokens, |tokens| self.items.to_tokens(tokens));
44 if let Some((arrow, returns)) = &self.returns {
45 arrow.to_tokens(tokens);
46 returns.to_tokens(tokens);
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct SignatureItemArgument {
53 pub ident: syn::Ident,
54 pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>,
55 pub eq_and_default: Option<(Token![=], syn::Expr)>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct SignatureItemPosargsSep {
60 pub slash: Token![/],
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct SignatureItemVarargsSep {
65 pub asterisk: Token![*],
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct SignatureItemVarargs {
70 pub sep: SignatureItemVarargsSep,
71 pub ident: syn::Ident,
72 pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct SignatureItemKwargs {
77 pub asterisks: (Token![*], Token![*]),
78 pub ident: syn::Ident,
79 pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>,
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum SignatureItem {
84 Argument(Box<SignatureItemArgument>),
85 PosargsSep(SignatureItemPosargsSep),
86 VarargsSep(SignatureItemVarargsSep),
87 Varargs(SignatureItemVarargs),
88 Kwargs(SignatureItemKwargs),
89}
90
91impl Parse for SignatureItem {
92 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
93 let lookahead = input.lookahead1();
94 if lookahead.peek(Token![*]) {
95 if input.peek2(Token![*]) {
96 input.parse().map(SignatureItem::Kwargs)
97 } else {
98 let sep = input.parse()?;
99 if input.is_empty() || input.peek(Token![,]) {
100 Ok(SignatureItem::VarargsSep(sep))
101 } else {
102 Ok(SignatureItem::Varargs(SignatureItemVarargs {
103 sep,
104 ident: input.parse()?,
105 colon_and_annotation: if input.peek(Token![:]) {
106 Some((input.parse()?, input.parse()?))
107 } else {
108 None
109 },
110 }))
111 }
112 }
113 } else if lookahead.peek(Token![/]) {
114 input.parse().map(SignatureItem::PosargsSep)
115 } else {
116 input.parse().map(SignatureItem::Argument)
117 }
118 }
119}
120
121impl ToTokens for SignatureItem {
122 fn to_tokens(&self, tokens: &mut TokenStream) {
123 match self {
124 SignatureItem::Argument(arg) => arg.to_tokens(tokens),
125 SignatureItem::Varargs(varargs) => varargs.to_tokens(tokens),
126 SignatureItem::VarargsSep(sep) => sep.to_tokens(tokens),
127 SignatureItem::Kwargs(kwargs) => kwargs.to_tokens(tokens),
128 SignatureItem::PosargsSep(sep) => sep.to_tokens(tokens),
129 }
130 }
131}
132
133impl Parse for SignatureItemArgument {
134 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
135 Ok(Self {
136 ident: input.parse()?,
137 colon_and_annotation: if input.peek(Token![:]) {
138 Some((input.parse()?, input.parse()?))
139 } else {
140 None
141 },
142 eq_and_default: if input.peek(Token![=]) {
143 Some((input.parse()?, input.parse()?))
144 } else {
145 None
146 },
147 })
148 }
149}
150
151impl ToTokens for SignatureItemArgument {
152 fn to_tokens(&self, tokens: &mut TokenStream) {
153 self.ident.to_tokens(tokens);
154 if let Some((colon, annotation)) = &self.colon_and_annotation {
155 colon.to_tokens(tokens);
156 annotation.to_tokens(tokens);
157 }
158 if let Some((eq, default)) = &self.eq_and_default {
159 eq.to_tokens(tokens);
160 default.to_tokens(tokens);
161 }
162 }
163}
164
165impl Parse for SignatureItemVarargsSep {
166 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
167 Ok(Self {
168 asterisk: input.parse()?,
169 })
170 }
171}
172
173impl ToTokens for SignatureItemVarargsSep {
174 fn to_tokens(&self, tokens: &mut TokenStream) {
175 self.asterisk.to_tokens(tokens);
176 }
177}
178
179impl Parse for SignatureItemVarargs {
180 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
181 Ok(Self {
182 sep: input.parse()?,
183 ident: input.parse()?,
184 colon_and_annotation: if input.peek(Token![:]) {
185 Some((input.parse()?, input.parse()?))
186 } else {
187 None
188 },
189 })
190 }
191}
192
193impl ToTokens for SignatureItemVarargs {
194 fn to_tokens(&self, tokens: &mut TokenStream) {
195 self.sep.to_tokens(tokens);
196 self.ident.to_tokens(tokens);
197 }
198}
199
200impl Parse for SignatureItemKwargs {
201 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
202 Ok(Self {
203 asterisks: (input.parse()?, input.parse()?),
204 ident: input.parse()?,
205 colon_and_annotation: if input.peek(Token![:]) {
206 Some((input.parse()?, input.parse()?))
207 } else {
208 None
209 },
210 })
211 }
212}
213
214impl ToTokens for SignatureItemKwargs {
215 fn to_tokens(&self, tokens: &mut TokenStream) {
216 self.asterisks.0.to_tokens(tokens);
217 self.asterisks.1.to_tokens(tokens);
218 self.ident.to_tokens(tokens);
219 }
220}
221
222impl Parse for SignatureItemPosargsSep {
223 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
224 Ok(Self {
225 slash: input.parse()?,
226 })
227 }
228}
229
230impl ToTokens for SignatureItemPosargsSep {
231 fn to_tokens(&self, tokens: &mut TokenStream) {
232 self.slash.to_tokens(tokens);
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Eq)]
237pub struct PyTypeAnnotation(syn::LitStr);
238
239impl Parse for PyTypeAnnotation {
240 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
241 Ok(Self(input.parse()?))
242 }
243}
244
245impl ToTokens for PyTypeAnnotation {
246 fn to_tokens(&self, tokens: &mut TokenStream) {
247 self.0.to_tokens(tokens);
248 }
249}
250
251impl PyTypeAnnotation {
252 pub fn to_python(&self) -> String {
253 self.0.value()
254 }
255}
256
257pub type SignatureAttribute = KeywordAttribute<kw::signature, Signature>;
258pub type ConstructorAttribute = KeywordAttribute<kw::constructor, Signature>;
259
260impl ConstructorAttribute {
261 pub fn into_signature(self) -> SignatureAttribute {
262 SignatureAttribute {
263 kw: kw::signature(self.kw.span),
264 value: self.value,
265 }
266 }
267}
268
269#[derive(Default, Clone)]
270pub struct PythonSignature {
271 pub positional_parameters: Vec<String>,
272 pub positional_only_parameters: usize,
273 pub required_positional_parameters: usize,
274 pub varargs: Option<String>,
275 pub keyword_only_parameters: Vec<(String, bool)>,
277 pub kwargs: Option<String>,
278}
279
280impl PythonSignature {
281 pub fn has_no_args(&self) -> bool {
282 self.positional_parameters.is_empty()
283 && self.keyword_only_parameters.is_empty()
284 && self.varargs.is_none()
285 && self.kwargs.is_none()
286 }
287}
288
289#[derive(Clone)]
290pub struct FunctionSignature<'a> {
291 pub arguments: Vec<FnArg<'a>>,
292 pub python_signature: PythonSignature,
293 pub attribute: Option<SignatureAttribute>,
294}
295
296pub enum ParseState {
297 Positional,
299 PositionalAfterPosargs,
301 Keywords,
303 Done,
305}
306
307impl ParseState {
308 fn add_argument(
309 &mut self,
310 signature: &mut PythonSignature,
311 name: String,
312 required: bool,
313 span: Span,
314 ) -> syn::Result<()> {
315 match self {
316 ParseState::Positional | ParseState::PositionalAfterPosargs => {
317 signature.positional_parameters.push(name);
318 if required {
319 signature.required_positional_parameters += 1;
320 ensure_spanned!(
321 signature.required_positional_parameters == signature.positional_parameters.len(),
322 span => "cannot have required positional parameter after an optional parameter"
323 );
324 }
325 Ok(())
326 }
327 ParseState::Keywords => {
328 signature.keyword_only_parameters.push((name, required));
329 Ok(())
330 }
331 ParseState::Done => {
332 bail_spanned!(span => format!("no more arguments are allowed after `**{}`", signature.kwargs.as_deref().unwrap_or("")))
333 }
334 }
335 }
336
337 fn add_varargs(
338 &mut self,
339 signature: &mut PythonSignature,
340 varargs: &SignatureItemVarargs,
341 ) -> syn::Result<()> {
342 match self {
343 ParseState::Positional | ParseState::PositionalAfterPosargs => {
344 signature.varargs = Some(varargs.ident.to_string());
345 *self = ParseState::Keywords;
346 Ok(())
347 }
348 ParseState::Keywords => {
349 bail_spanned!(varargs.span() => format!("`*{}` not allowed after `*{}`", varargs.ident, signature.varargs.as_deref().unwrap_or("")))
350 }
351 ParseState::Done => {
352 bail_spanned!(varargs.span() => format!("`*{}` not allowed after `**{}`", varargs.ident, signature.kwargs.as_deref().unwrap_or("")))
353 }
354 }
355 }
356
357 fn add_kwargs(
358 &mut self,
359 signature: &mut PythonSignature,
360 kwargs: &SignatureItemKwargs,
361 ) -> syn::Result<()> {
362 match self {
363 ParseState::Positional | ParseState::PositionalAfterPosargs | ParseState::Keywords => {
364 signature.kwargs = Some(kwargs.ident.to_string());
365 *self = ParseState::Done;
366 Ok(())
367 }
368 ParseState::Done => {
369 bail_spanned!(kwargs.span() => format!("`**{}` not allowed after `**{}`", kwargs.ident, signature.kwargs.as_deref().unwrap_or("")))
370 }
371 }
372 }
373
374 fn finish_pos_only_args(
375 &mut self,
376 signature: &mut PythonSignature,
377 span: Span,
378 ) -> syn::Result<()> {
379 match self {
380 ParseState::Positional => {
381 signature.positional_only_parameters = signature.positional_parameters.len();
382 *self = ParseState::PositionalAfterPosargs;
383 Ok(())
384 }
385 ParseState::PositionalAfterPosargs => {
386 bail_spanned!(span => "`/` not allowed after `/`")
387 }
388 ParseState::Keywords => {
389 bail_spanned!(span => format!("`/` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or("")))
390 }
391 ParseState::Done => {
392 bail_spanned!(span => format!("`/` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or("")))
393 }
394 }
395 }
396
397 fn finish_pos_args(&mut self, signature: &PythonSignature, span: Span) -> syn::Result<()> {
398 match self {
399 ParseState::Positional | ParseState::PositionalAfterPosargs => {
400 *self = ParseState::Keywords;
401 Ok(())
402 }
403 ParseState::Keywords => {
404 bail_spanned!(span => format!("`*` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or("")))
405 }
406 ParseState::Done => {
407 bail_spanned!(span => format!("`*` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or("")))
408 }
409 }
410 }
411}
412
413impl<'a> FunctionSignature<'a> {
414 pub fn from_arguments_and_attribute(
415 mut arguments: Vec<FnArg<'a>>,
416 attribute: SignatureAttribute,
417 ) -> syn::Result<Self> {
418 let mut parse_state = ParseState::Positional;
419 let mut python_signature = PythonSignature::default();
420
421 let mut args_iter = arguments.iter_mut();
422
423 let mut next_non_py_argument_checked = |name: &syn::Ident| {
424 for fn_arg in args_iter.by_ref() {
425 match fn_arg {
426 FnArg::Py(..) => {
427 ensure_spanned!(
430 name != fn_arg.name(),
431 name.span() => "arguments of type `Python` must not be part of the signature"
432 );
433 continue;
435 }
436 FnArg::CancelHandle(..) => {
437 ensure_spanned!(
440 name != fn_arg.name(),
441 name.span() => "`cancel_handle` argument must not be part of the signature"
442 );
443 continue;
445 }
446 _ => {
447 ensure_spanned!(
448 name == fn_arg.name(),
449 name.span() => format!(
450 "expected argument from function definition `{}` but got argument `{}`",
451 fn_arg.name().unraw(),
452 name.unraw(),
453 )
454 );
455 return Ok(fn_arg);
456 }
457 }
458 }
459 bail_spanned!(
460 name.span() => "signature entry does not have a corresponding function argument"
461 )
462 };
463
464 if let Some(returns) = &attribute.value.returns {
465 ensure_spanned!(
466 cfg!(feature = "experimental-inspect"),
467 returns.1.span() => "Return type annotation in the signature is only supported with the `experimental-inspect` feature"
468 );
469 }
470
471 for item in &attribute.value.items {
472 match item {
473 SignatureItem::Argument(arg) => {
474 let fn_arg = next_non_py_argument_checked(&arg.ident)?;
475 parse_state.add_argument(
476 &mut python_signature,
477 arg.ident.unraw().to_string(),
478 arg.eq_and_default.is_none(),
479 arg.span(),
480 )?;
481 let FnArg::Regular(fn_arg) = fn_arg else {
482 unreachable!(
483 "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \
484 parsed and transformed below. Because the have to come last and are only allowed \
485 once, this has to be a regular argument."
486 );
487 };
488 if let Some((_, default)) = &arg.eq_and_default {
489 fn_arg.default_value = Some(default.clone());
490 }
491 if let Some((_, annotation)) = &arg.colon_and_annotation {
492 ensure_spanned!(
493 cfg!(feature = "experimental-inspect"),
494 annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature"
495 );
496 #[cfg(feature = "experimental-inspect")]
497 {
498 fn_arg.annotation = Some(annotation.to_python());
499 }
500 }
501 }
502 SignatureItem::VarargsSep(sep) => {
503 parse_state.finish_pos_args(&python_signature, sep.span())?
504 }
505 SignatureItem::Varargs(varargs) => {
506 let fn_arg = next_non_py_argument_checked(&varargs.ident)?;
507 fn_arg.to_varargs_mut()?;
508 parse_state.add_varargs(&mut python_signature, varargs)?;
509 if let Some((_, annotation)) = &varargs.colon_and_annotation {
510 ensure_spanned!(
511 cfg!(feature = "experimental-inspect"),
512 annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature"
513 );
514 #[cfg(feature = "experimental-inspect")]
515 {
516 let FnArg::VarArgs(fn_arg) = fn_arg else {
517 unreachable!(
518 "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \
519 parsed and transformed below. Because the have to come last and are only allowed \
520 once, this has to be a regular argument."
521 );
522 };
523 fn_arg.annotation = Some(annotation.to_python());
524 }
525 }
526 }
527 SignatureItem::Kwargs(kwargs) => {
528 let fn_arg = next_non_py_argument_checked(&kwargs.ident)?;
529 fn_arg.to_kwargs_mut()?;
530 parse_state.add_kwargs(&mut python_signature, kwargs)?;
531 if let Some((_, annotation)) = &kwargs.colon_and_annotation {
532 ensure_spanned!(
533 cfg!(feature = "experimental-inspect"),
534 annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature"
535 );
536 #[cfg(feature = "experimental-inspect")]
537 {
538 let FnArg::KwArgs(fn_arg) = fn_arg else {
539 unreachable!(
540 "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \
541 parsed and transformed below. Because the have to come last and are only allowed \
542 once, this has to be a regular argument."
543 );
544 };
545 fn_arg.annotation = Some(annotation.to_python());
546 }
547 }
548 }
549 SignatureItem::PosargsSep(sep) => {
550 parse_state.finish_pos_only_args(&mut python_signature, sep.span())?
551 }
552 };
553 }
554
555 if let Some(arg) =
557 args_iter.find(|arg| !matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)))
558 {
559 bail_spanned!(
560 attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name())
561 );
562 }
563
564 Ok(FunctionSignature {
565 arguments,
566 python_signature,
567 attribute: Some(attribute),
568 })
569 }
570
571 pub fn from_arguments(arguments: Vec<FnArg<'a>>) -> Self {
573 let mut python_signature = PythonSignature::default();
574 for arg in &arguments {
575 if matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)) {
577 continue;
578 }
579
580 if let FnArg::Regular(RegularArg { .. }) = arg {
581 assert_eq!(
583 python_signature.required_positional_parameters,
584 python_signature.positional_parameters.len(),
585 );
586
587 python_signature.required_positional_parameters =
588 python_signature.positional_parameters.len() + 1;
589 }
590
591 python_signature
592 .positional_parameters
593 .push(arg.name().unraw().to_string());
594 }
595
596 Self {
597 arguments,
598 python_signature,
599 attribute: None,
600 }
601 }
602
603 fn default_value_for_parameter(&self, parameter: &str) -> String {
604 if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name() == parameter) {
605 fn_arg.default_value()
606 } else {
607 "...".to_string()
608 }
609 }
610
611 pub fn text_signature(&self, self_argument: Option<&str>) -> String {
612 let mut output = String::new();
613 output.push('(');
614
615 if let Some(arg) = self_argument {
616 output.push('$');
617 output.push_str(arg);
618 }
619
620 let mut maybe_push_comma = {
621 let mut first = self_argument.is_none();
622 move |output: &mut String| {
623 if !first {
624 output.push_str(", ");
625 } else {
626 first = false;
627 }
628 }
629 };
630
631 let py_sig = &self.python_signature;
632
633 for (i, parameter) in py_sig.positional_parameters.iter().enumerate() {
634 maybe_push_comma(&mut output);
635
636 output.push_str(parameter);
637
638 if i >= py_sig.required_positional_parameters {
639 output.push('=');
640 output.push_str(&self.default_value_for_parameter(parameter));
641 }
642
643 if py_sig.positional_only_parameters > 0 && i + 1 == py_sig.positional_only_parameters {
644 output.push_str(", /")
645 }
646 }
647
648 if let Some(varargs) = &py_sig.varargs {
649 maybe_push_comma(&mut output);
650 output.push('*');
651 output.push_str(varargs);
652 } else if !py_sig.keyword_only_parameters.is_empty() {
653 maybe_push_comma(&mut output);
654 output.push('*');
655 }
656
657 for (parameter, required) in &py_sig.keyword_only_parameters {
658 maybe_push_comma(&mut output);
659 output.push_str(parameter);
660 if !required {
661 output.push('=');
662 output.push_str(&self.default_value_for_parameter(parameter));
663 }
664 }
665
666 if let Some(kwargs) = &py_sig.kwargs {
667 maybe_push_comma(&mut output);
668 output.push_str("**");
669 output.push_str(kwargs);
670 }
671
672 output.push(')');
673 output
674 }
675}