@@ -2,13 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
22use clippy_utils:: macros:: { FormatArgsStorage , format_args_inputs_span, root_macro_call_first_node} ;
33use clippy_utils:: source:: snippet_with_applicability;
44use clippy_utils:: ty:: { is_type_diagnostic_item, is_type_lang_item} ;
5+ use clippy_utils:: visitors:: for_each_expr;
6+ use clippy_utils:: { contains_return, is_inside_always_const_context, peel_blocks} ;
57use rustc_errors:: Applicability ;
68use rustc_hir as hir;
79use rustc_lint:: LateContext ;
8- use rustc_middle:: ty;
910use rustc_span:: symbol:: sym;
1011use rustc_span:: { Span , Symbol } ;
1112use std:: borrow:: Cow ;
13+ use std:: ops:: ControlFlow ;
1214
1315use super :: EXPECT_FUN_CALL ;
1416
@@ -23,10 +25,10 @@ pub(super) fn check<'tcx>(
2325 receiver : & ' tcx hir:: Expr < ' tcx > ,
2426 args : & ' tcx [ hir:: Expr < ' tcx > ] ,
2527) {
26- // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
28+ // Strip `{}`, ` &`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
2729 // `&str`
2830 fn get_arg_root < ' a > ( cx : & LateContext < ' _ > , arg : & ' a hir:: Expr < ' a > ) -> & ' a hir:: Expr < ' a > {
29- let mut arg_root = arg;
31+ let mut arg_root = peel_blocks ( arg) ;
3032 loop {
3133 arg_root = match & arg_root. kind {
3234 hir:: ExprKind :: AddrOf ( hir:: BorrowKind :: Ref , _, expr) => expr,
@@ -47,124 +49,68 @@ pub(super) fn check<'tcx>(
4749 arg_root
4850 }
4951
50- // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
51- // converted to string.
52- fn requires_to_string ( cx : & LateContext < ' _ > , arg : & hir:: Expr < ' _ > ) -> bool {
53- let arg_ty = cx. typeck_results ( ) . expr_ty ( arg) ;
54- if is_type_lang_item ( cx, arg_ty, hir:: LangItem :: String ) {
55- return false ;
56- }
57- if let ty:: Ref ( _, ty, ..) = arg_ty. kind ( )
58- && ty. is_str ( )
59- && can_be_static_str ( cx, arg)
60- {
61- return false ;
62- }
63- true
52+ fn contains_call < ' a > ( cx : & LateContext < ' a > , arg : & ' a hir:: Expr < ' a > ) -> bool {
53+ for_each_expr ( cx, arg, |expr| {
54+ if matches ! ( expr. kind, hir:: ExprKind :: MethodCall { .. } | hir:: ExprKind :: Call { .. } )
55+ && !is_inside_always_const_context ( cx. tcx , expr. hir_id )
56+ {
57+ ControlFlow :: Break ( ( ) )
58+ } else {
59+ ControlFlow :: Continue ( ( ) )
60+ }
61+ } )
62+ . is_some ( )
6463 }
6564
66- // Check if an expression could have type `&'static str`, knowing that it
67- // has type `&str` for some lifetime.
68- fn can_be_static_str ( cx : & LateContext < ' _ > , arg : & hir:: Expr < ' _ > ) -> bool {
69- match arg. kind {
70- hir:: ExprKind :: Lit ( _) => true ,
71- hir:: ExprKind :: Call ( fun, _) => {
72- if let hir:: ExprKind :: Path ( ref p) = fun. kind {
73- match cx. qpath_res ( p, fun. hir_id ) {
74- hir:: def:: Res :: Def ( hir:: def:: DefKind :: Fn | hir:: def:: DefKind :: AssocFn , def_id) => matches ! (
75- cx. tcx. fn_sig( def_id) . instantiate_identity( ) . output( ) . skip_binder( ) . kind( ) ,
76- ty:: Ref ( re, ..) if re. is_static( ) ,
77- ) ,
78- _ => false ,
79- }
80- } else {
81- false
82- }
83- } ,
84- hir:: ExprKind :: MethodCall ( ..) => {
85- cx. typeck_results ( )
86- . type_dependent_def_id ( arg. hir_id )
87- . is_some_and ( |method_id| {
88- matches ! (
89- cx. tcx. fn_sig( method_id) . instantiate_identity( ) . output( ) . skip_binder( ) . kind( ) ,
90- ty:: Ref ( re, ..) if re. is_static( )
91- )
92- } )
93- } ,
94- hir:: ExprKind :: Path ( ref p) => matches ! (
95- cx. qpath_res( p, arg. hir_id) ,
96- hir:: def:: Res :: Def ( hir:: def:: DefKind :: Const | hir:: def:: DefKind :: Static { .. } , _)
97- ) ,
98- _ => false ,
99- }
100- }
65+ if name == sym:: expect
66+ && let [ arg] = args
67+ && let arg_root = get_arg_root ( cx, arg)
68+ && contains_call ( cx, arg_root)
69+ && !contains_return ( arg_root)
70+ {
71+ let receiver_type = cx. typeck_results ( ) . expr_ty_adjusted ( receiver) ;
72+ let closure_args = if is_type_diagnostic_item ( cx, receiver_type, sym:: Option ) {
73+ "||"
74+ } else if is_type_diagnostic_item ( cx, receiver_type, sym:: Result ) {
75+ "|_|"
76+ } else {
77+ return ;
78+ } ;
10179
102- fn is_call ( node : & hir:: ExprKind < ' _ > ) -> bool {
103- match node {
104- hir:: ExprKind :: AddrOf ( hir:: BorrowKind :: Ref , _, expr) => {
105- is_call ( & expr. kind )
106- } ,
107- hir:: ExprKind :: Call ( ..)
108- | hir:: ExprKind :: MethodCall ( ..)
109- // These variants are debatable or require further examination
110- | hir:: ExprKind :: If ( ..)
111- | hir:: ExprKind :: Match ( ..)
112- | hir:: ExprKind :: Block { .. } => true ,
113- _ => false ,
114- }
115- }
80+ let span_replace_word = method_span. with_hi ( expr. span . hi ( ) ) ;
11681
117- if args. len ( ) != 1 || name != sym:: expect || !is_call ( & args[ 0 ] . kind ) {
118- return ;
119- }
82+ let mut applicability = Applicability :: MachineApplicable ;
12083
121- let receiver_type = cx. typeck_results ( ) . expr_ty_adjusted ( receiver) ;
122- let closure_args = if is_type_diagnostic_item ( cx, receiver_type, sym:: Option ) {
123- "||"
124- } else if is_type_diagnostic_item ( cx, receiver_type, sym:: Result ) {
125- "|_|"
126- } else {
127- return ;
128- } ;
129-
130- let arg_root = get_arg_root ( cx, & args[ 0 ] ) ;
131-
132- let span_replace_word = method_span. with_hi ( expr. span . hi ( ) ) ;
133-
134- let mut applicability = Applicability :: MachineApplicable ;
135-
136- // Special handling for `format!` as arg_root
137- if let Some ( macro_call) = root_macro_call_first_node ( cx, arg_root) {
138- if cx. tcx . is_diagnostic_item ( sym:: format_macro, macro_call. def_id )
139- && let Some ( format_args) = format_args_storage. get ( cx, arg_root, macro_call. expn )
140- {
141- let span = format_args_inputs_span ( format_args) ;
142- let sugg = snippet_with_applicability ( cx, span, ".." , & mut applicability) ;
143- span_lint_and_sugg (
144- cx,
145- EXPECT_FUN_CALL ,
146- span_replace_word,
147- format ! ( "function call inside of `{name}`" ) ,
148- "try" ,
149- format ! ( "unwrap_or_else({closure_args} panic!({sugg}))" ) ,
150- applicability,
151- ) ;
84+ // Special handling for `format!` as arg_root
85+ if let Some ( macro_call) = root_macro_call_first_node ( cx, arg_root) {
86+ if cx. tcx . is_diagnostic_item ( sym:: format_macro, macro_call. def_id )
87+ && let Some ( format_args) = format_args_storage. get ( cx, arg_root, macro_call. expn )
88+ {
89+ let span = format_args_inputs_span ( format_args) ;
90+ let sugg = snippet_with_applicability ( cx, span, ".." , & mut applicability) ;
91+ span_lint_and_sugg (
92+ cx,
93+ EXPECT_FUN_CALL ,
94+ span_replace_word,
95+ format ! ( "function call inside of `{name}`" ) ,
96+ "try" ,
97+ format ! ( "unwrap_or_else({closure_args} panic!({sugg}))" ) ,
98+ applicability,
99+ ) ;
100+ }
101+ return ;
152102 }
153- return ;
154- }
155103
156- let mut arg_root_snippet: Cow < ' _ , _ > = snippet_with_applicability ( cx, arg_root. span , ".." , & mut applicability) ;
157- if requires_to_string ( cx, arg_root) {
158- arg_root_snippet. to_mut ( ) . push_str ( ".to_string()" ) ;
159- }
104+ let arg_root_snippet: Cow < ' _ , _ > = snippet_with_applicability ( cx, arg_root. span , ".." , & mut applicability) ;
160105
161- span_lint_and_sugg (
162- cx,
163- EXPECT_FUN_CALL ,
164- span_replace_word,
165- format ! ( "function call inside of `{name}`" ) ,
166- "try" ,
167- format ! ( "unwrap_or_else({closure_args} {{ panic!(\" {{}}\" , {arg_root_snippet}) }})" ) ,
168- applicability,
169- ) ;
106+ span_lint_and_sugg (
107+ cx,
108+ EXPECT_FUN_CALL ,
109+ span_replace_word,
110+ format ! ( "function call inside of `{name}`" ) ,
111+ "try" ,
112+ format ! ( "unwrap_or_else({closure_args} panic!(\" {{}}\" , {arg_root_snippet}))" ) ,
113+ applicability,
114+ ) ;
115+ }
170116}
0 commit comments