blob: 8840899f784bdddd7e5807227c53f433c4932352 [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use indexmap::set::IndexSet as HashSet;
10
11use autocxx_parser::{
12 directive_names::{EXTERN_RUST_FUN, EXTERN_RUST_TYPE},
13 RustFun, RustPath,
14};
15use itertools::Itertools;
16use proc_macro2::Ident;
17use syn::{
18 parse_quote, punctuated::Punctuated, Attribute, Binding, Expr, ExprAssign, ExprAssignOp,
19 ExprAwait, ExprBinary, ExprBox, ExprBreak, ExprCast, ExprField, ExprGroup, ExprLet, ExprParen,
20 ExprReference, ExprTry, ExprType, ExprUnary, ImplItem, Item, ItemEnum, ItemStruct, Pat, PatBox,
21 PatReference, PatSlice, PatTuple, Path, Receiver, ReturnType, Signature, Stmt, TraitItem, Type,
22 TypeArray, TypeGroup, TypeParamBound, TypeParen, TypePtr, TypeReference, TypeSlice,
23};
24use thiserror::Error;
25
26#[derive(Default)]
27pub(super) struct Discoveries {
28 pub(super) cpp_list: HashSet<String>,
29 pub(super) extern_rust_funs: Vec<RustFun>,
30 pub(super) extern_rust_types: Vec<RustPath>,
31}
32
33#[derive(Error, Debug)]
34pub enum DiscoveryErr {
35 #[error("#[extern_rust_function] was attached to a method in an impl block that was too complex for autocxx. autocxx supports only \"impl X {{...}}\" where X is a single identifier, not a path or more complex type.")]
36 FoundExternRustFunOnTypeWithoutClearReceiver,
37 #[error("#[extern_rust_function] was attached to a method taking no parameters.")]
38 NonReferenceReceiver,
39 #[error("#[extern_rust_function] was attached to a method taking a receiver by value.")]
40 NoParameterOnMethod,
41 #[error("#[extern_rust_function] was in an impl block nested wihtin another block. This is only supported in the outermost mod of a file, alongside the include_cpp!.")]
42 FoundExternRustFunWithinMod,
43}
44
45impl Discoveries {
46 pub(super) fn search_item(
47 &mut self,
48 item: &Item,
49 mod_path: Option<RustPath>,
50 ) -> Result<(), DiscoveryErr> {
51 let mut this_mod = PerModDiscoveries {
52 discoveries: self,
53 mod_path,
54 };
55 this_mod.search_item(item)
56 }
57
58 pub(crate) fn found_allowlist(&self) -> bool {
59 !self.cpp_list.is_empty()
60 }
61
62 pub(crate) fn found_rust(&self) -> bool {
63 !self.extern_rust_funs.is_empty() || !self.extern_rust_types.is_empty()
64 }
65
66 pub(crate) fn extend(&mut self, other: Self) {
67 self.cpp_list.extend(other.cpp_list);
68 self.extern_rust_funs.extend(other.extern_rust_funs);
69 self.extern_rust_types.extend(other.extern_rust_types);
70 }
71}
72
73struct PerModDiscoveries<'a> {
74 discoveries: &'a mut Discoveries,
75 mod_path: Option<RustPath>,
76}
77
78impl<'b> PerModDiscoveries<'b> {
79 fn deeper_path(&self, id: &Ident) -> RustPath {
80 match &self.mod_path {
81 None => RustPath::new_from_ident(id.clone()),
82 Some(mod_path) => mod_path.append(id.clone()),
83 }
84 }
85
86 fn search_item(&mut self, item: &Item) -> Result<(), DiscoveryErr> {
87 match item {
88 Item::Fn(fun) => {
89 for stmt in &fun.block.stmts {
90 self.search_stmt(stmt)?
91 }
92 self.search_return_type(&fun.sig.output)?;
93 for i in &fun.sig.inputs {
94 match i {
95 syn::FnArg::Receiver(_) => {}
96 syn::FnArg::Typed(pt) => {
97 self.search_pat(&pt.pat)?;
98 self.search_type(&pt.ty)?;
99 }
100 }
101 }
102 if Self::has_attr(&fun.attrs, EXTERN_RUST_FUN) {
103 self.discoveries.extern_rust_funs.push(RustFun {
104 path: self.deeper_path(&fun.sig.ident),
105 sig: fun.sig.clone(),
106 receiver: None,
107 });
108 }
109 }
110 Item::Impl(imp) => {
111 let receiver = match imp.trait_ {
112 // We do not allow 'extern_rust_fun' on trait impls
113 Some(_) => None,
114 None => match &*imp.self_ty {
115 Type::Path(typ) => {
116 let mut segs = typ.path.segments.iter();
117 let id = segs.next();
118 if let Some(seg) = id {
119 if segs.next().is_some() {
120 None
121 } else {
122 Some(self.deeper_path(&seg.ident))
123 }
124 } else {
125 None
126 }
127 }
128 _ => None,
129 },
130 };
131 for item in &imp.items {
132 self.search_impl_item(item, receiver.as_ref())?
133 }
134 }
135 Item::Mod(md) => {
136 if let Some((_, items)) = &md.content {
137 let mod_path = Some(self.deeper_path(&md.ident));
138 let mut new_mod = PerModDiscoveries {
139 discoveries: self.discoveries,
140 mod_path,
141 };
142 for item in items {
143 new_mod.search_item(item)?
144 }
145 }
146 }
147 Item::Trait(tr) => {
148 for item in &tr.items {
149 self.search_trait_item(item)?
150 }
151 }
152 Item::Struct(ItemStruct { ident, attrs, .. })
153 | Item::Enum(ItemEnum { ident, attrs, .. })
154 if Self::has_attr(attrs, EXTERN_RUST_TYPE) =>
155 {
156 self.discoveries
157 .extern_rust_types
158 .push(self.deeper_path(ident));
159 }
160 _ => {}
161 }
162 Ok(())
163 }
164
165 fn search_path(&mut self, path: &Path) -> Result<(), DiscoveryErr> {
166 let mut seg_iter = path.segments.iter();
167 if let Some(first_seg) = seg_iter.next() {
168 if first_seg.ident == "ffi" {
169 self.discoveries
170 .cpp_list
171 .insert(seg_iter.map(|seg| seg.ident.to_string()).join("::"));
172 }
173 }
174 for seg in path.segments.iter() {
175 self.search_path_arguments(&seg.arguments)?;
176 }
177 Ok(())
178 }
179
180 fn search_trait_item(&mut self, itm: &TraitItem) -> Result<(), DiscoveryErr> {
181 if let TraitItem::Method(itm) = itm {
182 if let Some(block) = &itm.default {
183 self.search_stmts(block.stmts.iter())?
184 }
185 }
186 Ok(())
187 }
188
189 fn search_stmts<'a>(
190 &mut self,
191 stmts: impl Iterator<Item = &'a Stmt>,
192 ) -> Result<(), DiscoveryErr> {
193 for stmt in stmts {
194 self.search_stmt(stmt)?
195 }
196 Ok(())
197 }
198
199 fn search_stmt(&mut self, stmt: &Stmt) -> Result<(), DiscoveryErr> {
200 match stmt {
201 Stmt::Local(lcl) => {
202 if let Some((_, expr)) = &lcl.init {
203 self.search_expr(expr)?
204 }
205 self.search_pat(&lcl.pat)
206 }
207 Stmt::Item(itm) => self.search_item(itm),
208 Stmt::Expr(exp) | Stmt::Semi(exp, _) => self.search_expr(exp),
209 }
210 }
211
212 fn search_expr(&mut self, expr: &Expr) -> Result<(), DiscoveryErr> {
213 match expr {
214 Expr::Path(exp) => {
215 self.search_path(&exp.path)?;
216 }
217 Expr::Macro(_) => {}
218 Expr::Array(array) => self.search_exprs(array.elems.iter())?,
219 Expr::Assign(ExprAssign { left, right, .. })
220 | Expr::AssignOp(ExprAssignOp { left, right, .. })
221 | Expr::Binary(ExprBinary { left, right, .. }) => {
222 self.search_expr(left)?;
223 self.search_expr(right)?;
224 }
225 Expr::Async(ass) => self.search_stmts(ass.block.stmts.iter())?,
226 Expr::Await(ExprAwait { base, .. }) | Expr::Field(ExprField { base, .. }) => {
227 self.search_expr(base)?
228 }
229 Expr::Block(blck) => self.search_stmts(blck.block.stmts.iter())?,
230 Expr::Box(ExprBox { expr, .. })
231 | Expr::Break(ExprBreak {
232 expr: Some(expr), ..
233 })
234 | Expr::Cast(ExprCast { expr, .. })
235 | Expr::Group(ExprGroup { expr, .. })
236 | Expr::Paren(ExprParen { expr, .. })
237 | Expr::Reference(ExprReference { expr, .. })
238 | Expr::Try(ExprTry { expr, .. })
239 | Expr::Type(ExprType { expr, .. })
240 | Expr::Unary(ExprUnary { expr, .. }) => self.search_expr(expr)?,
241 Expr::Call(exc) => {
242 self.search_expr(&exc.func)?;
243 self.search_exprs(exc.args.iter())?;
244 }
245 Expr::Closure(cls) => self.search_expr(&cls.body)?,
246 Expr::Continue(_)
247 | Expr::Lit(_)
248 | Expr::Break(ExprBreak { expr: None, .. })
249 | Expr::Verbatim(_) => {}
250 Expr::ForLoop(fl) => {
251 self.search_expr(&fl.expr)?;
252 self.search_stmts(fl.body.stmts.iter())?;
253 }
254 Expr::If(exif) => {
255 self.search_expr(&exif.cond)?;
256 self.search_stmts(exif.then_branch.stmts.iter())?;
257 if let Some((_, else_branch)) = &exif.else_branch {
258 self.search_expr(else_branch)?;
259 }
260 }
261 Expr::Index(exidx) => {
262 self.search_expr(&exidx.expr)?;
263 self.search_expr(&exidx.index)?;
264 }
265 Expr::Let(ExprLet { expr, pat, .. }) => {
266 self.search_expr(expr)?;
267 self.search_pat(pat)?;
268 }
269 Expr::Loop(exloo) => self.search_stmts(exloo.body.stmts.iter())?,
270 Expr::Match(exm) => {
271 self.search_expr(&exm.expr)?;
272 for a in &exm.arms {
273 self.search_expr(&a.body)?;
274 if let Some((_, guard)) = &a.guard {
275 self.search_expr(guard)?;
276 }
277 }
278 }
279 Expr::MethodCall(mtc) => {
280 self.search_expr(&mtc.receiver)?;
281 self.search_exprs(mtc.args.iter())?;
282 }
283 Expr::Range(exr) => {
284 self.search_option_expr(&exr.from)?;
285 self.search_option_expr(&exr.to)?;
286 }
287 Expr::Repeat(exr) => {
288 self.search_expr(&exr.expr)?;
289 self.search_expr(&exr.len)?;
290 }
291 Expr::Return(exret) => {
292 if let Some(expr) = &exret.expr {
293 self.search_expr(expr)?;
294 }
295 }
296 Expr::Struct(exst) => {
297 for f in &exst.fields {
298 self.search_expr(&f.expr)?;
299 }
300 self.search_option_expr(&exst.rest)?;
301 }
302 Expr::TryBlock(extb) => self.search_stmts(extb.block.stmts.iter())?,
303 Expr::Tuple(ext) => self.search_exprs(ext.elems.iter())?,
304 Expr::Unsafe(exs) => self.search_stmts(exs.block.stmts.iter())?,
305 Expr::While(exw) => {
306 self.search_expr(&exw.cond)?;
307 self.search_stmts(exw.body.stmts.iter())?;
308 }
309 Expr::Yield(exy) => self.search_option_expr(&exy.expr)?,
310 _ => {}
311 }
312 Ok(())
313 }
314
315 fn search_option_expr(&mut self, expr: &Option<Box<Expr>>) -> Result<(), DiscoveryErr> {
316 if let Some(expr) = &expr {
317 self.search_expr(expr)?;
318 }
319 Ok(())
320 }
321
322 fn search_exprs<'a>(
323 &mut self,
324 exprs: impl Iterator<Item = &'a Expr>,
325 ) -> Result<(), DiscoveryErr> {
326 for e in exprs {
327 self.search_expr(e)?;
328 }
329 Ok(())
330 }
331
332 fn search_impl_item(
333 &mut self,
334 impl_item: &ImplItem,
335 receiver: Option<&RustPath>,
336 ) -> Result<(), DiscoveryErr> {
337 if let ImplItem::Method(itm) = impl_item {
338 if Self::has_attr(&itm.attrs, EXTERN_RUST_FUN) {
339 if self.mod_path.is_some() {
340 return Err(DiscoveryErr::FoundExternRustFunWithinMod);
341 }
342 if let Some(receiver) = receiver {
343 // We have a method which we want to put into the cxx::bridge's
344 // "extern Rust" block.
345 let sig = add_receiver(&itm.sig, receiver.get_final_ident())?;
346 assert!(receiver.len() == 1);
347 self.discoveries.extern_rust_funs.push(RustFun {
348 path: self.deeper_path(&itm.sig.ident),
349 sig,
350 receiver: Some(receiver.get_final_ident().clone()),
351 });
352 self.discoveries.extern_rust_types.push(receiver.clone())
353 } else {
354 return Err(DiscoveryErr::FoundExternRustFunOnTypeWithoutClearReceiver);
355 }
356 }
357 for stmt in &itm.block.stmts {
358 self.search_stmt(stmt)?
359 }
360 }
361 Ok(())
362 }
363
364 fn search_pat(&mut self, pat: &Pat) -> Result<(), DiscoveryErr> {
365 match pat {
366 Pat::Box(PatBox { pat, .. }) | Pat::Reference(PatReference { pat, .. }) => {
367 self.search_pat(pat)
368 }
369 Pat::Ident(_) | Pat::Lit(_) | Pat::Macro(_) | Pat::Range(_) | Pat::Rest(_) => Ok(()),
370 Pat::Or(pator) => {
371 for case in &pator.cases {
372 self.search_pat(case)?;
373 }
374 Ok(())
375 }
376 Pat::Path(pp) => self.search_path(&pp.path),
377 Pat::Slice(PatSlice { elems, .. }) | Pat::Tuple(PatTuple { elems, .. }) => {
378 for case in elems {
379 self.search_pat(case)?;
380 }
381 Ok(())
382 }
383 Pat::Struct(ps) => {
384 self.search_path(&ps.path)?;
385 for f in &ps.fields {
386 self.search_pat(&f.pat)?;
387 }
388 Ok(())
389 }
390 Pat::TupleStruct(tps) => {
391 self.search_path(&tps.path)?;
392 for f in &tps.pat.elems {
393 self.search_pat(f)?;
394 }
395 Ok(())
396 }
397 Pat::Type(pt) => {
398 self.search_pat(&pt.pat)?;
399 self.search_type(&pt.ty)
400 }
401 _ => Ok(()),
402 }
403 }
404
405 fn search_type(&mut self, ty: &Type) -> Result<(), DiscoveryErr> {
406 match ty {
407 Type::Array(TypeArray { elem, .. })
408 | Type::Group(TypeGroup { elem, .. })
409 | Type::Paren(TypeParen { elem, .. })
410 | Type::Ptr(TypePtr { elem, .. })
411 | Type::Reference(TypeReference { elem, .. })
412 | Type::Slice(TypeSlice { elem, .. }) => self.search_type(elem)?,
413 Type::BareFn(tf) => {
414 for input in &tf.inputs {
415 self.search_type(&input.ty)?;
416 }
417 self.search_return_type(&tf.output)?;
418 }
419 Type::ImplTrait(tyit) => {
420 for b in &tyit.bounds {
421 if let syn::TypeParamBound::Trait(tyt) = b {
422 self.search_path(&tyt.path)?
423 }
424 }
425 }
426 Type::Infer(_) | Type::Macro(_) | Type::Never(_) => {}
427 Type::Path(typ) => self.search_path(&typ.path)?,
428 Type::TraitObject(tto) => self.search_type_param_bounds(&tto.bounds)?,
429 Type::Tuple(tt) => {
430 for e in &tt.elems {
431 self.search_type(e)?
432 }
433 }
434 _ => {}
435 }
436 Ok(())
437 }
438
439 fn search_type_param_bounds(
440 &mut self,
441 bounds: &Punctuated<TypeParamBound, syn::token::Add>,
442 ) -> Result<(), DiscoveryErr> {
443 for b in bounds {
444 if let syn::TypeParamBound::Trait(tpbt) = b {
445 self.search_path(&tpbt.path)?
446 }
447 }
448 Ok(())
449 }
450
451 fn search_return_type(&mut self, output: &ReturnType) -> Result<(), DiscoveryErr> {
452 if let ReturnType::Type(_, ty) = &output {
453 self.search_type(ty)
454 } else {
455 Ok(())
456 }
457 }
458
459 fn search_path_arguments(
460 &mut self,
461 arguments: &syn::PathArguments,
462 ) -> Result<(), DiscoveryErr> {
463 match arguments {
464 syn::PathArguments::None => {}
465 syn::PathArguments::AngleBracketed(paab) => {
466 for arg in &paab.args {
467 match arg {
468 syn::GenericArgument::Lifetime(_) => {}
469 syn::GenericArgument::Type(ty)
470 | syn::GenericArgument::Binding(Binding { ty, .. }) => {
471 self.search_type(ty)?
472 }
473 syn::GenericArgument::Constraint(c) => {
474 self.search_type_param_bounds(&c.bounds)?
475 }
476 syn::GenericArgument::Const(c) => self.search_expr(c)?,
477 }
478 }
479 }
480 syn::PathArguments::Parenthesized(pas) => {
481 self.search_return_type(&pas.output)?;
482 for t in &pas.inputs {
483 self.search_type(t)?;
484 }
485 }
486 }
487 Ok(())
488 }
489
490 fn has_attr(attrs: &[Attribute], attr_name: &str) -> bool {
491 attrs.iter().any(|attr| {
492 attr.path
493 .segments
494 .last()
495 .map(|seg| seg.ident == attr_name)
496 .unwrap_or_default()
497 })
498 }
499}
500
501/// Take a method signature that may be `fn a(&self)`
502/// and turn it into `fn a(self: &A)` which is what we will
503/// need to specify to cxx.
504fn add_receiver(sig: &Signature, receiver: &Ident) -> Result<Signature, DiscoveryErr> {
505 let mut sig = sig.clone();
506 match sig.inputs.iter_mut().next() {
507 Some(first_arg) => match first_arg {
508 syn::FnArg::Receiver(Receiver {
509 reference: Some(_),
510 mutability: Some(_),
511 ..
512 }) => {
513 *first_arg = parse_quote! {
514 self: &mut #receiver
515 }
516 }
517 syn::FnArg::Receiver(Receiver {
518 reference: Some(_),
519 mutability: None,
520 ..
521 }) => {
522 *first_arg = parse_quote! {
523 self: &#receiver
524 }
525 }
526 syn::FnArg::Receiver(..) => return Err(DiscoveryErr::NonReferenceReceiver),
527 syn::FnArg::Typed(_) => {}
528 },
529 None => return Err(DiscoveryErr::NoParameterOnMethod),
530 }
531 Ok(sig)
532}
533
534#[cfg(test)]
535mod tests {
536 use quote::{quote, ToTokens};
537 use syn::{parse_quote, ImplItemMethod};
538
539 use crate::{ast_discoverer::add_receiver, types::make_ident};
540
541 use super::Discoveries;
542
543 fn assert_cpp_found(discoveries: &Discoveries) {
544 assert!(!discoveries.cpp_list.is_empty());
545 assert!(discoveries.cpp_list.iter().next().unwrap() == "xxx");
546 }
547
548 #[test]
549 fn test_mod_plain_call() {
550 let mut discoveries = Discoveries::default();
551 let itm = parse_quote! {
552 mod foo {
553 fn bar() {
554 ffi::xxx()
555 }
556 }
557 };
558 discoveries.search_item(&itm, None).unwrap();
559 assert_cpp_found(&discoveries);
560 }
561
562 #[test]
563 fn test_plain_call() {
564 let mut discoveries = Discoveries::default();
565 let itm = parse_quote! {
566 fn bar() {
567 ffi::xxx()
568 }
569 };
570 discoveries.search_item(&itm, None).unwrap();
571 assert_cpp_found(&discoveries);
572 }
573
574 #[test]
575 fn test_plain_call_with_semi() {
576 let mut discoveries = Discoveries::default();
577 let itm = parse_quote! {
578 fn bar() {
579 ffi::xxx();
580 }
581 };
582 discoveries.search_item(&itm, None).unwrap();
583 assert_cpp_found(&discoveries);
584 }
585
586 #[test]
587 fn test_in_ns() {
588 let mut discoveries = Discoveries::default();
589 let itm = parse_quote! {
590 fn bar() {
591 ffi::a::b::xxx();
592 }
593 };
594 discoveries.search_item(&itm, None).unwrap();
595 assert!(!discoveries.cpp_list.is_empty());
596 assert!(discoveries.cpp_list.iter().next().unwrap() == "a::b::xxx");
597 }
598
599 #[test]
600 fn test_deep_nested_thingy() {
601 let mut discoveries = Discoveries::default();
602 let itm = parse_quote! {
603 fn bar() {
604 a + 3 * foo(ffi::xxx());
605 }
606 };
607 discoveries.search_item(&itm, None).unwrap();
608 assert_cpp_found(&discoveries);
609 }
610
611 #[test]
612 fn test_ty_in_let() {
613 let mut discoveries = Discoveries::default();
614 let itm = parse_quote! {
615 fn bar() {
616 let foo: ffi::xxx = bar();
617 }
618 };
619 discoveries.search_item(&itm, None).unwrap();
620 assert_cpp_found(&discoveries);
621 }
622
623 #[test]
624 fn test_ty_in_fn() {
625 let mut discoveries = Discoveries::default();
626 let itm = parse_quote! {
627 fn bar(a: &mut ffi::xxx) {
628 }
629 };
630 discoveries.search_item(&itm, None).unwrap();
631 assert_cpp_found(&discoveries);
632 }
633
634 #[test]
635 fn test_ty_in_fn_up() {
636 let mut discoveries = Discoveries::default();
637 let itm = parse_quote! {
638 fn bar(a: cxx::UniquePtr<ffi::xxx>) {
639 }
640 };
641 discoveries.search_item(&itm, None).unwrap();
642 assert_cpp_found(&discoveries);
643 }
644
645 #[test]
646 fn test_extern_rust_fun() {
647 let mut discoveries = Discoveries::default();
648 let itm = parse_quote! {
649 #[autocxx::extern_rust::extern_rust_function]
650 fn bar(a: cxx::UniquePtr<ffi::xxx>) {
651 }
652 };
653 discoveries.search_item(&itm, None).unwrap();
654 assert!(discoveries.extern_rust_funs.get(0).unwrap().sig.ident == "bar");
655 }
656
657 #[test]
658 fn test_extern_rust_method() {
659 let mut discoveries = Discoveries::default();
660 let itm = parse_quote! {
661 impl A {
662 #[autocxx::extern_rust::extern_rust_function]
663 fn bar(&self) {
664 }
665 }
666 };
667 discoveries.search_item(&itm, None).unwrap();
668 assert!(discoveries.extern_rust_funs.get(0).unwrap().sig.ident == "bar");
669 }
670
671 #[test]
672 fn test_extern_rust_ty() {
673 let mut discoveries = Discoveries::default();
674 let itm = parse_quote! {
675 #[autocxx::extern_rust::extern_rust_type]
676 struct Bar {
677
678 }
679 };
680 discoveries.search_item(&itm, None).unwrap();
681 assert!(
682 discoveries
683 .extern_rust_types
684 .get(0)
685 .unwrap()
686 .get_final_ident()
687 == "Bar"
688 );
689 }
690
691 #[test]
692 fn test_add_receiver() {
693 let meth: ImplItemMethod = parse_quote! {
694 fn a(&self) {}
695 };
696 let a = make_ident("A");
697 assert_eq!(
698 add_receiver(&meth.sig, &a)
699 .unwrap()
700 .to_token_stream()
701 .to_string(),
702 quote! { fn a(self: &A) }.to_string()
703 );
704
705 let meth: ImplItemMethod = parse_quote! {
706 fn a(&mut self, b: u32) -> Foo {}
707 };
708 assert_eq!(
709 add_receiver(&meth.sig, &a)
710 .unwrap()
711 .to_token_stream()
712 .to_string(),
713 quote! { fn a(self: &mut A, b: u32) -> Foo }.to_string()
714 );
715
716 let meth: ImplItemMethod = parse_quote! {
717 fn a(self) {}
718 };
719 assert!(add_receiver(&meth.sig, &a).is_err());
720
721 let meth: ImplItemMethod = parse_quote! {
722 fn a() {}
723 };
724 assert!(add_receiver(&meth.sig, &a).is_err());
725 }
726}