Implement BOXED_SLICE_INTO_ITER
This commit is contained in:
parent
1a81092531
commit
a502e7ac1d
22 changed files with 446 additions and 173 deletions
|
@ -1,144 +0,0 @@
|
|||
use crate::{
|
||||
lints::{ArrayIntoIterDiag, ArrayIntoIterDiagSub},
|
||||
LateContext, LateLintPass, LintContext,
|
||||
};
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
|
||||
use rustc_session::lint::FutureIncompatibilityReason;
|
||||
use rustc_session::{declare_lint, impl_lint_pass};
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_lint! {
|
||||
/// The `array_into_iter` lint detects calling `into_iter` on arrays.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,edition2018
|
||||
/// # #![allow(unused)]
|
||||
/// [1, 2, 3].into_iter().for_each(|n| { *n; });
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
|
||||
/// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
|
||||
/// behave as `(&array).into_iter()`, returning an iterator over
|
||||
/// references, just like in Rust 1.52 and earlier.
|
||||
/// This only applies to the method call syntax `array.into_iter()`, not to
|
||||
/// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
|
||||
pub ARRAY_INTO_ITER,
|
||||
Warn,
|
||||
"detects calling `into_iter` on arrays in Rust 2015 and 2018",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
|
||||
reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct ArrayIntoIter {
|
||||
for_expr_span: Span,
|
||||
}
|
||||
|
||||
impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
|
||||
// Save the span of expressions in `for _ in expr` syntax,
|
||||
// so we can give a better suggestion for those later.
|
||||
if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind {
|
||||
if let hir::ExprKind::Call(path, [arg]) = &arg.kind {
|
||||
if let hir::ExprKind::Path(hir::QPath::LangItem(
|
||||
hir::LangItem::IntoIterIntoIter,
|
||||
..,
|
||||
)) = &path.kind
|
||||
{
|
||||
self.for_expr_span = arg.span;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only care about method call expressions.
|
||||
if let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &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.typeck_results().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_ty = cx.typeck_results().expr_ty(receiver_arg);
|
||||
let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
|
||||
|
||||
let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let types =
|
||||
std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target));
|
||||
|
||||
let mut found_array = false;
|
||||
|
||||
for ty in types {
|
||||
match ty.kind() {
|
||||
// If we run into a &[T; N] or &[T] first, there's nothing to warn about.
|
||||
// It'll resolve to the reference version.
|
||||
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return,
|
||||
ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return,
|
||||
// Found an actual array type without matching a &[T; N] first.
|
||||
// This is the problematic case.
|
||||
ty::Array(..) => {
|
||||
found_array = true;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !found_array {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit lint diagnostic.
|
||||
let target = match *target.kind() {
|
||||
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]",
|
||||
ty::Ref(_, inner_ty, _) if matches!(inner_ty.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 sub = if self.for_expr_span == expr.span {
|
||||
Some(ArrayIntoIterDiagSub::RemoveIntoIter {
|
||||
span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
|
||||
})
|
||||
} else if receiver_ty.is_array() {
|
||||
Some(ArrayIntoIterDiagSub::UseExplicitIntoIter {
|
||||
start_span: expr.span.shrink_to_lo(),
|
||||
end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
cx.emit_span_lint(
|
||||
ARRAY_INTO_ITER,
|
||||
call.ident.span,
|
||||
ArrayIntoIterDiag { target, suggestion: call.ident.span, sub },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,6 @@
|
|||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
mod array_into_iter;
|
||||
mod async_fn_in_trait;
|
||||
pub mod builtin;
|
||||
mod context;
|
||||
|
@ -76,18 +75,18 @@ mod passes;
|
|||
mod ptr_nulls;
|
||||
mod redundant_semicolon;
|
||||
mod reference_casting;
|
||||
mod shadowed_into_iter;
|
||||
mod traits;
|
||||
mod types;
|
||||
mod unit_bindings;
|
||||
mod unused;
|
||||
|
||||
pub use array_into_iter::ARRAY_INTO_ITER;
|
||||
pub use shadowed_into_iter::ARRAY_INTO_ITER;
|
||||
|
||||
use rustc_hir::def_id::LocalModDefId;
|
||||
use rustc_middle::query::Providers;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use array_into_iter::ArrayIntoIter;
|
||||
use async_fn_in_trait::AsyncFnInTrait;
|
||||
use builtin::*;
|
||||
use deref_into_dyn_supertrait::*;
|
||||
|
@ -112,6 +111,7 @@ use pass_by_value::*;
|
|||
use ptr_nulls::*;
|
||||
use redundant_semicolon::*;
|
||||
use reference_casting::*;
|
||||
use shadowed_into_iter::ShadowedIntoIter;
|
||||
use traits::*;
|
||||
use types::*;
|
||||
use unit_bindings::*;
|
||||
|
@ -215,7 +215,7 @@ late_lint_methods!(
|
|||
DerefNullPtr: DerefNullPtr,
|
||||
UnstableFeatures: UnstableFeatures,
|
||||
UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller,
|
||||
ArrayIntoIter: ArrayIntoIter::default(),
|
||||
ShadowedIntoIter: ShadowedIntoIter,
|
||||
DropTraitConstraints: DropTraitConstraints,
|
||||
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
|
||||
NonPanicFmt: NonPanicFmt,
|
||||
|
|
|
@ -22,17 +22,18 @@ use crate::{
|
|||
|
||||
// array_into_iter.rs
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_array_into_iter)]
|
||||
pub struct ArrayIntoIterDiag<'a> {
|
||||
pub target: &'a str,
|
||||
#[diag(lint_shadowed_into_iter)]
|
||||
pub struct ShadowedIntoIterDiag {
|
||||
pub target: &'static str,
|
||||
pub edition: &'static str,
|
||||
#[suggestion(lint_use_iter_suggestion, code = "iter", applicability = "machine-applicable")]
|
||||
pub suggestion: Span,
|
||||
#[subdiagnostic]
|
||||
pub sub: Option<ArrayIntoIterDiagSub>,
|
||||
pub sub: Option<ShadowedIntoIterDiagSub>,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
pub enum ArrayIntoIterDiagSub {
|
||||
pub enum ShadowedIntoIterDiagSub {
|
||||
#[suggestion(lint_remove_into_iter_suggestion, code = "", applicability = "maybe-incorrect")]
|
||||
RemoveIntoIter {
|
||||
#[primary_span]
|
||||
|
|
157
compiler/rustc_lint/src/shadowed_into_iter.rs
Normal file
157
compiler/rustc_lint/src/shadowed_into_iter.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub};
|
||||
use crate::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::lint::FutureIncompatibilityReason;
|
||||
use rustc_session::{declare_lint, impl_lint_pass};
|
||||
use rustc_span::edition::Edition;
|
||||
|
||||
declare_lint! {
|
||||
/// The `array_into_iter` lint detects calling `into_iter` on arrays.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,edition2018
|
||||
/// # #![allow(unused)]
|
||||
/// [1, 2, 3].into_iter().for_each(|n| { *n; });
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
|
||||
/// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
|
||||
/// behave as `(&array).into_iter()`, returning an iterator over
|
||||
/// references, just like in Rust 1.52 and earlier.
|
||||
/// This only applies to the method call syntax `array.into_iter()`, not to
|
||||
/// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
|
||||
pub ARRAY_INTO_ITER,
|
||||
Warn,
|
||||
"detects calling `into_iter` on arrays in Rust 2015 and 2018",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
|
||||
reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
|
||||
};
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,edition2021
|
||||
/// # #![allow(unused)]
|
||||
/// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Since Rust CURRENT_RUSTC_VERSION, boxed slices implement `IntoIterator`. However, to avoid
|
||||
/// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
|
||||
/// behave as `(&boxed_slice).into_iter()`, returning an iterator over
|
||||
/// references, just like in Rust CURRENT_RUSTC_VERSION and earlier.
|
||||
/// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
|
||||
/// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
|
||||
pub BOXED_SLICE_INTO_ITER,
|
||||
Warn,
|
||||
"detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ShadowedIntoIter;
|
||||
|
||||
impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
|
||||
let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check if the method call actually calls the libcore
|
||||
// `IntoIterator::into_iter`.
|
||||
let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
|
||||
return;
|
||||
};
|
||||
if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() {
|
||||
return;
|
||||
}
|
||||
|
||||
// As this is a method call expression, we have at least one argument.
|
||||
let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
|
||||
let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
|
||||
|
||||
let adjusted_receiver_tys: Vec<_> =
|
||||
[receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect();
|
||||
|
||||
fn is_ref_to_array(ty: Ty<'_>) -> bool {
|
||||
if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false }
|
||||
}
|
||||
fn is_boxed_slice(ty: Ty<'_>) -> bool {
|
||||
ty.is_box() && ty.boxed_ty().is_slice()
|
||||
}
|
||||
fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool {
|
||||
if let ty::Ref(_, pointee_ty, _) = *ty.kind() {
|
||||
is_boxed_slice(pointee_ty)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let (lint, target, edition, can_suggest_ufcs) =
|
||||
if is_ref_to_array(*adjusted_receiver_tys.last().unwrap())
|
||||
&& let Some(idx) = adjusted_receiver_tys
|
||||
.iter()
|
||||
.copied()
|
||||
.take_while(|ty| !is_ref_to_array(*ty))
|
||||
.position(|ty| ty.is_array())
|
||||
{
|
||||
(ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0)
|
||||
} else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap())
|
||||
&& let Some(idx) = adjusted_receiver_tys
|
||||
.iter()
|
||||
.copied()
|
||||
.take_while(|ty| !is_ref_to_boxed_slice(*ty))
|
||||
.position(|ty| is_boxed_slice(ty))
|
||||
{
|
||||
(BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If this expression comes from the `IntoIter::into_iter` inside of a for loop,
|
||||
// we should just suggest removing the `.into_iter()` or changing it to `.iter()`
|
||||
// to disambiguate if we want to iterate by-value or by-ref.
|
||||
let sub = if let Some((_, hir::Node::Expr(parent_expr))) =
|
||||
cx.tcx.hir().parent_iter(expr.hir_id).nth(1)
|
||||
&& let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) =
|
||||
&parent_expr.kind
|
||||
&& let hir::ExprKind::Call(path, [_]) = &arg.kind
|
||||
&& let hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoIterIntoIter, ..)) =
|
||||
&path.kind
|
||||
{
|
||||
Some(ShadowedIntoIterDiagSub::RemoveIntoIter {
|
||||
span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
|
||||
})
|
||||
} else if can_suggest_ufcs {
|
||||
Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter {
|
||||
start_span: expr.span.shrink_to_lo(),
|
||||
end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.emit_span_lint(
|
||||
lint,
|
||||
call.ident.span,
|
||||
ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub },
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue