|
1 |
| -use crate::utils::{get_parent_expr, implements_trait, snippet, span_lint_and_sugg}; |
2 |
| -use if_chain::if_chain; |
3 |
| -use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX, PREC_PREFIX}; |
| 1 | +use crate::utils::{get_parent_node, in_macro, is_allowed, peel_mid_ty_refs, snippet_with_context, span_lint_and_sugg}; |
| 2 | +use rustc_ast::util::parser::PREC_PREFIX; |
4 | 3 | use rustc_errors::Applicability;
|
5 |
| -use rustc_hir::{Expr, ExprKind}; |
| 4 | +use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node, UnOp}; |
6 | 5 | use rustc_lint::{LateContext, LateLintPass};
|
7 |
| -use rustc_session::{declare_lint_pass, declare_tool_lint}; |
8 |
| -use rustc_span::source_map::Span; |
| 6 | +use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults}; |
| 7 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 8 | +use rustc_span::{symbol::sym, Span}; |
9 | 9 |
|
10 | 10 | declare_clippy_lint! {
|
11 | 11 | /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls.
|
@@ -34,76 +34,265 @@ declare_clippy_lint! {
|
34 | 34 | "Explicit use of deref or deref_mut method while not in a method chain."
|
35 | 35 | }
|
36 | 36 |
|
37 |
| -declare_lint_pass!(Dereferencing => [ |
38 |
| - EXPLICIT_DEREF_METHODS |
| 37 | +impl_lint_pass!(Dereferencing => [ |
| 38 | + EXPLICIT_DEREF_METHODS, |
39 | 39 | ]);
|
40 | 40 |
|
| 41 | +#[derive(Default)] |
| 42 | +pub struct Dereferencing { |
| 43 | + state: Option<(State, StateData)>, |
| 44 | + |
| 45 | + // While parsing a `deref` method call in ufcs form, the path to the function is itself an |
| 46 | + // expression. This is to store the id of that expression so it can be skipped when |
| 47 | + // `check_expr` is called for it. |
| 48 | + skip_expr: Option<HirId>, |
| 49 | +} |
| 50 | + |
| 51 | +struct StateData { |
| 52 | + /// Span of the top level expression |
| 53 | + span: Span, |
| 54 | + /// The required mutability |
| 55 | + target_mut: Mutability, |
| 56 | +} |
| 57 | + |
| 58 | +enum State { |
| 59 | + // Any number of deref method calls. |
| 60 | + DerefMethod { |
| 61 | + // The number of calls in a sequence which changed the referenced type |
| 62 | + ty_changed_count: usize, |
| 63 | + is_final_ufcs: bool, |
| 64 | + }, |
| 65 | +} |
| 66 | + |
| 67 | +// A reference operation considered by this lint pass |
| 68 | +enum RefOp { |
| 69 | + Method(Mutability), |
| 70 | + Deref, |
| 71 | + AddrOf, |
| 72 | +} |
| 73 | + |
41 | 74 | impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
42 | 75 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
43 |
| - if_chain! { |
44 |
| - if !expr.span.from_expansion(); |
45 |
| - if let ExprKind::MethodCall(ref method_name, _, ref args, _) = &expr.kind; |
46 |
| - if args.len() == 1; |
47 |
| - |
48 |
| - then { |
49 |
| - if let Some(parent_expr) = get_parent_expr(cx, expr) { |
50 |
| - // Check if we have the whole call chain here |
51 |
| - if let ExprKind::MethodCall(..) = parent_expr.kind { |
52 |
| - return; |
53 |
| - } |
54 |
| - // Check for Expr that we don't want to be linted |
55 |
| - let precedence = parent_expr.precedence(); |
56 |
| - match precedence { |
57 |
| - // Lint a Call is ok though |
58 |
| - ExprPrecedence::Call | ExprPrecedence::AddrOf => (), |
59 |
| - _ => { |
60 |
| - if precedence.order() >= PREC_PREFIX && precedence.order() <= PREC_POSTFIX { |
61 |
| - return; |
62 |
| - } |
63 |
| - } |
| 76 | + // Skip path expressions from deref calls. e.g. `Deref::deref(e)` |
| 77 | + if Some(expr.hir_id) == self.skip_expr.take() { |
| 78 | + return; |
| 79 | + } |
| 80 | + |
| 81 | + // Stop processing sub expressions when a macro call is seen |
| 82 | + if in_macro(expr.span) { |
| 83 | + if let Some((state, data)) = self.state.take() { |
| 84 | + report(cx, expr, state, data); |
| 85 | + } |
| 86 | + return; |
| 87 | + } |
| 88 | + |
| 89 | + let typeck = cx.typeck_results(); |
| 90 | + let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) { |
| 91 | + x |
| 92 | + } else { |
| 93 | + // The whole chain of reference operations has been seen |
| 94 | + if let Some((state, data)) = self.state.take() { |
| 95 | + report(cx, expr, state, data); |
| 96 | + } |
| 97 | + return; |
| 98 | + }; |
| 99 | + |
| 100 | + match (self.state.take(), kind) { |
| 101 | + (None, kind) => { |
| 102 | + let parent = get_parent_node(cx.tcx, expr.hir_id); |
| 103 | + let expr_ty = typeck.expr_ty(expr); |
| 104 | + |
| 105 | + match kind { |
| 106 | + RefOp::Method(target_mut) |
| 107 | + if !is_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) |
| 108 | + && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) => |
| 109 | + { |
| 110 | + self.state = Some(( |
| 111 | + State::DerefMethod { |
| 112 | + ty_changed_count: if deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)) { |
| 113 | + 0 |
| 114 | + } else { |
| 115 | + 1 |
| 116 | + }, |
| 117 | + is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), |
| 118 | + }, |
| 119 | + StateData { |
| 120 | + span: expr.span, |
| 121 | + target_mut, |
| 122 | + }, |
| 123 | + )); |
64 | 124 | }
|
| 125 | + _ => (), |
65 | 126 | }
|
66 |
| - let name = method_name.ident.as_str(); |
67 |
| - lint_deref(cx, &*name, &args[0], args[0].span, expr.span); |
68 |
| - } |
| 127 | + }, |
| 128 | + (Some((State::DerefMethod { ty_changed_count, .. }, data)), RefOp::Method(_)) => { |
| 129 | + self.state = Some(( |
| 130 | + State::DerefMethod { |
| 131 | + ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) { |
| 132 | + ty_changed_count |
| 133 | + } else { |
| 134 | + ty_changed_count + 1 |
| 135 | + }, |
| 136 | + is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), |
| 137 | + }, |
| 138 | + data, |
| 139 | + )); |
| 140 | + }, |
| 141 | + |
| 142 | + (Some((state, data)), _) => report(cx, expr, state, data), |
69 | 143 | }
|
70 | 144 | }
|
71 | 145 | }
|
72 | 146 |
|
73 |
| -fn lint_deref(cx: &LateContext<'_>, method_name: &str, call_expr: &Expr<'_>, var_span: Span, expr_span: Span) { |
74 |
| - match method_name { |
75 |
| - "deref" => { |
76 |
| - let impls_deref_trait = cx.tcx.lang_items().deref_trait().map_or(false, |id| { |
77 |
| - implements_trait(cx, cx.typeck_results().expr_ty(&call_expr), id, &[]) |
78 |
| - }); |
79 |
| - if impls_deref_trait { |
80 |
| - span_lint_and_sugg( |
81 |
| - cx, |
82 |
| - EXPLICIT_DEREF_METHODS, |
83 |
| - expr_span, |
84 |
| - "explicit deref method call", |
85 |
| - "try this", |
86 |
| - format!("&*{}", &snippet(cx, var_span, "..")), |
87 |
| - Applicability::MachineApplicable, |
88 |
| - ); |
89 |
| - } |
| 147 | +fn try_parse_ref_op( |
| 148 | + tcx: TyCtxt<'tcx>, |
| 149 | + typeck: &'tcx TypeckResults<'_>, |
| 150 | + expr: &'tcx Expr<'_>, |
| 151 | +) -> Option<(RefOp, &'tcx Expr<'tcx>)> { |
| 152 | + let (def_id, arg) = match expr.kind { |
| 153 | + ExprKind::MethodCall(_, _, [arg], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg), |
| 154 | + ExprKind::Call( |
| 155 | + Expr { |
| 156 | + kind: ExprKind::Path(path), |
| 157 | + hir_id, |
| 158 | + .. |
| 159 | + }, |
| 160 | + [arg], |
| 161 | + ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg), |
| 162 | + ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => { |
| 163 | + return Some((RefOp::Deref, sub_expr)); |
90 | 164 | },
|
91 |
| - "deref_mut" => { |
92 |
| - let impls_deref_mut_trait = cx.tcx.lang_items().deref_mut_trait().map_or(false, |id| { |
93 |
| - implements_trait(cx, cx.typeck_results().expr_ty(&call_expr), id, &[]) |
94 |
| - }); |
95 |
| - if impls_deref_mut_trait { |
96 |
| - span_lint_and_sugg( |
97 |
| - cx, |
98 |
| - EXPLICIT_DEREF_METHODS, |
99 |
| - expr_span, |
100 |
| - "explicit deref_mut method call", |
101 |
| - "try this", |
102 |
| - format!("&mut *{}", &snippet(cx, var_span, "..")), |
103 |
| - Applicability::MachineApplicable, |
104 |
| - ); |
105 |
| - } |
| 165 | + ExprKind::AddrOf(BorrowKind::Ref, _, sub_expr) => return Some((RefOp::AddrOf, sub_expr)), |
| 166 | + _ => return None, |
| 167 | + }; |
| 168 | + if tcx.is_diagnostic_item(sym::deref_method, def_id) { |
| 169 | + Some((RefOp::Method(Mutability::Not), arg)) |
| 170 | + } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? { |
| 171 | + Some((RefOp::Method(Mutability::Mut), arg)) |
| 172 | + } else { |
| 173 | + None |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +// Checks whether the type for a deref call actually changed the type, not just the mutability of |
| 178 | +// the reference. |
| 179 | +fn deref_method_same_type(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { |
| 180 | + match (result_ty.kind(), arg_ty.kind()) { |
| 181 | + (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => TyS::same_type(result_ty, arg_ty), |
| 182 | + |
| 183 | + // The result type for a deref method is always a reference |
| 184 | + // Not matching the previous pattern means the argument type is not a reference |
| 185 | + // This means that the type did change |
| 186 | + _ => false, |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +// Checks whether the parent node is a suitable context for switching from a deref method to the |
| 191 | +// deref operator. |
| 192 | +fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool { |
| 193 | + let parent = match parent { |
| 194 | + Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e, |
| 195 | + _ => return true, |
| 196 | + }; |
| 197 | + match parent.kind { |
| 198 | + // Leave deref calls in the middle of a method chain. |
| 199 | + // e.g. x.deref().foo() |
| 200 | + ExprKind::MethodCall(_, _, [self_arg, ..], _) if self_arg.hir_id == child_id => false, |
| 201 | + |
| 202 | + // Leave deref calls resulting in a called function |
| 203 | + // e.g. (x.deref())() |
| 204 | + ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false, |
| 205 | + |
| 206 | + // Makes an ugly suggestion |
| 207 | + // e.g. *x.deref() => *&*x |
| 208 | + ExprKind::Unary(UnOp::Deref, _) |
| 209 | + // Postfix expressions would require parens |
| 210 | + | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) |
| 211 | + | ExprKind::Field(..) |
| 212 | + | ExprKind::Index(..) |
| 213 | + | ExprKind::Err => false, |
| 214 | + |
| 215 | + ExprKind::Box(..) |
| 216 | + | ExprKind::ConstBlock(..) |
| 217 | + | ExprKind::Array(_) |
| 218 | + | ExprKind::Call(..) |
| 219 | + | ExprKind::MethodCall(..) |
| 220 | + | ExprKind::Tup(..) |
| 221 | + | ExprKind::Binary(..) |
| 222 | + | ExprKind::Unary(..) |
| 223 | + | ExprKind::Lit(..) |
| 224 | + | ExprKind::Cast(..) |
| 225 | + | ExprKind::Type(..) |
| 226 | + | ExprKind::DropTemps(..) |
| 227 | + | ExprKind::If(..) |
| 228 | + | ExprKind::Loop(..) |
| 229 | + | ExprKind::Match(..) |
| 230 | + | ExprKind::Closure(..) |
| 231 | + | ExprKind::Block(..) |
| 232 | + | ExprKind::Assign(..) |
| 233 | + | ExprKind::AssignOp(..) |
| 234 | + | ExprKind::Path(..) |
| 235 | + | ExprKind::AddrOf(..) |
| 236 | + | ExprKind::Break(..) |
| 237 | + | ExprKind::Continue(..) |
| 238 | + | ExprKind::Ret(..) |
| 239 | + | ExprKind::InlineAsm(..) |
| 240 | + | ExprKind::LlvmInlineAsm(..) |
| 241 | + | ExprKind::Struct(..) |
| 242 | + | ExprKind::Repeat(..) |
| 243 | + | ExprKind::Yield(..) => true, |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +#[allow(clippy::needless_pass_by_value)] |
| 248 | +fn report(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) { |
| 249 | + match state { |
| 250 | + State::DerefMethod { |
| 251 | + ty_changed_count, |
| 252 | + is_final_ufcs, |
| 253 | + } => { |
| 254 | + let mut app = Applicability::MachineApplicable; |
| 255 | + let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); |
| 256 | + let ty = cx.typeck_results().expr_ty(expr); |
| 257 | + let (_, ref_count) = peel_mid_ty_refs(ty); |
| 258 | + let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { |
| 259 | + // a deref call changing &T -> &U requires two deref operators the first time |
| 260 | + // this occurs. One to remove the reference, a second to call the deref impl. |
| 261 | + "*".repeat(ty_changed_count + 1) |
| 262 | + } else { |
| 263 | + "*".repeat(ty_changed_count) |
| 264 | + }; |
| 265 | + let addr_of_str = if ty_changed_count < ref_count { |
| 266 | + // Check if a reborrow from &mut T -> &T is required. |
| 267 | + if data.target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { |
| 268 | + "&*" |
| 269 | + } else { |
| 270 | + "" |
| 271 | + } |
| 272 | + } else if data.target_mut == Mutability::Mut { |
| 273 | + "&mut " |
| 274 | + } else { |
| 275 | + "&" |
| 276 | + }; |
| 277 | + |
| 278 | + let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX { |
| 279 | + format!("({})", expr_str) |
| 280 | + } else { |
| 281 | + expr_str.into_owned() |
| 282 | + }; |
| 283 | + |
| 284 | + span_lint_and_sugg( |
| 285 | + cx, |
| 286 | + EXPLICIT_DEREF_METHODS, |
| 287 | + data.span, |
| 288 | + match data.target_mut { |
| 289 | + Mutability::Not => "explicit `deref` method call", |
| 290 | + Mutability::Mut => "explicit `deref_mut` method call", |
| 291 | + }, |
| 292 | + "try this", |
| 293 | + format!("{}{}{}", addr_of_str, deref_str, expr_str), |
| 294 | + app, |
| 295 | + ); |
106 | 296 | },
|
107 |
| - _ => (), |
108 | 297 | }
|
109 | 298 | }
|
0 commit comments