]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/types/borrowed_box.rs
hir: Preserve used syntax in `TyKind::TraitObject`
[rust.git] / clippy_lints / src / types / borrowed_box.rs
1 use rustc_errors::Applicability;
2 use rustc_hir::{
3     self as hir, GenericArg, GenericBounds, GenericParamKind, HirId, Lifetime, MutTy, Mutability, Node, QPath,
4     SyntheticTyParamKind, TyKind,
5 };
6 use rustc_lint::LateContext;
7
8 use if_chain::if_chain;
9
10 use crate::utils::{match_path, paths, snippet, span_lint_and_sugg};
11
12 use super::BORROWED_BOX;
13
14 pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, mut_ty: &MutTy<'_>) -> bool {
15     match mut_ty.ty.kind {
16         TyKind::Path(ref qpath) => {
17             let hir_id = mut_ty.ty.hir_id;
18             let def = cx.qpath_res(qpath, hir_id);
19             if_chain! {
20                 if let Some(def_id) = def.opt_def_id();
21                 if Some(def_id) == cx.tcx.lang_items().owned_box();
22                 if let QPath::Resolved(None, ref path) = *qpath;
23                 if let [ref bx] = *path.segments;
24                 if let Some(ref params) = bx.args;
25                 if !params.parenthesized;
26                 if let Some(inner) = params.args.iter().find_map(|arg| match arg {
27                     GenericArg::Type(ty) => Some(ty),
28                     _ => None,
29                 });
30                 then {
31                     if is_any_trait(inner) {
32                         // Ignore `Box<Any>` types; see issue #1884 for details.
33                         return false;
34                     }
35
36                     let ltopt = if lt.is_elided() {
37                         String::new()
38                     } else {
39                         format!("{} ", lt.name.ident().as_str())
40                     };
41
42                     if mut_ty.mutbl == Mutability::Mut {
43                         // Ignore `&mut Box<T>` types; see issue #2907 for
44                         // details.
45                         return false;
46                     }
47
48                     // When trait objects or opaque types have lifetime or auto-trait bounds,
49                     // we need to add parentheses to avoid a syntax error due to its ambiguity.
50                     // Originally reported as the issue #3128.
51                     let inner_snippet = snippet(cx, inner.span, "..");
52                     let suggestion = match &inner.kind {
53                         TyKind::TraitObject(bounds, lt_bound, _) if bounds.len() > 1 || !lt_bound.is_elided() => {
54                             format!("&{}({})", ltopt, &inner_snippet)
55                         },
56                         TyKind::Path(qpath)
57                             if get_bounds_if_impl_trait(cx, qpath, inner.hir_id)
58                                 .map_or(false, |bounds| bounds.len() > 1) =>
59                         {
60                             format!("&{}({})", ltopt, &inner_snippet)
61                         },
62                         _ => format!("&{}{}", ltopt, &inner_snippet),
63                     };
64                     span_lint_and_sugg(
65                         cx,
66                         BORROWED_BOX,
67                         hir_ty.span,
68                         "you seem to be trying to use `&Box<T>`. Consider using just `&T`",
69                         "try",
70                         suggestion,
71                         // To make this `MachineApplicable`, at least one needs to check if it isn't a trait item
72                         // because the trait impls of it will break otherwise;
73                         // and there may be other cases that result in invalid code.
74                         // For example, type coercion doesn't work nicely.
75                         Applicability::Unspecified,
76                     );
77                     return true;
78                 }
79             };
80             false
81         },
82         _ => false,
83     }
84 }
85
86 // Returns true if given type is `Any` trait.
87 fn is_any_trait(t: &hir::Ty<'_>) -> bool {
88     if_chain! {
89         if let TyKind::TraitObject(ref traits, ..) = t.kind;
90         if !traits.is_empty();
91         // Only Send/Sync can be used as additional traits, so it is enough to
92         // check only the first trait.
93         if match_path(&traits[0].trait_ref.path, &paths::ANY_TRAIT);
94         then {
95             return true;
96         }
97     }
98
99     false
100 }
101
102 fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> {
103     if_chain! {
104         if let Some(did) = cx.qpath_res(qpath, id).opt_def_id();
105         if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did);
106         if let GenericParamKind::Type { synthetic, .. } = generic_param.kind;
107         if synthetic == Some(SyntheticTyParamKind::ImplTrait);
108         then {
109             Some(generic_param.bounds)
110         } else {
111             None
112         }
113     }
114 }