1use tracing::debug;
2use proc_macro2::TokenStream;
3use pyo3::{Borrowed, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods};
4use quote::quote;
5use serde::{Deserialize, Serialize};
6use crate::ast::tree::statement::PyStatementTrait;
7
8use crate::{
9 CodeGen, CodeGenContext, ExprType, Object, ParameterList, PythonOptions, Statement,
10 StatementType, SymbolTableNode, SymbolTableScopes,
11};
12
13#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
14pub struct FunctionDef {
15 pub name: String,
16 pub args: ParameterList,
17 pub body: Vec<Statement>,
18 pub decorator_list: Vec<ExprType>,
19 pub returns: Option<Box<ExprType>>,
21}
22
23impl<'a, 'py> FromPyObject<'a, 'py> for FunctionDef {
24 type Error = pyo3::PyErr;
25 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
26 let name: String = ob.getattr("name")?.extract()?;
27 let args: ParameterList = ob.getattr("args")?.extract()?;
28 let body: Vec<Statement> = ob.getattr("body")?.extract()?;
29
30 let decorator_list: Vec<ExprType> = ob.getattr("decorator_list")?.extract().unwrap_or_default();
32
33 let returns: Option<Box<ExprType>> = match ob.getattr("returns") {
35 Ok(r) if !r.is_none() => r.extract().ok().map(Box::new),
36 _ => None,
37 };
38
39 Ok(FunctionDef {
40 name,
41 args,
42 body,
43 decorator_list,
44 returns,
45 })
46 }
47}
48
49impl PyStatementTrait for FunctionDef {
50}
51
52struct ArgparseSpec {
54 name: String,
55 kind: &'static str, default: Option<ExprType>,
57 help: Option<String>,
58}
59
60struct ArgparseRewrite {
66 skip: std::collections::HashSet<usize>,
67 parse_index: usize,
68 args_var: String,
69 prog: Option<String>,
70 description: Option<String>,
71 specs: Vec<ArgparseSpec>,
72}
73
74fn literal_str(e: &ExprType) -> Option<String> {
75 match e {
76 ExprType::Constant(c) => match &c.0 {
77 Some(litrs::Literal::String(s)) => Some(s.value().to_string()),
78 _ => None,
79 },
80 _ => None,
81 }
82}
83
84fn scan_argparse(
85 body: &[Statement],
86) -> Result<Option<ArgparseRewrite>, Box<dyn std::error::Error>> {
87 let mut parser: Option<(usize, String, Option<String>, Option<String>)> = None;
89 for (i, stmt) in body.iter().enumerate() {
90 let StatementType::Assign(assign) = &stmt.statement else {
91 continue;
92 };
93 let ExprType::Call(call) = &assign.value else {
94 continue;
95 };
96 let ExprType::Attribute(attr) = call.func.as_ref() else {
97 continue;
98 };
99 let is_ctor = attr.attr == "ArgumentParser"
100 && matches!(attr.value.as_ref(), ExprType::Name(m) if m.id == "argparse");
101 if !is_ctor {
102 continue;
103 }
104 let [ExprType::Name(target)] = assign.targets.as_slice() else {
105 return Err("argparse.ArgumentParser must be assigned to a plain name".into());
106 };
107 if !call.args.is_empty() {
108 return Err(
109 "argparse.ArgumentParser: pass prog=/description= by keyword".into(),
110 );
111 }
112 let mut prog = None;
113 let mut description = None;
114 for kw in &call.keywords {
115 let value = literal_str(&kw.value).ok_or_else(|| {
116 format!(
117 "argparse.ArgumentParser: {} must be a string literal (the parser \
118 is evaluated at conversion time)",
119 kw.arg.as_deref().unwrap_or("argument")
120 )
121 })?;
122 match kw.arg.as_deref() {
123 Some("prog") => prog = Some(value),
124 Some("description") => description = Some(value),
125 other => {
126 return Err(format!(
127 "argparse.ArgumentParser keyword '{}' is not supported yet",
128 other.unwrap_or("**kwargs")
129 )
130 .into())
131 }
132 }
133 }
134 parser = Some((i, target.id.clone(), prog, description));
135 break;
136 }
137 let Some((ctor_index, pvar, prog, description)) = parser else {
138 return Ok(None);
139 };
140
141 let mut skip = std::collections::HashSet::from([ctor_index]);
144 let mut specs = Vec::new();
145 let mut parse: Option<(usize, String)> = None;
146 for (i, stmt) in body.iter().enumerate().skip(ctor_index + 1) {
147 let call_on_parser = |call: &crate::Call| -> Option<String> {
148 let ExprType::Attribute(attr) = call.func.as_ref() else {
149 return None;
150 };
151 match attr.value.as_ref() {
152 ExprType::Name(m) if m.id == pvar => Some(attr.attr.clone()),
153 _ => None,
154 }
155 };
156 let stmt_call: Option<&crate::Call> = match &stmt.statement {
159 StatementType::Call(c) => Some(c),
160 StatementType::Expr(e) => match &e.value {
161 ExprType::Call(c) => Some(c),
162 _ => None,
163 },
164 _ => None,
165 };
166 match &stmt.statement {
167 _ if stmt_call.is_some_and(|c| call_on_parser(c) == Some("add_argument".into())) => {
168 let call = stmt_call.expect("checked");
169 if parse.is_some() {
170 return Err("add_argument after parse_args is not supported".into());
171 }
172 let [name_expr] = call.args.as_slice() else {
173 return Err(
174 "add_argument takes exactly one name (short aliases are not \
175 supported yet)"
176 .into(),
177 );
178 };
179 let name = literal_str(name_expr)
180 .ok_or("add_argument: the name must be a string literal")?;
181 if name.starts_with('-') && !name.starts_with("--") {
182 return Err(format!(
183 "add_argument: short option '{}' is not supported yet; use the \
184 --long form",
185 name
186 )
187 .into());
188 }
189 let mut kind: Option<&'static str> = None;
190 let mut default = None;
191 let mut help = None;
192 let mut store_true = false;
193 for kw in &call.keywords {
194 match kw.arg.as_deref() {
195 Some("type") => {
196 kind = Some(match &kw.value {
197 ExprType::Name(n) if n.id == "int" => "Int",
198 ExprType::Name(n) if n.id == "float" => "Float",
199 ExprType::Name(n) if n.id == "str" => "Str",
200 _ => {
201 return Err(format!(
202 "add_argument('{}'): type must be int, float, \
203 or str",
204 name
205 )
206 .into())
207 }
208 });
209 }
210 Some("default") => default = Some(kw.value.clone()),
211 Some("help") => {
212 help = Some(literal_str(&kw.value).ok_or_else(|| {
213 format!(
214 "add_argument('{}'): help must be a string literal",
215 name
216 )
217 })?)
218 }
219 Some("action") => match literal_str(&kw.value).as_deref() {
220 Some("store_true") => store_true = true,
221 _ => {
222 return Err(format!(
223 "add_argument('{}'): only action=\"store_true\" is \
224 supported",
225 name
226 )
227 .into())
228 }
229 },
230 other => {
231 return Err(format!(
232 "add_argument('{}'): keyword '{}' is not supported yet",
233 name,
234 other.unwrap_or("**kwargs")
235 )
236 .into())
237 }
238 }
239 }
240 let kind = if store_true {
241 if kind.is_some() || default.is_some() {
242 return Err(format!(
243 "add_argument('{}'): store_true takes neither type nor \
244 default",
245 name
246 )
247 .into());
248 }
249 "StoreTrue"
250 } else {
251 kind.unwrap_or("Str")
252 };
253 let is_positional = !name.starts_with('-');
254 if is_positional && default.is_some() {
255 return Err(format!(
256 "add_argument('{}'): defaults on positionals are not supported",
257 name
258 )
259 .into());
260 }
261 if !is_positional && !store_true && default.is_none() {
262 return Err(format!(
263 "add_argument('{}'): a value-taking option needs default= (its \
264 Python default None cannot inhabit a typed field)",
265 name
266 )
267 .into());
268 }
269 specs.push(ArgparseSpec {
270 name,
271 kind,
272 default,
273 help,
274 });
275 skip.insert(i);
276 }
277 StatementType::Assign(assign) => {
278 if let ExprType::Call(call) = &assign.value {
279 if call_on_parser(call) == Some("parse_args".into()) {
280 if !call.args.is_empty() || !call.keywords.is_empty() {
281 return Err("parse_args with arguments is not supported".into());
282 }
283 let [ExprType::Name(t)] = assign.targets.as_slice() else {
284 return Err("parse_args must be assigned to a plain name".into());
285 };
286 parse = Some((i, t.id.clone()));
287 } else if call_on_parser(call).is_some() {
288 return Err(format!(
289 "argparse parser `{}`: only add_argument and parse_args \
290 are supported",
291 pvar
292 )
293 .into());
294 }
295 }
296 }
297 _ if stmt_call.is_some_and(|c| call_on_parser(c).is_some()) => {
298 return Err(format!(
299 "argparse parser `{}`: only add_argument and parse_args are \
300 supported",
301 pvar
302 )
303 .into());
304 }
305 _ => {}
306 }
307 }
308 let Some((parse_index, args_var)) = parse else {
309 return Err("argparse.ArgumentParser built but parse_args() never assigned".into());
310 };
311 Ok(Some(ArgparseRewrite {
312 skip,
313 parse_index,
314 args_var,
315 prog,
316 description,
317 specs,
318 }))
319}
320
321fn lower_parse_args(
325 rw: &ArgparseRewrite,
326 ctx: &CodeGenContext,
327 options: &PythonOptions,
328 symbols: &SymbolTableScopes,
329) -> Result<TokenStream, Box<dyn std::error::Error>> {
330 use quote::format_ident;
331 let mut fields = Vec::new();
332 let mut field_types = Vec::new();
333 let mut spec_tokens = Vec::new();
334 let mut accessors = Vec::new();
335 for spec in &rw.specs {
336 let dest = spec.name.trim_start_matches('-').replace('-', "_");
337 fields.push(crate::safe_ident(&dest));
338 let (fty, kind, accessor) = match spec.kind {
339 "Int" => (quote!(i64), quote!(Int), format_ident!("into_int")),
340 "Float" => (quote!(f64), quote!(Float), format_ident!("into_float")),
341 "StoreTrue" => (quote!(bool), quote!(StoreTrue), format_ident!("into_flag")),
342 _ => (quote!(String), quote!(Str), format_ident!("into_str")),
343 };
344 field_types.push(fty);
345 accessors.push(accessor);
346 let default = match &spec.default {
347 None => quote!(None),
348 Some(e) => {
349 let d = e
350 .clone()
351 .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
352 match spec.kind {
355 "Int" => quote!(Some(argparse::ParsedValue::Int((#d) as i64))),
356 "Float" => quote!(Some(argparse::ParsedValue::Float((#d) as f64))),
357 _ => quote!(Some(argparse::ParsedValue::Str((#d).to_string()))),
358 }
359 }
360 };
361 let name = &spec.name;
362 let help = match &spec.help {
363 Some(h) => quote!(Some(#h)),
364 None => quote!(None),
365 };
366 spec_tokens.push(quote!(argparse::ArgSpec {
367 name: #name,
368 kind: argparse::ArgKind::#kind,
369 default: #default,
370 help: #help,
371 }));
372 }
373 let prog = match &rw.prog {
374 Some(p) => quote!(Some(#p)),
375 None => quote!(None),
376 };
377 let description = match &rw.description {
378 Some(d) => quote!(Some(#d)),
379 None => quote!(None),
380 };
381 let args_var = crate::safe_ident(&rw.args_var);
382 Ok(quote! {
383 #[allow(non_camel_case_types)]
384 struct __ArgparseArgs {
385 #(#fields: #field_types,)*
386 }
387 let mut __parsed = argparse::run_parser(
388 #prog,
389 #description,
390 &[#(#spec_tokens),*],
391 )?
392 .into_iter();
393 #args_var = __ArgparseArgs {
394 #(#fields: __parsed.next().expect("one value per spec").#accessors(),)*
395 }
396 })
397}
398
399fn parse_cache_decorator(
406 decorators: &[ExprType],
407) -> Result<Option<Option<i64>>, Box<dyn std::error::Error>> {
408 let unsupported = |what: &str| -> Box<dyn std::error::Error> {
409 format!(
410 "decorator `{}` is not supported yet (only functools.lru_cache and \
411 functools.cache are); rython refuses to silently ignore it",
412 what
413 )
414 .into()
415 };
416 let name_of = |e: &ExprType| -> Option<String> {
417 match e {
418 ExprType::Name(n) => Some(n.id.clone()),
419 ExprType::Attribute(a) => match a.value.as_ref() {
420 ExprType::Name(m) if m.id == "functools" => Some(a.attr.clone()),
421 _ => None,
422 },
423 _ => None,
424 }
425 };
426 match decorators {
427 [] => Ok(None),
428 [single] => {
429 let (base, call) = match single {
430 ExprType::Call(c) => (name_of(c.func.as_ref()), Some(c)),
431 other => (name_of(other), None),
432 };
433 match (base.as_deref(), call) {
434 (Some("cache"), None) => Ok(Some(None)),
435 (Some("cache"), Some(c)) if c.args.is_empty() && c.keywords.is_empty() => {
436 Ok(Some(None))
437 }
438 (Some("lru_cache"), None) => Ok(Some(Some(128))),
439 (Some("lru_cache"), Some(c)) => {
440 let maxsize = match (c.args.as_slice(), c.keywords.as_slice()) {
441 ([], []) => return Ok(Some(Some(128))),
442 ([e], []) => e.clone(),
443 ([], [kw]) if kw.arg.as_deref() == Some("maxsize") => {
444 kw.value.clone()
445 }
446 _ => {
447 return Err(
448 "lru_cache() takes at most a single maxsize argument"
449 .to_string()
450 .into(),
451 )
452 }
453 };
454 if crate::is_none_expr(&maxsize) {
455 return Ok(Some(None));
456 }
457 match &maxsize {
458 ExprType::Constant(c) => match &c.0 {
459 Some(litrs::Literal::Integer(i)) => {
460 let n: i64 = i.value().ok_or("maxsize out of range")?;
461 Ok(Some(Some(n)))
462 }
463 _ => Err("lru_cache maxsize must be an integer literal or None"
464 .to_string()
465 .into()),
466 },
467 _ => Err("lru_cache maxsize must be an integer literal or None"
468 .to_string()
469 .into()),
470 }
471 }
472 _ => Err(unsupported(&format!("{:?}", single))),
473 }
474 }
475 many => Err(unsupported(&format!("{:?}", many[0]))),
476 }
477}
478
479impl CodeGen for FunctionDef {
480 type Context = CodeGenContext;
481 type Options = PythonOptions;
482 type SymbolTable = SymbolTableScopes;
483
484 fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
485 let mut symbols = symbols;
486 symbols.insert(
487 self.name.clone(),
488 SymbolTableNode::FunctionDef(self.clone()),
489 );
490 symbols
491 }
492
493 fn to_rust(
494 self,
495 ctx: Self::Context,
496 options: Self::Options,
497 symbols: SymbolTableScopes,
498 ) -> Result<TokenStream, Box<dyn std::error::Error>> {
499 let mut streams = TokenStream::new();
500 let fn_name = crate::safe_ident(&self.name);
501
502 let argparse_rewrite = scan_argparse(&self.body)?;
505 let effective_body: Vec<Statement> = match &argparse_rewrite {
506 None => self.body.clone(),
507 Some(rw) => self
508 .body
509 .iter()
510 .enumerate()
511 .filter(|(i, _)| !rw.skip.contains(i))
512 .map(|(_, s)| s.clone())
513 .collect(),
514 };
515 let argparse_parse_at: Option<usize> = argparse_rewrite.as_ref().map(|rw| {
517 (0..rw.parse_index)
518 .filter(|i| !rw.skip.contains(i))
519 .count()
520 });
521
522 let cache_spec = parse_cache_decorator(&self.decorator_list)?;
525 if cache_spec.is_some() && options.no_std {
526 return Err(format!(
527 "@lru_cache on `{}` needs a global Mutex, which the no_std \
528 profile does not provide",
529 self.name
530 )
531 .into());
532 }
533
534 let visibility = if self.name.starts_with("_") && !self.name.starts_with("__") {
537 quote!() } else if self.name.starts_with("__") && self.name.ends_with("__") {
539 quote!(pub(crate)) } else {
541 quote!(pub) };
543
544 let ctx = ctx.strip_exception_scopes();
547
548 let is_async = match ctx.clone() {
549 CodeGenContext::Async(_) => {
550 quote!(async)
551 }
552 _ => quote!(),
553 };
554
555 let mut symbols = symbols;
558 for s in &self.body {
559 symbols = s.clone().find_symbols(symbols);
560 }
561
562 let is_method = matches!(&ctx, CodeGenContext::Class(_))
567 && self
568 .args
569 .posonlyargs
570 .first()
571 .or(self.args.args.first())
572 .is_some_and(|p| p.arg == "self");
573 let mut render_args = self.args.clone();
574 let method_mutates_self = is_method
575 && match &ctx {
576 CodeGenContext::Class(class_name) => match symbols.get(class_name) {
577 Some(crate::SymbolTableNode::ClassDef(c)) => {
578 c.method_needs_mut_self(&self.name, &symbols)
579 }
580 _ => false,
581 },
582 _ => false,
583 };
584 if is_method {
585 crate::strip_self(&mut render_args);
586 }
587
588 let parameters = render_args
589 .clone()
590 .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
591
592 let cache_key: Option<Vec<(proc_macro2::Ident, TokenStream)>> = match cache_spec {
597 None => None,
598 Some(_) => {
599 if is_method {
600 return Err(format!(
601 "@lru_cache on method `{}` is not supported yet",
602 self.name
603 )
604 .into());
605 }
606 if !self.args.posonlyargs.is_empty()
607 || !self.args.kwonlyargs.is_empty()
608 || self.args.vararg.is_some()
609 || self.args.kwarg.is_some()
610 {
611 return Err(format!(
612 "@lru_cache on `{}`: only plain positional parameters are \
613 supported",
614 self.name
615 )
616 .into());
617 }
618 let mut key = Vec::new();
619 for p in &self.args.args {
620 let ty = match p.annotation.as_deref() {
621 Some(ExprType::Name(n)) if n.id == "int" => quote!(i64),
622 Some(ExprType::Name(n)) if n.id == "bool" => quote!(bool),
623 Some(ExprType::Name(n)) if n.id == "str" => quote!(String),
624 _ => {
625 return Err(format!(
626 "@lru_cache on `{}`: parameter `{}` must be annotated \
627 int, bool, or str (the arguments form the cache key)",
628 self.name, p.arg
629 )
630 .into());
631 }
632 };
633 key.push((crate::safe_ident(&p.arg), ty));
634 }
635 Some(key)
636 }
637 };
638
639 let mut param_names: Vec<String> = render_args
648 .args
649 .iter()
650 .chain(render_args.posonlyargs.iter())
651 .chain(render_args.kwonlyargs.iter())
652 .map(|p| p.arg.clone())
653 .chain(render_args.vararg.iter().map(|p| p.arg.clone()))
654 .chain(render_args.kwarg.iter().map(|p| p.arg.clone()))
655 .collect();
656 if is_method {
657 param_names.push("self".to_string());
661 }
662 let scope = crate::analyze_scope_with(
667 &effective_body,
668 ¶m_names,
669 &crate::class_call_resolver(&ctx, &symbols),
670 );
671 if is_method {
672 param_names.pop();
673 }
674 let receiver = if is_method {
675 if method_mutates_self || scope.needs_mut.contains("self") {
676 quote!(&mut self,)
677 } else {
678 quote!(&self,)
679 }
680 } else {
681 quote!()
682 };
683 let mut options = options;
687 {
688 let mut optional = scope.optional.clone();
689 for p in self
690 .args
691 .posonlyargs
692 .iter()
693 .chain(self.args.args.iter())
694 .chain(self.args.kwonlyargs.iter())
695 {
696 if let Some(ann) = p.annotation.as_deref() {
697 if crate::is_optional_annotation(ann) {
698 optional.insert(p.arg.clone());
699 }
700 }
701 }
702 options.optional_names = std::rc::Rc::new(optional);
703 options.clone_str_attribute_returns =
704 matches!(self.returns.as_deref(), Some(ExprType::Name(n)) if n.id == "str");
705 }
706 {
709 let mut known: std::collections::HashMap<String, String> =
710 std::collections::HashMap::new();
711 for param in self
712 .args
713 .args
714 .iter()
715 .chain(self.args.posonlyargs.iter())
716 .chain(self.args.kwonlyargs.iter())
717 {
718 if let Some(ExprType::Name(ann)) = param.annotation.as_deref() {
719 if matches!(ann.id.as_str(), "int" | "float" | "str" | "bool") {
720 known.insert(param.arg.clone(), ann.id.clone());
721 }
722 }
723 }
724 let mut literal_types = std::collections::HashMap::new();
725 collect_local_types(&self.body, &mut literal_types);
726 for (name, ty) in literal_types {
727 let py = match ty.to_string().as_str() {
728 "i64" => "int",
729 "f64" => "float",
730 "bool" => "bool",
731 s if s.contains("str") || s.contains("String") => "str",
732 _ => continue,
733 };
734 known.entry(name).or_insert_with(|| py.to_string());
736 }
737 options.local_types = std::rc::Rc::new(known);
738 }
739 let str_params: std::collections::HashSet<&str> = self
742 .args
743 .args
744 .iter()
745 .chain(self.args.posonlyargs.iter())
746 .chain(self.args.kwonlyargs.iter())
747 .filter(|p| {
748 matches!(
749 p.annotation.as_deref(),
750 Some(ExprType::Name(n)) if n.id == "str"
751 )
752 })
753 .map(|p| p.arg.as_str())
754 .collect();
755 let mut streams_prologue = TokenStream::new();
756 for name in ¶m_names {
757 let ident = crate::safe_ident(name);
758 if str_params.contains(name.as_str()) {
759 if scope.needs_mut.contains(name) {
760 streams_prologue.extend(quote!(let mut #ident: String = #ident.into();));
761 } else {
762 streams_prologue.extend(quote!(let #ident: String = #ident.into();));
763 }
764 } else if scope.needs_mut.contains(name) {
765 streams_prologue.extend(quote!(let mut #ident = #ident;));
766 }
767 }
768 for name in &scope.assigned {
769 let ident = crate::safe_ident(name);
770 if scope.needs_mut.contains(name) {
771 streams_prologue.extend(quote!(let mut #ident;));
772 } else {
773 streams_prologue.extend(quote!(let #ident;));
774 }
775 }
776 streams.extend(streams_prologue);
777
778 let body_start = if self.get_docstring().is_some() { 1 } else { 0 };
781 for (i, s) in effective_body.iter().enumerate().skip(body_start) {
782 if Some(i) == argparse_parse_at {
783 let rw = argparse_rewrite.as_ref().expect("index implies rewrite");
784 streams.extend(lower_parse_args(
785 rw,
786 &ctx,
787 &options,
788 &symbols,
789 )?);
790 streams.extend(quote!(;));
791 continue;
792 }
793 streams.extend(
794 s.clone()
795 .to_rust(ctx.clone(), options.clone(), symbols.clone())?,
796 );
797 streams.extend(quote!(;));
798 }
799
800 let return_type = match self.resolved_return_type() {
806 Some(ty) => quote!(-> Result<#ty, PyException>),
807 None => quote!(-> Result<(), PyException>),
808 };
809
810 if !guarantees_return(&self.body) {
814 streams.extend(quote!(Ok(())));
815 }
816
817 let streams = if let (Some(maxsize), Some(key)) = (cache_spec, cache_key.as_ref()) {
823 let maxsize_tokens = match maxsize {
824 None => quote!(None),
825 Some(n) => quote!(Some(#n as usize)),
826 };
827 let key_types: Vec<&TokenStream> = key.iter().map(|(_, t)| t).collect();
828 let key_names: Vec<&proc_macro2::Ident> = key.iter().map(|(n, _)| n).collect();
829 let ret = match self.resolved_return_type() {
830 Some(ty) => quote!(#ty),
831 None => quote!(()),
832 };
833 let mut outer_rebinds = TokenStream::new();
836 for (p, (name, _)) in self.args.args.iter().zip(key.iter()) {
837 if matches!(p.annotation.as_deref(), Some(ExprType::Name(n)) if n.id == "str")
838 {
839 outer_rebinds.extend(quote!(let #name: String = #name.into();));
840 }
841 }
842 quote! {
843 #outer_rebinds
844 static __LRU_CACHE: std::sync::LazyLock<
845 std::sync::Mutex<PyLruCache<(#(#key_types,)*), #ret>>,
846 > = std::sync::LazyLock::new(|| {
847 std::sync::Mutex::new(PyLruCache::new(#maxsize_tokens))
848 });
849 if let Some(__hit) = __LRU_CACHE
850 .lock()
851 .expect("lru_cache mutex poisoned")
852 .get(&(#((#key_names).clone(),)*))
853 {
854 return Ok(__hit);
855 }
856 #[allow(non_snake_case)]
857 fn __lru_uncached(#(#key_names: #key_types),*) -> Result<#ret, PyException> {
858 #streams
859 }
860 let __lru_value = __lru_uncached(#((#key_names).clone()),*)?;
861 __LRU_CACHE
862 .lock()
863 .expect("lru_cache mutex poisoned")
864 .put((#((#key_names).clone(),)*), __lru_value.clone());
865 Ok(__lru_value)
866 }
867 } else {
868 streams
869 };
870
871 let lossy_warning = if options.lossy_warnings {
877 let notes = self.lossy_conversion_notes();
878 if notes.is_empty() {
879 quote!()
880 } else {
881 let note = notes.join("; ");
882 quote!(#[deprecated(note = #note)])
883 }
884 } else {
885 quote!()
886 };
887
888 let function = if let Some(docstring) = self.get_docstring() {
889 let doc_lines: Vec<_> = docstring
891 .lines()
892 .map(|line| {
893 if line.trim().is_empty() {
894 quote! { #[doc = ""] }
895 } else {
896 let doc_line = format!("{}", line);
897 quote! { #[doc = #doc_line] }
898 }
899 })
900 .collect();
901
902 quote! {
903 #(#doc_lines)*
904 #lossy_warning
905 #visibility #is_async fn #fn_name(#receiver #parameters) #return_type {
906 #streams
907 }
908 }
909 } else {
910 quote! {
911 #lossy_warning
912 #visibility #is_async fn #fn_name(#receiver #parameters) #return_type {
913 #streams
914 }
915 }
916 };
917
918 debug!("function: {}", function);
919 Ok(function)
920 }
921}
922
923fn collect_returns<'a>(body: &'a [Statement], out: &mut Vec<Option<&'a ExprType>>) {
927 for stmt in body {
928 match &stmt.statement {
929 StatementType::Return(value) => {
930 out.push(value.as_ref().map(|e| &e.value));
931 }
932 StatementType::If(s) => {
933 collect_returns(&s.body, out);
934 collect_returns(&s.orelse, out);
935 }
936 StatementType::For(s) => {
937 collect_returns(&s.body, out);
938 collect_returns(&s.orelse, out);
939 }
940 StatementType::While(s) => {
941 collect_returns(&s.body, out);
942 collect_returns(&s.orelse, out);
943 }
944 StatementType::With(s) => collect_returns(&s.body, out),
945 StatementType::AsyncWith(s) => collect_returns(&s.body, out),
946 StatementType::AsyncFor(s) => collect_returns(&s.body, out),
947 StatementType::Try(s) => {
948 collect_returns(&s.body, out);
949 for handler in &s.handlers {
950 collect_returns(&handler.body, out);
951 }
952 collect_returns(&s.orelse, out);
953 collect_returns(&s.finalbody, out);
954 }
955 _ => {}
958 }
959 }
960}
961
962pub(crate) fn simple_expr_type(expr: &ExprType) -> Option<TokenStream> {
964 match expr {
965 ExprType::Constant(c) => match &c.0 {
966 Some(litrs::Literal::Integer(_)) => Some(quote!(i64)),
967 Some(litrs::Literal::Float(_)) => Some(quote!(f64)),
968 Some(litrs::Literal::Bool(_)) => Some(quote!(bool)),
969 Some(litrs::Literal::String(_)) => Some(quote!(&'static str)),
971 _ => None,
972 },
973 ExprType::JoinedStr(_) => Some(quote!(String)),
974 _ => None,
975 }
976}
977
978pub(crate) fn collect_local_types(
981 body: &[Statement],
982 out: &mut std::collections::HashMap<String, TokenStream>,
983) {
984 for stmt in body {
985 match &stmt.statement {
986 StatementType::Assign(assign) => {
987 if let [ExprType::Name(name)] = assign.targets.as_slice() {
988 if let Some(ty) = simple_expr_type(&assign.value) {
989 out.insert(name.id.clone(), ty);
990 }
991 }
992 }
993 StatementType::If(s) => {
994 collect_local_types(&s.body, out);
995 collect_local_types(&s.orelse, out);
996 }
997 StatementType::For(s) => {
998 collect_local_types(&s.body, out);
999 collect_local_types(&s.orelse, out);
1000 }
1001 StatementType::While(s) => {
1002 collect_local_types(&s.body, out);
1003 collect_local_types(&s.orelse, out);
1004 }
1005 StatementType::With(s) => collect_local_types(&s.body, out),
1006 _ => {}
1007 }
1008 }
1009}
1010
1011pub(crate) fn is_none_expr(ann: &ExprType) -> bool {
1015 match ann {
1016 ExprType::NoneType(_) => true,
1017 ExprType::Constant(c) => c.0.is_none(),
1018 ExprType::Name(name) => name.id == "None",
1019 _ => false,
1020 }
1021}
1022
1023pub(crate) fn expr_yields_option(
1028 expr: &ExprType,
1029 options: &PythonOptions,
1030 symbols: &SymbolTableScopes,
1031) -> bool {
1032 match expr {
1033 ExprType::Name(name) => options.optional_names.contains(&name.id),
1036 ExprType::Call(call) => match call.func.as_ref() {
1037 ExprType::Attribute(attr) => attr.attr == "get" && call.args.len() == 1,
1039 ExprType::Name(name) => match symbols.get(&name.id) {
1043 Some(SymbolTableNode::FunctionDef(f)) => f
1044 .returns
1045 .as_deref()
1046 .is_some_and(crate::is_optional_annotation),
1047 _ => false,
1048 },
1049 _ => false,
1050 },
1051 ExprType::IfExp(e) => {
1056 let arm = |x: &ExprType| {
1057 crate::is_none_expr(x) || expr_yields_option(x, options, symbols)
1058 };
1059 arm(&e.body) || arm(&e.orelse)
1060 }
1061 _ => false,
1062 }
1063}
1064
1065pub(crate) fn lower_optional_value(
1072 expr: &ExprType,
1073 ctx: CodeGenContext,
1074 options: PythonOptions,
1075 symbols: SymbolTableScopes,
1076) -> Result<TokenStream, Box<dyn std::error::Error>> {
1077 if let ExprType::IfExp(e) = expr {
1081 let test =
1082 crate::condition_to_rust(&e.test, ctx.clone(), options.clone(), symbols.clone())?;
1083 let body = lower_optional_value(&e.body, ctx.clone(), options.clone(), symbols.clone())?;
1084 let orelse = lower_optional_value(&e.orelse, ctx, options, symbols)?;
1085 return Ok(quote!(if #test { #body } else { #orelse }));
1086 }
1087 if is_none_expr(expr) || expr_yields_option(expr, &options, &symbols) {
1088 return expr.clone().to_rust(ctx, options, symbols);
1089 }
1090 let tokens = expr.clone().to_rust(ctx, options, symbols)?;
1091 Ok(quote!(Some(#tokens)))
1092}
1093
1094fn annotation_display(ann: &ExprType) -> String {
1097 match ann {
1098 ExprType::Name(name) => name.id.clone(),
1099 ExprType::Constant(c) => c.to_string(),
1100 _ => "<annotation>".to_string(),
1101 }
1102}
1103
1104pub(crate) fn guarantees_return(body: &[Statement]) -> bool {
1110 match body.last().map(|stmt| &stmt.statement) {
1111 Some(StatementType::Return(Some(_))) => true,
1112 Some(StatementType::If(s)) => {
1113 !s.orelse.is_empty() && guarantees_return(&s.body) && guarantees_return(&s.orelse)
1114 }
1115 Some(StatementType::Raise(_)) => true,
1117 Some(StatementType::Try(t)) => {
1122 let normal_path = if t.orelse.is_empty() {
1123 guarantees_return(&t.body)
1124 } else {
1125 guarantees_return(&t.body) || guarantees_return(&t.orelse)
1126 };
1127 let handlers = t.handlers.iter().all(|h| guarantees_return(&h.body));
1128 (normal_path && handlers) || guarantees_return(&t.finalbody)
1129 }
1130 _ => false,
1131 }
1132}
1133
1134impl FunctionDef {
1135 pub fn resolved_return_type(&self) -> Option<TokenStream> {
1148 let annotated = if guarantees_return(&self.body) {
1149 self.returns.as_deref().and_then(|ann| {
1150 if is_none_expr(ann) {
1151 None
1152 } else {
1153 crate::python_annotation_to_rust_type(ann)
1154 }
1155 })
1156 } else {
1157 None
1158 };
1159 self.inferred_return_type().or(annotated)
1160 }
1161
1162 pub fn ignored_return_annotation(&self) -> Option<String> {
1169 let ann = self.returns.as_deref()?;
1170 if is_none_expr(ann) || guarantees_return(&self.body) {
1171 return None;
1172 }
1173 Some(annotation_display(ann))
1174 }
1175
1176 pub fn lossy_conversion_notes(&self) -> Vec<String> {
1180 let mut notes = Vec::new();
1181 let dropped = self.dropped_default_parameters();
1182 if !dropped.is_empty() {
1183 notes.push(format!(
1184 "rython: Python default value(s) for parameter(s) `{}` were dropped \
1185 (Rust has no default arguments); every argument must be passed explicitly",
1186 dropped.join("`, `")
1187 ));
1188 }
1189 if let Some(ann) = self.ignored_return_annotation() {
1190 notes.push(format!(
1191 "rython: the `-> {}` return annotation was ignored because the function \
1192 body does not return a value on every path; the generated function \
1193 returns `()` where Python would implicitly return None",
1194 ann
1195 ));
1196 }
1197 notes
1198 }
1199
1200 pub fn dropped_default_parameters(&self) -> Vec<String> {
1205 let mut dropped = Vec::new();
1206 let defaults_offset = self
1207 .args
1208 .args
1209 .len()
1210 .saturating_sub(self.args.defaults.len());
1211 for arg in &self.args.args[defaults_offset..] {
1212 dropped.push(arg.arg.clone());
1213 }
1214 for (i, arg) in self.args.kwonlyargs.iter().enumerate() {
1215 if self.args.kw_defaults.get(i).is_some_and(Option::is_some) {
1216 dropped.push(arg.arg.clone());
1217 }
1218 }
1219 dropped
1220 }
1221
1222 pub fn inferred_return_type(&self) -> Option<TokenStream> {
1230 if !guarantees_return(&self.body) {
1233 return None;
1234 }
1235
1236 let mut returns = Vec::new();
1237 collect_returns(&self.body, &mut returns);
1238
1239 let mut locals = std::collections::HashMap::new();
1240 collect_local_types(&self.body, &mut locals);
1241
1242 let mut inferred: Option<TokenStream> = None;
1243 for ret in &returns {
1244 let value = (*ret)?; let ty = match value {
1246 ExprType::Name(name) => locals.get(&name.id)?.clone(),
1247 other => simple_expr_type(other)?,
1248 };
1249 match &inferred {
1250 None => inferred = Some(ty),
1251 Some(prev) if prev.to_string() == ty.to_string() => {}
1252 _ => return None,
1253 }
1254 }
1255 inferred
1256 }
1257}
1258
1259impl FunctionDef {
1260 fn get_docstring(&self) -> Option<String> {
1261 if self.body.is_empty() {
1262 return None;
1263 }
1264
1265 let expr = self.body[0].clone();
1266 match expr.statement {
1267 StatementType::Expr(e) => match e.value {
1268 ExprType::Constant(c) => {
1269 let raw_string = c.to_string();
1270 Some(self.format_docstring(&raw_string))
1272 },
1273 _ => None,
1274 },
1275 _ => None,
1276 }
1277 }
1278
1279 fn format_docstring(&self, raw: &str) -> String {
1280 let content = raw.trim_matches('"');
1282
1283 let lines: Vec<&str> = content.lines().collect();
1285 if lines.is_empty() {
1286 return String::new();
1287 }
1288
1289 let mut formatted = vec![lines[0].trim().to_string()];
1291
1292 if lines.len() > 1 {
1293 if !lines[0].trim().is_empty() && !lines[1].trim().is_empty() {
1295 formatted.push(String::new());
1296 }
1297
1298 for line in lines.iter().skip(1) {
1300 let cleaned = line.trim();
1301 if cleaned.starts_with("Args:") {
1302 formatted.push(String::new());
1303 formatted.push("# Arguments".to_string());
1304 } else if cleaned.starts_with("Returns:") {
1305 formatted.push(String::new());
1306 formatted.push("# Returns".to_string());
1307 } else if cleaned.starts_with("Example:") {
1308 formatted.push(String::new());
1309 formatted.push("# Examples".to_string());
1310 } else if cleaned.starts_with(">>>") {
1311 formatted.push(format!("```rust"));
1313 formatted.push(format!("// {}", cleaned));
1314 } else if !cleaned.is_empty() {
1315 formatted.push(cleaned.to_string());
1316 }
1317 }
1318
1319 if content.contains(">>>") {
1321 formatted.push("```".to_string());
1322 }
1323 }
1324
1325 formatted.join("\n")
1326 }
1327}
1328
1329impl Object for FunctionDef {}