Skip to content

Commit f77f1db

Browse files
korratebroto
andcommitted
Add a lint for maps with zero-sized values
Co-authored-by: Eduardo Broto <[email protected]>
1 parent a2d9925 commit f77f1db

7 files changed

+437
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,5 +2171,6 @@ Released 2018-09-13
21712171
[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
21722172
[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
21732173
[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
2174+
[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
21742175
[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
21752176
<!-- end autogenerated links to lint list -->

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ mod wildcard_dependencies;
345345
mod wildcard_imports;
346346
mod write;
347347
mod zero_div_zero;
348+
mod zero_sized_map_values;
348349
// end lints modules, do not remove this comment, it’s used in `update_lints`
349350

350351
pub use crate::utils::conf::Conf;
@@ -944,6 +945,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
944945
&write::WRITE_LITERAL,
945946
&write::WRITE_WITH_NEWLINE,
946947
&zero_div_zero::ZERO_DIVIDED_BY_ZERO,
948+
&zero_sized_map_values::ZERO_SIZED_MAP_VALUES,
947949
]);
948950
// end register lints, do not remove this comment, it’s used in `update_lints`
949951

@@ -1204,6 +1206,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12041206
store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops);
12051207
store.register_late_pass(|| box strings::StrToString);
12061208
store.register_late_pass(|| box strings::StringToString);
1209+
store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues);
12071210

12081211
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
12091212
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1336,6 +1339,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13361339
LintId::of(&unused_self::UNUSED_SELF),
13371340
LintId::of(&wildcard_imports::ENUM_GLOB_USE),
13381341
LintId::of(&wildcard_imports::WILDCARD_IMPORTS),
1342+
LintId::of(&zero_sized_map_values::ZERO_SIZED_MAP_VALUES),
13391343
]);
13401344

