1use rustpython_parser::{ast, Parse};
4use syn::{Error, LitStr, Result, Type};
5
6use super::pyfunction::PythonFunctionStub;
7use super::{
8 build_parameters_from_ast, dedent, extract_deprecated_from_decorators, extract_docstring,
9 extract_return_type, has_overload_decorator,
10};
11use crate::gen_stub::{method::MethodInfo, method::MethodType, pymethods::PyMethodsInfo};
12
13pub struct PythonMethodStub {
15 pub func_stub: PythonFunctionStub,
16 pub method_type: MethodType,
17}
18
19impl TryFrom<PythonMethodStub> for MethodInfo {
20 type Error = syn::Error;
21
22 fn try_from(stub: PythonMethodStub) -> Result<Self> {
23 let func_name = stub.func_stub.func_def.name.to_string();
24
25 let doc = extract_docstring(&stub.func_stub.func_def);
27
28 let parameters =
30 build_parameters_from_ast(&stub.func_stub.func_def.args, &stub.func_stub.imports)?;
31
32 let return_type =
38 extract_return_type(&stub.func_stub.func_def.returns, &stub.func_stub.imports)?;
39
40 let deprecated =
42 extract_deprecated_from_decorators(&stub.func_stub.func_def.decorator_list);
43
44 Ok(MethodInfo {
46 name: func_name,
47 parameters,
48 r#return: return_type,
49 doc,
50 r#type: stub.method_type,
51 is_async: stub.func_stub.is_async,
52 deprecated,
53 type_ignored: None,
54 is_overload: stub.func_stub.is_overload,
55 })
56 }
57}
58
59pub struct PythonClassStub {
61 pub class_def: ast::StmtClassDef,
62 pub imports: Vec<String>,
63}
64
65impl PythonClassStub {
66 pub fn new(input: &LitStr) -> Result<Self> {
68 let stub_content = input.value();
69
70 let dedented_content = dedent(&stub_content);
72
73 let parsed = ast::Suite::parse(&dedented_content, "<stub>")
75 .map_err(|e| Error::new(input.span(), format!("Failed to parse Python stub: {}", e)))?;
76
77 let mut imports = Vec::new();
79 let mut class_def: Option<ast::StmtClassDef> = None;
80
81 for stmt in parsed {
82 match stmt {
83 ast::Stmt::Import(import_stmt) => {
84 for alias in &import_stmt.names {
85 imports.push(alias.name.to_string());
86 }
87 }
88 ast::Stmt::ImportFrom(import_from_stmt) => {
89 if let Some(module) = &import_from_stmt.module {
90 imports.push(module.to_string());
91 }
92 }
93 ast::Stmt::ClassDef(cls_def) => {
94 if class_def.is_some() {
95 return Err(Error::new(
96 input.span(),
97 "Multiple class definitions found. Only one class is allowed per gen_methods_from_python! call",
98 ));
99 }
100 class_def = Some(cls_def);
101 }
102 _ => {
103 }
105 }
106 }
107
108 let class_def = class_def
110 .ok_or_else(|| Error::new(input.span(), "No class definition found in Python stub"))?;
111
112 Ok(Self { class_def, imports })
113 }
114}
115
116impl TryFrom<PythonClassStub> for PyMethodsInfo {
117 type Error = syn::Error;
118
119 fn try_from(stub: PythonClassStub) -> Result<Self> {
120 let class_name = stub.class_def.name.to_string();
121 let mut methods = Vec::new();
122
123 for stmt in &stub.class_def.body {
125 match stmt {
126 ast::Stmt::FunctionDef(func_def) => {
127 let method_type = determine_method_type(func_def, &func_def.args);
129
130 let is_overload = has_overload_decorator(&func_def.decorator_list);
132
133 let func_stub = PythonFunctionStub {
135 func_def: func_def.clone(),
136 imports: stub.imports.clone(),
137 is_async: false,
138 is_overload,
139 };
140
141 let method_stub = PythonMethodStub {
143 func_stub,
144 method_type,
145 };
146 let method = MethodInfo::try_from(method_stub)?;
147 methods.push(method);
148 }
149 ast::Stmt::AsyncFunctionDef(func_def) => {
150 let is_overload = has_overload_decorator(&func_def.decorator_list);
152
153 let sync_func = ast::StmtFunctionDef {
155 range: func_def.range,
156 name: func_def.name.clone(),
157 type_params: func_def.type_params.clone(),
158 args: func_def.args.clone(),
159 body: func_def.body.clone(),
160 decorator_list: func_def.decorator_list.clone(),
161 returns: func_def.returns.clone(),
162 type_comment: func_def.type_comment.clone(),
163 };
164
165 let method_type = determine_method_type(&sync_func, &sync_func.args);
167
168 let func_stub = PythonFunctionStub {
170 func_def: sync_func,
171 imports: stub.imports.clone(),
172 is_async: true,
173 is_overload,
174 };
175
176 let method_stub = PythonMethodStub {
178 func_stub,
179 method_type,
180 };
181 let method = MethodInfo::try_from(method_stub)?;
182 methods.push(method);
183 }
184 _ => {
185 }
187 }
188 }
189
190 if methods.is_empty() {
191 return Err(Error::new(
192 proc_macro2::Span::call_site(),
193 "No method definitions found in class body",
194 ));
195 }
196
197 let struct_id: Type = syn::parse_str(&class_name).map_err(|e| {
199 Error::new(
200 proc_macro2::Span::call_site(),
201 format!("Failed to parse class name '{}': {}", class_name, e),
202 )
203 })?;
204
205 Ok(PyMethodsInfo {
206 struct_id,
207 attrs: Vec::new(),
208 getters: Vec::new(),
209 setters: Vec::new(),
210 methods,
211 })
212 }
213}
214
215pub fn parse_python_methods_stub(input: &LitStr) -> Result<PyMethodsInfo> {
217 let stub = PythonClassStub::new(input)?;
218 PyMethodsInfo::try_from(stub).map_err(|e| Error::new(input.span(), format!("{}", e)))
219}
220
221fn determine_method_type(func_def: &ast::StmtFunctionDef, args: &ast::Arguments) -> MethodType {
223 for decorator in &func_def.decorator_list {
225 if let ast::Expr::Name(name) = decorator {
226 match name.id.as_str() {
227 "staticmethod" => return MethodType::Static,
228 "classmethod" => return MethodType::Class,
229 _ => {}
230 }
231 }
232 }
233
234 if func_def.name.as_str() == "__new__" {
236 return MethodType::New;
237 }
238
239 if let Some(first_arg) = args.args.first() {
241 let arg_name = first_arg.def.arg.as_str();
242 if arg_name == "self" {
243 return MethodType::Instance;
244 } else if arg_name == "cls" {
245 return MethodType::Class;
246 }
247 }
248
249 MethodType::Instance
251}
252
253#[cfg(test)]
254mod test {
255 use super::*;
256 use proc_macro2::TokenStream as TokenStream2;
257 use quote::{quote, ToTokens};
258
259 #[test]
260 fn test_single_method_class() -> Result<()> {
261 let stub_str: LitStr = syn::parse2(quote! {
262 r#"
263 class Incrementer:
264 def increment(self, x: int) -> int:
265 """Increment by one"""
266 "#
267 })?;
268 let py_methods_info = parse_python_methods_stub(&stub_str)?;
269 assert_eq!(py_methods_info.methods.len(), 1);
270
271 let out = py_methods_info.methods[0].to_token_stream();
272 insta::assert_snapshot!(format_as_value(out), @r#"
273 ::pyo3_stub_gen::type_info::MethodInfo {
274 name: "increment",
275 parameters: &[
276 ::pyo3_stub_gen::type_info::ParameterInfo {
277 name: "x",
278 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
279 type_info: || ::pyo3_stub_gen::TypeInfo {
280 name: "int".to_string(),
281 source_module: None,
282 import: ::std::collections::HashSet::from([]),
283 type_refs: ::std::collections::HashMap::new(),
284 },
285 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
286 },
287 ],
288 r#return: || ::pyo3_stub_gen::TypeInfo {
289 name: "int".to_string(),
290 source_module: None,
291 import: ::std::collections::HashSet::from([]),
292 type_refs: ::std::collections::HashMap::new(),
293 },
294 doc: "Increment by one",
295 r#type: ::pyo3_stub_gen::type_info::MethodType::Instance,
296 is_async: false,
297 deprecated: None,
298 type_ignored: None,
299 is_overload: false,
300 }
301 "#);
302 Ok(())
303 }
304
305 #[test]
306 fn test_multiple_methods_class() -> Result<()> {
307 let stub_str: LitStr = syn::parse2(quote! {
308 r#"
309 class Incrementer:
310 def increment_1(self, x: int) -> int:
311 """First method"""
312
313 def increment_2(self, x: float) -> float:
314 """Second method"""
315 "#
316 })?;
317 let py_methods_info = parse_python_methods_stub(&stub_str)?;
318 assert_eq!(py_methods_info.methods.len(), 2);
319
320 assert_eq!(py_methods_info.methods[0].name, "increment_1");
321 assert_eq!(py_methods_info.methods[1].name, "increment_2");
322 Ok(())
323 }
324
325 #[test]
326 fn test_static_method_in_class() -> Result<()> {
327 let stub_str: LitStr = syn::parse2(quote! {
328 r#"
329 class MyClass:
330 @staticmethod
331 def create(name: str) -> str:
332 """Create something"""
333 "#
334 })?;
335 let py_methods_info = parse_python_methods_stub(&stub_str)?;
336 assert_eq!(py_methods_info.methods.len(), 1);
337
338 let out = py_methods_info.methods[0].to_token_stream();
339 insta::assert_snapshot!(format_as_value(out), @r#"
340 ::pyo3_stub_gen::type_info::MethodInfo {
341 name: "create",
342 parameters: &[
343 ::pyo3_stub_gen::type_info::ParameterInfo {
344 name: "name",
345 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
346 type_info: || ::pyo3_stub_gen::TypeInfo {
347 name: "str".to_string(),
348 source_module: None,
349 import: ::std::collections::HashSet::from([]),
350 type_refs: ::std::collections::HashMap::new(),
351 },
352 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
353 },
354 ],
355 r#return: || ::pyo3_stub_gen::TypeInfo {
356 name: "str".to_string(),
357 source_module: None,
358 import: ::std::collections::HashSet::from([]),
359 type_refs: ::std::collections::HashMap::new(),
360 },
361 doc: "Create something",
362 r#type: ::pyo3_stub_gen::type_info::MethodType::Static,
363 is_async: false,
364 deprecated: None,
365 type_ignored: None,
366 is_overload: false,
367 }
368 "#);
369 Ok(())
370 }
371
372 #[test]
373 fn test_class_method_in_class() -> Result<()> {
374 let stub_str: LitStr = syn::parse2(quote! {
375 r#"
376 class MyClass:
377 @classmethod
378 def from_string(cls, s: str) -> int:
379 """Create from string"""
380 "#
381 })?;
382 let py_methods_info = parse_python_methods_stub(&stub_str)?;
383 assert_eq!(py_methods_info.methods.len(), 1);
384
385 let out = py_methods_info.methods[0].to_token_stream();
386 insta::assert_snapshot!(format_as_value(out), @r#"
387 ::pyo3_stub_gen::type_info::MethodInfo {
388 name: "from_string",
389 parameters: &[
390 ::pyo3_stub_gen::type_info::ParameterInfo {
391 name: "s",
392 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
393 type_info: || ::pyo3_stub_gen::TypeInfo {
394 name: "str".to_string(),
395 source_module: None,
396 import: ::std::collections::HashSet::from([]),
397 type_refs: ::std::collections::HashMap::new(),
398 },
399 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
400 },
401 ],
402 r#return: || ::pyo3_stub_gen::TypeInfo {
403 name: "int".to_string(),
404 source_module: None,
405 import: ::std::collections::HashSet::from([]),
406 type_refs: ::std::collections::HashMap::new(),
407 },
408 doc: "Create from string",
409 r#type: ::pyo3_stub_gen::type_info::MethodType::Class,
410 is_async: false,
411 deprecated: None,
412 type_ignored: None,
413 is_overload: false,
414 }
415 "#);
416 Ok(())
417 }
418
419 #[test]
420 fn test_new_method_in_class() -> Result<()> {
421 let stub_str: LitStr = syn::parse2(quote! {
422 r#"
423 class MyClass:
424 def __new__(cls) -> object:
425 """Constructor"""
426 "#
427 })?;
428 let py_methods_info = parse_python_methods_stub(&stub_str)?;
429 assert_eq!(py_methods_info.methods.len(), 1);
430
431 let out = py_methods_info.methods[0].to_token_stream();
432 insta::assert_snapshot!(format_as_value(out), @r#"
433 ::pyo3_stub_gen::type_info::MethodInfo {
434 name: "__new__",
435 parameters: &[],
436 r#return: || ::pyo3_stub_gen::TypeInfo {
437 name: "object".to_string(),
438 source_module: None,
439 import: ::std::collections::HashSet::from([]),
440 type_refs: ::std::collections::HashMap::new(),
441 },
442 doc: "Constructor",
443 r#type: ::pyo3_stub_gen::type_info::MethodType::New,
444 is_async: false,
445 deprecated: None,
446 type_ignored: None,
447 is_overload: false,
448 }
449 "#);
450 Ok(())
451 }
452
453 #[test]
454 fn test_method_with_imports_in_class() -> Result<()> {
455 let stub_str: LitStr = syn::parse2(quote! {
456 r#"
457 import typing
458 from collections.abc import Callable
459
460 class MyClass:
461 def process(self, func: Callable[[str], int]) -> typing.Optional[int]:
462 """Process a callback"""
463 "#
464 })?;
465 let py_methods_info = parse_python_methods_stub(&stub_str)?;
466 assert_eq!(py_methods_info.methods.len(), 1);
467
468 let out = py_methods_info.methods[0].to_token_stream();
469 insta::assert_snapshot!(format_as_value(out), @r#"
470 ::pyo3_stub_gen::type_info::MethodInfo {
471 name: "process",
472 parameters: &[
473 ::pyo3_stub_gen::type_info::ParameterInfo {
474 name: "func",
475 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
476 type_info: || ::pyo3_stub_gen::TypeInfo {
477 name: "Callable[[str], int]".to_string(),
478 source_module: None,
479 import: ::std::collections::HashSet::from([
480 "typing".into(),
481 "collections.abc".into(),
482 ]),
483 type_refs: ::std::collections::HashMap::new(),
484 },
485 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
486 },
487 ],
488 r#return: || ::pyo3_stub_gen::TypeInfo {
489 name: "typing.Optional[int]".to_string(),
490 source_module: None,
491 import: ::std::collections::HashSet::from([
492 "typing".into(),
493 "collections.abc".into(),
494 ]),
495 type_refs: ::std::collections::HashMap::new(),
496 },
497 doc: "Process a callback",
498 r#type: ::pyo3_stub_gen::type_info::MethodType::Instance,
499 is_async: false,
500 deprecated: None,
501 type_ignored: None,
502 is_overload: false,
503 }
504 "#);
505 Ok(())
506 }
507
508 #[test]
509 fn test_async_method_in_class() -> Result<()> {
510 let stub_str: LitStr = syn::parse2(quote! {
511 r#"
512 class MyClass:
513 async def fetch_data(self, url: str) -> str:
514 """Fetch data asynchronously"""
515 "#
516 })?;
517 let py_methods_info = parse_python_methods_stub(&stub_str)?;
518 assert_eq!(py_methods_info.methods.len(), 1);
519
520 let out = py_methods_info.methods[0].to_token_stream();
521 insta::assert_snapshot!(format_as_value(out), @r#"
522 ::pyo3_stub_gen::type_info::MethodInfo {
523 name: "fetch_data",
524 parameters: &[
525 ::pyo3_stub_gen::type_info::ParameterInfo {
526 name: "url",
527 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
528 type_info: || ::pyo3_stub_gen::TypeInfo {
529 name: "str".to_string(),
530 source_module: None,
531 import: ::std::collections::HashSet::from([]),
532 type_refs: ::std::collections::HashMap::new(),
533 },
534 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
535 },
536 ],
537 r#return: || ::pyo3_stub_gen::TypeInfo {
538 name: "str".to_string(),
539 source_module: None,
540 import: ::std::collections::HashSet::from([]),
541 type_refs: ::std::collections::HashMap::new(),
542 },
543 doc: "Fetch data asynchronously",
544 r#type: ::pyo3_stub_gen::type_info::MethodType::Instance,
545 is_async: true,
546 deprecated: None,
547 type_ignored: None,
548 is_overload: false,
549 }
550 "#);
551 Ok(())
552 }
553
554 #[test]
555 fn test_rust_type_marker_in_method() -> Result<()> {
556 let stub_str: LitStr = syn::parse2(quote! {
557 r#"
558 class PyProblem:
559 def __iadd__(self, other: pyo3_stub_gen.RustType["SomeRustType"]) -> pyo3_stub_gen.RustType["PyProblem"]:
560 """In-place addition using Rust type marker"""
561 "#
562 })?;
563 let py_methods_info = parse_python_methods_stub(&stub_str)?;
564 assert_eq!(py_methods_info.methods.len(), 1);
565
566 let out = py_methods_info.methods[0].to_token_stream();
567 insta::assert_snapshot!(format_as_value(out), @r###"
568 ::pyo3_stub_gen::type_info::MethodInfo {
569 name: "__iadd__",
570 parameters: &[
571 ::pyo3_stub_gen::type_info::ParameterInfo {
572 name: "other",
573 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
574 type_info: <SomeRustType as ::pyo3_stub_gen::PyStubType>::type_input,
575 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
576 },
577 ],
578 r#return: <PyProblem as pyo3_stub_gen::PyStubType>::type_output,
579 doc: "In-place addition using Rust type marker",
580 r#type: ::pyo3_stub_gen::type_info::MethodType::Instance,
581 is_async: false,
582 deprecated: None,
583 type_ignored: None,
584 is_overload: false,
585 }
586 "###);
587 Ok(())
588 }
589
590 #[test]
591 fn test_keyword_only_params_with_defaults() -> Result<()> {
592 let stub_str: LitStr = syn::parse2(quote! {
593 r#"
594 import builtins
595 import typing
596
597 class Placeholder:
598 def configure(
599 self,
600 name: builtins.str,
601 *,
602 dtype: builtins.str,
603 ndim: builtins.int,
604 shape: typing.Optional[builtins.str],
605 jagged: builtins.bool = False,
606 latex: typing.Optional[builtins.str] = None,
607 description: typing.Optional[builtins.str] = None,
608 ) -> pyo3_stub_gen.RustType["Placeholder"]:
609 """
610 Configure placeholder with keyword-only parameters.
611
612 This demonstrates keyword-only parameters (after *) which should be
613 preserved in the generated stub file.
614 """
615 "#
616 })?;
617 let py_methods_info = parse_python_methods_stub(&stub_str)?;
618 assert_eq!(py_methods_info.methods.len(), 1);
619
620 let out = py_methods_info.to_token_stream();
621 insta::assert_snapshot!(format_as_value(out), @r#"
622 ::pyo3_stub_gen::type_info::PyMethodsInfo {
623 struct_id: std::any::TypeId::of::<Placeholder>,
624 attrs: &[],
625 getters: &[],
626 setters: &[],
627 methods: &[
628 ::pyo3_stub_gen::type_info::MethodInfo {
629 name: "configure",
630 parameters: &[
631 ::pyo3_stub_gen::type_info::ParameterInfo {
632 name: "name",
633 kind: ::pyo3_stub_gen::type_info::ParameterKind::PositionalOrKeyword,
634 type_info: || ::pyo3_stub_gen::TypeInfo {
635 name: "builtins.str".to_string(),
636 source_module: None,
637 import: ::std::collections::HashSet::from([
638 "builtins".into(),
639 "typing".into(),
640 ]),
641 type_refs: ::std::collections::HashMap::new(),
642 },
643 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
644 },
645 ::pyo3_stub_gen::type_info::ParameterInfo {
646 name: "dtype",
647 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
648 type_info: || ::pyo3_stub_gen::TypeInfo {
649 name: "builtins.str".to_string(),
650 source_module: None,
651 import: ::std::collections::HashSet::from([
652 "builtins".into(),
653 "typing".into(),
654 ]),
655 type_refs: ::std::collections::HashMap::new(),
656 },
657 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
658 },
659 ::pyo3_stub_gen::type_info::ParameterInfo {
660 name: "ndim",
661 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
662 type_info: || ::pyo3_stub_gen::TypeInfo {
663 name: "builtins.int".to_string(),
664 source_module: None,
665 import: ::std::collections::HashSet::from([
666 "builtins".into(),
667 "typing".into(),
668 ]),
669 type_refs: ::std::collections::HashMap::new(),
670 },
671 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
672 },
673 ::pyo3_stub_gen::type_info::ParameterInfo {
674 name: "shape",
675 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
676 type_info: || ::pyo3_stub_gen::TypeInfo {
677 name: "typing.Optional[builtins.str]".to_string(),
678 source_module: None,
679 import: ::std::collections::HashSet::from([
680 "builtins".into(),
681 "typing".into(),
682 ]),
683 type_refs: ::std::collections::HashMap::new(),
684 },
685 default: ::pyo3_stub_gen::type_info::ParameterDefault::None,
686 },
687 ::pyo3_stub_gen::type_info::ParameterInfo {
688 name: "jagged",
689 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
690 type_info: || ::pyo3_stub_gen::TypeInfo {
691 name: "builtins.bool".to_string(),
692 source_module: None,
693 import: ::std::collections::HashSet::from([
694 "builtins".into(),
695 "typing".into(),
696 ]),
697 type_refs: ::std::collections::HashMap::new(),
698 },
699 default: ::pyo3_stub_gen::type_info::ParameterDefault::Expr({
700 fn _fmt() -> String {
701 "False".to_string()
702 }
703 _fmt
704 }),
705 },
706 ::pyo3_stub_gen::type_info::ParameterInfo {
707 name: "latex",
708 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
709 type_info: || ::pyo3_stub_gen::TypeInfo {
710 name: "typing.Optional[builtins.str]".to_string(),
711 source_module: None,
712 import: ::std::collections::HashSet::from([
713 "builtins".into(),
714 "typing".into(),
715 ]),
716 type_refs: ::std::collections::HashMap::new(),
717 },
718 default: ::pyo3_stub_gen::type_info::ParameterDefault::Expr({
719 fn _fmt() -> String {
720 "None".to_string()
721 }
722 _fmt
723 }),
724 },
725 ::pyo3_stub_gen::type_info::ParameterInfo {
726 name: "description",
727 kind: ::pyo3_stub_gen::type_info::ParameterKind::KeywordOnly,
728 type_info: || ::pyo3_stub_gen::TypeInfo {
729 name: "typing.Optional[builtins.str]".to_string(),
730 source_module: None,
731 import: ::std::collections::HashSet::from([
732 "builtins".into(),
733 "typing".into(),
734 ]),
735 type_refs: ::std::collections::HashMap::new(),
736 },
737 default: ::pyo3_stub_gen::type_info::ParameterDefault::Expr({
738 fn _fmt() -> String {
739 "None".to_string()
740 }
741 _fmt
742 }),
743 },
744 ],
745 r#return: <Placeholder as pyo3_stub_gen::PyStubType>::type_output,
746 doc: "\n Configure placeholder with keyword-only parameters.\n\n This demonstrates keyword-only parameters (after *) which should be\n preserved in the generated stub file.\n ",
747 r#type: ::pyo3_stub_gen::type_info::MethodType::Instance,
748 is_async: false,
749 deprecated: None,
750 type_ignored: None,
751 is_overload: false,
752 },
753 ],
754 file: file!(),
755 line: line!(),
756 column: column!(),
757 }
758 "#);
759 Ok(())
760 }
761
762 fn format_as_value(tt: TokenStream2) -> String {
763 let ttt = quote! { const _: () = #tt; };
764 let formatted = prettyplease::unparse(&syn::parse_file(&ttt.to_string()).unwrap());
765 formatted
766 .trim()
767 .strip_prefix("const _: () = ")
768 .unwrap()
769 .strip_suffix(';')
770 .unwrap()
771 .to_string()
772 }
773}