Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7039,6 +7039,7 @@ Released 2018-09-13
[`iter_filter_is_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_ok
[`iter_filter_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_some
[`iter_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map
[`iter_missing_exact_size`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_missing_exact_size
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::item_name_repetitions::STRUCT_FIELD_NAMES_INFO,
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
crate::iter_missing_exact_size::ITER_MISSING_EXACT_SIZE_INFO,
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
crate::iter_over_hash_type::ITER_OVER_HASH_TYPE_INFO,
crate::iter_without_into_iter::INTO_ITER_WITHOUT_ITER_INFO,
Expand Down
221 changes: 221 additions & 0 deletions clippy_lints/src/iter_missing_exact_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::paths::{PathNS, lookup_path_str};
use clippy_utils::sym;
use clippy_utils::ty::{get_field_by_name, implements_trait, ty_from_hir_ty};
use rustc_hir::{Body, Expr, ExprKind, Impl, ImplItemKind, Item, ItemKind, OwnerId, OwnerNode, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::kw;

declare_clippy_lint! {
/// ### What it does
///
/// Suggest that iterators be marked as `ExactSizeIterator` when they wrap
/// around another iterator that *does* implement `ExactSizeIterator`.
///
/// ### Why is this bad?
///
/// When the size of an iterator is based on some other iterator that is
/// known to have an exact size, the wrapping iterator also has an exact
/// size and should be marked as such.
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated
///
/// ### Example
/// ```no_run
/// struct StringRepeater {
/// original: String,
/// range: std::ops::Range<usize>,
/// }
///
/// impl Iterator for StringRepeater {
/// type Item = String;
/// fn next(&mut self) -> Option<Self::Item> {
/// self.range.next().map(|i| self.original.repeat(i) )
/// }
/// fn size_hint(&self) -> (usize, Option<usize>) {
/// self.range.size_hint()
/// }
/// }
///
/// let repeater = StringRepeater { original: "Foo".to_string(), range: 1..5 };
/// for value in repeater {
/// println!("{value}");
/// }
///
/// ```
/// Use instead:
///
/// ```no_run
/// struct StringRepeater {
/// original: String,
/// range: std::ops::Range<usize>,
/// }
///
/// impl Iterator for StringRepeater {
/// type Item = String;
/// fn next(&mut self) -> Option<Self::Item> {
/// self.range.next().map(|i| self.original.repeat(i) )
/// }
/// fn size_hint(&self) -> (usize, Option<usize>) {
/// self.range.size_hint()
/// }
/// }
///
/// impl ExactSizeIterator for StringRepeater {}
///
/// let repeater = StringRepeater { original: "Foo".to_string(), range: 1..5 };
/// for value in repeater {
/// println!("{value}");
/// }
/// ```
#[clippy::version = "1.99.0"]
pub ITER_MISSING_EXACT_SIZE,
pedantic,
"iterator delegates to an ExactSizeIterator for its size hint but does not itself implement ExactSizeIterator"
}

impl_lint_pass!(IterMissingExactSize => [ITER_MISSING_EXACT_SIZE]);

pub struct IterMissingExactSize {
exact_size_lookup: Option<Vec<DefId>>,
}
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated
impl IterMissingExactSize {
pub fn new() -> Self {
IterMissingExactSize {
exact_size_lookup: None,
}
}

fn get_exact_size_lookup_defs(&mut self, cx: &LateContext<'_>) -> &[DefId] {
// ExactSizeIterator doesn't have a diagnostic item, or even a symobl
// that we can use in paths.rs to add a static type path. Lookup up the
// paths for the DefId the first time needed
if self.exact_size_lookup.is_none() {
self.exact_size_lookup = Some(lookup_path_str(cx.tcx, PathNS::Type, "core::iter::ExactSizeIterator"));
}
self.exact_size_lookup.as_ref().unwrap()
}
}
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated

/// Given an `OwnerId` for an item that is `impl Iterator for {type}`, try to
/// locate the `size_hint()` function being defined in that block.
fn size_hint_body<'tcx>(cx: &LateContext<'tcx>, owner_id: OwnerId) -> Option<&'tcx Body<'tcx>> {
let size_hint_fn = cx
.tcx
.associated_items(owner_id)
.in_definition_order()
.find(|assoc_item| {
assoc_item.expect_trait_impl().is_ok() && assoc_item.is_method() && assoc_item.name() == sym::size_hint
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated
})?;

let node = cx.tcx.expect_hir_owner_node(size_hint_fn.def_id.expect_local());
let OwnerNode::ImplItem(impl_item) = node else {
return None;
};
let ImplItemKind::Fn(_, body_id) = impl_item.kind else {
return None;
};
Some(cx.tcx.hir_body(body_id))
}

