rustc_lint/
pass_by_value.rs

1use rustc_hir::attrs::AttributeKind;
2use rustc_hir::def::Res;
3use rustc_hir::{self as hir, AmbigArg, GenericArg, PathSegment, QPath, TyKind, find_attr};
4use rustc_middle::ty;
5use rustc_session::{declare_lint_pass, declare_tool_lint};
6
7use crate::lints::PassByValueDiag;
8use crate::{LateContext, LateLintPass, LintContext};
9
10declare_tool_lint! {
11    /// The `rustc_pass_by_value` lint marks a type with `#[rustc_pass_by_value]` requiring it to
12    /// always be passed by value. This is usually used for types that are thin wrappers around
13    /// references, so there is no benefit to an extra layer of indirection. (Example: `Ty` which
14    /// is a reference to an `Interned<TyKind>`)
15    pub rustc::PASS_BY_VALUE,
16    Warn,
17    "pass by reference of a type flagged as `#[rustc_pass_by_value]`",
18    report_in_external_macro: true
19}
20
21declare_lint_pass!(PassByValue => [PASS_BY_VALUE]);
22
23impl<'tcx> LateLintPass<'tcx> for PassByValue {
24    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx, AmbigArg>) {
25        match &ty.kind {
26            TyKind::Ref(_, hir::MutTy { ty: inner_ty, mutbl: hir::Mutability::Not }) => {
27                if cx.tcx.trait_impl_of_assoc(ty.hir_id.owner.to_def_id()).is_some() {
28                    return;
29                }
30                if let Some(t) = path_for_pass_by_value(cx, inner_ty) {
31                    cx.emit_span_lint(
32                        PASS_BY_VALUE,
33                        ty.span,
34                        PassByValueDiag { ty: t, suggestion: ty.span },
35                    );
36                }
37            }
38            _ => {}
39        }
40    }
41}
42
43fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<String> {
44    if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
45        match path.res {
46            Res::Def(_, def_id)
47                if find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::PassByValue(_)) =>
48            {
49                let name = cx.tcx.item_ident(def_id);
50                let path_segment = path.segments.last().unwrap();
51                return Some(format!("{}{}", name, gen_args(cx, path_segment)));
52            }
53            Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
54                if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() {
55                    if find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::PassByValue(_)) {
56                        return Some(cx.tcx.def_path_str_with_args(adt.did(), args));
57                    }
58                }
59            }
60            _ => (),
61        }
62    }
63
64    None
65}
66
67fn gen_args(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> String {
68    if let Some(args) = &segment.args {
69        let params = args
70            .args
71            .iter()
72            .map(|arg| match arg {
73                GenericArg::Lifetime(lt) => lt.to_string(),
74                GenericArg::Type(ty) => {
75                    cx.tcx.sess.source_map().span_to_snippet(ty.span).unwrap_or_else(|_| "_".into())
76                }
77                GenericArg::Const(c) => cx
78                    .tcx
79                    .sess
80                    .source_map()
81                    .span_to_snippet(c.span())
82                    .unwrap_or_else(|_| "_".into()),
83                GenericArg::Infer(_) => String::from("_"),
84            })
85            .collect::<Vec<_>>();
86
87        if !params.is_empty() {
88            return format!("<{}>", params.join(", "));
89        }
90    }
91
92    String::new()
93}