Skip to content

Commit e6a6df5

Browse files
authored
Rollup merge of #80723 - rylev:noop-lint-pass, r=estebank
Implement NOOP_METHOD_CALL lint Implements the beginnings of rust-lang/lang-team#67 - a lint for detecting noop method calls (e.g, calling `<&T as Clone>::clone()` when `T: !Clone`). This PR does not fully realize the vision and has a few limitations that need to be addressed either before merging or in subsequent PRs: * [ ] No UFCS support * [ ] The warning message is pretty plain * [ ] Doesn't work for `ToOwned` The implementation uses [`Instance::resolve`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/instance/struct.Instance.html#method.resolve) which is normally later in the compiler. It seems that there are some invariants that this function relies on that we try our best to respect. For instance, it expects substitutions to have happened, which haven't yet performed, but we check first for `needs_subst` to ensure we're dealing with a monomorphic type. Thank you to ```@davidtwco,``` ```@Aaron1011,``` and ```@wesleywiser``` for helping me at various points through out this PR ❤️.
2 parents a0d66b5 + 25637b2 commit e6a6df5

File tree

20 files changed

+240
-22
lines changed

20 files changed

+240
-22
lines changed

compiler/rustc_codegen_ssa/src/back/rpath.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub fn get_rpath_flags(config: &mut RPathConfig<'_>) -> Vec<String> {
2424

2525
debug!("preparing the RPATH!");
2626

27-
let libs = config.used_crates.clone();
27+
let libs = config.used_crates;
2828
let libs = libs.iter().filter_map(|&(_, ref l)| l.option()).collect::<Vec<_>>();
2929
let rpaths = get_rpaths(config, &libs);
3030
let mut flags = rpaths_to_flags(&rpaths);

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mod methods;
5757
mod non_ascii_idents;
5858
mod non_fmt_panic;
5959
mod nonstandard_style;
60+
mod noop_method_call;
6061
mod passes;
6162
mod redundant_semicolon;
6263
mod traits;
@@ -81,6 +82,7 @@ use methods::*;
8182
use non_ascii_idents::*;
8283
use non_fmt_panic::NonPanicFmt;
8384
use nonstandard_style::*;
85+
use noop_method_call::*;
8486
use redundant_semicolon::*;
8587
use traits::*;
8688
use types::*;
@@ -168,6 +170,7 @@ macro_rules! late_lint_passes {
168170
DropTraitConstraints: DropTraitConstraints,
169171
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
170172
NonPanicFmt: NonPanicFmt,
173+
NoopMethodCall: NoopMethodCall,
171174
]
172175
);
173176
};
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use crate::context::LintContext;
2+
use crate::rustc_middle::ty::TypeFoldable;
3+
use crate::LateContext;
4+
use crate::LateLintPass;
5+
use rustc_hir::def::DefKind;
6+
use rustc_hir::{Expr, ExprKind};
7+
use rustc_middle::ty;
8+
use rustc_span::symbol::sym;
9+
10+
declare_lint! {
11+
/// The `noop_method_call` lint detects specific calls to noop methods
12+
/// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
13+
///
14+
/// ### Example
15+
///
16+
/// ```rust
17+
/// # #![allow(unused)]
18+
/// #![warn(noop_method_call)]
19+
/// struct Foo;
20+
/// let foo = &Foo;
21+
/// let clone: &Foo = foo.clone();
22+
/// ```
23+
///
24+
/// {{produces}}
25+
///
26+
/// ### Explanation
27+
///
28+
/// Some method calls are noops meaning that they do nothing. Usually such methods
29+
/// are the result of blanket implementations that happen to create some method invocations
30+
/// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
31+
/// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
32+
/// as references are copy. This lint detects these calls and warns the user about them.
33+
pub NOOP_METHOD_CALL,
34+
Allow,
35+
"detects the use of well-known noop methods"
36+
}
37+
38+
declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL]);
39+
40+
impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
41+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
42+
// We only care about method calls.
43+
let (call, elements) = match expr.kind {
44+
ExprKind::MethodCall(call, _, elements, _) => (call, elements),
45+
_ => return,
46+
};
47+
// We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
48+
// traits and ignore any other method call.
49+
let (trait_id, did) = match cx.typeck_results().type_dependent_def(expr.hir_id) {
50+
// Verify we are dealing with a method/associated function.
51+
Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) {
52+
// Check that we're dealing with a trait method for one of the traits we care about.
53+
Some(trait_id)
54+
if [sym::Clone, sym::Deref, sym::Borrow]
55+
.iter()
56+
.any(|s| cx.tcx.is_diagnostic_item(*s, trait_id)) =>
57+
{
58+
(trait_id, did)
59+
}
60+
_ => return,
61+
},
62+
_ => return,
63+
};
64+
let substs = cx.typeck_results().node_substs(expr.hir_id);
65+
if substs.needs_subst() {
66+
// We can't resolve on types that require monomorphization, so we don't handle them if
67+
// we need to perfom substitution.
68+
return;
69+
}
70+
let param_env = cx.tcx.param_env(trait_id);
71+
// Resolve the trait method instance.
72+
let i = match ty::Instance::resolve(cx.tcx, param_env, did, substs) {
73+
Ok(Some(i)) => i,
74+
_ => return,
75+
};
76+
// (Re)check that it implements the noop diagnostic.
77+
for s in [sym::noop_method_clone, sym::noop_method_deref, sym::noop_method_borrow].iter() {
78+
if cx.tcx.is_diagnostic_item(*s, i.def_id()) {
79+
let method = &call.ident.name;
80+
let receiver = &elements[0];
81+
let receiver_ty = cx.typeck_results().expr_ty(receiver);
82+
let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
83+
if receiver_ty != expr_ty {
84+
// This lint will only trigger if the receiver type and resulting expression \
85+
// type are the same, implying that the method call is unnecessary.
86+
return;
87+
}
88+
let expr_span = expr.span;
89+
let note = format!(
90+
"the type `{:?}` which `{}` is being called on is the same as \
91+
the type returned from `{}`, so the method call does not do \
92+
anything and can be removed",
93+
receiver_ty, method, method,
94+
);
95+
96+
let span = expr_span.with_lo(receiver.span.hi());
97+
cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| {
98+
let method = &call.ident.name;
99+
let message = format!(
100+
"call to `.{}()` on a reference in this situation does nothing",
101+
&method,
102+
);
103+
lint.build(&message)
104+
.span_label(span, "unnecessary method call")
105+
.note(&note)
106+
.emit()
107+
});
108+
}
109+
}
110+
}
111+
}

