From f77f1db35b1f263286962cfa5e5c08a55add83cb Mon Sep 17 00:00:00 2001 From: Korrat Date: Sat, 24 Oct 2020 16:48:10 +0200 Subject: [PATCH] Add a lint for maps with zero-sized values Co-authored-by: Eduardo Broto --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 4 + clippy_lints/src/zero_sized_map_values.rs | 82 ++++++++++++++++ tests/ui/zero_sized_btreemap_values.rs | 68 +++++++++++++ tests/ui/zero_sized_btreemap_values.stderr | 107 +++++++++++++++++++++ tests/ui/zero_sized_hashmap_values.rs | 68 +++++++++++++ tests/ui/zero_sized_hashmap_values.stderr | 107 +++++++++++++++++++++ 7 files changed, 437 insertions(+) create mode 100644 clippy_lints/src/zero_sized_map_values.rs create mode 100644 tests/ui/zero_sized_btreemap_values.rs create mode 100644 tests/ui/zero_sized_btreemap_values.stderr create mode 100644 tests/ui/zero_sized_hashmap_values.rs create mode 100644 tests/ui/zero_sized_hashmap_values.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f2ad7ec4e8..af3b1c1db2ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2171,5 +2171,6 @@ Released 2018-09-13 [`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero [`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal [`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr +[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values [`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a92ae9ed8d93..5fca088c9b40 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -345,6 +345,7 @@ mod wildcard_dependencies; mod wildcard_imports; mod write; mod zero_div_zero; +mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` pub use crate::utils::conf::Conf; @@ -944,6 +945,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &write::WRITE_LITERAL, &write::WRITE_WITH_NEWLINE, &zero_div_zero::ZERO_DIVIDED_BY_ZERO, + &zero_sized_map_values::ZERO_SIZED_MAP_VALUES, ]); // end register lints, do not remove this comment, it’s used in `update_lints` @@ -1204,6 +1206,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops); store.register_late_pass(|| box strings::StrToString); store.register_late_pass(|| box strings::StringToString); + store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1336,6 +1339,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unused_self::UNUSED_SELF), LintId::of(&wildcard_imports::ENUM_GLOB_USE), LintId::of(&wildcard_imports::WILDCARD_IMPORTS), + LintId::of(&zero_sized_map_values::ZERO_SIZED_MAP_VALUES), ]); #[cfg(feature = "internal-lints")] diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs new file mode 100644 index 000000000000..1d5fa8d06a99 --- /dev/null +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -0,0 +1,82 @@ +use if_chain::if_chain; +use rustc_hir::{self as hir, HirId, ItemKind, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{Adt, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_target::abi::LayoutOf as _; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{is_type_diagnostic_item, match_type, paths, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for maps with zero-sized value types anywhere in the code. + /// + /// **Why is this bad?** Since there is only a single value for a zero-sized type, a map + /// containing zero sized values is effectively a set. Using a set in that case improves + /// readability and communicates intent more clearly. + /// + /// **Known problems:** + /// * A zero-sized type cannot be recovered later if it contains private fields. + /// * This lints the signature of public items + /// + /// **Example:** + /// + /// ```rust + /// # use std::collections::HashMap; + /// fn unique_words(text: &str) -> HashMap<&str, ()> { + /// todo!(); + /// } + /// ``` + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; + /// fn unique_words(text: &str) -> HashSet<&str> { + /// todo!(); + /// } + /// ``` + pub ZERO_SIZED_MAP_VALUES, + pedantic, + "usage of map with zero-sized value type" +} + +declare_lint_pass!(ZeroSizedMapValues => [ZERO_SIZED_MAP_VALUES]); + +impl LateLintPass<'_> for ZeroSizedMapValues { + fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) { + if_chain! { + if !hir_ty.span.from_expansion(); + if !in_trait_impl(cx, hir_ty.hir_id); + let ty = ty_from_hir_ty(cx, hir_ty); + if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP); + if let Adt(_, ref substs) = ty.kind(); + let ty = substs.type_at(1); + if let Ok(layout) = cx.layout_of(ty); + if layout.is_zst(); + then { + span_lint_and_help(cx, ZERO_SIZED_MAP_VALUES, hir_ty.span, "map with zero-sized value type", None, "consider using a set instead"); + } + } + } +} + +fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool { + let parent_id = cx.tcx.hir().get_parent_item(hir_id); + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(parent_id)) { + if let ItemKind::Impl { of_trait: Some(_), .. } = item.kind { + return true; + } + } + false +} + +fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Ty<'tcx> { + cx.maybe_typeck_results() + .and_then(|results| { + if results.hir_owner == hir_ty.hir_id.owner { + results.node_type_opt(hir_ty.hir_id) + } else { + None + } + }) + .unwrap_or_else(|| hir_ty_to_ty(cx.tcx, hir_ty)) +} diff --git a/tests/ui/zero_sized_btreemap_values.rs b/tests/ui/zero_sized_btreemap_values.rs new file mode 100644 index 000000000000..5cd254787d83 --- /dev/null +++ b/tests/ui/zero_sized_btreemap_values.rs @@ -0,0 +1,68 @@ +#![warn(clippy::zero_sized_map_values)] +use std::collections::BTreeMap; + +const CONST_OK: Option> = None; +const CONST_NOT_OK: Option> = None; + +static STATIC_OK: Option> = None; +static STATIC_NOT_OK: Option> = None; + +type OkMap = BTreeMap; +type NotOkMap = BTreeMap; + +enum TestEnum { + Ok(BTreeMap), + NotOk(BTreeMap), +} + +struct Test { + ok: BTreeMap, + not_ok: BTreeMap, + + also_not_ok: Vec>, +} + +trait TestTrait { + type Output; + + fn produce_output() -> Self::Output; + + fn weird_map(&self, map: BTreeMap); +} + +impl Test { + fn ok(&self) -> BTreeMap { + todo!() + } + + fn not_ok(&self) -> BTreeMap { + todo!() + } +} + +impl TestTrait for Test { + type Output = BTreeMap; + + fn produce_output() -> Self::Output { + todo!(); + } + + fn weird_map(&self, map: BTreeMap) { + todo!(); + } +} + +fn test(map: BTreeMap, key: &str) -> BTreeMap { + todo!(); +} + +fn test2(map: BTreeMap, key: &str) -> BTreeMap { + todo!(); +} + +fn main() { + let _: BTreeMap = BTreeMap::new(); + let _: BTreeMap = BTreeMap::new(); + + let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect(); +} diff --git a/tests/ui/zero_sized_btreemap_values.stderr b/tests/ui/zero_sized_btreemap_values.stderr new file mode 100644 index 000000000000..334d921a9af3 --- /dev/null +++ b/tests/ui/zero_sized_btreemap_values.stderr @@ -0,0 +1,107 @@ +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:5:28 + | +LL | const CONST_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::zero-sized-map-values` implied by `-D warnings` + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:8:30 + | +LL | static STATIC_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:11:17 + | +LL | type NotOkMap = BTreeMap; + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:15:11 + | +LL | NotOk(BTreeMap), + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:20:13 + | +LL | not_ok: BTreeMap, + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:22:22 + | +LL | also_not_ok: Vec>, + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:30:30 + | +LL | fn weird_map(&self, map: BTreeMap); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:38:25 + | +LL | fn not_ok(&self) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:55:14 + | +LL | fn test(map: BTreeMap, key: &str) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:55:50 + | +LL | fn test(map: BTreeMap, key: &str) -> BTreeMap { + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:64:35 + | +LL | let _: BTreeMap = BTreeMap::new(); + | ^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:64:12 + | +LL | let _: BTreeMap = BTreeMap::new(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_btreemap_values.rs:67:12 + | +LL | let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect(); + | ^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: aborting due to 13 previous errors + diff --git a/tests/ui/zero_sized_hashmap_values.rs b/tests/ui/zero_sized_hashmap_values.rs new file mode 100644 index 000000000000..a1608d863fb5 --- /dev/null +++ b/tests/ui/zero_sized_hashmap_values.rs @@ -0,0 +1,68 @@ +#![warn(clippy::zero_sized_map_values)] +use std::collections::HashMap; + +const CONST_OK: Option> = None; +const CONST_NOT_OK: Option> = None; + +static STATIC_OK: Option> = None; +static STATIC_NOT_OK: Option> = None; + +type OkMap = HashMap; +type NotOkMap = HashMap; + +enum TestEnum { + Ok(HashMap), + NotOk(HashMap), +} + +struct Test { + ok: HashMap, + not_ok: HashMap, + + also_not_ok: Vec>, +} + +trait TestTrait { + type Output; + + fn produce_output() -> Self::Output; + + fn weird_map(&self, map: HashMap); +} + +impl Test { + fn ok(&self) -> HashMap { + todo!() + } + + fn not_ok(&self) -> HashMap { + todo!() + } +} + +impl TestTrait for Test { + type Output = HashMap; + + fn produce_output() -> Self::Output { + todo!(); + } + + fn weird_map(&self, map: HashMap) { + todo!(); + } +} + +fn test(map: HashMap, key: &str) -> HashMap { + todo!(); +} + +fn test2(map: HashMap, key: &str) -> HashMap { + todo!(); +} + +fn main() { + let _: HashMap = HashMap::new(); + let _: HashMap = HashMap::new(); + + let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect(); +} diff --git a/tests/ui/zero_sized_hashmap_values.stderr b/tests/ui/zero_sized_hashmap_values.stderr new file mode 100644 index 000000000000..43987b3d01d1 --- /dev/null +++ b/tests/ui/zero_sized_hashmap_values.stderr @@ -0,0 +1,107 @@ +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:5:28 + | +LL | const CONST_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::zero-sized-map-values` implied by `-D warnings` + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:8:30 + | +LL | static STATIC_NOT_OK: Option> = None; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:11:17 + | +LL | type NotOkMap = HashMap; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:15:11 + | +LL | NotOk(HashMap), + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:20:13 + | +LL | not_ok: HashMap, + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:22:22 + | +LL | also_not_ok: Vec>, + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:30:30 + | +LL | fn weird_map(&self, map: HashMap); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:38:25 + | +LL | fn not_ok(&self) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:55:14 + | +LL | fn test(map: HashMap, key: &str) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:55:49 + | +LL | fn test(map: HashMap, key: &str) -> HashMap { + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:64:34 + | +LL | let _: HashMap = HashMap::new(); + | ^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:64:12 + | +LL | let _: HashMap = HashMap::new(); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: map with zero-sized value type + --> $DIR/zero_sized_hashmap_values.rs:67:12 + | +LL | let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect(); + | ^^^^^^^^^^^^^ + | + = help: consider using a set instead + +error: aborting due to 13 previous errors +