diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58ea0f9ab9d2..e4607b564d55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2734,6 +2734,7 @@ Released 2018-09-13
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
+[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
@@ -3011,6 +3012,7 @@ Released 2018-09-13
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
+[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs
index 508cac33848f..bc3163a4e6c7 100644
--- a/clippy_lints/src/format.rs
+++ b/clippy_lints/src/format.rs
@@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::format::{check_unformatted, is_display_arg};
use clippy_utils::higher::FormatExpn;
-use clippy_utils::last_path_segment;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
-use rustc_hir::{BorrowKind, Expr, ExprKind, QPath};
+use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -106,47 +106,3 @@ fn span_useless_format(cx: &LateContext<'_>, span: Span, mut sugg: String, mut a
applicability,
);
}
-
-fn is_display_arg(expr: &Expr<'_>) -> bool {
- if_chain! {
- if let ExprKind::Call(_, [_, fmt]) = expr.kind;
- if let ExprKind::Path(QPath::Resolved(_, path)) = fmt.kind;
- if let [.., t, _] = path.segments;
- if t.ident.name.as_str() == "Display";
- then { true } else { false }
- }
-}
-
-/// Checks if the expression matches
-/// ```rust,ignore
-/// &[_ {
-/// format: _ {
-/// width: _::Implied,
-/// precision: _::Implied,
-/// ...
-/// },
-/// ...,
-/// }]
-/// ```
-fn check_unformatted(expr: &Expr<'_>) -> bool {
- if_chain! {
- if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
- if let ExprKind::Array([expr]) = expr.kind;
- // struct `core::fmt::rt::v1::Argument`
- if let ExprKind::Struct(_, fields, _) = expr.kind;
- if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
- // struct `core::fmt::rt::v1::FormatSpec`
- if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
- if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
- if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
- if last_path_segment(precision_path).ident.name == sym::Implied;
- if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width);
- if let ExprKind::Path(ref width_qpath) = width_field.expr.kind;
- if last_path_segment(width_qpath).ident.name == sym::Implied;
- then {
- return true;
- }
- }
-
- false
-}
diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs
new file mode 100644
index 000000000000..edee93d189f2
--- /dev/null
+++ b/clippy_lints/src/format_args.rs
@@ -0,0 +1,262 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::format::{check_unformatted, is_display_arg};
+use clippy_utils::higher::{FormatArgsExpn, FormatExpn};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{get_trait_def_id, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{BytePos, ExpnKind, Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on `format!` within the arguments of another macro that does
+ /// formatting such as `format!` itself, `write!` or `println!`. Suggests
+ /// inlining the `format!` call.
+ ///
+ /// ### Why is this bad?
+ /// The recommended code is both shorter and avoids a temporary allocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: {}", format!("something failed at {}", Location::caller()));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
+ pub FORMAT_IN_FORMAT_ARGS,
+ perf,
+ "`format!` used in a macro that does formatting"
+}
+
+declare_lint_pass!(FormatInFormatArgs => [FORMAT_IN_FORMAT_ARGS]);
+
+impl<'tcx> LateLintPass<'tcx> for FormatInFormatArgs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ check_expr(cx, expr, format_in_format_args);
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
+ /// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+ /// in a macro that does formatting.
+ ///
+ /// ### Why is this bad?
+ /// Since the type implements `Display`, the use of `to_string` is
+ /// unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller().to_string());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
+ pub TO_STRING_IN_FORMAT_ARGS,
+ perf,
+ "`to_string` applied to a type that implements `Display` in format args"
+}
+
+declare_lint_pass!(ToStringInFormatArgs => [TO_STRING_IN_FORMAT_ARGS]);
+
+impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ check_expr(cx, expr, to_string_in_format_args);
+ }
+}
+
+fn check_expr<'tcx, F>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, check_value: F)
+where
+ F: Fn(&LateContext<'_>, &FormatArgsExpn<'_>, Span, Symbol, usize, &Expr<'_>) -> bool,
+{
+ if_chain! {
+ if let Some(format_args) = FormatArgsExpn::parse(expr);
+ let call_site = expr.span.ctxt().outer_expn_data().call_site;
+ if call_site.from_expansion();
+ let expn_data = call_site.ctxt().outer_expn_data();
+ if let ExpnKind::Macro(_, name) = expn_data.kind;
+ if format_args.fmt_expr.map_or(true, check_unformatted);
+ then {
+ assert_eq!(format_args.args.len(), format_args.value_args.len());
+ for (i, (arg, value)) in format_args.args.iter().zip(format_args.value_args.iter()).enumerate() {
+ if !is_display_arg(arg) {
+ continue;
+ }
+ if check_value(cx, &format_args, expn_data.call_site, name, i, value) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+fn format_in_format_args(
+ cx: &LateContext<'_>,
+ format_args: &FormatArgsExpn<'_>,
+ call_site: Span,
+ name: Symbol,
+ i: usize,
+ value: &Expr<'_>,
+) -> bool {
+ if_chain! {
+ if let Some(FormatExpn{ format_args: inner_format_args, .. }) = FormatExpn::parse(value);
+ if let Some(format_string) = snippet_opt(cx, format_args.format_string_span);
+ if let Some(inner_format_string) = snippet_opt(cx, inner_format_args.format_string_span);
+ if let Some((sugg, applicability)) = format_in_format_args_sugg(
+ cx,
+ name,
+ &format_string,
+ &format_args.value_args,
+ i,
+ &inner_format_string,
+ &inner_format_args.value_args
+ );
+ then {
+ span_lint_and_sugg(
+ cx,
+ FORMAT_IN_FORMAT_ARGS,
+ trim_semicolon(cx, call_site),
+ &format!("`format!` in `{}!` args", name),
+ "inline the `format!` call",
+ sugg,
+ applicability,
+ );
+ // Report only the first instance.
+ return true;
+ }
+ }
+ false
+}
+
+fn to_string_in_format_args(
+ cx: &LateContext<'_>,
+ _format_args: &FormatArgsExpn<'_>,
+ _call_site: Span,
+ name: Symbol,
+ _i: usize,
+ value: &Expr<'_>,
+) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(_, _, args, _) = value.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
+ if match_def_path(cx, method_def_id, &paths::TO_STRING_METHOD);
+ if let Some(receiver) = args.get(0);
+ let ty = cx.typeck_results().expr_ty(receiver);
+ if let Some(display_trait_id) = get_trait_def_id(cx, &paths::DISPLAY_TRAIT);
+ if implements_trait(cx, ty, display_trait_id, &[]);
+ if let Some(snippet) = snippet_opt(cx, value.span);
+ if let Some(dot) = snippet.rfind('.');
+ then {
+ let span = value.span.with_lo(
+ value.span.lo() + BytePos(u32::try_from(dot).unwrap())
+ );
+ span_lint_and_sugg(
+ cx,
+ TO_STRING_IN_FORMAT_ARGS,
+ span,
+ &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ false
+}
+
+fn format_in_format_args_sugg(
+ cx: &LateContext<'_>,
+ name: Symbol,
+ format_string: &str,
+ values: &[&Expr<'_>],
+ i: usize,
+ inner_format_string: &str,
+ inner_values: &[&Expr<'_>],
+) -> Option<(String, Applicability)> {
+ let (left, right) = split_format_string(format_string, i);
+ // If the inner format string is raw, the user is on their own.
+ let (new_format_string, applicability) = if inner_format_string.starts_with('r') {
+ (left + ".." + &right, Applicability::HasPlaceholders)
+ } else {
+ (
+ left + &trim_quotes(inner_format_string) + &right,
+ Applicability::MachineApplicable,
+ )
+ };
+ let values = values
+ .iter()
+ .map(|value| snippet_opt(cx, value.span))
+ .collect::