summary refs log tree commit diff
path: root/src/librustc_lint/array_into_iter.rs
blob: e73414174fb3524002bedbaee0d342660c26ff46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use crate::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
use rustc::{
    lint::FutureIncompatibleInfo,
    hir,
    ty::{
        self,
        adjustment::{Adjust, Adjustment},
    },
};
use syntax::{
    errors::Applicability,
    symbol::sym,
};


declare_lint! {
    pub ARRAY_INTO_ITER,
    Warn,
    "detects calling `into_iter` on arrays",
    @future_incompatible = FutureIncompatibleInfo {
        reference: "issue #66145 <https://github.com/rust-lang/rust/issues/66145>",
        edition: None,
    };
}

declare_lint_pass!(
    /// Checks for instances of calling `into_iter` on arrays.
    ArrayIntoIter => [ARRAY_INTO_ITER]
);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ArrayIntoIter {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
        // We only care about method call expressions.
        if let hir::ExprKind::MethodCall(call, span, args) = &expr.kind {
            if call.ident.name != sym::into_iter {
                return;
            }

            // Check if the method call actually calls the libcore
            // `IntoIterator::into_iter`.
            let def_id = cx.tables.type_dependent_def_id(expr.hir_id).unwrap();
            match cx.tcx.trait_of_item(def_id) {
                Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {},
                _ => return,
            };

            // As this is a method call expression, we have at least one
            // argument.
            let receiver_arg = &args[0];

            // Test if the original `self` type is an array type.
            match cx.tables.expr_ty(receiver_arg).kind {
                ty::Array(..) => {}
                _ => return,
            }

            // Make sure that the first adjustment is an autoref coercion.
            match cx.tables.expr_adjustments(receiver_arg).get(0) {
                Some(Adjustment { kind: Adjust::Borrow(_), .. }) => {}
                _ => return,
            }

            // Emit lint diagnostic.
            let target = match cx.tables.expr_ty_adjusted(receiver_arg).kind {
                ty::Ref(_, ty::TyS { kind: ty::Array(..), ..}, _) => "[T; N]",
                ty::Ref(_, ty::TyS { kind: ty::Slice(..), ..}, _) => "[T]",

                // We know the original first argument type is an array type,
                // we know that the first adjustment was an autoref coercion
                // and we know that `IntoIterator` is the trait involved. The
                // array cannot be coerced to something other than a reference
                // to an array or to a slice.
                _ => bug!("array type coerced to something other than array or slice"),
            };
            let msg = format!(
                "this method call currently resolves to `<&{} as IntoIterator>::into_iter` (due \
                    to autoref coercions), but that might change in the future when \
                    `IntoIterator` impls for arrays are added.",
                target,
            );
            cx.struct_span_lint(ARRAY_INTO_ITER, *span, &msg)
                .span_suggestion(
                    call.ident.span,
                    "use `.iter()` instead of `.into_iter()` to avoid ambiguity",
                    "iter".into(),
                    Applicability::MachineApplicable,
                )
                .emit();
        }
    }
}