rustc_lint/
pass_by_value.rs

1use rustc_attr_data_structures::{AttributeKind, find_attr};
2use rustc_hir::def::Res;
3use rustc_hir::{self as hir, AmbigArg, GenericArg, PathSegment, QPath, TyKind};
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 let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
28                    if cx.tcx.impl_trait_ref(impl_did).is_some() {
29                        return;
30                    }
31                }
32                if let Some(t) = path_for_pass_by_value(cx, inner_ty) {
33                    cx.emit_span_lint(
34                        PASS_BY_VALUE,
35                        ty.span,
36                        PassByValueDiag { ty: t, suggestion: ty.span },
37                    );
38                }
39            }
40            _ => {}
41        }
42    }
43}
44
45fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<String> {
46    if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
47        match path.res {
48            Res::Def(_, def_id)
49                if find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::PassByValue(_)) =>
50            {
51                let name = cx.tcx.item_ident(def_id);
52                let path_segment = path.segments.last().unwrap();
53                return Some(format!("{}{}", name, gen_args(cx, path_segment)));
54            }
55            Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
56                if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() {
57                    if find_attr!(cx.tcx.get_all_attrs(adt.did()), AttributeKind::PassByValue(_)) {
58                        return Some(cx.tcx.def_path_str_with_args(adt.did(), args));
59                    }
60                }
61            }
62            _ => (),
63        }
64    }
65
66    None
67}
68
69fn gen_args(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> String {
70    if let Some(args) = &segment.args {
71        let params = args
72            .args
73            .iter()
74            .map(|arg| match arg {
75                GenericArg::Lifetime(lt) => lt.to_string(),
76                GenericArg::Type(ty) => {
77                    cx.tcx.sess.source_map().span_to_snippet(ty.span).unwrap_or_else(|_| "_".into())
78                }
79                GenericArg::Const(c) => cx
80                    .tcx
81                    .sess
82                    .source_map()
83                    .span_to_snippet(c.span())
84                    .unwrap_or_else(|_| "_".into()),
85                GenericArg::Infer(_) => String::from("_"),
86            })
87            .collect::<Vec<_>>();
88
89        if !params.is_empty() {
90            return format!("<{}>", params.join(", "));
91        }
92    }
93
94    String::new()
95}