rustc_lint/
pass_by_value.rs

1use rustc_hir::def::Res;
2use rustc_hir::{self as hir, AmbigArg, GenericArg, PathSegment, QPath, TyKind};
3use rustc_middle::ty;
4use rustc_session::{declare_lint_pass, declare_tool_lint};
5use rustc_span::sym;
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) if cx.tcx.has_attr(def_id, sym::rustc_pass_by_value) => {
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 cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) {
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}