Skip to content

Migrate format_args.rs to rustc_ast::FormatArgs #10484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 28, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clippy_lints/Cargo.toml
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ keywords = ["clippy", "lint", "plugin"]
edition = "2021"

[dependencies]
arrayvec = { version = "0.7", default-features = false }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clippy_utils depends on this already so it's not a new dependency

cargo_metadata = "0.15.3"
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
210 changes: 124 additions & 86 deletions clippy_lints/src/format_args.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
use arrayvec::ArrayVec;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred};
use clippy_utils::macros::{
is_assert_macro, is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam,
FormatParamUsage,
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage,
};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
FormatPlaceholder, FormatTrait,
};
use rustc_errors::{
Applicability,
SuggestionStyle::{CompletelyHidden, ShowCode},
};
use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
use rustc_span::{sym, Span, Symbol};

declare_clippy_lint! {
/// ### What it does
@@ -184,111 +188,120 @@ impl FormatArgs {

impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(format_args) = FormatArgsExpn::parse(cx, expr)
&& let expr_expn_data = expr.span.ctxt().outer_expn_data()
&& let outermost_expn_data = outermost_expn_data(expr_expn_data)
&& let Some(macro_def_id) = outermost_expn_data.macro_def_id
&& is_format_macro(cx, macro_def_id)
&& let ExpnKind::Macro(_, name) = outermost_expn_data.kind
{
for arg in &format_args.args {
check_unused_format_specifier(cx, arg);
if !arg.format.is_default() {
continue;
}
if is_aliased(&format_args, arg.param.value.hir_id) {
continue;
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if !is_format_macro(cx, macro_call.def_id) {
return;
}
let name = cx.tcx.item_name(macro_call.def_id);

find_format_args(cx, expr, macro_call.expn, |format_args| {
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index
&& let Some(arg) = format_args.arguments.all_args().get(index)
{
let arg_expr = find_format_arg_expr(expr, arg);

check_unused_format_specifier(cx, placeholder, arg_expr);

if placeholder.format_trait != FormatTrait::Display
|| placeholder.format_options != FormatOptions::default()
|| is_aliased(format_args, index)
{
continue;
}

if let Ok(arg_hir_expr) = arg_expr {
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
check_to_string_in_format_args(cx, name, arg_hir_expr);
}
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
check_to_string_in_format_args(cx, name, arg.param.value);
}

if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed);
check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
}
}
});
}

extract_msrv_attr!(LateContext);
}

fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs();
fn check_unused_format_specifier(
cx: &LateContext<'_>,
placeholder: &FormatPlaceholder,
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
) {
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());

if let Count::Implied(Some(mut span)) = arg.format.precision
&& !span.is_empty()
{
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
span,
"empty precision specifier has no effect",
|diag| {
if param_ty.is_floating_point() {
diag.note("a precision specifier is not required to format floats");
}
let is_format_args = match ty_or_ast_expr {
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
};

if arg.format.is_default() {
// If there's no other specifiers remove the `:` too
span = arg.format_span();
}
let options = &placeholder.format_options;

diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable);
},
);
}
let arg_span = match arg_expr {
Ok(expr) => expr.span,
Err(expr) => expr.span,
};