compiler/rustc_middle/src/ty/error.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_target::spec::abi;
1212

1313
use std::borrow::Cow;
1414
use std::fmt;
15-
use std::ops::Deref;
1615

1716
#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable)]
1817
pub struct ExpectedFound<T> {
@@ -548,7 +547,6 @@ impl<T> Trait<T> for X {
548547
TargetFeatureCast(def_id) => {
549548
let attrs = self.get_attrs(*def_id);
550549
let target_spans = attrs
551-
.deref()
552550
.iter()
553551
.filter(|attr| attr.has_name(sym::target_feature))
554552
.map(|attr| attr.span);

compiler/rustc_mir/src/borrow_check/invalidation.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
165165
self.consume_operand(location, value);
166166

167167
// Invalidate all borrows of local places
168-
let borrow_set = self.borrow_set.clone();
168+
let borrow_set = self.borrow_set;
169169
let resume = self.location_table.start_index(resume.start_location());
170170
for (i, data) in borrow_set.iter_enumerated() {
171171
if borrow_of_local_data(data.borrowed_place) {
@@ -177,7 +177,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
177177
}
178178
TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
179179
// Invalidate all borrows of local places
180-
let borrow_set = self.borrow_set.clone();
180+
let borrow_set = self.borrow_set;
181181
let start = self.location_table.start_index(location);
182182
for (i, data) in borrow_set.iter_enumerated() {
183183
if borrow_of_local_data(data.borrowed_place) {
@@ -369,15 +369,15 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> {
369369
);
370370
let tcx = self.tcx;
371371
let body = self.body;
372-
let borrow_set = self.borrow_set.clone();
372+
let borrow_set = self.borrow_set;
373373
let indices = self.borrow_set.indices();
374374
each_borrow_involving_path(
375375
self,
376376
tcx,
377377
body,
378378
location,
379379
(sd, place),
380-
&borrow_set.clone(),
380+
borrow_set,
381381
indices,
382382
|this, borrow_index, borrow| {
383383
match (rw, borrow.kind) {

compiler/rustc_mir_build/src/build/matches/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
5151

5252
PatKind::Constant { value } => Test {
5353
span: match_pair.pattern.span,
54-
kind: TestKind::Eq { value, ty: match_pair.pattern.ty.clone() },
54+
kind: TestKind::Eq { value, ty: match_pair.pattern.ty },
5555
},
5656

5757
PatKind::Range(range) => {

compiler/rustc_span/src/symbol.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ symbols! {
129129
BTreeMap,
130130
BTreeSet,
131131
BinaryHeap,
132+
Borrow,
132133
C,
133134
CString,
134135
Center,
@@ -141,6 +142,7 @@ symbols! {
141142
Decodable,
142143
Decoder,
143144
Default,
145+
Deref,
144146
Encodable,
145147
Encoder,
146148
Eq,
@@ -789,6 +791,9 @@ symbols! {
789791
none_error,
790792
nontemporal_store,
791793
nontrapping_dash_fptoint: "nontrapping-fptoint",
794+
noop_method_borrow,
795+
noop_method_clone,
796+
noop_method_deref,
792797
noreturn,
793798
nostack,
794799
not,

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
819819
sig.decl
820820
.inputs
821821
.iter()
822-
.map(|arg| match arg.clone().kind {
822+
.map(|arg| match arg.kind {
823823
hir::TyKind::Tup(ref tys) => ArgKind::Tuple(
824824
Some(arg.span),
825825
vec![("_".to_owned(), "_".to_owned()); tys.len()],

compiler/rustc_traits/src/chalk/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ crate fn evaluate_goal<'tcx>(
165165
// let's just ignore that
166166
let sol = Canonical {
167167
max_universe: ty::UniverseIndex::from_usize(0),
168-
variables: obligation.variables.clone(),
168+
variables: obligation.variables,
169169
value: QueryResponse {
170170
var_values: CanonicalVarValues { var_values: IndexVec::new() }
171171
.make_identity(tcx),

compiler/rustc_typeck/src/check/callee.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
465465
let expected_arg_tys = self.expected_inputs_for_expected_output(
466466
call_expr.span,
467467
expected,
468-
fn_sig.output().clone(),
468+
fn_sig.output(),
469469
fn_sig.inputs(),
470470
);
471471

0 commit comments

Comments
 (0)