Improve position named arguments lint underline and formatting names

For named arguments used as implicit position arguments, underline both
the opening curly brace and either:
* if there is formatting, the next character (which will either be the
  closing curl brace or the `:` denoting the start of formatting args)
* if there is no formatting, the entire arg span (important if there is
  whitespace like `{  }`)

This should make it more obvious where the named argument should be.

Additionally, in the lint message, emit the formatting argument names
without a dollar sign to avoid potentially confusion.

Fixes #99907
This commit is contained in:
Preston From 2022-07-29 22:52:46 -06:00
parent 9d5cd21a5d
commit d0ea440dfe
8 changed files with 305 additions and 136 deletions

View file

@ -16,6 +16,7 @@ use smallvec::SmallVec;
use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
use rustc_parse_format::Count;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
@ -57,26 +58,47 @@ struct PositionalNamedArg {
replacement: Symbol,
/// The span for the positional named argument (so the lint can point a message to it)
positional_named_arg_span: Span,
has_formatting: bool,
}
impl PositionalNamedArg {
/// Determines what span to replace with the name of the named argument
fn get_span_to_replace(&self, cx: &Context<'_, '_>) -> Option<Span> {
/// Determines:
/// 1) span to be replaced with the name of the named argument and
/// 2) span to be underlined for error messages
fn get_positional_arg_spans(&self, cx: &Context<'_, '_>) -> (Option<Span>, Option<Span>) {
if let Some(inner_span) = &self.inner_span_to_replace {
return Some(
cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end }),
);
let span =
cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end });
(Some(span), Some(span))
} else if self.ty == PositionalNamedArgType::Arg {
// In the case of a named argument whose position is implicit, there will not be a span
// to replace. Instead, we insert the name after the `{`, which is the first character
// of arg_span.
return cx
.arg_spans
.get(self.cur_piece)
.map(|arg_span| arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo());
// In the case of a named argument whose position is implicit, if the argument *has*
// formatting, there will not be a span to replace. Instead, we insert the name after
// the `{`, which will be the first character of arg_span. If the argument does *not*
// have formatting, there may or may not be a span to replace. This is because
// whitespace is allowed in arguments without formatting (such as `format!("{ }", 1);`)
// but is not allowed in arguments with formatting (an error will be generated in cases
// like `format!("{ :1.1}", 1.0f32);`.
// For the message span, if there is formatting, we want to use the opening `{` and the
// next character, which will the `:` indicating the start of formatting. If there is
// not any formatting, we want to underline the entire span.
if self.has_formatting {
cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| {
(
Some(arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo()),
Some(arg_span.with_hi(arg_span.lo() + BytePos(2))),
)
})
} else {
cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| {
let replace_start = arg_span.lo() + BytePos(1);
let replace_end = arg_span.hi() - BytePos(1);
let to_replace = arg_span.with_lo(replace_start).with_hi(replace_end);
(Some(to_replace), Some(*arg_span))
})
}
} else {
(None, None)
}
None
}
}
@ -117,10 +139,18 @@ impl PositionalNamedArgsLint {
cur_piece: usize,
inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
names: &FxHashMap<Symbol, (usize, Span)>,
has_formatting: bool,
) {
let start_of_named_args = total_args_length - names.len();
if current_positional_arg >= start_of_named_args {
self.maybe_push(format_argument_index, ty, cur_piece, inner_span_to_replace, names)
self.maybe_push(
format_argument_index,
ty,
cur_piece,
inner_span_to_replace,
names,
has_formatting,
)
}
}
@ -134,6 +164,7 @@ impl PositionalNamedArgsLint {
cur_piece: usize,
inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
names: &FxHashMap<Symbol, (usize, Span)>,
has_formatting: bool,
) {
let named_arg = names
.iter()
@ -156,6 +187,7 @@ impl PositionalNamedArgsLint {
inner_span_to_replace,
replacement,
positional_named_arg_span,
has_formatting,
});
}
}
@ -414,6 +446,9 @@ impl<'a, 'b> Context<'a, 'b> {
PositionalNamedArgType::Precision,
);
let has_precision = arg.format.precision != Count::CountImplied;
let has_width = arg.format.width != Count::CountImplied;
// argument second, if it's an implicit positional parameter
// it's written second, so it should come after width/precision.
let pos = match arg.position {
@ -426,6 +461,7 @@ impl<'a, 'b> Context<'a, 'b> {
self.curpiece,
arg_end,
&self.names,
has_precision || has_width,
);
Exact(i)
@ -439,6 +475,7 @@ impl<'a, 'b> Context<'a, 'b> {
self.curpiece,
None,
&self.names,
has_precision || has_width,
);
Exact(i)
}
@ -529,6 +566,7 @@ impl<'a, 'b> Context<'a, 'b> {
self.curpiece,
*inner_span,
&self.names,
true,
);
self.verify_arg_type(Exact(i), Count);
}
@ -1150,24 +1188,22 @@ pub fn expand_format_args_nl<'cx>(
fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) {
for named_arg in &cx.unused_names_lint.positional_named_args {
let arg_span = named_arg.get_span_to_replace(cx);
let (position_sp_to_replace, position_sp_for_msg) = named_arg.get_positional_arg_spans(cx);
let msg = format!("named argument `{}` is not used by name", named_arg.replacement);
let replacement = match named_arg.ty {
PositionalNamedArgType::Arg => named_arg.replacement.to_string(),
_ => named_arg.replacement.to_string() + "$",
};
cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
span: MultiSpan::from_span(named_arg.positional_named_arg_span),
msg: msg.clone(),
node_id: ast::CRATE_NODE_ID,
lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally(
arg_span,
named_arg.positional_named_arg_span,
replacement,
),
diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
position_sp_to_replace,
position_sp_for_msg,
named_arg_sp: named_arg.positional_named_arg_span,
named_arg_name: named_arg.replacement.to_string(),
is_formatting_arg: named_arg.ty != PositionalNamedArgType::Arg,
},
});
}
}