|
| 1 | +use crate::utils::{is_expn_of, is_type_diagnostic_item, snippet, snippet_with_applicability, span_lint_and_sugg}; |
| 2 | +use if_chain::if_chain; |
| 3 | +use rustc_errors::Applicability; |
| 4 | +use rustc_hir as hir; |
| 5 | +use rustc_lint::LateContext; |
| 6 | +use rustc_middle::ty; |
| 7 | +use rustc_span::source_map::Span; |
| 8 | +use rustc_span::symbol::sym; |
| 9 | +use std::borrow::Cow; |
| 10 | + |
| 11 | +use super::EXPECT_FUN_CALL; |
| 12 | + |
| 13 | +/// Checks for the `EXPECT_FUN_CALL` lint. |
| 14 | +#[allow(clippy::too_many_lines)] |
| 15 | +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) { |
| 16 | + // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or |
| 17 | + // `&str` |
| 18 | + fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { |
| 19 | + let mut arg_root = arg; |
| 20 | + loop { |
| 21 | + arg_root = match &arg_root.kind { |
| 22 | + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, |
| 23 | + hir::ExprKind::MethodCall(method_name, _, call_args, _) => { |
| 24 | + if call_args.len() == 1 |
| 25 | + && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref)) |
| 26 | + && { |
| 27 | + let arg_type = cx.typeck_results().expr_ty(&call_args[0]); |
| 28 | + let base_type = arg_type.peel_refs(); |
| 29 | + *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type) |
| 30 | + } |
| 31 | + { |
| 32 | + &call_args[0] |
| 33 | + } else { |
| 34 | + break; |
| 35 | + } |
| 36 | + }, |
| 37 | + _ => break, |
| 38 | + }; |
| 39 | + } |
| 40 | + arg_root |
| 41 | + } |
| 42 | + |
| 43 | + // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be |
| 44 | + // converted to string. |
| 45 | + fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { |
| 46 | + let arg_ty = cx.typeck_results().expr_ty(arg); |
| 47 | + if is_type_diagnostic_item(cx, arg_ty, sym::string_type) { |
| 48 | + return false; |
| 49 | + } |
| 50 | + if let ty::Ref(_, ty, ..) = arg_ty.kind() { |
| 51 | + if *ty.kind() == ty::Str && can_be_static_str(cx, arg) { |
| 52 | + return false; |
| 53 | + } |
| 54 | + }; |
| 55 | + true |
| 56 | + } |
| 57 | + |
| 58 | + // Check if an expression could have type `&'static str`, knowing that it |
| 59 | + // has type `&str` for some lifetime. |
| 60 | + fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { |
| 61 | + match arg.kind { |
| 62 | + hir::ExprKind::Lit(_) => true, |
| 63 | + hir::ExprKind::Call(fun, _) => { |
| 64 | + if let hir::ExprKind::Path(ref p) = fun.kind { |
| 65 | + match cx.qpath_res(p, fun.hir_id) { |
| 66 | + hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( |
| 67 | + cx.tcx.fn_sig(def_id).output().skip_binder().kind(), |
| 68 | + ty::Ref(ty::ReStatic, ..) |
| 69 | + ), |
| 70 | + _ => false, |
| 71 | + } |
| 72 | + } else { |
| 73 | + false |
| 74 | + } |
| 75 | + }, |
| 76 | + hir::ExprKind::MethodCall(..) => { |
| 77 | + cx.typeck_results() |
| 78 | + .type_dependent_def_id(arg.hir_id) |
| 79 | + .map_or(false, |method_id| { |
| 80 | + matches!( |
| 81 | + cx.tcx.fn_sig(method_id).output().skip_binder().kind(), |
| 82 | + ty::Ref(ty::ReStatic, ..) |
| 83 | + ) |
| 84 | + }) |
| 85 | + }, |
| 86 | + hir::ExprKind::Path(ref p) => matches!( |
| 87 | + cx.qpath_res(p, arg.hir_id), |
| 88 | + hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) |
| 89 | + ), |
| 90 | + _ => false, |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + fn generate_format_arg_snippet( |
| 95 | + cx: &LateContext<'_>, |
| 96 | + a: &hir::Expr<'_>, |
| 97 | + applicability: &mut Applicability, |
| 98 | + ) -> Vec<String> { |
| 99 | + if_chain! { |
| 100 | + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; |
| 101 | + if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; |
| 102 | + if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; |
| 103 | + |
| 104 | + then { |
| 105 | + format_arg_expr_tup |
| 106 | + .iter() |
| 107 | + .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned()) |
| 108 | + .collect() |
| 109 | + } else { |
| 110 | + unreachable!() |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + fn is_call(node: &hir::ExprKind<'_>) -> bool { |
| 116 | + match node { |
| 117 | + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { |
| 118 | + is_call(&expr.kind) |
| 119 | + }, |
| 120 | + hir::ExprKind::Call(..) |
| 121 | + | hir::ExprKind::MethodCall(..) |
| 122 | + // These variants are debatable or require further examination |
| 123 | + | hir::ExprKind::If(..) |
| 124 | + | hir::ExprKind::Match(..) |
| 125 | + | hir::ExprKind::Block{ .. } => true, |
| 126 | + _ => false, |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) { |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]); |
| 135 | + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) { |
| 136 | + "||" |
| 137 | + } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) { |
| 138 | + "|_|" |
| 139 | + } else { |
| 140 | + return; |
| 141 | + }; |
| 142 | + |
| 143 | + let arg_root = get_arg_root(cx, &args[1]); |
| 144 | + |
| 145 | + let span_replace_word = method_span.with_hi(expr.span.hi()); |
| 146 | + |
| 147 | + let mut applicability = Applicability::MachineApplicable; |
| 148 | + |
| 149 | + //Special handling for `format!` as arg_root |
| 150 | + if_chain! { |
| 151 | + if let hir::ExprKind::Block(block, None) = &arg_root.kind; |
| 152 | + if block.stmts.len() == 1; |
| 153 | + if let hir::StmtKind::Local(local) = &block.stmts[0].kind; |
| 154 | + if let Some(arg_root) = &local.init; |
| 155 | + if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; |
| 156 | + if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; |
| 157 | + if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; |
| 158 | + then { |
| 159 | + let fmt_spec = &format_args[0]; |
| 160 | + let fmt_args = &format_args[1]; |
| 161 | + |
| 162 | + let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()]; |
| 163 | + |
| 164 | + args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability)); |
| 165 | + |
| 166 | + let sugg = args.join(", "); |
| 167 | + |
| 168 | + span_lint_and_sugg( |
| 169 | + cx, |
| 170 | + EXPECT_FUN_CALL, |
| 171 | + span_replace_word, |
| 172 | + &format!("use of `{}` followed by a function call", name), |
| 173 | + "try this", |
| 174 | + format!("unwrap_or_else({} panic!({}))", closure_args, sugg), |
| 175 | + applicability, |
| 176 | + ); |
| 177 | + |
| 178 | + return; |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); |
| 183 | + if requires_to_string(cx, arg_root) { |
| 184 | + arg_root_snippet.to_mut().push_str(".to_string()"); |
| 185 | + } |
| 186 | + |
| 187 | + span_lint_and_sugg( |
| 188 | + cx, |
| 189 | + EXPECT_FUN_CALL, |
| 190 | + span_replace_word, |
| 191 | + &format!("use of `{}` followed by a function call", name), |
| 192 | + "try this", |
| 193 | + format!( |
| 194 | + "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})", |
| 195 | + closure_args, arg_root_snippet |
| 196 | + ), |
| 197 | + applicability, |
| 198 | + ); |
| 199 | +} |
0 commit comments