/// Given a `Body` for the `size_hint()` function, try to get the return
/// expression, either
/// - a trailing expression when the block has no statements
/// - the singular `return` statement in a block with exactly one statement (the return) (and
/// optionally a dead-code trailing expression that we can ignore)
fn size_hint_return<'tcx>(body: &'tcx Body<'tcx>) -> Option<&'tcx Expr<'tcx>> {
let ExprKind::Block(block, None) = body.value.kind else {
// Function body isn't a block?
return None;
};
// Block without statements - either it has a trailing expression (and we
// return that) or it doesn't (and we return none)
if block.stmts.is_empty() {
return block.expr;
}
if let [only_statement] = block.stmts
&& let StmtKind::Semi(statement_semi) = only_statement.kind
&& let ExprKind::Ret(returned_value) = statement_semi.kind
{
returned_value
} else {
None
}
}

impl<'tcx> LateLintPass<'tcx> for IterMissingExactSize {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
// Check for this item being an implementation of the iterator trait:
// 1) is it implementing a trait?
let ItemKind::Impl(Impl {
of_trait: Some(of_trait),
self_ty: current_type,
..
}) = item.kind
else {
return;
};
// 2) can we find the trait definition id?
let Some(trait_id) = of_trait.trait_ref.trait_def_id() else {
return;
};
// 3) is it the iterator trait?
if !cx.tcx.is_diagnostic_item(sym::Iterator, trait_id) {
return;
}

// We know that this item is an `impl Iterator for _` block; find the
// size_hint() function (if present)
let Some(size_hint_body) = size_hint_body(cx, item.owner_id) else {
return;
};
// We found the function body for size_hint()!
let Some(size_hint_return) = size_hint_return(size_hint_body) else {
return;
};
if let ExprKind::MethodCall(method_name, receiver, args, _) = size_hint_return.kind
&& method_name.ident.name == sym::size_hint
&& let ExprKind::Field(object, field_name) = receiver.kind
&& let ExprKind::Path(QPath::Resolved(_, object_path)) = object.kind
&& let [path_segment] = object_path.segments
&& path_segment.ident.name == kw::SelfLower
&& args.is_empty()
{
// The function body is just `self.{field}.size_hint()`, check
// for the type of the field
let current_middle_ty = ty_from_hir_ty(cx, current_type);
let field = get_field_by_name(cx.tcx, current_middle_ty, field_name.name);
let Some(field) = field else {
return;
};
// Does that type implement ExactSizeIterator ?
let exact_traits = self.get_exact_size_lookup_defs(cx);
let mut found = false;
for exact_trait_def in exact_traits {
if implements_trait(cx, field, *exact_trait_def, &[]) {
found = true;
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated
break;
}
}
if !found {
return;
}
// Delegates size hint to a field that implements ExactSizeIterator
// so this iterator should do so too
for exact_trait_def in exact_traits {
if implements_trait(cx, current_middle_ty, *exact_trait_def, &[]) {
// This iterator type already implements ExactSizeIterator
return;
}
}
Comment thread
DanielEScherzer marked this conversation as resolved.
Outdated
span_lint_and_help(
cx,
ITER_MISSING_EXACT_SIZE,
item.span,
"iterator can implement `ExactSizeIterator`",
Some(size_hint_return.span),
"this `size_hint()` implementation delegates to to the `size_hint()` of an `ExactSizeIterator`, so the overall iterator is likely to have an exact size",
);
}
}
}
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ mod int_plus_one;
mod item_name_repetitions;
mod items_after_statements;
mod items_after_test_module;
mod iter_missing_exact_size;
mod iter_not_returning_iterator;
mod iter_over_hash_type;
mod iter_without_into_iter;
Expand Down Expand Up @@ -868,6 +869,7 @@ rustc_lint::late_lint_methods!(
RedundantElse: redundant_else::RedundantElse = redundant_else::RedundantElse,
RestWhenDestructuringStruct: rest_when_destructuring_struct::RestWhenDestructuringStruct = rest_when_destructuring_struct::RestWhenDestructuringStruct,
BlockScrutinee: block_scrutinee::BlockScrutinee = block_scrutinee::BlockScrutinee,
IterMissingExactSize: iter_missing_exact_size::IterMissingExactSize = iter_missing_exact_size::IterMissingExactSize::new(),
// add late passes here, used by `cargo dev new_lint`
]]
);
1 change: 1 addition & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ generate! {
set_readonly,
signum,
single_component_path_imports,
size_hint,
skip,
skip_while,
slice_from_ref,
Expand Down
Loading