if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() {
if let Some(placeholder_span) = placeholder.span
&& is_format_args
&& *options != FormatOptions::default()
{
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
arg.span,
placeholder_span,
"format specifiers have no effect on `format_args!()`",
|diag| {
let mut suggest_format = |spec, span| {
let mut suggest_format = |spec| {
let message = format!("for the {spec} to apply consider using `format!()`");

if let Some(mac_call) = root_macro_call(arg.param.value.span)
if let Some(mac_call) = root_macro_call(arg_span)
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
&& arg.span.eq_ctxt(mac_call.span)
{
diag.span_suggestion(
cx.sess().source_map().span_until_char(mac_call.span, '!'),
message,
"format",
Applicability::MaybeIncorrect,
);
} else if let Some(span) = span {
diag.span_help(span, message);
} else {
diag.help(message);
}
};

if !arg.format.width.is_implied() {
suggest_format("width", arg.format.width.span());
if options.width.is_some() {
suggest_format("width");
}

if !arg.format.precision.is_implied() {
suggest_format("precision", arg.format.precision.span());
if options.precision.is_some() {
suggest_format("precision");
}

diag.span_suggestion_verbose(
arg.format_span(),
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
if let Some(format_span) = format_placeholder_format_span(placeholder) {
diag.span_suggestion_verbose(
format_span,
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
}
},
);
}
}

fn check_uninlined_args(
cx: &LateContext<'_>,
args: &FormatArgsExpn<'_>,
args: &rustc_ast::FormatArgs,
call_site: Span,
def_id: DefId,
ignore_mixed: bool,
) {
if args.format_string.span.from_expansion() {
if args.span.from_expansion() {
return;
}
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
@@ -303,7 +316,13 @@ fn check_uninlined_args(
// we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() {
for (pos, usage) in format_arg_positions(args) {
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
return;
}
}

if fixes.is_empty() {
return;
}

@@ -332,38 +351,36 @@ fn check_uninlined_args(
}

fn check_one_arg(
args: &FormatArgsExpn<'_>,
param: &FormatParam<'_>,
args: &rustc_ast::FormatArgs,
pos: &FormatArgPosition,
usage: FormatParamUsage,
fixes: &mut Vec<(Span, String)>,
ignore_mixed: bool,
) -> bool {
if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
&& let [segment] = path.segments
let index = pos.index.unwrap();
let arg = &args.arguments.all_args()[index];

if !matches!(arg.kind, FormatArgumentKind::Captured(_))
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
&& let [segment] = path.segments.as_slice()
&& segment.args.is_none()
&& let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
&& let Some(arg_span) = format_arg_removal_span(args, index)
&& let Some(pos_span) = pos.span
{
let replacement = match param.usage {
let replacement = match usage {
FormatParamUsage::Argument => segment.ident.name.to_string(),
FormatParamUsage::Width => format!("{}$", segment.ident.name),
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
};
fixes.push((param.span, replacement));
fixes.push((pos_span, replacement));
fixes.push((arg_span, String::new()));
true // successful inlining, continue checking
} else {
// Do not continue inlining (return false) in case
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_)))
}
}

fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
if expn_data.call_site.from_expansion() {
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
} else {
expn_data
pos.kind != FormatArgPositionKind::Number
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
}
}

@@ -438,10 +455,31 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
}
}

/// Returns true if `hir_id` is referred to by multiple format params
fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
args.params()
.filter(|param| param.value.hir_id == hir_id)
fn format_arg_positions(
format_args: &rustc_ast::FormatArgs,
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
format_args.template.iter().flat_map(|piece| match piece {
FormatArgsPiece::Placeholder(placeholder) => {
let mut positions = ArrayVec::<_, 3>::new();

positions.push((&placeholder.argument, FormatParamUsage::Argument));
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
positions.push((position, FormatParamUsage::Width));
}
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
positions.push((position, FormatParamUsage::Precision));
}

positions
},
FormatArgsPiece::Literal(_) => ArrayVec::new(),
})
}

/// Returns true if the format argument at `index` is referred to by multiple format params
fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool {
format_arg_positions(format_args)
.filter(|(position, _)| position.index == Ok(index))
.at_most_one()
.is_err()
}
79 changes: 77 additions & 2 deletions clippy_lints/src/utils/format_args_collector.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use clippy_utils::macros::collect_ast_format_args;
use rustc_ast::{Expr, ExprKind};
use clippy_utils::source::snippet_opt;
use itertools::Itertools;
use rustc_ast::{Expr, ExprKind, FormatArgs};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene;
use std::iter::once;

declare_clippy_lint! {
/// ### What it does
@@ -15,9 +20,79 @@ declare_clippy_lint! {
declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);

impl EarlyLintPass for FormatArgsCollector {
fn check_expr(&mut self, _: &EarlyContext<'_>, expr: &Expr) {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::FormatArgs(args) = &expr.kind {
if has_span_from_proc_macro(cx, args) {
return;
}

collect_ast_format_args(expr.span, args);
}
}
}

/// Detects if the format string or an argument has its span set by a proc macro to something inside
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: this is neat

