Skip to content

Commit 6c70133

Browse files
committed
Auto merge of #6218 - korrat:master, r=ebroto
Add lint for maps with zero-sized value types Hi, this is my first time contributing to clippy or rust in general, so I'm not sure about the details of contributing. Please excuse me and let me now if I did anything wrong. I have a couple of questions: 1. I'm not sure what category this lint should be. I've put it in "nursery" for now. 1. Should I squash commits this is reviewed/merged? changelog: Add lint for maps with zero-sized value types Fixes #1641
2 parents a2d9925 + f77f1db commit 6c70133

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)