1use proc_macro2::TokenStream;
3use pyo3::{Borrowed, Bound, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods};
4use quote::quote;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 CodeGen, CodeGenContext, ExprType, Node, PythonOptions, SymbolTableScopes,
9};
10
11#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
14pub struct Argument {
15 pub value: ExprType,
17 pub lineno: Option<usize>,
19 pub col_offset: Option<usize>,
20 pub end_lineno: Option<usize>,
21 pub end_col_offset: Option<usize>,
22}
23
24pub type Arg = ExprType;
27
28#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
30pub struct Parameter {
31 pub arg: String,
33 pub annotation: Option<Box<ExprType>>,
35 pub type_comment: Option<String>,
37 pub lineno: Option<usize>,
39 pub col_offset: Option<usize>,
40 pub end_lineno: Option<usize>,
41 pub end_col_offset: Option<usize>,
42}
43
44#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
46pub struct Arguments {
47 pub posonlyargs: Vec<Parameter>,
49 pub args: Vec<Parameter>,
51 pub vararg: Option<Parameter>,
53 pub kwonlyargs: Vec<Parameter>,
55 pub kw_defaults: Vec<Option<Box<ExprType>>>,
57 pub kwarg: Option<Parameter>,
59 pub defaults: Vec<Box<ExprType>>,
61}
62
63
64#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
66pub struct CallArguments {
67 pub args: Vec<ExprType>,
69 pub keywords: Vec<crate::Keyword>,
71}
72
73impl<'a, 'py> FromPyObject<'a, 'py> for Argument {
75 type Error = pyo3::PyErr;
76 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
77 let value: ExprType = ob.extract()?;
79
80 Ok(Self {
81 value,
82 lineno: ob.lineno(),
83 col_offset: ob.col_offset(),
84 end_lineno: ob.end_lineno(),
85 end_col_offset: ob.end_col_offset(),
86 })
87 }
88}
89
90impl CodeGen for Argument {
91 type Context = CodeGenContext;
92 type Options = PythonOptions;
93 type SymbolTable = SymbolTableScopes;
94
95 fn to_rust(
96 self,
97 ctx: Self::Context,
98 options: Self::Options,
99 symbols: Self::SymbolTable,
100 ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
101 self.value.to_rust(ctx, options, symbols)
102 }
103}
104
105impl<'a, 'py> FromPyObject<'a, 'py> for Parameter {
107 type Error = pyo3::PyErr;
108 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
109 let arg: String = ob.getattr("arg")?.extract()?;
110
111 let annotation = if let Ok(ann) = ob.getattr("annotation") {
113 if ann.is_none() {
114 None
115 } else {
116 Some(Box::new(ann.extract()?))
117 }
118 } else {
119 None
120 };
121
122 let type_comment = if let Ok(tc) = ob.getattr("type_comment") {
124 if tc.is_none() {
125 None
126 } else {
127 Some(tc.extract()?)
128 }
129 } else {
130 None
131 };
132
133 Ok(Self {
134 arg,
135 annotation,
136 type_comment,
137 lineno: ob.lineno(),
138 col_offset: ob.col_offset(),
139 end_lineno: ob.end_lineno(),
140 end_col_offset: ob.end_col_offset(),
141 })
142 }
143}
144
145pub(crate) fn is_optional_annotation(ann: &ExprType) -> bool {
149 match ann {
150 ExprType::Subscript(sub) => {
151 matches!(sub.value.as_ref(), ExprType::Name(n) if n.id == "Optional")
152 }
153 ExprType::BinOp(op) if matches!(op.op, crate::BinOps::BitOr) => {
154 crate::is_none_expr(&op.left) || crate::is_none_expr(&op.right)
155 }
156 _ => false,
157 }
158}
159
160pub fn python_annotation_to_rust_type(annotation: &ExprType) -> Option<TokenStream> {
166 match annotation {
167 ExprType::BinOp(op) if matches!(op.op, crate::BinOps::BitOr) => {
169 let inner = if crate::is_none_expr(&op.left) {
170 op.right.as_ref()
171 } else if crate::is_none_expr(&op.right) {
172 op.left.as_ref()
173 } else {
174 return None;
175 };
176 let inner = python_annotation_to_rust_type(inner)?;
177 return Some(quote!(Option<#inner>));
178 }
179 _ => {}
180 }
181 match annotation {
182 ExprType::Name(name) => match name.id.as_str() {
183 "int" => Some(quote!(i64)),
184 "float" => Some(quote!(f64)),
185 "str" => Some(quote!(String)),
186 "bool" => Some(quote!(bool)),
187 "bytes" => Some(quote!(Vec<u8>)),
188 _ => None,
189 },
190 ExprType::Subscript(sub) => {
194 let container = match sub.value.as_ref() {
195 ExprType::Name(n) => n.id.as_str(),
196 _ => return None,
197 };
198 match (&sub.kind, container) {
199 (crate::SubscriptKind::Index(elt), "Optional") => {
200 let inner = python_annotation_to_rust_type(elt)?;
201 Some(quote!(Option<#inner>))
202 }
203 (crate::SubscriptKind::Index(elt), "list") => {
204 let inner = python_annotation_to_rust_type(elt)?;
205 Some(quote!(Vec<#inner>))
206 }
207 (crate::SubscriptKind::Index(elt), "set" | "frozenset") => {
208 let inner = python_annotation_to_rust_type(elt)?;
209 Some(quote!(std::collections::HashSet<#inner>))
210 }
211 (crate::SubscriptKind::Index(kv), "dict") => {
212 if let ExprType::Tuple(t) = kv.as_ref() {
216 if let [k, v] = t.elts.as_slice() {
217 let k = python_annotation_to_rust_type(k)?;
218 let v = python_annotation_to_rust_type(v)?;
219 return Some(quote!(PyDict<#k, #v>));
220 }
221 }
222 None
223 }
224 _ => None,
225 }
226 }
227 _ => None,
228 }
229}
230
231impl CodeGen for Parameter {
232 type Context = CodeGenContext;
233 type Options = PythonOptions;
234 type SymbolTable = SymbolTableScopes;
235
236 fn to_rust(
237 self,
238 ctx: Self::Context,
239 options: Self::Options,
240 symbols: Self::SymbolTable,
241 ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
242
243 let param_name = crate::safe_ident(&self.arg);
244
245 if let Some(annotation) = self.annotation {
247 if matches!(&*annotation, ExprType::Name(n) if n.id == "str") {
251 return Ok(quote!(#param_name: impl Into<String>));
252 }
253 let rust_type = match python_annotation_to_rust_type(&annotation) {
257 Some(mapped) => mapped,
258 None => annotation.to_rust(ctx, options, symbols)?,
259 };
260 Ok(quote!(#param_name: #rust_type))
261 } else {
262 Ok(quote!(#param_name: impl Into<PyObject>))
264 }
265 }
266}
267
268impl<'a, 'py> FromPyObject<'a, 'py> for Arguments {
270 type Error = pyo3::PyErr;
271 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
272 let posonlyargs: Vec<Parameter> = ob.getattr("posonlyargs")?.extract().unwrap_or_default();
274 let args: Vec<Parameter> = ob.getattr("args")?.extract().unwrap_or_default();
275
276 let vararg = if let Ok(va) = ob.getattr("vararg") {
277 if va.is_none() { None } else { Some(va.extract()?) }
278 } else { None };
279
280 let kwonlyargs: Vec<Parameter> = ob.getattr("kwonlyargs")?.extract().unwrap_or_default();
281
282 let kw_defaults = if let Ok(kw_def) = ob.getattr("kw_defaults") {
284 let defaults_list: Vec<Bound<PyAny>> = kw_def.extract().unwrap_or_default();
285 let mut processed_defaults = Vec::new();
286 for default in defaults_list {
287 if default.is_none() {
288 processed_defaults.push(None);
289 } else {
290 processed_defaults.push(Some(Box::new(default.extract()?)));
291 }
292 }
293 processed_defaults
294 } else {
295 Vec::new()
296 };
297
298 let kwarg = if let Ok(kw) = ob.getattr("kwarg") {
299 if kw.is_none() { None } else { Some(kw.extract()?) }
300 } else { None };
301
302 let defaults_raw: Vec<ExprType> = ob.getattr("defaults")?.extract().unwrap_or_default();
303 let defaults = defaults_raw.into_iter().map(Box::new).collect();
304
305 Ok(Self {
306 posonlyargs,
307 args,
308 vararg,
309 kwonlyargs,
310 kw_defaults,
311 kwarg,
312 defaults,
313 })
314 }
315}
316
317impl CodeGen for Arguments {
318 type Context = CodeGenContext;
319 type Options = PythonOptions;
320 type SymbolTable = SymbolTableScopes;
321
322 fn to_rust(
323 self,
324 ctx: Self::Context,
325 options: Self::Options,
326 symbols: Self::SymbolTable,
327 ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
328 let mut params = Vec::new();
329
330 for arg in self.posonlyargs {
332 let param = arg.to_rust(ctx.clone(), options.clone(), symbols.clone())?;
333 params.push(param);
334 }
335
336 for arg in self.args {
343 let param = arg.to_rust(ctx.clone(), options.clone(), symbols.clone())?;
344 params.push(param);
345 }
346
347 if let Some(vararg) = self.vararg {
349 let vararg_name = crate::safe_ident(&vararg.arg);
350 params.push(quote!(#vararg_name: impl IntoIterator<Item = impl Into<PyObject>>));
351 }
352
353 for arg in self.kwonlyargs {
356 let param = arg.to_rust(ctx.clone(), options.clone(), symbols.clone())?;
357 params.push(param);
358 }
359
360 if let Some(kwarg) = self.kwarg {
362 let kwarg_name = crate::safe_ident(&kwarg.arg);
363 params.push(quote!(#kwarg_name: impl IntoIterator<Item = (impl AsRef<str>, impl Into<PyObject>)>));
364 }
365
366 Ok(quote!(#(#params),*))
367 }
368}
369
370
371impl<'a, 'py> FromPyObject<'a, 'py> for CallArguments {
373 type Error = pyo3::PyErr;
374 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
375 let args: Vec<ExprType> = ob.getattr("args")?.extract().unwrap_or_default();
376 let keywords: Vec<crate::Keyword> = ob.getattr("keywords")?.extract().unwrap_or_default();
377
378 Ok(Self { args, keywords })
379 }
380}
381
382impl CodeGen for CallArguments {
383 type Context = CodeGenContext;
384 type Options = PythonOptions;
385 type SymbolTable = SymbolTableScopes;
386
387 fn to_rust(
388 self,
389 ctx: Self::Context,
390 options: Self::Options,
391 symbols: Self::SymbolTable,
392 ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
393 let mut all_args = Vec::new();
394
395 for arg in self.args {
397 let rust_arg = arg.to_rust(ctx.clone(), options.clone(), symbols.clone())?;
398 all_args.push(rust_arg);
399 }
400
401 for keyword in self.keywords {
403 let rust_kw = keyword.to_rust(ctx.clone(), options.clone(), symbols.clone())?;
404 all_args.push(rust_kw);
405 }
406
407 Ok(quote!(#(#all_args),*))
408 }
409}
410
411
412impl Node for Argument {
414 fn lineno(&self) -> Option<usize> { self.lineno }
415 fn col_offset(&self) -> Option<usize> { self.col_offset }
416 fn end_lineno(&self) -> Option<usize> { self.end_lineno }
417 fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
418}
419
420impl Node for Parameter {
421 fn lineno(&self) -> Option<usize> { self.lineno }
422 fn col_offset(&self) -> Option<usize> { self.col_offset }
423 fn end_lineno(&self) -> Option<usize> { self.end_lineno }
424 fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
425}
426
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::{parse, CodeGenContext, ExprType, PythonOptions, SymbolTableScopes};
432 use test_log::test;
433
434 #[test]
435 fn test_simple_function_call() {
436 let code = "func(1, 2, 3)";
437 let result = parse(code, "test.py").unwrap();
438
439 let options = PythonOptions::default();
441 let symbols = SymbolTableScopes::new();
442 let _rust_code = result.to_rust(
443 CodeGenContext::Module("test".to_string()),
444 options,
445 symbols,
446 ).unwrap();
447
448 }
450
451 #[test]
452 fn test_keyword_arguments() {
453 let code = "def func(a, b):\n pass\n\nfunc(b=2, a=1)";
456 let result = parse(code, "test.py").unwrap();
457
458 let options = PythonOptions::default();
459 let symbols = result.clone().find_symbols(SymbolTableScopes::new());
460 let rust_code = result.to_rust(
461 CodeGenContext::Module("test".to_string()),
462 options,
463 symbols,
464 ).unwrap().to_string();
465 assert!(rust_code.contains("func (1 , 2)"), "generated: {}", rust_code);
466 }
467
468 #[test]
469 fn test_mixed_arguments() {
470 let code = "def func(a, b, c, d):\n pass\n\nfunc(1, 2, d=4, c=3)";
471 let result = parse(code, "test.py").unwrap();
472
473 let options = PythonOptions::default();
474 let symbols = result.clone().find_symbols(SymbolTableScopes::new());
475 let rust_code = result.to_rust(
476 CodeGenContext::Module("test".to_string()),
477 options,
478 symbols,
479 ).unwrap().to_string();
480 assert!(rust_code.contains("func (1 , 2 , 3 , 4)"), "generated: {}", rust_code);
481 }
482
483 #[test]
484 fn test_function_with_defaults() {
485 let code = r#"
486def func(a, b=2, c=3):
487 pass
488 "#;
489 let result = parse(code, "test.py").unwrap();
490
491 let options = PythonOptions::default();
492 let symbols = SymbolTableScopes::new();
493 let _rust_code = result.to_rust(
494 CodeGenContext::Module("test".to_string()),
495 options,
496 symbols,
497 ).unwrap();
498
499 }
501
502 #[test]
503 fn test_function_with_varargs() {
504 let code = r#"
505def func(a, *args):
506 pass
507 "#;
508 let result = parse(code, "test.py").unwrap();
509
510 let options = PythonOptions::default();
511 let symbols = SymbolTableScopes::new();
512 let _rust_code = result.to_rust(
513 CodeGenContext::Module("test".to_string()),
514 options,
515 symbols,
516 ).unwrap();
517
518 }
520
521 #[test]
522 fn test_function_with_kwargs() {
523 let code = r#"
524def func(a, **kwargs):
525 pass
526 "#;
527 let result = parse(code, "test.py").unwrap();
528
529 let options = PythonOptions::default();
530 let symbols = SymbolTableScopes::new();
531 let _rust_code = result.to_rust(
532 CodeGenContext::Module("test".to_string()),
533 options,
534 symbols,
535 ).unwrap();
536
537 }
539
540 #[test]
541 fn test_complex_function_signature() {
542 let code = r#"
543def func(a, b=2, *args, c, d=4, **kwargs):
544 pass
545 "#;
546 let result = parse(code, "test.py").unwrap();
547
548 let options = PythonOptions::default();
549 let symbols = SymbolTableScopes::new();
550 let _rust_code = result.to_rust(
551 CodeGenContext::Module("test".to_string()),
552 options,
553 symbols,
554 ).unwrap();
555
556 }
558
559 #[test]
560 fn test_keyword_only_arguments() {
561 let code = r#"
562def func(a, *, b, c=3):
563 pass
564 "#;
565 let result = parse(code, "test.py").unwrap();
566
567 let options = PythonOptions::default();
568 let symbols = SymbolTableScopes::new();
569 let _rust_code = result.to_rust(
570 CodeGenContext::Module("test".to_string()),
571 options,
572 symbols,
573 ).unwrap();
574
575 }
577
578 #[test]
579 fn test_argument_unpacking_call() {
580 let code = "func(*args, **kwargs)";
582 let result = parse(code, "test.py");
583
584 match result {
585 Ok(ast) => {
586 let options = PythonOptions::default();
587 let symbols = SymbolTableScopes::new();
588 let rust_code = ast.to_rust(
589 CodeGenContext::Module("test".to_string()),
590 options,
591 symbols,
592 );
593
594 match rust_code {
595 Ok(_code) => { },
596 Err(_e) => { },
597 }
598 }
599 Err(_e) => { },
600 }
601 }
602
603 #[test]
604 fn test_arg_with_constant() {
605 use litrs::Literal;
607 let literal = Literal::parse("42").unwrap().into_owned();
608 let constant = crate::Constant(Some(literal));
609 let arg: Arg = ExprType::Constant(constant);
610
611 let options = PythonOptions::default();
612 let symbols = SymbolTableScopes::new();
613 let rust_code = arg.to_rust(
614 CodeGenContext::Module("test".to_string()),
615 options,
616 symbols,
617 ).unwrap();
618
619 assert!(rust_code.to_string().contains("42"));
620 }
621
622 #[test]
623 fn test_arg_with_name() {
624 let name_expr = ExprType::Name(crate::Name {
626 id: "variable".to_string(),
627 });
628 let arg: Arg = name_expr;
629
630 let options = PythonOptions::default();
631 let symbols = SymbolTableScopes::new();
632 let rust_code = arg.to_rust(
633 CodeGenContext::Module("test".to_string()),
634 options,
635 symbols,
636 ).unwrap();
637
638 assert!(rust_code.to_string().contains("variable"));
639 }
640}