1
Fork 0

Turn format arguments Vec into its own struct.

With efficient lookup through a hash map.
This commit is contained in:
Mara Bos 2022-09-06 23:15:13 +02:00
parent 14065639ca
commit cf53fef0d6
3 changed files with 146 additions and 84 deletions

View file

@ -45,14 +45,14 @@ use PositionUsedAs::*;
/// If parsing succeeds, the return value is: /// If parsing succeeds, the return value is:
/// ///
/// ```text /// ```text
/// Some((fmtstr, parsed arguments)) /// Ok((fmtstr, parsed arguments))
/// ``` /// ```
fn parse_args<'a>( fn parse_args<'a>(
ecx: &mut ExtCtxt<'a>, ecx: &mut ExtCtxt<'a>,
sp: Span, sp: Span,
tts: TokenStream, tts: TokenStream,
) -> PResult<'a, (P<Expr>, Vec<(P<Expr>, FormatArgKind)>)> { ) -> PResult<'a, (P<Expr>, FormatArguments)> {
let mut args = Vec::<(P<Expr>, FormatArgKind)>::new(); let mut args = FormatArguments::new();
let mut p = ecx.new_parser_from_tts(tts); let mut p = ecx.new_parser_from_tts(tts);
@ -81,7 +81,6 @@ fn parse_args<'a>(
}; };
let mut first = true; let mut first = true;
let mut named = false;
while p.token != token::Eof { while p.token != token::Eof {
if !p.eat(&token::Comma) { if !p.eat(&token::Comma) {
@ -113,40 +112,40 @@ fn parse_args<'a>(
} // accept trailing commas } // accept trailing commas
match p.token.ident() { match p.token.ident() {
Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
named = true;
p.bump(); p.bump();
p.expect(&token::Eq)?; p.expect(&token::Eq)?;
let e = p.parse_expr()?; let expr = p.parse_expr()?;
if let Some(prev) = if let Some((_, prev)) = args.by_name(ident.name) {
args.iter().rev().map_while(|a| a.1.ident()).find(|n| n.name == ident.name)
{
ecx.struct_span_err( ecx.struct_span_err(
ident.span, ident.span,
&format!("duplicate argument named `{}`", ident), &format!("duplicate argument named `{}`", ident),
) )
.span_label(prev.span, "previously here") .span_label(prev.kind.ident().unwrap().span, "previously here")
.span_label(ident.span, "duplicate argument") .span_label(ident.span, "duplicate argument")
.emit(); .emit();
continue; continue;
} }
args.push((e, FormatArgKind::Named(ident))); args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
} }
_ => { _ => {
let e = p.parse_expr()?; let expr = p.parse_expr()?;
if named { if !args.named_args().is_empty() {
let mut err = ecx.struct_span_err( let mut err = ecx.struct_span_err(
e.span, expr.span,
"positional arguments cannot follow named arguments", "positional arguments cannot follow named arguments",
); );
err.span_label(e.span, "positional arguments must be before named arguments"); err.span_label(
for arg in &args { expr.span,
if let Some(name) = arg.1.ident() { "positional arguments must be before named arguments",
err.span_label(name.span.to(arg.0.span), "named argument"); );
for arg in args.named_args() {
if let Some(name) = arg.kind.ident() {
err.span_label(name.span.to(arg.expr.span), "named argument");
} }
} }
err.emit(); err.emit();
} }
args.push((e, FormatArgKind::Normal)); args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
} }
} }
} }
@ -156,12 +155,9 @@ fn parse_args<'a>(
pub fn make_format_args( pub fn make_format_args(
ecx: &mut ExtCtxt<'_>, ecx: &mut ExtCtxt<'_>,
efmt: P<Expr>, efmt: P<Expr>,
mut args: Vec<(P<Expr>, FormatArgKind)>, mut args: FormatArguments,
append_newline: bool, append_newline: bool,
) -> Result<FormatArgs, ()> { ) -> Result<FormatArgs, ()> {
let start_of_named_args =
args.iter().position(|arg| arg.1.ident().is_some()).unwrap_or(args.len());
let msg = "format argument must be a string literal"; let msg = "format argument must be a string literal";
let fmt_span = efmt.span; let fmt_span = efmt.span;
let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
@ -172,9 +168,9 @@ pub fn make_format_args(
Ok(fmt) => fmt, Ok(fmt) => fmt,
Err(err) => { Err(err) => {
if let Some((mut err, suggested)) = err { if let Some((mut err, suggested)) = err {
let sugg_fmt = match args.len() { let sugg_fmt = match args.explicit_args().len() {
0 => "{}".to_string(), 0 => "{}".to_string(),
_ => format!("{}{{}}", "{} ".repeat(args.len())), _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
}; };
if !suggested { if !suggested {
err.span_suggestion( err.span_suggestion(
@ -243,14 +239,14 @@ pub fn make_format_args(
let captured_arg_span = let captured_arg_span =
fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
let span = match args[..start_of_named_args].last() { let span = match args.unnamed_args().last() {
Some(arg) => arg.0.span, Some(arg) => arg.expr.span,
None => fmt_span, None => fmt_span,
}; };
e.multipart_suggestion_verbose( e.multipart_suggestion_verbose(
"consider using a positional formatting argument instead", "consider using a positional formatting argument instead",
vec![ vec![
(captured_arg_span, start_of_named_args.to_string()), (captured_arg_span, args.unnamed_args().len().to_string()),
(span.shrink_to_hi(), format!(", {}", arg)), (span.shrink_to_hi(), format!(", {}", arg)),
], ],
Applicability::MachineApplicable, Applicability::MachineApplicable,
@ -267,8 +263,7 @@ pub fn make_format_args(
}) })
}; };
let num_explicit_args = args.len(); let mut used = vec![false; args.explicit_args().len()];
let mut used = vec![false; num_explicit_args];
let mut invalid_refs = Vec::new(); let mut invalid_refs = Vec::new();
let mut numeric_refences_to_named_arg = Vec::new(); let mut numeric_refences_to_named_arg = Vec::new();
@ -285,32 +280,24 @@ pub fn make_format_args(
-> FormatArgPosition { -> FormatArgPosition {
let index = match arg { let index = match arg {
Index(index) => { Index(index) => {
match args.get(index) { if let Some(arg) = args.by_index(index) {
Some((_, FormatArgKind::Normal)) => {
used[index] = true;
Ok(index)
}
Some((_, FormatArgKind::Named(_))) => {
used[index] = true; used[index] = true;
if arg.kind.ident().is_some() {
// This was a named argument, but it was used as a positional argument.
numeric_refences_to_named_arg.push((index, span, used_as)); numeric_refences_to_named_arg.push((index, span, used_as));
Ok(index)
} }
Some((_, FormatArgKind::Captured(_))) | None => { Ok(index)
} else {
// Doesn't exist as an explicit argument. // Doesn't exist as an explicit argument.
invalid_refs.push((index, span, used_as, kind)); invalid_refs.push((index, span, used_as, kind));
Err(index) Err(index)
} }
} }
}
Name(name, span) => { Name(name, span) => {
let name = Symbol::intern(name); let name = Symbol::intern(name);
if let Some(i) = args[start_of_named_args..] if let Some((index, _)) = args.by_name(name) {
.iter() // Name found in `args`, so we resolve it to its index.
.position(|arg| arg.1.ident().is_some_and(|id| id.name == name)) if index < args.explicit_args().len() {
{
// Name found in `args`, so we resolve it to its index in that Vec.
let index = start_of_named_args + i;
if !matches!(args[index].1, FormatArgKind::Captured(_)) {
// Mark it as used, if it was an explicit argument. // Mark it as used, if it was an explicit argument.
used[index] = true; used[index] = true;
} }
@ -319,7 +306,7 @@ pub fn make_format_args(
// Name not found in `args`, so we add it as an implicitly captured argument. // Name not found in `args`, so we add it as an implicitly captured argument.
let span = span.unwrap_or(fmt_span); let span = span.unwrap_or(fmt_span);
let ident = Ident::new(name, span); let ident = Ident::new(name, span);
let arg = if is_literal { let expr = if is_literal {
ecx.expr_ident(span, ident) ecx.expr_ident(span, ident)
} else { } else {
// For the moment capturing variables from format strings expanded from macros is // For the moment capturing variables from format strings expanded from macros is
@ -330,8 +317,7 @@ pub fn make_format_args(
.emit(); .emit();
DummyResult::raw_expr(span, true) DummyResult::raw_expr(span, true)
}; };
args.push((arg, FormatArgKind::Captured(ident))); Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
Ok(args.len() - 1)
} }
} }
}; };
@ -466,15 +452,7 @@ pub fn make_format_args(
} }
if !invalid_refs.is_empty() { if !invalid_refs.is_empty() {
report_invalid_references( report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
ecx,
&invalid_refs,
&template,
fmt_span,
num_explicit_args,
&args,
parser,
);
} }
let unused = used let unused = used
@ -482,19 +460,19 @@ pub fn make_format_args(
.enumerate() .enumerate()
.filter(|&(_, used)| !used) .filter(|&(_, used)| !used)
.map(|(i, _)| { .map(|(i, _)| {
let msg = if let FormatArgKind::Named(_) = args[i].1 { let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
"named argument never used" "named argument never used"
} else { } else {
"argument never used" "argument never used"
}; };
(args[i].0.span, msg) (args.explicit_args()[i].expr.span, msg)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !unused.is_empty() { if !unused.is_empty() {
// If there's a lot of unused arguments, // If there's a lot of unused arguments,
// let's check if this format arguments looks like another syntax (printf / shell). // let's check if this format arguments looks like another syntax (printf / shell).
let detect_foreign_fmt = unused.len() > num_explicit_args / 2; let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span); report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span);
} }
@ -511,7 +489,7 @@ pub fn make_format_args(
} }
Width => (span, span), Width => (span, span),
}; };
let arg_name = args[index].1.ident().unwrap(); let arg_name = args.explicit_args()[index].kind.ident().unwrap();
ecx.buffered_early_lint.push(BufferedEarlyLint { ecx.buffered_early_lint.push(BufferedEarlyLint {
span: arg_name.span.into(), span: arg_name.span.into(),
msg: format!("named argument `{}` is not used by name", arg_name.name).into(), msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
@ -695,11 +673,10 @@ fn report_invalid_references(
invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
template: &[FormatArgsPiece], template: &[FormatArgsPiece],
fmt_span: Span, fmt_span: Span,
num_explicit_args: usize, args: &FormatArguments,
args: &[(P<Expr>, FormatArgKind)],
parser: parse::Parser<'_>, parser: parse::Parser<'_>,
) { ) {
let num_args_desc = match num_explicit_args { let num_args_desc = match args.explicit_args().len() {
0 => "no arguments were given".to_string(), 0 => "no arguments were given".to_string(),
1 => "there is 1 argument".to_string(), 1 => "there is 1 argument".to_string(),
n => format!("there are {} arguments", n), n => format!("there are {} arguments", n),
@ -785,8 +762,8 @@ fn report_invalid_references(
num_args_desc, num_args_desc,
), ),
); );
for (arg, _) in &args[..num_explicit_args] { for arg in args.explicit_args() {
e.span_label(arg.span, ""); e.span_label(arg.expr.span, "");
} }
// Point out `{:.*}` placeholders: those take an extra argument. // Point out `{:.*}` placeholders: those take an extra argument.
let mut has_precision_star = false; let mut has_precision_star = false;

View file

@ -1,5 +1,6 @@
use rustc_ast::ptr::P; use rustc_ast::ptr::P;
use rustc_ast::Expr; use rustc_ast::Expr;
use rustc_data_structures::fx::FxHashMap;
use rustc_span::symbol::{Ident, Symbol}; use rustc_span::symbol::{Ident, Symbol};
use rustc_span::Span; use rustc_span::Span;
@ -42,17 +43,97 @@ use rustc_span::Span;
pub struct FormatArgs { pub struct FormatArgs {
pub span: Span, pub span: Span,
pub template: Vec<FormatArgsPiece>, pub template: Vec<FormatArgsPiece>,
pub arguments: Vec<(P<Expr>, FormatArgKind)>, pub arguments: FormatArguments,
} }
/// A piece of a format template string.
///
/// E.g. "hello" or "{name}".
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum FormatArgsPiece { pub enum FormatArgsPiece {
Literal(Symbol), Literal(Symbol),
Placeholder(FormatPlaceholder), Placeholder(FormatPlaceholder),
} }
/// The arguments to format_args!().
///
/// E.g. `1, 2, name="ferris", n=3`,
/// but also implicit captured arguments like `x` in `format_args!("{x}")`.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum FormatArgKind { pub struct FormatArguments {
arguments: Vec<FormatArgument>,
num_unnamed_args: usize,
num_explicit_args: usize,
names: FxHashMap<Symbol, usize>,
}
impl FormatArguments {
pub fn new() -> Self {
Self {
arguments: Vec::new(),
names: FxHashMap::default(),
num_unnamed_args: 0,
num_explicit_args: 0,
}
}
pub fn add(&mut self, arg: FormatArgument) -> usize {
let index = self.arguments.len();
if let Some(name) = arg.kind.ident() {
self.names.insert(name.name, index);
} else if self.names.is_empty() {
// Only count the unnamed args before the first named arg.
// (Any later ones are errors.)
self.num_unnamed_args += 1;
}
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
// This is an explicit argument.
// Make sure that all arguments so far are explcit.
assert_eq!(
self.num_explicit_args,
self.arguments.len(),
"captured arguments must be added last"
);
self.num_explicit_args += 1;
}
self.arguments.push(arg);
index
}
pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> {
let i = *self.names.get(&name)?;
Some((i, &self.arguments[i]))
}
pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
(i < self.num_explicit_args).then(|| &self.arguments[i])
}
pub fn unnamed_args(&self) -> &[FormatArgument] {
&self.arguments[..self.num_unnamed_args]
}
pub fn named_args(&self) -> &[FormatArgument] {
&self.arguments[self.num_unnamed_args..self.num_explicit_args]
}
pub fn explicit_args(&self) -> &[FormatArgument] {
&self.arguments[..self.num_explicit_args]
}
pub fn into_vec(self) -> Vec<FormatArgument> {
self.arguments
}
}
#[derive(Clone, Debug)]
pub struct FormatArgument {
pub kind: FormatArgumentKind,
pub expr: P<Expr>,
}
#[derive(Clone, Debug)]
pub enum FormatArgumentKind {
/// `format_args(…, arg)` /// `format_args(…, arg)`
Normal, Normal,
/// `format_args(…, arg = 1)` /// `format_args(…, arg = 1)`
@ -61,7 +142,7 @@ pub enum FormatArgKind {
Captured(Ident), Captured(Ident),
} }
impl FormatArgKind { impl FormatArgumentKind {
pub fn ident(&self) -> Option<Ident> { pub fn ident(&self) -> Option<Ident> {
match self { match self {
&Self::Normal => None, &Self::Normal => None,

View file

@ -201,13 +201,15 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as
) )
}); });
let arguments = fmt.arguments.into_vec();
// If the args array contains exactly all the original arguments once, // If the args array contains exactly all the original arguments once,
// in order, we can use a simple array instead of a `match` construction. // in order, we can use a simple array instead of a `match` construction.
// However, if there's a yield point in any argument except the first one, // However, if there's a yield point in any argument except the first one,
// we don't do this, because an ArgumentV1 cannot be kept across yield points. // we don't do this, because an ArgumentV1 cannot be kept across yield points.
let use_simple_array = argmap.len() == fmt.arguments.len() let use_simple_array = argmap.len() == arguments.len()
&& argmap.iter().enumerate().all(|(i, &(j, _))| i == j) && argmap.iter().enumerate().all(|(i, &(j, _))| i == j)
&& fmt.arguments.iter().skip(1).all(|(arg, _)| !may_contain_yield_point(arg)); && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
let args = if use_simple_array { let args = if use_simple_array {
// Generate: // Generate:
@ -218,12 +220,12 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as
// ] // ]
ecx.expr_array_ref( ecx.expr_array_ref(
macsp, macsp,
fmt.arguments arguments
.into_iter() .into_iter()
.zip(argmap) .zip(argmap)
.map(|((arg, _), (_, ty))| { .map(|(arg, (_, ty))| {
let sp = arg.span.with_ctxt(macsp.ctxt()); let sp = arg.expr.span.with_ctxt(macsp.ctxt());
make_argument(ecx, sp, ecx.expr_addr_of(sp, arg), ty) make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty)
}) })
.collect(), .collect(),
) )
@ -240,8 +242,8 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as
let args = argmap let args = argmap
.iter() .iter()
.map(|&(arg_index, ty)| { .map(|&(arg_index, ty)| {
if let Some((arg, _)) = fmt.arguments.get(arg_index) { if let Some(arg) = arguments.get(arg_index) {
let sp = arg.span.with_ctxt(macsp.ctxt()); let sp = arg.expr.span.with_ctxt(macsp.ctxt());
make_argument( make_argument(
ecx, ecx,
sp, sp,
@ -263,9 +265,11 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as
macsp, macsp,
ecx.expr_tuple( ecx.expr_tuple(
macsp, macsp,
fmt.arguments arguments
.into_iter() .into_iter()
.map(|(arg, _)| ecx.expr_addr_of(arg.span.with_ctxt(macsp.ctxt()), arg)) .map(|arg| {
ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr)
})
.collect(), .collect(),
), ),
vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))], vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))],