/// a macro callsite, e.g.
///
/// ```ignore
/// println!(some_proc_macro!("input {}"), a);
/// ```
///
/// Where `some_proc_macro` expands to
///
/// ```ignore
/// println!("output {}", a);
/// ```
///
/// But with the span of `"output {}"` set to the macro input
///
/// ```ignore
/// println!(some_proc_macro!("input {}"), a);
/// // ^^^^^^^^^^
/// ```
fn has_span_from_proc_macro(cx: &EarlyContext<'_>, args: &FormatArgs) -> bool {
let ctxt = args.span.ctxt();

// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^^^^ ^^^^^ ^^^^^^^
let argument_span = args
.arguments
.explicit_args()
.iter()
.map(|argument| hygiene::walk_chain(argument.expr.span, ctxt));

// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^ ^^ ^^^^^^
let between_spans = once(args.span)
.chain(argument_span)
.tuple_windows()
.map(|(start, end)| start.between(end));

for between_span in between_spans {
let mut seen_comma = false;

let Some(snippet) = snippet_opt(cx, between_span) else { return true };
for token in tokenize(&snippet) {
match token.kind {
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
TokenKind::Comma if !seen_comma => seen_comma = true,
// named arguments, `start_val, name = end_val`
// ^^^^^^^^^ between_span
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
// An unexpected token usually indicates that we crossed a macro boundary
//
// `println!(some_proc_macro!("input {}"), a)`
// ^^^ between_span
// `println!("{}", val!(x))`
// ^^^^^^^ between_span
_ => return true,
}
}

if !seen_comma {
return true;
}
}

false
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a slightly simplified version of

/// Gets the spans of the commas inbetween the format string and explicit args, not including
/// any trailing comma
///
/// ```ignore
/// format!("{} {}", a, b)
/// // ^ ^
/// ```
///
/// Ensures that the format string and values aren't coming from a proc macro that sets the
/// output span to that of its input
fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^^^^ ^^^^^ ^^^^^^^
let value_spans = explicit_values
.iter()
.map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^ ^^ ^^^^^^
let between_spans = once(fmt_span)
.chain(value_spans)
.tuple_windows()
.map(|(start, end)| start.between(end));
let mut comma_spans = Vec::new();
for between_span in between_spans {
let mut offset = 0;
let mut seen_comma = false;
for token in tokenize(&snippet_opt(cx, between_span)?) {
match token.kind {
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
TokenKind::Comma if !seen_comma => {
seen_comma = true;
let base = between_span.data();
comma_spans.push(Span::new(
base.lo + BytePos(offset),
base.lo + BytePos(offset + 1),
base.ctxt,
base.parent,
));
},
// named arguments, `start_val, name = end_val`
// ^^^^^^^^^ between_span
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
// An unexpected token usually indicates the format string or a value came from a proc macro output
// that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
// emits a string literal with the span set to that of `"input"`
_ => return None,
}
offset += token.len;
}
if !seen_comma {
return None;
}
}
Some(comma_spans)
}

73 changes: 47 additions & 26 deletions clippy_utils/src/macros.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
use itertools::{izip, Either, Itertools};
use rustc_ast::ast::LitKind;
use rustc_ast::FormatArgs;
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind};
@@ -391,30 +391,67 @@ pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
});
}

/// Calls `callback` with an AST [`FormatArgs`] node if one is found
/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
/// descendant of `expn_id`
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt == start.span.ctxt() {
ControlFlow::Continue(Descend::Yes)
} else if ctxt.outer_expn().is_descendant_of(expn_id)
&& macro_backtrace(expr.span)
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
{
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(Descend::Yes)
}
} else {
ControlFlow::Continue(Descend::No)
}
});

if let Some(format_args_expr) = format_args_expr {
if let Some(expr) = format_args_expr {
AST_FORMAT_ARGS.with(|ast_format_args| {
ast_format_args.borrow().get(&format_args_expr.span).map(callback);
ast_format_args.borrow().get(&expr.span).map(callback);
});
}
}

/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
/// it cannot be found it will return the [`rustc_ast::Expr`].
pub fn find_format_arg_expr<'hir, 'ast>(
start: &'hir Expr<'hir>,
target: &'ast FormatArgument,
) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
for_each_expr(start, |expr| {
if expr.span == target.expr.span {
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(())
}
})
.ok_or(&target.expr)
}

/// Span of the `:` and format specifiers
///
/// ```ignore
/// format!("{:.}"), format!("{foo:.}")
/// ^^ ^^
/// ```
pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
let base = placeholder.span?.data();

// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
// brace `{...|}`
Some(Span::new(
placeholder.argument.span?.hi(),
base.hi - BytePos(1),
base.ctxt,
base.parent,
))
}

/// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
/// `10`
///
@@ -897,22 +934,6 @@ pub struct FormatArg<'tcx> {
pub span: Span,
}

impl<'tcx> FormatArg<'tcx> {
/// Span of the `:` and format specifiers
///
/// ```ignore
/// format!("{:.}"), format!("{foo:.}")
/// ^^ ^^
/// ```
pub fn format_span(&self) -> Span {
let base = self.span.data();

// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
// brace `{...|}`
Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
}
}

/// A parsed `format_args!` expansion.
#[derive(Debug)]
pub struct FormatArgsExpn<'tcx> {
Original file line number Diff line number Diff line change
@@ -11,29 +11,29 @@ LL - println!("val='{}'", local_i32);
LL + println!("val='{local_i32}'");
|

error: literal with an empty format string
--> $DIR/uninlined_format_args.rs:10:35
error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:10:5
|
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
| ^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::print-literal` implied by `-D warnings`
help: try this
help: change this to
|
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
LL + println!("Hello x is {:.*}", local_i32, local_f64);
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
|

error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:10:5
error: literal with an empty format string
--> $DIR/uninlined_format_args.rs:10:35
|
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^
|
help: change this to
= note: `-D clippy::print-literal` implied by `-D warnings`
help: try this
|
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
LL + println!("Hello x is {:.*}", local_i32, local_f64);
|

error: variables can be used directly in the `format!` string
2 changes: 1 addition & 1 deletion tests/ui/uninlined_format_args.fixed
Original file line number Diff line number Diff line change
@@ -119,7 +119,7 @@ fn tester(fn_arg: i32) {
println!("Width = {local_i32}, value with width = {local_f64:local_i32$}");
println!("{local_i32:width$.prec$}");
println!("{width:width$.prec$}");
println!("{}", format!("{local_i32}"));
println!("{}", format!("{}", local_i32));
my_println!("{}", local_i32);
my_println_args!("{}", local_i32);

14 changes: 1 addition & 13 deletions tests/ui/uninlined_format_args.stderr
Original file line number Diff line number Diff line change
@@ -774,18 +774,6 @@ LL - println!("{:w$.p$}", w = width, p = prec);
LL + println!("{width:width$.prec$}");
|

error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:125:20
|
LL | println!("{}", format!("{}", local_i32));
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: change this to
|
LL - println!("{}", format!("{}", local_i32));
LL + println!("{}", format!("{local_i32}"));
|

Comment on lines -777 to -788
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug with root_macro_call_first_node, not sure why but it doesn't pick up that format!() as a macro call

error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:143:5
|
@@ -856,5 +844,5 @@ LL - println!("expand='{}'", local_i32);
LL + println!("expand='{local_i32}'");
|

error: aborting due to 72 previous errors
error: aborting due to 71 previous errors

18 changes: 0 additions & 18 deletions tests/ui/unused_format_specs.fixed

This file was deleted.

18 changes: 0 additions & 18 deletions tests/ui/unused_format_specs.rs

This file was deleted.

54 changes: 0 additions & 54 deletions tests/ui/unused_format_specs.stderr

This file was deleted.

12 changes: 2 additions & 10 deletions tests/ui/unused_format_specs_unfixable.stderr
Original file line number Diff line number Diff line change
@@ -37,11 +37,7 @@ error: format specifiers have no effect on `format_args!()`
LL | println!("{:5}.", format_args_from_macro!());
| ^^^^
|
help: for the width to apply consider using `format!()`
--> $DIR/unused_format_specs_unfixable.rs:16:17
|
LL | println!("{:5}.", format_args_from_macro!());
| ^
= help: for the width to apply consider using `format!()`
help: if the current behavior is intentional, remove the format specifiers
|
LL - println!("{:5}.", format_args_from_macro!());
@@ -54,11 +50,7 @@ error: format specifiers have no effect on `format_args!()`
LL | println!("{args:5}");
| ^^^^^^^^
|
help: for the width to apply consider using `format!()`
--> $DIR/unused_format_specs_unfixable.rs:19:21
|
LL | println!("{args:5}");
| ^
= help: for the width to apply consider using `format!()`
help: if the current behavior is intentional, remove the format specifiers
|
LL - println!("{args:5}");