mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-13 04:26:48 +00:00
Auto merge of #9701 - smoelius:improve-possible-borrower, r=Jarcho
Improve `possible_borrower` This PR makes several improvements to `clippy_uitls::mir::possible_borrower`. These changes benefit both `needless_borrow` and `redundant clone`. 1. **Use the compiler's `MaybeStorageLive` analysis** I could spot not functional differences between the one in the compiler and the one in Clippy's repository. So, I removed the latter in favor of the the former. 2. **Make `PossibleBorrower` a dataflow analysis instead of a visitor** The main benefit of this change is that allows `possible_borrower` to take advantage of statements' relative locations, which is easier to do in an analysis than in a visitor. This is easier to illustrate with an example, so consider this one: ```rust fn foo(cx: &LateContext<'_>, lint: &'static Lint) { cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new())); // ^ } ``` We would like to flag the `&` pointed to by the `^` for removal. `foo`'s MIR begins like this: ```rust fn span_lint::foo::{closure#0}(_1: [closure@$DIR/needless_borrow.rs:396:68: 396:74], _2: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>) -> &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> { debug diag => _2; // in scope 0 at $DIR/needless_borrow.rs:396:69: 396:73 let mut _0: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // return place in scope 0 at $DIR/needless_borrow.rs:396:75: 396:75 let mut _3: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 let mut _4: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 let mut _5: &std::string::String; // in scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 let _6: std::string::String; // in scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 bb0: { StorageLive(_3); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 StorageLive(_4); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 _4 = &mut (*_2); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 StorageLive(_5); // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 StorageLive(_6); // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 _6 = std::string::String::new() -> bb1; // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 // mir::Constant // + span: $DIR/needless_borrow.rs:396:86: 396:97 // + literal: Const { ty: fn() -> std::string::String {std::string::String::new}, val: Value(<ZST>) } } bb1: { _5 = &_6; // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 _3 = rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>(move _4, move _5) -> [return: bb2, unwind: bb4]; // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 // mir::Constant // + span: $DIR/needless_borrow.rs:396:80: 396:84 // + literal: Const { ty: for<'a> fn(&'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>, &std::string::String) -> &'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>}, val: Value(<ZST>) } } ``` The call to `diag.note` appears in `bb1` on the line beginning with `_3 =`. The `String` is owned by `_6`. So, in the call to `diag.note`, we would like to know whether there are any references to `_6` besides `_5`. The old, visitor approach did not consider the relative locations of statements. So all borrows were treated the same, *even if they occurred after the location of interest*. For example, before the `_3 = ...` call, the possible borrowers of `_6` would be just `_5`. But after the call, the possible borrowers would include `_2`, `_3`, and `_4`. So, in a sense, the call from which we are try to remove the needless borrow is trying to prevent us from removing the needless borrow(!). With an analysis, things do not get so muddled. We can determine the set of possible borrowers at any specific location, e.g., using a `ResultsCursor`. 3. **Change `only_borrowers` to `at_most_borrowers`** `possible_borrowers` exposed a function `only_borrowers` that determined whether the borrowers of some local were *exactly* some set `S`. But, from what I can tell, this was overkill. For the lints that currently use `possible_borrower` (`needless_borrow` and `redundant_clone`), all we really want to know is whether there are borrowers *other than* those in `S`. (Put another way, we only care about the subset relation in one direction.) The new function `at_most_borrowers` takes this more tailored approach. 4. **Compute relations "on the fly" rather than using `transitive_relation`** The visitor would compute and store the transitive closure of the possible borrower relation for an entire MIR body. But with an analysis, there is effectively a different possible borrower relation at each location in the body. Computing and storing a transitive closure at each location would not be practical. So the new approach is to compute the transitive closure on the fly, as needed. But the new approach might actually be more efficient, as I now explain. In all current uses of `at_most_borrowers` (previously `only_borrowers`), the size of the set of borrowers `S` is at most 2. So you need only check at most three borrowers to determine whether the subset relation holds. That is, once you have found a third borrower, you can stop, since you know the relation cannot hold. Note that `transitive_relation` is still used by `clippy_uitls::mir::possible_origin` (a kind of "subroutine" of `possible_borrower`). cc: `@Jarcho` --- changelog: [`needless_borrow`], [`redundant_clone`]: Now track references better and detect more cases [#9701](https://github.com/rust-lang/rust-clippy/pull/9701) <!-- changelog_checked -->
This commit is contained in:
commit
4fe3727c39
@ -54,7 +54,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
|
||||
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
|
||||
sugg,
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
);
|
||||
|
@ -1282,10 +1282,10 @@ fn referent_used_exactly_once<'tcx>(
|
||||
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
|
||||
}
|
||||
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
|
||||
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
|
||||
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
|
||||
// itself. See the comment in that method for an explanation as to why.
|
||||
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
|
||||
// If `place.local` were not included here, the `copyable_iterator::warn` test would fail. The
|
||||
// reason is that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible
|
||||
// borrower of itself. See the comment in that method for an explanation as to why.
|
||||
possible_borrower.at_most_borrowers(cx, &[local, place.local], place.local, location)
|
||||
&& used_exactly_once(mir, place.local).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
|
@ -377,7 +377,7 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
|
||||
call_site,
|
||||
&format!("`format!` in `{name}!` args"),
|
||||
|diag| {
|
||||
diag.help(&format!(
|
||||
diag.help(format!(
|
||||
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
|
@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
|
||||
);
|
||||
diag.span_label(
|
||||
def.variants[variants_size[1].ind].span,
|
||||
&if variants_size[1].fields_size.is_empty() {
|
||||
if variants_size[1].fields_size.is_empty() {
|
||||
"the second-largest variant carries no data at all".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
|
@ -361,7 +361,7 @@ fn check_for_is_empty<'tcx>(
|
||||
db.span_note(span, "`is_empty` defined here");
|
||||
}
|
||||
if let Some(self_kind) = self_kind {
|
||||
db.note(&output.expected_sig(self_kind));
|
||||
db.note(output.expected_sig(self_kind));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
|
||||
Ok(Some(path)) => path,
|
||||
Ok(None) => return Conf::default(),
|
||||
Err(error) => {
|
||||
sess.struct_err(&format!("error finding Clippy's configuration file: {error}"))
|
||||
sess.struct_err(format!("error finding Clippy's configuration file: {error}"))
|
||||
.emit();
|
||||
return Conf::default();
|
||||
},
|
||||
|
@ -77,7 +77,7 @@ pub(super) fn check<'tcx>(
|
||||
applicability,
|
||||
);
|
||||
|
||||
diag.note(&format!(
|
||||
diag.note(format!(
|
||||
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
|
||||
));
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
|
||||
let help = format!("make the function `async` and {ret_sugg}");
|
||||
diag.span_suggestion(
|
||||
header_span,
|
||||
&help,
|
||||
help,
|
||||
format!("async {}{ret_snip}", &header_snip[..ret_pos]),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
|
@ -109,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
|
||||
|
||||
let test_span = expr.span.until(then.span);
|
||||
span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
|
||||
diag.span_note(test_span, &format!("the {kind_word} was tested here"));
|
||||
diag.span_note(test_span, format!("the {kind_word} was tested here"));
|
||||
multispan_sugg(
|
||||
diag,
|
||||
&format!("try using the `strip_{kind_word}` method"),
|
||||
|
@ -36,7 +36,7 @@ pub fn check(
|
||||
expr.span,
|
||||
&format!("calling `to_string` on `{arg_ty}`"),
|
||||
|diag| {
|
||||
diag.help(&format!(
|
||||
diag.help(format!(
|
||||
"`{self_ty}` implements `ToString` through a slower blanket impl, but `{deref_self_ty}` has a fast specialization of `ToString`"
|
||||
));
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
@ -29,7 +29,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
|
||||
application = Applicability::Unspecified;
|
||||
diag.span_help(
|
||||
pat.span,
|
||||
&format!("for this change `{}` has to be mutable", snippet(cx, pat.span, "..")),
|
||||
format!("for this change `{}` has to be mutable", snippet(cx, pat.span, "..")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ pub(super) fn check<'tcx>(
|
||||
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
|
||||
}
|
||||
|
||||
diag.multipart_suggestion(&format!("use `{suggest}` instead"), suggestion, applicability);
|
||||
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ fn check_manual_split_once_indirect(
|
||||
};
|
||||
diag.span_suggestion_verbose(
|
||||
local.span,
|
||||
&format!("try `{r}split_once`"),
|
||||
format!("try `{r}split_once`"),
|
||||
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
|
||||
app,
|
||||
);
|
||||
|
@ -62,7 +62,7 @@ pub(super) fn check<'tcx>(
|
||||
span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
&format!("use `{simplify_using}(..)` instead"),
|
||||
format!("use `{simplify_using}(..)` instead"),
|
||||
format!("{simplify_using}({})", snippet(cx, body_expr.span, "..")),
|
||||
applicability,
|
||||
);
|
||||
|
@ -284,7 +284,7 @@ fn check<'tcx>(
|
||||
|
||||
diag.span_suggestion(
|
||||
assign.lhs_span,
|
||||
&format!("declare `{binding_name}` here"),
|
||||
format!("declare `{binding_name}` here"),
|
||||
let_snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
@ -304,7 +304,7 @@ fn check<'tcx>(
|
||||
|
||||
diag.span_suggestion_verbose(
|
||||
usage.stmt.span.shrink_to_lo(),
|
||||
&format!("declare `{binding_name}` here"),
|
||||
format!("declare `{binding_name}` here"),
|
||||
format!("{let_snippet} = "),
|
||||
applicability,
|
||||
);
|
||||
@ -335,7 +335,7 @@ fn check<'tcx>(
|
||||
|
||||
diag.span_suggestion_verbose(
|
||||
usage.stmt.span.shrink_to_lo(),
|
||||
&format!("declare `{binding_name}` here"),
|
||||
format!("declare `{binding_name}` here"),
|
||||
format!("{let_snippet} = "),
|
||||
applicability,
|
||||
);
|
||||
|
@ -125,7 +125,7 @@ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
|
||||
if is_string { "string" } else { "byte string" }
|
||||
),
|
||||
|diag| {
|
||||
diag.help(&format!(
|
||||
diag.help(format!(
|
||||
"octal escapes are not supported, `\\0` is always a null {}",
|
||||
if is_string { "character" } else { "byte" }
|
||||
));
|
||||
@ -139,7 +139,7 @@ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
|
||||
// suggestion 2: unambiguous null byte
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
&format!(
|
||||
format!(
|
||||
"if the null {} is intended, disambiguate using",
|
||||
if is_string { "character" } else { "byte" }
|
||||
),
|
||||
|
@ -50,7 +50,7 @@ fn lint_misrefactored_assign_op(
|
||||
let long = format!("{snip_a} = {}", sugg::make_binop(op.into(), a, r));
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
&format!(
|
||||
format!(
|
||||
"did you mean `{snip_a} = {snip_a} {} {snip_r}` or `{long}`? Consider replacing it with",
|
||||
op.as_str()
|
||||
),
|
||||
|
@ -131,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
|
||||
// `res = clone(arg)` can be turned into `res = move arg;`
|
||||
// if `arg` is the only borrow of `cloned` at this point.
|
||||
|
||||
if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) {
|
||||
if cannot_move_out || !possible_borrower.at_most_borrowers(cx, &[arg], cloned, loc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
|
||||
// StorageDead(pred_arg);
|
||||
// res = to_path_buf(cloned);
|
||||
// ```
|
||||
if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) {
|
||||
if cannot_move_out || !possible_borrower.at_most_borrowers(cx, &[arg, cloned], local, loc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
trait_method_span,
|
||||
&format!("existing `{method_name}` defined here"),
|
||||
format!("existing `{method_name}` defined here"),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -151,7 +151,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
// iterate on trait_spans?
|
||||
diag.span_note(
|
||||
trait_spans[0],
|
||||
&format!("existing `{method_name}` defined here"),
|
||||
format!("existing `{method_name}` defined here"),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -132,7 +132,7 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa
|
||||
applicability,
|
||||
);
|
||||
if !is_xor_based {
|
||||
diag.note(&format!("or maybe you should use `{sugg}::mem::replace`?"));
|
||||
diag.note(format!("or maybe you should use `{sugg}::mem::replace`?"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -214,7 +214,7 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.note(
|
||||
&format!("or maybe you should use `{sugg}::mem::replace`?")
|
||||
format!("or maybe you should use `{sugg}::mem::replace`?")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -77,7 +77,7 @@ pub(super) fn check<'tcx>(
|
||||
&format!("transmute from `{from_ty_orig}` which has an undefined layout"),
|
||||
|diag| {
|
||||
if from_ty_orig.peel_refs() != from_ty.peel_refs() {
|
||||
diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -91,7 +91,7 @@ pub(super) fn check<'tcx>(
|
||||
&format!("transmute to `{to_ty_orig}` which has an undefined layout"),
|
||||
|diag| {
|
||||
if to_ty_orig.peel_refs() != to_ty.peel_refs() {
|
||||
diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -119,16 +119,16 @@ pub(super) fn check<'tcx>(
|
||||
),
|
||||
|diag| {
|
||||
if let Some(same_adt_did) = same_adt_did {
|
||||
diag.note(&format!(
|
||||
diag.note(format!(
|
||||
"two instances of the same generic type (`{}`) may have different layouts",
|
||||
cx.tcx.item_name(same_adt_did)
|
||||
));
|
||||
} else {
|
||||
if from_ty_orig.peel_refs() != from_ty {
|
||||
diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
}
|
||||
if to_ty_orig.peel_refs() != to_ty {
|
||||
diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -146,7 +146,7 @@ pub(super) fn check<'tcx>(
|
||||
&format!("transmute from `{from_ty_orig}` which has an undefined layout"),
|
||||
|diag| {
|
||||
if from_ty_orig.peel_refs() != from_ty {
|
||||
diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -163,7 +163,7 @@ pub(super) fn check<'tcx>(
|
||||
&format!("transmute into `{to_ty_orig}` which has an undefined layout"),
|
||||
|diag| {
|
||||
if to_ty_orig.peel_refs() != to_ty {
|
||||
diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
|
||||
&format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"),
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
let sugg = arg.as_ty(&to_ty.to_string()).to_string();
|
||||
let sugg = arg.as_ty(to_ty.to_string()).to_string();
|
||||
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
|
||||
}
|
||||
},
|
||||
|
@ -61,7 +61,7 @@ pub(super) fn check<'tcx>(
|
||||
"transmute from an integer to a pointer",
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
diag.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()), Applicability::Unspecified);
|
||||
diag.span_suggestion(e.span, "try", arg.as_ty(to_ty.to_string()), Applicability::Unspecified);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||
&format!("usage of `{outer_sym}<{generic_snippet}>`"),
|
||||
|diag| {
|
||||
diag.span_suggestion(hir_ty.span, "try", format!("{generic_snippet}"), applicability);
|
||||
diag.note(&format!(
|
||||
diag.note(format!(
|
||||
"`{generic_snippet}` is already a pointer, `{outer_sym}<{generic_snippet}>` allocates a pointer on the heap"
|
||||
));
|
||||
},
|
||||
@ -78,7 +78,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||
format!("{outer_sym}<{generic_snippet}>"),
|
||||
applicability,
|
||||
);
|
||||
diag.note(&format!(
|
||||
diag.note(format!(
|
||||
"`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation"
|
||||
));
|
||||
},
|
||||
@ -91,10 +91,10 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||
hir_ty.span,
|
||||
&format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"),
|
||||
|diag| {
|
||||
diag.note(&format!(
|
||||
diag.note(format!(
|
||||
"`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation"
|
||||
));
|
||||
diag.help(&format!(
|
||||
diag.help(format!(
|
||||
"consider using just `{outer_sym}<{generic_snippet}>` or `{inner_sym}<{generic_snippet}>`"
|
||||
));
|
||||
},
|
||||
|
@ -129,7 +129,7 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
|
||||
|
||||
if arg_snippets_without_empty_blocks.is_empty() {
|
||||
db.multipart_suggestion(
|
||||
&format!("use {singular}unit literal{plural} instead"),
|
||||
format!("use {singular}unit literal{plural} instead"),
|
||||
args_to_recover
|
||||
.iter()
|
||||
.map(|arg| (arg.span, "()".to_string()))
|
||||
@ -142,7 +142,7 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
|
||||
let it_or_them = if plural { "them" } else { "it" };
|
||||
db.span_suggestion(
|
||||
expr.span,
|
||||
&format!(
|
||||
format!(
|
||||
"{or}move the expression{empty_or_s} in front of the call and replace {it_or_them} with the unit literal `()`"
|
||||
),
|
||||
sugg,
|
||||
|
@ -377,7 +377,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
|
||||
// print!("\n"), write!(f, "\n")
|
||||
|
||||
diag.multipart_suggestion(
|
||||
&format!("use `{name}ln!` instead"),
|
||||
format!("use `{name}ln!` instead"),
|
||||
vec![(name_span, format!("{name}ln")), (format_string_span, String::new())],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
@ -388,7 +388,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
|
||||
let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1));
|
||||
|
||||
diag.multipart_suggestion(
|
||||
&format!("use `{name}ln!` instead"),
|
||||
format!("use `{name}ln!` instead"),
|
||||
vec![(name_span, format!("{name}ln")), (newline_span, String::new())],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
@ -17,7 +17,7 @@ use std::env;
|
||||
fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
|
||||
if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
|
||||
if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
|
||||
diag.help(&format!(
|
||||
diag.help(format!(
|
||||
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}",
|
||||
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
|
@ -1,52 +0,0 @@
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir;
|
||||
use rustc_mir_dataflow::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
|
||||
|
||||
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(super) struct MaybeStorageLive;
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
|
||||
type Domain = BitSet<mir::Local>;
|
||||
const NAME: &'static str = "maybe_storage_live";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg in body.args_iter() {
|
||||
state.insert(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
|
||||
type Idx = mir::Local;
|
||||
|
||||
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
|
||||
match stmt.kind {
|
||||
mir::StatementKind::StorageLive(l) => trans.gen(l),
|
||||
mir::StatementKind::StorageDead(l) => trans.kill(l),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_loc: mir::Location,
|
||||
) {
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
// Nothing to do when a call returns successfully
|
||||
}
|
||||
}
|
@ -5,8 +5,6 @@ use rustc_middle::mir::{
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
mod maybe_storage_live;
|
||||
|
||||
mod possible_borrower;
|
||||
pub use possible_borrower::PossibleBorrowerMap;
|
||||
|
||||
|
@ -1,92 +1,137 @@
|
||||
use super::{
|
||||
maybe_storage_live::MaybeStorageLive, possible_origin::PossibleOriginVisitor,
|
||||
transitive_relation::TransitiveRelation,
|
||||
};
|
||||
use super::possible_origin::PossibleOriginVisitor;
|
||||
use crate::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_index::bit_set::{BitSet, HybridBitSet};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::{self, visit::Visitor as _, Mutability};
|
||||
use rustc_middle::ty::{self, visit::TypeVisitor};
|
||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||
use rustc_middle::mir::{
|
||||
self, visit::Visitor as _, BasicBlock, Local, Location, Mutability, Statement, StatementKind, Terminator,
|
||||
};
|
||||
use rustc_middle::ty::{self, visit::TypeVisitor, TyCtxt};
|
||||
use rustc_mir_dataflow::{
|
||||
fmt::DebugWithContext, impls::MaybeStorageLive, lattice::JoinSemiLattice, Analysis, AnalysisDomain,
|
||||
CallReturnPlaces, ResultsCursor,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
/// Collects the possible borrowers of each local.
|
||||
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
|
||||
/// possible borrowers of `a`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
struct PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
possible_borrower: TransitiveRelation,
|
||||
struct PossibleBorrowerAnalysis<'b, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'b mir::Body<'tcx>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct PossibleBorrowerState {
|
||||
map: FxIndexMap<Local, BitSet<Local>>,
|
||||
domain_size: usize,
|
||||
}
|
||||
|
||||
impl PossibleBorrowerState {
|
||||
fn new(domain_size: usize) -> Self {
|
||||
Self {
|
||||
map: FxIndexMap::default(),
|
||||
domain_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn add(&mut self, borrowed: Local, borrower: Local) {
|
||||
self.map
|
||||
.entry(borrowed)
|
||||
.or_insert(BitSet::new_empty(self.domain_size))
|
||||
.insert(borrower);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> DebugWithContext<C> for PossibleBorrowerState {
|
||||
fn fmt_with(&self, _ctxt: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<_ as std::fmt::Debug>::fmt(self, f)
|
||||
}
|
||||
fn fmt_diff_with(&self, _old: &Self, _ctxt: &C, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl JoinSemiLattice for PossibleBorrowerState {
|
||||
fn join(&mut self, other: &Self) -> bool {
|
||||
let mut changed = false;
|
||||
for (&borrowed, borrowers) in other.map.iter() {
|
||||
if !borrowers.is_empty() {
|
||||
changed |= self
|
||||
.map
|
||||
.entry(borrowed)
|
||||
.or_insert(BitSet::new_empty(self.domain_size))
|
||||
.union(borrowers);
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, 'tcx> AnalysisDomain<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> {
|
||||
type Domain = PossibleBorrowerState;
|
||||
|
||||
const NAME: &'static str = "possible_borrower";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
PossibleBorrowerState::new(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, _body: &mir::Body<'tcx>, _entry_set: &mut Self::Domain) {}
|
||||
}
|
||||
|
||||
impl<'b, 'tcx> PossibleBorrowerAnalysis<'b, 'tcx> {
|
||||
fn new(
|
||||
cx: &'a LateContext<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'b mir::Body<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
possible_borrower: TransitiveRelation::default(),
|
||||
cx,
|
||||
tcx,
|
||||
body,
|
||||
possible_origin,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_map(
|
||||
self,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
) -> PossibleBorrowerMap<'b, 'tcx> {
|
||||
let mut map = FxHashMap::default();
|
||||
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
|
||||
if is_copy(cx, self.body.local_decls[row].ty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
|
||||
borrowers.remove(mir::Local::from_usize(0));
|
||||
if !borrowers.is_empty() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
|
||||
let bs = BitSet::new_empty(self.body.local_decls.len());
|
||||
PossibleBorrowerMap {
|
||||
map,
|
||||
maybe_live,
|
||||
bitset: (bs.clone(), bs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
mir::Rvalue::Ref(_, _, borrowed) => {
|
||||
self.possible_borrower.add(borrowed.local, lhs);
|
||||
},
|
||||
other => {
|
||||
if ContainsRegion
|
||||
.visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
|
||||
.is_continue()
|
||||
{
|
||||
return;
|
||||
}
|
||||
rvalue_locals(other, |rhs| {
|
||||
if lhs != rhs {
|
||||
self.possible_borrower.add(rhs, lhs);
|
||||
impl<'b, 'tcx> Analysis<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> {
|
||||
fn apply_call_return_effect(
|
||||
&self,
|
||||
_state: &mut Self::Domain,
|
||||
_block: BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn apply_statement_effect(&self, state: &mut Self::Domain, statement: &Statement<'tcx>, _location: Location) {
|
||||
if let StatementKind::Assign(box (place, rvalue)) = &statement.kind {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
mir::Rvalue::Ref(_, _, borrowed) => {
|
||||
state.add(borrowed.local, lhs);
|
||||
},
|
||||
other => {
|
||||
if ContainsRegion
|
||||
.visit_ty(place.ty(&self.body.local_decls, self.tcx).ty)
|
||||
.is_continue()
|
||||
{
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
rvalue_locals(other, |rhs| {
|
||||
if lhs != rhs {
|
||||
state.add(rhs, lhs);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
|
||||
fn apply_terminator_effect(&self, state: &mut Self::Domain, terminator: &Terminator<'tcx>, _location: Location) {
|
||||
if let mir::TerminatorKind::Call {
|
||||
args,
|
||||
destination: mir::Place { local: dest, .. },
|
||||
@ -126,10 +171,10 @@ impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b,
|
||||
|
||||
for y in mutable_variables {
|
||||
for x in &immutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
state.add(*x, y);
|
||||
}
|
||||
for x in &mutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
state.add(*x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,73 +210,98 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of `PossibleBorrowerVisitor`.
|
||||
/// Result of `PossibleBorrowerAnalysis`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct PossibleBorrowerMap<'b, 'tcx> {
|
||||
/// Mapping `Local -> its possible borrowers`
|
||||
pub map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
// Caches to avoid allocation of `BitSet` on every query
|
||||
pub bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
|
||||
body: &'b mir::Body<'tcx>,
|
||||
possible_borrower: ResultsCursor<'b, 'tcx, PossibleBorrowerAnalysis<'b, 'tcx>>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive<'b>>,
|
||||
pushed: BitSet<Local>,
|
||||
stack: Vec<Local>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> {
|
||||
pub fn new(cx: &'a LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
|
||||
impl<'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> {
|
||||
pub fn new(cx: &LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
|
||||
let possible_origin = {
|
||||
let mut vis = PossibleOriginVisitor::new(mir);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx)
|
||||
};
|
||||
let maybe_storage_live_result = MaybeStorageLive
|
||||
let possible_borrower = PossibleBorrowerAnalysis::new(cx.tcx, mir, possible_origin)
|
||||
.into_engine(cx.tcx, mir)
|
||||
.pass_name("redundant_clone")
|
||||
.pass_name("possible_borrower")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(mir);
|
||||
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx, maybe_storage_live_result)
|
||||
let maybe_live = MaybeStorageLive::new(Cow::Owned(BitSet::new_empty(mir.local_decls.len())))
|
||||
.into_engine(cx.tcx, mir)
|
||||
.pass_name("possible_borrower")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(mir);
|
||||
PossibleBorrowerMap {
|
||||
body: mir,
|
||||
possible_borrower,
|
||||
maybe_live,
|
||||
pushed: BitSet::new_empty(mir.local_decls.len()),
|
||||
stack: Vec::with_capacity(mir.local_decls.len()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
|
||||
pub fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
|
||||
self.bounded_borrowers(borrowers, borrowers, borrowed, at)
|
||||
}
|
||||
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` includes at least `below`
|
||||
/// but no more than `above`.
|
||||
pub fn bounded_borrowers(
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` includes no more than
|
||||
/// `borrowers`.
|
||||
/// Notes:
|
||||
/// 1. It would be nice if `PossibleBorrowerMap` could store `cx` so that `at_most_borrowers`
|
||||
/// would not require it to be passed in. But a `PossibleBorrowerMap` is stored in `LintPass`
|
||||
/// `Dereferencing`, which outlives any `LateContext`.
|
||||
/// 2. In all current uses of `at_most_borrowers`, `borrowers` is a slice of at most two
|
||||
/// elements. Thus, `borrowers.contains(...)` is effectively a constant-time operation. If
|
||||
/// `at_most_borrowers`'s uses were to expand beyond this, its implementation might have to be
|
||||
/// adjusted.
|
||||
pub fn at_most_borrowers(
|
||||
&mut self,
|
||||
below: &[mir::Local],
|
||||
above: &[mir::Local],
|
||||
cx: &LateContext<'tcx>,
|
||||
borrowers: &[mir::Local],
|
||||
borrowed: mir::Local,
|
||||
at: mir::Location,
|
||||
) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
if is_copy(cx, self.body.local_decls[borrowed].ty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.bitset.0.clear();
|
||||
let maybe_live = &mut self.maybe_live;
|
||||
if let Some(bitset) = self.map.get(&borrowed) {
|
||||
for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
|
||||
self.bitset.0.insert(b);
|
||||
self.possible_borrower.seek_before_primary_effect(at);
|
||||
self.maybe_live.seek_before_primary_effect(at);
|
||||
|
||||
let possible_borrower = &self.possible_borrower.get().map;
|
||||
let maybe_live = &self.maybe_live;
|
||||
|
||||
self.pushed.clear();
|
||||
self.stack.clear();
|
||||
|
||||
if let Some(borrowers) = possible_borrower.get(&borrowed) {
|
||||
for b in borrowers.iter() {
|
||||
if self.pushed.insert(b) {
|
||||
self.stack.push(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
// Nothing borrows `borrowed` at `at`.
|
||||
return true;
|
||||
}
|
||||
|
||||
self.bitset.1.clear();
|
||||
for b in below {
|
||||
self.bitset.1.insert(*b);
|
||||
while let Some(borrower) = self.stack.pop() {
|
||||
if maybe_live.contains(borrower) && !borrowers.contains(&borrower) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(borrowers) = possible_borrower.get(&borrower) {
|
||||
for b in borrowers.iter() {
|
||||
if self.pushed.insert(b) {
|
||||
self.stack.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.bitset.0.superset(&self.bitset.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for b in above {
|
||||
self.bitset.0.remove(*b);
|
||||
}
|
||||
|
||||
self.bitset.0.is_empty()
|
||||
true
|
||||
}
|
||||
|
||||
pub fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
|
||||
|
@ -1,6 +1,6 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::manual_retain)]
|
||||
#![allow(unused)]
|
||||
#![allow(unused, clippy::redundant_clone)]
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::BinaryHeap;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::manual_retain)]
|
||||
#![allow(unused)]
|
||||
#![allow(unused, clippy::redundant_clone)]
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::BinaryHeap;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// run-rustfix
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(custom_inner_attributes, lint_reasons, rustc_private)]
|
||||
#![allow(
|
||||
unused,
|
||||
clippy::uninlined_format_args,
|
||||
@ -491,3 +491,14 @@ mod issue_9782_method_variant {
|
||||
S.foo::<&[u8; 100]>(&a);
|
||||
}
|
||||
}
|
||||
|
||||
extern crate rustc_lint;
|
||||
extern crate rustc_span;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod span_lint {
|
||||
use rustc_lint::{LateContext, Lint, LintContext};
|
||||
fn foo(cx: &LateContext<'_>, lint: &'static Lint) {
|
||||
cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(String::new()));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// run-rustfix
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(custom_inner_attributes, lint_reasons, rustc_private)]
|
||||
#![allow(
|
||||
unused,
|
||||
clippy::uninlined_format_args,
|
||||
@ -491,3 +491,14 @@ mod issue_9782_method_variant {
|
||||
S.foo::<&[u8; 100]>(&a);
|
||||
}
|
||||
}
|
||||
|
||||
extern crate rustc_lint;
|
||||
extern crate rustc_span;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod span_lint {
|
||||
use rustc_lint::{LateContext, Lint, LintContext};
|
||||
fn foo(cx: &LateContext<'_>, lint: &'static Lint) {
|
||||
cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new()));
|
||||
}
|
||||
}
|
||||
|
@ -216,5 +216,11 @@ error: the borrowed expression implements the required traits
|
||||
LL | foo(&a);
|
||||
| ^^ help: change this to: `a`
|
||||
|
||||
error: aborting due to 36 previous errors
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:502:85
|
||||
|
|
||||
LL | cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new()));
|
||||
| ^^^^^^^^^^^^^^ help: change this to: `String::new()`
|
||||
|
||||
error: aborting due to 37 previous errors
|
||||
|
||||
|
@ -239,3 +239,9 @@ fn false_negative_5707() {
|
||||
let _z = x.clone(); // pr 7346 can't lint on `x`
|
||||
drop(y);
|
||||
}
|
||||
|
||||
#[allow(unused, clippy::manual_retain)]
|
||||
fn possible_borrower_improvements() {
|
||||
let mut s = String::from("foobar");
|
||||
s = s.chars().filter(|&c| c != 'o').collect();
|
||||
}
|
||||
|
@ -239,3 +239,9 @@ fn false_negative_5707() {
|
||||
let _z = x.clone(); // pr 7346 can't lint on `x`
|
||||
drop(y);
|
||||
}
|
||||
|
||||
#[allow(unused, clippy::manual_retain)]
|
||||
fn possible_borrower_improvements() {
|
||||
let mut s = String::from("foobar");
|
||||
s = s.chars().filter(|&c| c != 'o').to_owned().collect();
|
||||
}
|
||||
|
@ -179,5 +179,17 @@ note: this value is dropped without further use
|
||||
LL | foo(&x.clone(), move || {
|
||||
| ^
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
error: redundant clone
|
||||
--> $DIR/redundant_clone.rs:246:40
|
||||
|
|
||||
LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect();
|
||||
| ^^^^^^^^^^^ help: remove this
|
||||
|
|
||||
note: this value is dropped without further use
|
||||
--> $DIR/redundant_clone.rs:246:9
|
||||
|
|
||||
LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 16 previous errors
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user