13411345
#[cfg(feature = "internal-lints")]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use if_chain::if_chain;
2+
use rustc_hir::{self as hir, HirId, ItemKind, Node};
3+
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_middle::ty::{Adt, Ty};
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
use rustc_target::abi::LayoutOf as _;
7+
use rustc_typeck::hir_ty_to_ty;
8+
9+
use crate::utils::{is_type_diagnostic_item, match_type, paths, span_lint_and_help};
10+
11+
declare_clippy_lint! {
12+
/// **What it does:** Checks for maps with zero-sized value types anywhere in the code.
13+
///
14+
/// **Why is this bad?** Since there is only a single value for a zero-sized type, a map
15+
/// containing zero sized values is effectively a set. Using a set in that case improves
16+
/// readability and communicates intent more clearly.
17+
///
18+
/// **Known problems:**
19+
/// * A zero-sized type cannot be recovered later if it contains private fields.
20+
/// * This lints the signature of public items
21+
///
22+
/// **Example:**
23+
///
24+
/// ```rust
25+
/// # use std::collections::HashMap;
26+
/// fn unique_words(text: &str) -> HashMap<&str, ()> {
27+
/// todo!();
28+
/// }
29+
/// ```
30+
/// Use instead:
31+
/// ```rust
32+
/// # use std::collections::HashSet;
33+
/// fn unique_words(text: &str) -> HashSet<&str> {
34+
/// todo!();
35+
/// }
36+
/// ```
37+
pub ZERO_SIZED_MAP_VALUES,
38+
pedantic,
39+
"usage of map with zero-sized value type"
40+
}
41+
42+
declare_lint_pass!(ZeroSizedMapValues => [ZERO_SIZED_MAP_VALUES]);
43+
44+
impl LateLintPass<'_> for ZeroSizedMapValues {
45+
fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
46+
if_chain! {
47+
if !hir_ty.span.from_expansion();
48+
if !in_trait_impl(cx, hir_ty.hir_id);
49+
let ty = ty_from_hir_ty(cx, hir_ty);
50+
if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP);
51+
if let Adt(_, ref substs) = ty.kind();
52+
let ty = substs.type_at(1);
53+
if let Ok(layout) = cx.layout_of(ty);
54+
if layout.is_zst();
55+
then {
56+
span_lint_and_help(cx, ZERO_SIZED_MAP_VALUES, hir_ty.span, "map with zero-sized value type", None, "consider using a set instead");
57+
}
58+
}
59+
}
60+
}
61+
62+
fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool {
63+
let parent_id = cx.tcx.hir().get_parent_item(hir_id);
64+
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(parent_id)) {
65+
if let ItemKind::Impl { of_trait: Some(_), .. } = item.kind {
66+
return true;
67+
}
68+
}
69+
false
70+
}
71+
72+
fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Ty<'tcx> {
73+
cx.maybe_typeck_results()
74+
.and_then(|results| {
75+
if results.hir_owner == hir_ty.hir_id.owner {
76+
results.node_type_opt(hir_ty.hir_id)
77+
} else {
78+
None
79+
}
80+
})
81+
.unwrap_or_else(|| hir_ty_to_ty(cx.tcx, hir_ty))
82+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#![warn(clippy::zero_sized_map_values)]
2+
use std::collections::BTreeMap;
3+
4+
const CONST_OK: Option<BTreeMap<String, usize>> = None;
5+
const CONST_NOT_OK: Option<BTreeMap<String, ()>> = None;
6+
7+
static STATIC_OK: Option<BTreeMap<String, usize>> = None;
8+
static STATIC_NOT_OK: Option<BTreeMap<String, ()>> = None;
9+
10+
type OkMap = BTreeMap<String, usize>;
11+
type NotOkMap = BTreeMap<String, ()>;
12+
13+
enum TestEnum {
14+
Ok(BTreeMap<String, usize>),
15+
NotOk(BTreeMap<String, ()>),
16+
}
17+
18+
struct Test {
19+
ok: BTreeMap<String, usize>,
20+
not_ok: BTreeMap<String, ()>,
21+
22+
also_not_ok: Vec<BTreeMap<usize, ()>>,
23+
}
24+
25+
trait TestTrait {
26+
type Output;
27+
28+
fn produce_output() -> Self::Output;
29+
30+
fn weird_map(&self, map: BTreeMap<usize, ()>);
31+
}
32+
33+
impl Test {
34+
fn ok(&self) -> BTreeMap<String, usize> {
35+
todo!()
36+
}
37+
38+
fn not_ok(&self) -> BTreeMap<String, ()> {
39+
todo!()
40+
}
41+
}
42+
43+
impl TestTrait for Test {
44+
type Output = BTreeMap<String, ()>;
45+
46+
fn produce_output() -> Self::Output {
47+
todo!();
48+
}
49+
50+
fn weird_map(&self, map: BTreeMap<usize, ()>) {
51+
todo!();
52+
}
53+
}
54+
55+
fn test(map: BTreeMap<String, ()>, key: &str) -> BTreeMap<String, ()> {
56+
todo!();
57+
}
58+
59+
fn test2(map: BTreeMap<String, usize>, key: &str) -> BTreeMap<String, usize> {
60+
todo!();
61+
}
62+
63+
fn main() {
64+
let _: BTreeMap<String, ()> = BTreeMap::new();
65+
let _: BTreeMap<String, usize> = BTreeMap::new();
66+
67+
let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect();
68+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
error: map with zero-sized value type
2+
--> $DIR/zero_sized_btreemap_values.rs:5:28
3+
|
4+
LL | const CONST_NOT_OK: Option<BTreeMap<String, ()>> = None;
5+
| ^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::zero-sized-map-values` implied by `-D warnings`
8+
= help: consider using a set instead
9+
10+
error: map with zero-sized value type
11+
--> $DIR/zero_sized_btreemap_values.rs:8:30
12+
|
13+
LL | static STATIC_NOT_OK: Option<BTreeMap<String, ()>> = None;
14+
| ^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= help: consider using a set instead
17+
18+
error: map with zero-sized value type
19+
--> $DIR/zero_sized_btreemap_values.rs:11:17
20+
|
21+
LL | type NotOkMap = BTreeMap<String, ()>;
22+
| ^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= help: consider using a set instead
25+
26+
error: map with zero-sized value type
27+
--> $DIR/zero_sized_btreemap_values.rs:15:11
28+
|
29+
LL | NotOk(BTreeMap<String, ()>),
30+
| ^^^^^^^^^^^^^^^^^^^^
31+
|
32+
= help: consider using a set instead
33+
34+
error: map with zero-sized value type
35+
--> $DIR/zero_sized_btreemap_values.rs:20:13
36+
|
37+
LL | not_ok: BTreeMap<String, ()>,
38+
| ^^^^^^^^^^^^^^^^^^^^
39+
|
40+
= help: consider using a set instead
41+
42+
error: map with zero-sized value type
43+
--> $DIR/zero_sized_btreemap_values.rs:22:22
44+
|
45+
LL | also_not_ok: Vec<BTreeMap<usize, ()>>,
46+
| ^^^^^^^^^^^^^^^^^^^
47+
|
48+
= help: consider using a set instead
49+
50+
error: map with zero-sized value type
51+
--> $DIR/zero_sized_btreemap_values.rs:30:30
52+
|
53+
LL | fn weird_map(&self, map: BTreeMap<usize, ()>);
54+
| ^^^^^^^^^^^^^^^^^^^
55+
|
56+
= help: consider using a set instead
57+
58+
error: map with zero-sized value type
59+
--> $DIR/zero_sized_btreemap_values.rs:38:25
60+
|
61+
LL | fn not_ok(&self) -> BTreeMap<String, ()> {
62+
| ^^^^^^^^^^^^^^^^^^^^
63+
|
64+
= help: consider using a set instead
65+
66+
error: map with zero-sized value type
67+
--> $DIR/zero_sized_btreemap_values.rs:55:14
68+
|
69+
LL | fn test(map: BTreeMap<String, ()>, key: &str) -> BTreeMap<String, ()> {
70+
| ^^^^^^^^^^^^^^^^^^^^
71+
|
72+
= help: consider using a set instead
73+
74+
error: map with zero-sized value type
75+
--> $DIR/zero_sized_btreemap_values.rs:55:50
76+
|
77+
LL | fn test(map: BTreeMap<String, ()>, key: &str) -> BTreeMap<String, ()> {
78+
| ^^^^^^^^^^^^^^^^^^^^
79+
|
80+
= help: consider using a set instead
81+
82+
error: map with zero-sized value type
83+
--> $DIR/zero_sized_btreemap_values.rs:64:35
84+
|
85+
LL | let _: BTreeMap<String, ()> = BTreeMap::new();
86+
| ^^^^^^^^^^^^^
87+
|
88+
= help: consider using a set instead
89+
90+
error: map with zero-sized value type
91+
--> $DIR/zero_sized_btreemap_values.rs:64:12
92+
|
93+
LL | let _: BTreeMap<String, ()> = BTreeMap::new();
94+
| ^^^^^^^^^^^^^^^^^^^^
95+
|
96+
= help: consider using a set instead
97+
98+
error: map with zero-sized value type
99+
--> $DIR/zero_sized_btreemap_values.rs:67:12
100+
|
101+
LL | let _: BTreeMap<_, _> = std::iter::empty::<(String, ())>().collect();
102+
| ^^^^^^^^^^^^^^
103+
|
104+
= help: consider using a set instead
105+
106+
error: aborting due to 13 previous errors
107+

tests/ui/zero_sized_hashmap_values.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#![warn(clippy::zero_sized_map_values)]
2+
use std::collections::HashMap;
3+
4+
const CONST_OK: Option<HashMap<String, usize>> = None;
5+
const CONST_NOT_OK: Option<HashMap<String, ()>> = None;
6+
7+
static STATIC_OK: Option<HashMap<String, usize>> = None;
8+
static STATIC_NOT_OK: Option<HashMap<String, ()>> = None;
9+
10+
type OkMap = HashMap<String, usize>;
11+
type NotOkMap = HashMap<String, ()>;
12+
13+
enum TestEnum {
14+
Ok(HashMap<String, usize>),
15+
NotOk(HashMap<String, ()>),
16+
}
17+
18+
struct Test {
19+
ok: HashMap<String, usize>,
20+
not_ok: HashMap<String, ()>,
21+
22+
also_not_ok: Vec<HashMap<usize, ()>>,
23+
}
24+
25+
trait TestTrait {
26+
type Output;
27+
28+
fn produce_output() -> Self::Output;
29+
30+
fn weird_map(&self, map: HashMap<usize, ()>);
31+
}
32+
33+
impl Test {
34+
fn ok(&self) -> HashMap<String, usize> {
35+
todo!()
36+
}
37+
38+
fn not_ok(&self) -> HashMap<String, ()> {
39+
todo!()
40+
}
41+
}
42+
43+
impl TestTrait for Test {
44+
type Output = HashMap<String, ()>;
45+
46+
fn produce_output() -> Self::Output {
47+
todo!();
48+
}
49+
50+
fn weird_map(&self, map: HashMap<usize, ()>) {
51+
todo!();
52+
}
53+
}
54+
55+
fn test(map: HashMap<String, ()>, key: &str) -> HashMap<String, ()> {
56+
todo!();
57+
}
58+
59+
fn test2(map: HashMap<String, usize>, key: &str) -> HashMap<String, usize> {
60+
todo!();
61+
}
62+
63+
fn main() {
64+
let _: HashMap<String, ()> = HashMap::new();
65+
let _: HashMap<String, usize> = HashMap::new();
66+
67+
let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect();
68+
}

0 commit comments

Comments
 (0)