Skip to content

Commit 86ac6e8

Browse files
committed
Auto merge of #9040 - miam-miam100:unused_named_parameter, r=dswij
Add new lint [`positional_named_format_parameters`] *Please write a short comment explaining your change (or "none" for internal only changes)* changelog: Add new lint [`positional_named_format_parameters`] to warn when named parameters in format strings are used as positional arguments.
2 parents a427b12 + 9ffddf5 commit 86ac6e8

8 files changed

+657
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3975,6 +3975,7 @@ Released 2018-09-13
39753975
[`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none
39763976
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
39773977
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
3978+
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
39783979
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
39793980
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
39803981
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
344344
LintId::of(vec::USELESS_VEC),
345345
LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
346346
LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
347+
LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS),
347348
LintId::of(write::PRINTLN_EMPTY_STRING),
348349
LintId::of(write::PRINT_LITERAL),
349350
LintId::of(write::PRINT_WITH_NEWLINE),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ store.register_lints(&[
587587
verbose_file_reads::VERBOSE_FILE_READS,
588588
wildcard_imports::ENUM_GLOB_USE,
589589
wildcard_imports::WILDCARD_IMPORTS,
590+
write::POSITIONAL_NAMED_FORMAT_PARAMETERS,
590591
write::PRINTLN_EMPTY_STRING,
591592
write::PRINT_LITERAL,
592593
write::PRINT_STDERR,

clippy_lints/src/lib.register_suspicious.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
3232
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
3333
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
3434
LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF),
35+
LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS),
3536
])

clippy_lints/src/write.rs

+123-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::iter;
33
use std::ops::{Deref, Range};
44

55
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
6-
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
6+
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
77
use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
8+
use rustc_ast::ptr::P;
89
use rustc_ast::token::{self, LitKind};
910
use rustc_ast::tokenstream::TokenStream;
1011
use rustc_errors::{Applicability, DiagnosticBuilder};
@@ -256,6 +257,28 @@ declare_clippy_lint! {
256257
"writing a literal with a format string"
257258
}
258259

260+
declare_clippy_lint! {
261+
/// ### What it does
262+
/// This lint warns when a named parameter in a format string is used as a positional one.
263+
///
264+
/// ### Why is this bad?
265+
/// It may be confused for an assignment and obfuscates which parameter is being used.
266+
///
267+
/// ### Example
268+
/// ```rust
269+
/// println!("{}", x = 10);
270+
/// ```
271+
///
272+
/// Use instead:
273+
/// ```rust
274+
/// println!("{x}", x = 10);
275+
/// ```
276+
#[clippy::version = "1.63.0"]
277+
pub POSITIONAL_NAMED_FORMAT_PARAMETERS,
278+
suspicious,
279+
"named parameter in a format string is used positionally"
280+
}
281+
259282
#[derive(Default)]
260283
pub struct Write {
261284
in_debug_impl: bool,
@@ -270,7 +293,8 @@ impl_lint_pass!(Write => [
270293
PRINT_LITERAL,
271294
WRITE_WITH_NEWLINE,
272295
WRITELN_EMPTY_STRING,
273-
WRITE_LITERAL
296+
WRITE_LITERAL,
297+
POSITIONAL_NAMED_FORMAT_PARAMETERS,
274298
]);
275299

276300
impl EarlyLintPass for Write {
@@ -408,6 +432,7 @@ fn newline_span(fmtstr: &StrLit) -> (Span, bool) {
408432
#[derive(Default)]
409433
struct SimpleFormatArgs {
410434
unnamed: Vec<Vec<Span>>,
435+
complex_unnamed: Vec<Vec<Span>>,
411436
named: Vec<(Symbol, Vec<Span>)>,
412437
}
413438
impl SimpleFormatArgs {
@@ -419,6 +444,10 @@ impl SimpleFormatArgs {
419444
})
420445
}
421446

447+
fn get_complex_unnamed(&self) -> impl Iterator<Item = &[Span]> {
448+
self.complex_unnamed.iter().map(Vec::as_slice)
449+
}
450+
422451
fn get_named(&self, n: &Path) -> &[Span] {
423452
self.named.iter().find(|x| *n == x.0).map_or(&[], |x| x.1.as_slice())
424453
}
@@ -479,6 +508,61 @@ impl SimpleFormatArgs {
479508
},
480509
};
481510
}
511+
512+
fn push_to_complex(&mut self, span: Span, position: usize) {
513+
if self.complex_unnamed.len() <= position {
514+
self.complex_unnamed.resize_with(position, Vec::new);
515+
self.complex_unnamed.push(vec![span]);
516+
} else {
517+
let args: &mut Vec<Span> = &mut self.complex_unnamed[position];
518+
args.push(span);
519+
}
520+
}
521+
522+
fn push_complex(
523+
&mut self,
524+
cx: &EarlyContext<'_>,
525+
arg: rustc_parse_format::Argument<'_>,
526+
str_lit_span: Span,
527+
fmt_span: Span,
528+
) {
529+
use rustc_parse_format::{ArgumentImplicitlyIs, ArgumentIs, CountIsParam};
530+
531+
let snippet = snippet_opt(cx, fmt_span);
532+
533+
let end = snippet
534+
.as_ref()
535+
.and_then(|s| s.find(':'))
536+
.or_else(|| fmt_span.hi().0.checked_sub(fmt_span.lo().0 + 1).map(|u| u as usize));
537+
538+
if let (ArgumentIs(n) | ArgumentImplicitlyIs(n), Some(end)) = (arg.position, end) {
539+
let span = fmt_span.from_inner(InnerSpan::new(1, end));
540+
self.push_to_complex(span, n);
541+
};
542+
543+
if let (CountIsParam(n), Some(span)) = (arg.format.precision, arg.format.precision_span) {
544+
// We need to do this hack as precision spans should be converted from .* to .foo$
545+
let hack = if snippet.as_ref().and_then(|s| s.find('*')).is_some() {
546+
0
547+
} else {
548+
1
549+
};
550+
551+
let span = str_lit_span.from_inner(InnerSpan {
552+
start: span.start + 1,
553+
end: span.end - hack,
554+
});
555+
self.push_to_complex(span, n);
556+
};
557+
558+
if let (CountIsParam(n), Some(span)) = (arg.format.width, arg.format.width_span) {
559+
let span = str_lit_span.from_inner(InnerSpan {
560+
start: span.start,
561+
end: span.end - 1,
562+
});
563+
self.push_to_complex(span, n);
564+
};
565+
}
482566
}
483567

