mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 13:04:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			158 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use rustc_hir::def::DefKind;
 | |
| use rustc_hir::{Expr, ExprKind};
 | |
| use rustc_middle::ty;
 | |
| use rustc_middle::ty::adjustment::Adjust;
 | |
| use rustc_session::{declare_lint, declare_lint_pass};
 | |
| use rustc_span::symbol::sym;
 | |
| 
 | |
| use crate::context::LintContext;
 | |
| use crate::lints::{
 | |
|     NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
 | |
| };
 | |
| use crate::{LateContext, LateLintPass};
 | |
| 
 | |
| declare_lint! {
 | |
|     /// The `noop_method_call` lint detects specific calls to noop methods
 | |
|     /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
 | |
|     ///
 | |
|     /// ### Example
 | |
|     ///
 | |
|     /// ```rust
 | |
|     /// # #![allow(unused)]
 | |
|     /// struct Foo;
 | |
|     /// let foo = &Foo;
 | |
|     /// let clone: &Foo = foo.clone();
 | |
|     /// ```
 | |
|     ///
 | |
|     /// {{produces}}
 | |
|     ///
 | |
|     /// ### Explanation
 | |
|     ///
 | |
|     /// Some method calls are noops meaning that they do nothing. Usually such methods
 | |
|     /// are the result of blanket implementations that happen to create some method invocations
 | |
|     /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
 | |
|     /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
 | |
|     /// as references are copy. This lint detects these calls and warns the user about them.
 | |
|     pub NOOP_METHOD_CALL,
 | |
|     Warn,
 | |
|     "detects the use of well-known noop methods"
 | |
| }
 | |
| 
 | |
| declare_lint! {
 | |
|     /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`
 | |
|     /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,
 | |
|     /// instead of performing the operation on the underlying `T` and can be confusing.
 | |
|     ///
 | |
|     /// ### Example
 | |
|     ///
 | |
|     /// ```rust
 | |
|     /// # #![allow(unused)]
 | |
|     /// struct Foo;
 | |
|     /// let foo = &&Foo;
 | |
|     /// let clone: &Foo = foo.clone();
 | |
|     /// ```
 | |
|     ///
 | |
|     /// {{produces}}
 | |
|     ///
 | |
|     /// ### Explanation
 | |
|     ///
 | |
|     /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double
 | |
|     /// reference, instead of cloning the inner type which should be what was intended.
 | |
|     pub SUSPICIOUS_DOUBLE_REF_OP,
 | |
|     Warn,
 | |
|     "suspicious call of trait method on `&&T`"
 | |
| }
 | |
| 
 | |
| declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
 | |
| 
 | |
| impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
 | |
|     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
 | |
|         // We only care about method calls.
 | |
|         let ExprKind::MethodCall(call, receiver, _, call_span) = &expr.kind else {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         if call_span.from_expansion() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
 | |
|         // traits and ignore any other method call.
 | |
| 
 | |
|         let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id)
 | |
|         else {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         let Some(trait_id) = cx.tcx.trait_of_item(did) else { return };
 | |
| 
 | |
|         let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return };
 | |
| 
 | |
|         if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         let args = cx
 | |
|             .tcx
 | |
|             .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_args(expr.hir_id));
 | |
|         // Resolve the trait method instance.
 | |
|         let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.param_env, did, args) else {
 | |
|             return;
 | |
|         };
 | |
|         // (Re)check that it implements the noop diagnostic.
 | |
|         let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
 | |
|         if !matches!(
 | |
|             name,
 | |
|             sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
 | |
|         ) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let receiver_ty = cx.typeck_results().expr_ty(receiver);
 | |
|         let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
 | |
|         let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
 | |
| 
 | |
|         // If there is any user defined auto-deref step, then we don't want to warn.
 | |
|         // https://github.com/rust-lang/rust-clippy/issues/9272
 | |
|         if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let expr_span = expr.span;
 | |
|         let span = expr_span.with_lo(receiver.span.hi());
 | |
| 
 | |
|         let orig_ty = expr_ty.peel_refs();
 | |
| 
 | |
|         if receiver_ty == expr_ty {
 | |
|             let suggest_derive = match orig_ty.kind() {
 | |
|                 ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()),
 | |
|                 _ => None,
 | |
|             };
 | |
|             cx.emit_span_lint(NOOP_METHOD_CALL, span, NoopMethodCallDiag {
 | |
|                 method: call.ident.name,
 | |
|                 orig_ty,
 | |
|                 trait_,
 | |
|                 label: span,
 | |
|                 suggest_derive,
 | |
|             });
 | |
|         } else {
 | |
|             match name {
 | |
|                 // If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
 | |
|                 // then that should be allowed
 | |
|                 sym::noop_method_borrow => return,
 | |
|                 sym::noop_method_clone => cx.emit_span_lint(
 | |
|                     SUSPICIOUS_DOUBLE_REF_OP,
 | |
|                     span,
 | |
|                     SuspiciousDoubleRefCloneDiag { ty: expr_ty },
 | |
|                 ),
 | |
|                 sym::noop_method_deref => cx.emit_span_lint(
 | |
|                     SUSPICIOUS_DOUBLE_REF_OP,
 | |
|                     span,
 | |
|                     SuspiciousDoubleRefDerefDiag { ty: expr_ty },
 | |
|                 ),
 | |
|                 _ => return,
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
