1+ use super :: method:: probe:: { IsSuggestion , Mode , ProbeScope } ;
12use super :: method:: MethodCallee ;
23use super :: { DefIdOrName , Expectation , FnCtxt , TupleArgumentsFlag } ;
34use crate :: type_error_struct;
45
6+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
57use rustc_errors:: { struct_span_err, Applicability , Diagnostic , StashKey } ;
68use rustc_hir as hir;
79use rustc_hir:: def:: { self , Namespace , Res } ;
@@ -407,7 +409,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
407409 . diagnostic ( )
408410 . steal_diagnostic ( segment. ident . span , StashKey :: CallIntoMethod )
409411 {
410- diag. emit ( ) ;
412+ // Try suggesting `foo(a)` -> `a.foo()` if possible.
413+ if let Some ( ty) =
414+ self . suggest_call_as_method (
415+ & mut diag,
416+ segment,
417+ arg_exprs,
418+ call_expr,
419+ expected
420+ )
421+ {
422+ diag. emit ( ) ;
423+ return ty;
424+ } else {
425+ diag. emit ( ) ;
426+ }
411427 }
412428
413429 self . report_invalid_callee ( call_expr, callee_expr, callee_ty, arg_exprs) ;
@@ -457,6 +473,105 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
457473 fn_sig. output ( )
458474 }
459475
476+ /// Attempts to reinterpret `method(rcvr, args...)` as `method.rcvr(args...)`
477+ /// and suggesting the fix if the method probe is successful.
478+ fn suggest_call_as_method (
479+ & self ,
480+ diag : & mut Diagnostic ,
481+ segment : & ' tcx hir:: PathSegment < ' tcx > ,
482+ arg_exprs : & ' tcx [ hir:: Expr < ' tcx > ] ,
483+ call_expr : & ' tcx hir:: Expr < ' tcx > ,
484+ expected : Expectation < ' tcx > ,
485+ ) -> Option < Ty < ' tcx > > {
486+ if let [ callee_expr, rest @ ..] = arg_exprs {
487+ let callee_ty = self . check_expr ( callee_expr) ;
488+ // First, do a probe with `IsSuggestion(true)` to avoid emitting
489+ // any strange errors. If it's successful, then we'll do a true
490+ // method lookup.
491+ let Ok ( pick) = self
492+ . probe_for_name (
493+ call_expr. span ,
494+ Mode :: MethodCall ,
495+ segment. ident ,
496+ IsSuggestion ( true ) ,
497+ callee_ty,
498+ call_expr. hir_id ,
499+ // We didn't record the in scope traits during late resolution
500+ // so we need to probe AllTraits unfortunately
501+ ProbeScope :: AllTraits ,
502+ ) else {
503+ return None ;
504+ } ;
505+
506+ let pick = self . confirm_method (
507+ call_expr. span ,
508+ callee_expr,
509+ call_expr,
510+ callee_ty,
511+ pick,
512+ segment,
513+ ) ;
514+ if pick. illegal_sized_bound . is_some ( ) {
515+ return None ;
516+ }
517+
518+ let up_to_rcvr_span = segment. ident . span . until ( callee_expr. span ) ;
519+ let rest_span = callee_expr. span . shrink_to_hi ( ) . to ( call_expr. span . shrink_to_hi ( ) ) ;
520+ let rest_snippet = if let Some ( first) = rest. first ( ) {
521+ self . tcx
522+ . sess
523+ . source_map ( )
524+ . span_to_snippet ( first. span . to ( call_expr. span . shrink_to_hi ( ) ) )
525+ } else {
526+ Ok ( ")" . to_string ( ) )
527+ } ;
528+
529+ if let Ok ( rest_snippet) = rest_snippet {
530+ let sugg = if callee_expr. precedence ( ) . order ( ) >= PREC_POSTFIX {
531+ vec ! [
532+ ( up_to_rcvr_span, "" . to_string( ) ) ,
533+ ( rest_span, format!( ".{}({rest_snippet}" , segment. ident) ) ,
534+ ]
535+ } else {
536+ vec ! [
537+ ( up_to_rcvr_span, "(" . to_string( ) ) ,
538+ ( rest_span, format!( ").{}({rest_snippet}" , segment. ident) ) ,
539+ ]
540+ } ;
541+ let self_ty = self . resolve_vars_if_possible ( pick. callee . sig . inputs ( ) [ 0 ] ) ;
542+ diag. multipart_suggestion (
543+ format ! (
544+ "use the `.` operator to call the method `{}{}` on `{self_ty}`" ,
545+ self . tcx
546+ . associated_item( pick. callee. def_id)
547+ . trait_container( self . tcx)
548+ . map_or_else(
549+ || String :: new( ) ,
550+ |trait_def_id| self . tcx. def_path_str( trait_def_id) + "::"
551+ ) ,
552+ segment. ident
553+ ) ,
554+ sugg,
555+ Applicability :: MaybeIncorrect ,
556+ ) ;
557+
558+ // Let's check the method fully now
559+ let return_ty = self . check_method_argument_types (
560+ segment. ident . span ,
561+ call_expr,
562+ Ok ( pick. callee ) ,
563+ rest,
564+ TupleArgumentsFlag :: DontTupleArguments ,
565+ expected,
566+ ) ;
567+
568+ return Some ( return_ty) ;
569+ }
570+ }
571+
572+ None
573+ }
574+
460575 fn report_invalid_callee (
461576 & self ,
462577 call_expr : & ' tcx hir:: Expr < ' tcx > ,
@@ -475,10 +590,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
475590 def:: CtorOf :: Struct => "struct" ,
476591 def:: CtorOf :: Variant => "enum variant" ,
477592 } ;
478- let removal_span =
479- callee_expr. span . shrink_to_hi ( ) . to ( call_expr. span . shrink_to_hi ( ) ) ;
480- unit_variant =
481- Some ( ( removal_span, descr, rustc_hir_pretty:: qpath_to_string ( qpath) ) ) ;
593+ let removal_span = callee_expr. span . shrink_to_hi ( ) . to ( call_expr. span . shrink_to_hi ( ) ) ;
594+ unit_variant = Some ( ( removal_span, descr, rustc_hir_pretty:: qpath_to_string ( qpath) ) ) ;
482595 }
483596
484597 let callee_ty = self . resolve_vars_if_possible ( callee_ty) ;
@@ -541,7 +654,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
541654 } ;
542655
543656 if !self . maybe_suggest_bad_array_definition ( & mut err, call_expr, callee_expr) {
544- if let Some ( ( maybe_def, output_ty, _) ) = self . extract_callable_info ( callee_expr, callee_ty)
657+ if let Some ( ( maybe_def, output_ty, _) ) =
658+ self . extract_callable_info ( callee_expr, callee_ty)
545659 && !self . type_is_sized_modulo_regions ( self . param_env , output_ty, callee_expr. span )
546660 {
547661 let descr = match maybe_def {
0 commit comments