Skip to content

Commit 99d4ea4

Browse files
committed
Auto merge of rust-lang#10356 - JirkaVebr:let_underscore_untyped, r=llogiq
Add `let_underscore_untyped` Fixes rust-lang#6842 This adds a new pedantic `let_underscore_untyped` lint which checks for `let _ = <expr>`, and suggests to either provide a type annotation, or to remove the `let` keyword. That way the author is forced to specify the type they intended to ignore, and thus get forced to re-visit the decision should the type of `<expr>` change. Alternatively, they can drop the `let` keyword to truly just ignore the value no matter what. r? `@llogiq` changelog: New lint: [let_underscore_untyped]
2 parents 4369a67 + 0b1ae20 commit 99d4ea4

19 files changed

+199
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4494,6 +4494,7 @@ Released 2018-09-13
44944494
[`let_underscore_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_future
44954495
[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
44964496
[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use
4497+
[`let_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped
44974498
[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
44984499
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
44994500
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug

clippy_dev/src/new_lint.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::clippy_project_root;
22
use indoc::{formatdoc, writedoc};
3+
use std::fmt;
34
use std::fmt::Write as _;
45
use std::fs::{self, OpenOptions};
56
use std::io::prelude::*;
@@ -256,7 +257,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
256257
)
257258
});
258259

259-
let _ = write!(result, "{}", get_lint_declaration(&name_upper, category));
260+
let _: fmt::Result = write!(result, "{}", get_lint_declaration(&name_upper, category));
260261

