Skip to content

Commit

Permalink
Add implementation and tests for literal checking in print/println fo…
Browse files Browse the repository at this point in the history
…rmat args
  • Loading branch information
Michael Recachinas committed Apr 1, 2018
1 parent 83e2109 commit 62220ab
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 1 deletion.
65 changes: 64 additions & 1 deletion clippy_lints/src/print.rs
Expand Up @@ -78,12 +78,28 @@ declare_clippy_lint! {
"use of `Debug`-based formatting"
}

/// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
///
/// **Why is this bad?** Using literals as `println!` args is inefficient
/// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
/// (i.e., just put the literal in the format string)
///
/// **Example:**
/// ```rust
/// println!("{}", "foo");
/// ```
declare_lint! {
pub PRINT_LITERAL,
Allow,
"printing a literal with a format string"
}

#[derive(Copy, Clone, Debug)]
pub struct Pass;

impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(PRINT_WITH_NEWLINE, PRINTLN_EMPTY_STRING, PRINT_STDOUT, USE_DEBUG)
lint_array!(PRINT_WITH_NEWLINE, PRINTLN_EMPTY_STRING, PRINT_STDOUT, USE_DEBUG, PRINT_LITERAL)
}
}

Expand All @@ -107,6 +123,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {

span_lint(cx, PRINT_STDOUT, span, &format!("use of `{}!`", name));

// Check for literals in the print!/println! args
// Also, ensure the format string is `{}` with no special options, like `{:X}`
check_print_args_for_literal(cx, args);

if_chain! {
// ensure we're calling Arguments::new_v1
if args.len() == 1;
Expand Down Expand Up @@ -146,6 +166,49 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
}
}

// Check for literals in print!/println! args
// ensuring the format string for the literal is `DISPLAY_FMT_METHOD`
// e.g., `println!("... {} ...", "foo")`
// ^ literal in `println!`
fn check_print_args_for_literal<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
args: &HirVec<Expr>
) {
if_chain! {
if args.len() == 1;
if let ExprCall(_, ref args_args) = args[0].node;
if args_args.len() > 1;
if let ExprAddrOf(_, ref match_expr) = args_args[1].node;
if let ExprMatch(ref matchee, ref arms, _) = match_expr.node;
if let ExprTup(ref tup) = matchee.node;
if arms.len() == 1;
if let ExprArray(ref arm_body_exprs) = arms[0].body.node;
then {
// it doesn't matter how many args there are in the `print!`/`println!`,
// if there's one literal, we should warn the user
for (idx, tup_arg) in tup.iter().enumerate() {
if_chain! {
// first, make sure we're dealing with a literal (i.e., an ExprLit)
if let ExprAddrOf(_, ref tup_val) = tup_arg.node;
if let ExprLit(_) = tup_val.node;

// next, check the corresponding match arm body to ensure
// this is "{}", or DISPLAY_FMT_METHOD
if let ExprCall(_, ref body_args) = arm_body_exprs[idx].node;
if body_args.len() == 2;
if let ExprPath(ref body_qpath) = body_args[1].node;
if let Some(fun_def_id) = opt_def_id(resolve_node(cx, body_qpath, body_args[1].hir_id));
if match_def_path(cx.tcx, fun_def_id, &paths::DISPLAY_FMT_METHOD) ||
match_def_path(cx.tcx, fun_def_id, &paths::DEBUG_FMT_METHOD);
then {
span_lint(cx, PRINT_LITERAL, tup_val.span, "printing a literal with an empty format string");
}
}
}
}
}
}

// Check for print!("... \n", ...).
fn check_print<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/print_literal.rs
@@ -0,0 +1,32 @@


#![warn(print_literal)]

fn main() {
// these should be fine
print!("Hello");
println!("Hello");
let world = "world";
println!("Hello {}", world);
println!("3 in hex is {:X}", 3);

// these should throw warnings
print!("Hello {}", "world");
println!("Hello {} {}", world, "world");
println!("Hello {}", "world");
println!("10 / 4 is {}", 2.5);
println!("2 + 1 = {}", 3);
println!("2 + 1 = {:.4}", 3);
println!("2 + 1 = {:5.4}", 3);
println!("Debug test {:?}", "hello, world");

// positional args don't change the fact
// that we're using a literal -- this should
// throw a warning
println!("{0} {1}", "hello", "world");
println!("{1} {0}", "hello", "world");

// named args shouldn't change anything either
println!("{foo} {bar}", foo="hello", bar="world");
println!("{bar} {foo}", foo="hello", bar="world");
}
100 changes: 100 additions & 0 deletions tests/ui/print_literal.stderr
@@ -0,0 +1,100 @@
error: printing a literal with an empty format string
--> $DIR/print_literal.rs:14:24
|
14 | print!("Hello {}", "world");
| ^^^^^^^
|
= note: `-D print-literal` implied by `-D warnings`

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:15:36
|
15 | println!("Hello {} {}", world, "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:16:26
|
16 | println!("Hello {}", "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:17:30
|
17 | println!("10 / 4 is {}", 2.5);
| ^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:18:28
|
18 | println!("2 + 1 = {}", 3);
| ^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:19:31
|
19 | println!("2 + 1 = {:.4}", 3);
| ^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:20:32
|
20 | println!("2 + 1 = {:5.4}", 3);
| ^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:21:33
|
21 | println!("Debug test {:?}", "hello, world");
| ^^^^^^^^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:26:25
|
26 | println!("{0} {1}", "hello", "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:26:34
|
26 | println!("{0} {1}", "hello", "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:27:25
|
27 | println!("{1} {0}", "hello", "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:27:34
|
27 | println!("{1} {0}", "hello", "world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:30:33
|
30 | println!("{foo} {bar}", foo="hello", bar="world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:30:46
|
30 | println!("{foo} {bar}", foo="hello", bar="world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:31:33
|
31 | println!("{bar} {foo}", foo="hello", bar="world");
| ^^^^^^^

error: printing a literal with an empty format string
--> $DIR/print_literal.rs:31:46
|
31 | println!("{bar} {foo}", foo="hello", bar="world");
| ^^^^^^^

error: aborting due to 16 previous errors

0 comments on commit 62220ab

Please sign in to comment.