484568
impl Write {
@@ -511,8 +595,8 @@ impl Write {
511595
// FIXME: modify rustc's fmt string parser to give us the current span
512596
span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting");
513597
}
514-
515598
args.push(arg, span);
599+
args.push_complex(cx, arg, str_lit.span, span);
516600
}
517601

518602
parser.errors.is_empty().then_some(args)
@@ -566,6 +650,7 @@ impl Write {
566650

567651
let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
568652
let mut unnamed_args = args.get_unnamed();
653+
let mut complex_unnamed_args = args.get_complex_unnamed();
569654
loop {
570655
if !parser.eat(&token::Comma) {
571656
return (Some(fmtstr), expr);
@@ -577,11 +662,20 @@ impl Write {
577662
} else {
578663
return (Some(fmtstr), None);
579664
};
665+
let complex_unnamed_arg = complex_unnamed_args.next();
666+
580667
let (fmt_spans, lit) = match &token_expr.kind {
581668
ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit),
582-
ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) {
583-
(ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
584-
_ => continue,
669+
ExprKind::Assign(lhs, rhs, _) => {
670+
if let Some(span) = complex_unnamed_arg {
671+
for x in span {
672+
Self::report_positional_named_param(cx, *x, lhs, rhs);
673+
}
674+
}
675+
match (&lhs.kind, &rhs.kind) {
676+
(ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
677+
_ => continue,
678+
}
585679
},
586680
_ => {
587681
unnamed_args.next();
@@ -637,6 +731,29 @@ impl Write {
637731
}
638732
}
639733

734+
fn report_positional_named_param(cx: &EarlyContext<'_>, span: Span, lhs: &P<Expr>, _rhs: &P<Expr>) {
735+
if let ExprKind::Path(_, _p) = &lhs.kind {
736+
let mut applicability = Applicability::MachineApplicable;
737+
let name = snippet_with_applicability(cx, lhs.span, "name", &mut applicability);
738+
// We need to do this hack as precision spans should be converted from .* to .foo$
739+
let hack = snippet(cx, span, "").contains('*');
740+
741+
span_lint_and_sugg(
742+
cx,
743+
POSITIONAL_NAMED_FORMAT_PARAMETERS,
744+
span,
745+
&format!("named parameter {} is used as a positional parameter", name),
746+
"replace it with",
747+
if hack {
748+
format!("{}$", name)
749+
} else {
750+
format!("{}", name)
751+
},
752+
applicability,
753+
);
754+
};
755+
}
756+
640757
fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
641758
if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
642759
if fmt_str.symbol == kw::Empty {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
#![allow(unused_must_use)]
3+
#![allow(named_arguments_used_positionally)] // Unstable at time of writing.
4+
#![warn(clippy::positional_named_format_parameters)]
5+
6+
use std::io::Write;
7+
8+
fn main() {
9+
let mut v = Vec::new();
10+
let hello = "Hello";
11+
12+
println!("{hello:.foo$}", foo = 2);
13+
writeln!(v, "{hello:.foo$}", foo = 2);
14+
15+
// Warnings
16+
println!("{zero} {one:?}", zero = 0, one = 1);
17+
println!("This is a test {zero} {one:?}", zero = 0, one = 1);
18+
println!("Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01);
19+
println!("Hello {one:zero$}!", zero = 5, one = 1);
20+
println!("Hello {zero:one$}!", zero = 4, one = 1);
21+
println!("Hello {zero:0one$}!", zero = 4, one = 1);
22+
println!("Hello is {one:.zero$}", zero = 5, one = 0.01);
23+
println!("Hello is {one:<6.zero$}", zero = 5, one = 0.01);
24+
println!("{zero}, `{two:>8.one$}` has 3", zero = hello, one = 3, two = hello);
25+
println!("Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01);
26+
println!("Hello {world} {world}!", world = 5);
27+
28+
writeln!(v, "{zero} {one:?}", zero = 0, one = 1);
29+
writeln!(v, "This is a test {zero} {one:?}", zero = 0, one = 1);
30+
writeln!(v, "Hello {one} is {two:.zero$}", zero = 5, one = hello, two = 0.01);
31+
writeln!(v, "Hello {one:zero$}!", zero = 4, one = 1);
32+
writeln!(v, "Hello {zero:one$}!", zero = 4, one = 1);
33+
writeln!(v, "Hello {zero:0one$}!", zero = 4, one = 1);
34+
writeln!(v, "Hello is {one:.zero$}", zero = 3, one = 0.01);
35+
writeln!(v, "Hello is {one:<6.zero$}", zero = 2, one = 0.01);
36+
writeln!(v, "{zero}, `{two:>8.one$}` has 3", zero = hello, one = 3, two = hello);
37+
writeln!(v, "Hello {one} is {two:.zero$}", zero = 1, one = hello, two = 0.01);
38+
writeln!(v, "Hello {world} {world}!", world = 0);
39+
40+
// Tests from other files
41+
println!("{w:w$}", w = 1);
42+
println!("{p:.p$}", p = 1);
43+
println!("{v}", v = 1);
44+
println!("{v:v$}", v = 1);
45+
println!("{v:v$}", v = 1);
46+
println!("{v:v$.v$}", v = 1);
47+
println!("{v:v$.v$}", v = 1);
48+
println!("{v:v$.v$}", v = 1);
49+
println!("{v:v$.v$}", v = 1);
50+
println!("{v:v$.v$}", v = 1);
51+
println!("{v:v$.v$}", v = 1);
52+
println!("{v:v$.v$}", v = 1);
53+
println!("{w:w$}", w = 1);
54+
println!("{p:.p$}", p = 1);
55+
println!("{:p$.w$}", 1, w = 1, p = 1);
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
#![allow(unused_must_use)]
3+
#![allow(named_arguments_used_positionally)] // Unstable at time of writing.
4+
#![warn(clippy::positional_named_format_parameters)]
5+
6+
use std::io::Write;
7+
8+
fn main() {
9+
let mut v = Vec::new();
10+
let hello = "Hello";
11+
12+
println!("{hello:.foo$}", foo = 2);
13+
writeln!(v, "{hello:.foo$}", foo = 2);
14+
15+
// Warnings
16+
println!("{} {1:?}", zero = 0, one = 1);
17+
println!("This is a test { } {000001:?}", zero = 0, one = 1);
18+
println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01);
19+
println!("Hello {1:0$}!", zero = 5, one = 1);
20+
println!("Hello {0:1$}!", zero = 4, one = 1);
21+
println!("Hello {0:01$}!", zero = 4, one = 1);
22+
println!("Hello is {1:.*}", zero = 5, one = 0.01);
23+
println!("Hello is {:<6.*}", zero = 5, one = 0.01);
24+
println!("{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello);
25+
println!("Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01);
26+
println!("Hello {world} {}!", world = 5);
27+
28+
writeln!(v, "{} {1:?}", zero = 0, one = 1);
29+
writeln!(v, "This is a test { } {000001:?}", zero = 0, one = 1);
30+
writeln!(v, "Hello {1} is {2:.0$}", zero = 5, one = hello, two = 0.01);
31+
writeln!(v, "Hello {1:0$}!", zero = 4, one = 1);
32+
writeln!(v, "Hello {0:1$}!", zero = 4, one = 1);
33+
writeln!(v, "Hello {0:01$}!", zero = 4, one = 1);
34+
writeln!(v, "Hello is {1:.*}", zero = 3, one = 0.01);
35+
writeln!(v, "Hello is {:<6.*}", zero = 2, one = 0.01);
36+
writeln!(v, "{}, `{two:>8.*}` has 3", zero = hello, one = 3, two = hello);
37+
writeln!(v, "Hello {1} is {2:.0$}", zero = 1, one = hello, two = 0.01);
38+
writeln!(v, "Hello {world} {}!", world = 0);
39+
40+
// Tests from other files
41+
println!("{:w$}", w = 1);
42+
println!("{:.p$}", p = 1);
43+
println!("{}", v = 1);
44+
println!("{:0$}", v = 1);
45+
println!("{0:0$}", v = 1);
46+
println!("{:0$.0$}", v = 1);
47+
println!("{0:0$.0$}", v = 1);
48+
println!("{0:0$.v$}", v = 1);
49+
println!("{0:v$.0$}", v = 1);
50+
println!("{v:0$.0$}", v = 1);
51+
println!("{v:v$.0$}", v = 1);
52+
println!("{v:0$.v$}", v = 1);
53+
println!("{:w$}", w = 1);
54+
println!("{:.p$}", p = 1);
55+
println!("{:p$.w$}", 1, w = 1, p = 1);
56+
}

0 commit comments

Comments
 (0)