261262
result.push_str(&if enable_msrv {
262263
formatdoc!(
@@ -353,7 +354,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
353354
let mut lint_file_contents = String::new();
354355

355356
if enable_msrv {
356-
let _ = writedoc!(
357+
let _: fmt::Result = writedoc!(
357358
lint_file_contents,
358359
r#"
359360
use clippy_utils::msrvs::{{self, Msrv}};
@@ -373,7 +374,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
373374
name_upper = name_upper,
374375
);
375376
} else {
376-
let _ = writedoc!(
377+
let _: fmt::Result = writedoc!(
377378
lint_file_contents,
378379
r#"
379380
use rustc_lint::{{{context_import}, LintContext}};
@@ -521,7 +522,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
521522
.chain(std::iter::once(&*lint_name_upper))
522523
.filter(|s| !s.is_empty())
523524
{
524-
let _ = write!(new_arr_content, "\n {ident},");
525+
let _: fmt::Result = write!(new_arr_content, "\n {ident},");
525526
}
526527
new_arr_content.push('\n');
527528

clippy_dev/src/update_lints.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use itertools::Itertools;
55
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
66
use std::collections::{HashMap, HashSet};
77
use std::ffi::OsStr;
8-
use std::fmt::Write;
8+
use std::fmt::{self, Write};
99
use std::fs::{self, OpenOptions};
1010
use std::io::{self, Read, Seek, SeekFrom, Write as _};
1111
use std::ops::Range;
@@ -691,7 +691,7 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
691691
let mut output = GENERATED_FILE_COMMENT.to_string();
692692
output.push_str("{\n");
693693
for lint in lints {
694-
let _ = write!(
694+
let _: fmt::Result = write!(
695695
output,
696696
concat!(
697697
" store.register_removed(\n",
@@ -726,7 +726,7 @@ fn gen_declared_lints<'a>(
726726
if !is_public {
727727
output.push_str(" #[cfg(feature = \"internal\")]\n");
728728
}
729-
let _ = writeln!(output, " crate::{module_name}::{lint_name}_INFO,");
729+
let _: fmt::Result = writeln!(output, " crate::{module_name}::{lint_name}_INFO,");
730730
}
731731
output.push_str("];\n");
732732

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
224224
crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO,
225225
crate::let_underscore::LET_UNDERSCORE_LOCK_INFO,
226226
crate::let_underscore::LET_UNDERSCORE_MUST_USE_INFO,
227+
crate::let_underscore::LET_UNDERSCORE_UNTYPED_INFO,
227228
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
228229
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
229230
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,

clippy_lints/src/entry.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use clippy_utils::{
66
source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context},
77
SpanlessEq,
88
};
9-
use core::fmt::Write;
9+
use core::fmt::{self, Write};
1010
use rustc_errors::Applicability;
1111
use rustc_hir::{
1212
hir_id::HirIdSet,
@@ -536,7 +536,7 @@ impl<'tcx> InsertSearchResults<'tcx> {
536536
if is_expr_used_or_unified(cx.tcx, insertion.call) {
537537
write_wrapped(&mut res, insertion, ctxt, app);
538538
} else {
539-
let _ = write!(
539+
let _: fmt::Result = write!(
540540
res,
541541
"e.insert({})",
542542
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
@@ -552,7 +552,7 @@ impl<'tcx> InsertSearchResults<'tcx> {
552552
(
553553
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
554554
// Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value`
555-
let _ = write!(
555+
let _: fmt::Result = write!(
556556
res,
557557
"Some(e.insert({}))",
558558
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
@@ -566,7 +566,7 @@ impl<'tcx> InsertSearchResults<'tcx> {
566566
(
567567
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
568568
// Insertion into a map would return `None`, but the entry returns a mutable reference.
569-
let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
569+
let _: fmt::Result = if is_expr_final_block_expr(cx.tcx, insertion.call) {
570570
write!(
571571
res,
572572
"e.insert({});\n{}None",

clippy_lints/src/inconsistent_struct_constructor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hir::{self as hir, ExprKind};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_session::{declare_lint_pass, declare_tool_lint};
99
use rustc_span::symbol::Symbol;
10-
use std::fmt::Write as _;
10+
use std::fmt::{self, Write as _};
1111

1212
declare_clippy_lint! {
1313
/// ### What it does
@@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
9090
let mut fields_snippet = String::new();
9191
let (last_ident, idents) = ordered_fields.split_last().unwrap();
9292
for ident in idents {
93-
let _ = write!(fields_snippet, "{ident}, ");
93+
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
9494
}
9595
fields_snippet.push_str(&last_ident.to_string());
9696

clippy_lints/src/let_underscore.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,45 @@ declare_clippy_lint! {
9090
"non-binding `let` on a future"
9191
}
9292

93-
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]);
93+
declare_clippy_lint! {
94+
/// ### What it does
95+
/// Checks for `let _ = <expr>` without a type annotation, and suggests to either provide one,
96+
/// or remove the `let` keyword altogether.
97+
///
98+
/// ### Why is this bad?
99+
/// The `let _ = <expr>` expression ignores the value of `<expr>` but will remain doing so even
100+
/// if the type were to change, thus potentially introducing subtle bugs. By supplying a type
101+
/// annotation, one will be forced to re-visit the decision to ignore the value in such cases.
102+
///
103+
/// ### Known problems
104+
/// The `_ = <expr>` is not properly supported by some tools (e.g. IntelliJ) and may seem odd
105+
/// to many developers. This lint also partially overlaps with the other `let_underscore_*`
106+
/// lints.
107+
///
108+
/// ### Example
109+
/// ```rust
110+
/// fn foo() -> Result<u32, ()> {
111+
/// Ok(123)
112+
/// }
113+
/// let _ = foo();
114+
/// ```
115+
/// Use instead:
116+
/// ```rust
117+
/// fn foo() -> Result<u32, ()> {
118+
/// Ok(123)
119+
/// }
120+
/// // Either provide a type annotation:
121+
/// let _: Result<u32, ()> = foo();
122+
/// // …or drop the let keyword:
123+
/// _ = foo();
124+
/// ```
125+
#[clippy::version = "1.69.0"]
126+
pub LET_UNDERSCORE_UNTYPED,
127+
pedantic,
128+
"non-binding `let` without a type annotation"
129+
}
130+
131+
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]);
94132

95133
const SYNC_GUARD_PATHS: [&[&str]; 3] = [
96134
&paths::PARKING_LOT_MUTEX_GUARD,
@@ -148,6 +186,18 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
148186
"consider explicitly using function result",
149187
);
150188
}
189+
190+
if local.pat.default_binding_modes && local.ty.is_none() {
191+
// When `default_binding_modes` is true, the `let` keyword is present.
192+
span_lint_and_help(
193+
cx,
194+
LET_UNDERSCORE_UNTYPED,
195+
local.span,
196+
"non-binding `let` without a type annotation",
197+
None,
198+
"consider adding a type annotation or removing the `let` keyword",
199+
);
200+
}
151201
}
152202
}
153203
}

clippy_lints/src/literal_representation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ impl DecimalLiteralRepresentation {
488488
then {
489489
let hex = format!("{val:#X}");
490490
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
491-
let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
491+
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
492492
warning_type.display(num_lit.format(), cx, span);
493493
});
494494
}

clippy_lints/src/module_style.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ fn process_paths_for_mod_files<'a>(
134134
mod_folders: &mut FxHashSet<&'a OsStr>,
135135
) {
136136
let mut comp = path.components().rev().peekable();
137-
let _ = comp.next();
137+
let _: Option<_> = comp.next();
138138
if path.ends_with("mod.rs") {
139139
mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
140140
}

clippy_utils/src/numeric_literal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl<'a> NumericLiteral<'a> {
186186
// The exponent may have a sign, output it early, otherwise it will be
187187
// treated as a digit
188188
if digits.clone().next() == Some('-') {
189-
let _ = digits.next();
189+
let _: Option<char> = digits.next();
190190
output.push('-');
191191
}
192192

clippy_utils/src/sugg.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability};
2020
use rustc_middle::ty;
2121
use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
2222
use std::borrow::Cow;
23-
use std::fmt::{Display, Write as _};
23+
use std::fmt::{self, Display, Write as _};
2424
use std::ops::{Add, Neg, Not, Sub};
2525

2626
/// A helper type to build suggestion correctly handling parentheses.
@@ -932,7 +932,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
932932
if cmt.place.projections.is_empty() {
933933
// handle item without any projection, that needs an explicit borrowing
934934
// i.e.: suggest `&x` instead of `x`
935-
let _ = write!(self.suggestion_start, "{start_snip}&{ident_str}");
935+
let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}");
936936
} else {
937937
// cases where a parent `Call` or `MethodCall` is using the item
938938
// i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()`
@@ -947,7 +947,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
947947
// given expression is the self argument and will be handled completely by the compiler
948948
// i.e.: `|x| x.is_something()`
949949
ExprKind::MethodCall(_, self_expr, ..) if self_expr.hir_id == cmt.hir_id => {
950-
let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
950+
let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
951951
self.next_pos = span.hi();
952952
return;
953953
},
@@ -1055,7 +1055,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
10551055
}
10561056
}
10571057

1058-
let _ = write!(self.suggestion_start, "{start_snip}{replacement_str}");
1058+
let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{replacement_str}");
10591059
}
10601060
self.next_pos = span.hi();
10611061
}

lintcheck/src/main.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use crate::recursive::LintcheckServer;
1717
use std::collections::{HashMap, HashSet};
1818
use std::env;
1919
use std::env::consts::EXE_SUFFIX;
20-
use std::fmt::Write as _;
20+
use std::fmt::{self, Write as _};
2121
use std::fs;
22-
use std::io::ErrorKind;
22+
use std::io::{self, ErrorKind};
2323
use std::path::{Path, PathBuf};
2424
use std::process::Command;
2525
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -145,8 +145,8 @@ impl ClippyWarning {
145145
}
146146

147147
let mut output = String::from("| ");
148-
let _ = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line);
149-
let _ = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message);
148+
let _: fmt::Result = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line);
149+
let _: fmt::Result = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message);
150150
output.push('\n');
151151
output
152152
} else {
@@ -632,7 +632,7 @@ fn main() {
632632
.unwrap();
633633

634634
let server = config.recursive.then(|| {
635-
let _ = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive");
635+
let _: io::Result<()> = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive");
636636

637637
LintcheckServer::spawn(recursive_options)
638638
});
@@ -689,7 +689,7 @@ fn main() {
689689
write!(text, "{}", all_msgs.join("")).unwrap();
690690
text.push_str("\n\n### ICEs:\n");
691691
for (cratename, msg) in &ices {
692-
let _ = write!(text, "{cratename}: '{msg}'");
692+
let _: fmt::Result = write!(text, "{cratename}: '{msg}'");
693693
}
694694

695695
println!("Writing logs to {}", config.lintcheck_results_path.display());

tests/ui/let_underscore_untyped.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#![allow(unused)]
2+
#![warn(clippy::let_underscore_untyped)]
3+
4+
use std::future::Future;
5+
use std::{boxed::Box, fmt::Display};
6+
7+
fn a() -> u32 {
8+
1
9+
}
10+
11+
fn b<T>(x: T) -> T {
12+
x
13+
}
14+
15+
fn c() -> impl Display {
16+
1
17+
}
18+
19+
fn d(x: &u32) -> &u32 {
20+
x
21+
}
22+
23+
fn e() -> Result<u32, ()> {
24+
Ok(1)
25+
}
26+
27+
fn f() -> Box<dyn Display> {
28+
Box::new(1)
29+
}
30+
31+
fn main() {
32+
let _ = a();
33+
let _ = b(1);
34+
let _ = c();
35+
let _ = d(&1);
36+
let _ = e();
37+
let _ = f();
38+
39+
_ = a();
40+
_ = b(1);
41+
_ = c();
42+
_ = d(&1);
43+
_ = e();
44+
_ = f();
45+
46+
let _: u32 = a();
47+
let _: u32 = b(1);
48+
let _: &u32 = d(&1);
49+
let _: Result<_, _> = e();
50+
let _: Box<_> = f();
51+
52+
#[allow(clippy::let_underscore_untyped)]
53+
let _ = a();
54+
}

0 commit comments

Comments
 (0)