|
1 | 1 | use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
2 | 2 | use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
|
3 |
| -use clippy_utils::sugg::Sugg; |
| 3 | +use clippy_utils::sugg::{DiagExt as _, Sugg}; |
4 | 4 | use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
|
5 |
| -use clippy_utils::{get_parent_expr, is_trait_method, is_ty_alias, path_to_local}; |
| 5 | +use clippy_utils::{ |
| 6 | + get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local, |
| 7 | +}; |
6 | 8 | use rustc_errors::Applicability;
|
7 | 9 | use rustc_hir::def_id::DefId;
|
8 | 10 | use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
|
9 | 11 | use rustc_infer::infer::TyCtxtInferExt;
|
10 | 12 | use rustc_infer::traits::Obligation;
|
11 | 13 | use rustc_lint::{LateContext, LateLintPass};
|
12 | 14 | use rustc_middle::traits::ObligationCause;
|
13 |
| -use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt}; |
| 15 | +use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt}; |
14 | 16 | use rustc_session::impl_lint_pass;
|
15 |
| -use rustc_span::{Span, sym}; |
| 17 | +use rustc_span::{Span, Symbol, sym}; |
16 | 18 | use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
17 | 19 |
|
18 | 20 | declare_clippy_lint! {
|
@@ -382,3 +384,57 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
382 | 384 | }
|
383 | 385 | }
|
384 | 386 | }
|
| 387 | + |
| 388 | +/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a |
| 389 | +/// higher-order mapping function. |
| 390 | +pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { |
| 391 | + if has_eligible_receiver(cx, recv, expr) |
| 392 | + && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) |
| 393 | + && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() |
| 394 | + && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() |
| 395 | + && same_type_and_consts(from_ty, to_ty) |
| 396 | + { |
| 397 | + span_lint_and_then( |
| 398 | + cx, |
| 399 | + USELESS_CONVERSION, |
| 400 | + expr.span.with_lo(recv.span.hi()), |
| 401 | + format!("useless conversion to the same type: `{from_ty}`"), |
| 402 | + |diag| { |
| 403 | + diag.suggest_remove_item( |
| 404 | + cx, |
| 405 | + expr.span.with_lo(recv.span.hi()), |
| 406 | + "consider removing", |
| 407 | + Applicability::MachineApplicable, |
| 408 | + ); |
| 409 | + }, |
| 410 | + ); |
| 411 | + } |
| 412 | +} |
| 413 | + |
| 414 | +fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { |
| 415 | + let recv_ty = cx.typeck_results().expr_ty(recv); |
| 416 | + if is_inherent_method_call(cx, expr) |
| 417 | + && let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) |
| 418 | + { |
| 419 | + if let Some(diag_name) = cx.tcx.get_diagnostic_name(recv_ty_defid) |
| 420 | + && matches!(diag_name, sym::Option | sym::Result) |
| 421 | + { |
| 422 | + return true; |
| 423 | + } |
| 424 | + |
| 425 | + // FIXME: Add ControlFlow diagnostic item |
| 426 | + let def_path = cx.get_def_path(recv_ty_defid); |
| 427 | + if def_path |
| 428 | + .iter() |
| 429 | + .map(Symbol::as_str) |
| 430 | + .zip(["core", "ops", "control_flow", "ControlFlow"]) |
| 431 | + .all(|(sym, s)| sym == s) |
| 432 | + { |
| 433 | + return true; |
| 434 | + } |
| 435 | + } |
| 436 | + if is_trait_method(cx, expr, sym::Iterator) { |
| 437 | + return true; |
| 438 | + } |
| 439 | + false |
| 440 | +} |
0 commit comments