Auto merge of #33642 - xen0n:ergonomic-format-macro, r=alexcrichton
Ergonomic format_args! Fixes #9456 (at last). Not a ground-up rewrite of the existing machinery, but more like an added intermediary layer between macro arguments and format placeholders. This is now implementing Rust RFC 1618!
This commit is contained in:
commit
db71987ee1
5 changed files with 322 additions and 200 deletions
|
@ -80,8 +80,6 @@ pub struct FormatSpec<'a> {
|
|||
/// Enum describing where an argument for a format can be located.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Position<'a> {
|
||||
/// The argument will be in the next position. This is the default.
|
||||
ArgumentNext,
|
||||
/// The argument is located at a specific index.
|
||||
ArgumentIs(usize),
|
||||
/// The argument has a name.
|
||||
|
@ -127,8 +125,6 @@ pub enum Count<'a> {
|
|||
CountIsName(&'a str),
|
||||
/// The count is specified by the argument at the given index.
|
||||
CountIsParam(usize),
|
||||
/// The count is specified by the next parameter.
|
||||
CountIsNextParam,
|
||||
/// The count is implied and cannot be explicitly specified.
|
||||
CountImplied,
|
||||
}
|
||||
|
@ -144,6 +140,8 @@ pub struct Parser<'a> {
|
|||
cur: iter::Peekable<str::CharIndices<'a>>,
|
||||
/// Error messages accumulated during parsing
|
||||
pub errors: Vec<string::String>,
|
||||
/// Current position of implicit positional argument pointer
|
||||
curarg: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Parser<'a> {
|
||||
|
@ -186,6 +184,7 @@ impl<'a> Parser<'a> {
|
|||
input: s,
|
||||
cur: s.char_indices().peekable(),
|
||||
errors: vec![],
|
||||
curarg: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,21 +258,40 @@ impl<'a> Parser<'a> {
|
|||
/// Parses an Argument structure, or what's contained within braces inside
|
||||
/// the format string
|
||||
fn argument(&mut self) -> Argument<'a> {
|
||||
let pos = self.position();
|
||||
let format = self.format();
|
||||
|
||||
// Resolve position after parsing format spec.
|
||||
let pos = match pos {
|
||||
Some(position) => position,
|
||||
None => {
|
||||
let i = self.curarg;
|
||||
self.curarg += 1;
|
||||
ArgumentIs(i)
|
||||
}
|
||||
};
|
||||
|
||||
Argument {
|
||||
position: self.position(),
|
||||
format: self.format(),
|
||||
position: pos,
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a positional argument for a format. This could either be an
|
||||
/// integer index of an argument, a named argument, or a blank string.
|
||||
fn position(&mut self) -> Position<'a> {
|
||||
/// Returns `Some(parsed_position)` if the position is not implicitly
|
||||
/// consuming a macro argument, `None` if it's the case.
|
||||
fn position(&mut self) -> Option<Position<'a>> {
|
||||
if let Some(i) = self.integer() {
|
||||
ArgumentIs(i)
|
||||
Some(ArgumentIs(i))
|
||||
} else {
|
||||
match self.cur.peek() {
|
||||
Some(&(_, c)) if c.is_alphabetic() => ArgumentNamed(self.word()),
|
||||
_ => ArgumentNext,
|
||||
Some(&(_, c)) if c.is_alphabetic() => Some(ArgumentNamed(self.word())),
|
||||
|
||||
// This is an `ArgumentNext`.
|
||||
// Record the fact and do the resolution after parsing the
|
||||
// format spec, to make things like `{:.*}` work.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +358,11 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
if self.consume('.') {
|
||||
if self.consume('*') {
|
||||
spec.precision = CountIsNextParam;
|
||||
// Resolve `CountIsNextParam`.
|
||||
// We can do this immediately as `position` is resolved later.
|
||||
let i = self.curarg;
|
||||
self.curarg += 1;
|
||||
spec.precision = CountIsParam(i);
|
||||
} else {
|
||||
spec.precision = self.count();
|
||||
}
|
||||
|
@ -487,7 +509,7 @@ mod tests {
|
|||
fn format_nothing() {
|
||||
same("{}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: fmtdflt(),
|
||||
})]);
|
||||
}
|
||||
|
@ -565,7 +587,7 @@ mod tests {
|
|||
fn format_counts() {
|
||||
same("{:10s}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
@ -577,7 +599,7 @@ mod tests {
|
|||
})]);
|
||||
same("{:10$.10s}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
@ -589,19 +611,19 @@ mod tests {
|
|||
})]);
|
||||
same("{:.*s}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(1),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
flags: 0,
|
||||
precision: CountIsNextParam,
|
||||
precision: CountIsParam(0),
|
||||
width: CountImplied,
|
||||
ty: "s",
|
||||
},
|
||||
})]);
|
||||
same("{:.10$s}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
@ -613,7 +635,7 @@ mod tests {
|
|||
})]);
|
||||
same("{:a$.b$s}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
@ -628,7 +650,7 @@ mod tests {
|
|||
fn format_flags() {
|
||||
same("{:-}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
@ -640,7 +662,7 @@ mod tests {
|
|||
})]);
|
||||
same("{:+#}",
|
||||
&[NextArgument(Argument {
|
||||
position: ArgumentNext,
|
||||
position: ArgumentIs(0),
|
||||
format: FormatSpec {
|
||||
fill: None,
|
||||
align: AlignUnknown,
|
||||
|
|
|
@ -881,7 +881,7 @@ fn check_on_unimplemented<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>,
|
|||
}
|
||||
},
|
||||
// `{:1}` and `{}` are not to be used
|
||||
Position::ArgumentIs(_) | Position::ArgumentNext => {
|
||||
Position::ArgumentIs(_) => {
|
||||
span_err!(ccx.tcx.sess, attr.span, E0231,
|
||||
"only named substitution \
|
||||
parameters are allowed");
|
||||
|
|
|
@ -24,11 +24,12 @@ use syntax_pos::{Span, DUMMY_SP};
|
|||
use syntax::tokenstream;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum ArgumentType {
|
||||
Known(String),
|
||||
Unsigned
|
||||
Placeholder(String),
|
||||
Count,
|
||||
}
|
||||
|
||||
enum Position {
|
||||
|
@ -44,17 +45,25 @@ struct Context<'a, 'b:'a> {
|
|||
/// The span of the format string literal.
|
||||
fmtsp: Span,
|
||||
|
||||
/// Parsed argument expressions and the types that we've found so far for
|
||||
/// them.
|
||||
/// List of parsed argument expressions.
|
||||
/// Named expressions are resolved early, and are appended to the end of
|
||||
/// argument expressions.
|
||||
///
|
||||
/// Example showing the various data structures in motion:
|
||||
///
|
||||
/// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"`
|
||||
/// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
|
||||
/// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
|
||||
/// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]`
|
||||
/// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
|
||||
/// * `names` (in JSON): `{"foo": 2}`
|
||||
args: Vec<P<ast::Expr>>,
|
||||
arg_types: Vec<Option<ArgumentType>>,
|
||||
/// Parsed named expressions and the types that we've found for them so far.
|
||||
/// Note that we keep a side-array of the ordering of the named arguments
|
||||
/// found to be sure that we can translate them in the same order that they
|
||||
/// were declared in.
|
||||
names: HashMap<String, P<ast::Expr>>,
|
||||
name_types: HashMap<String, ArgumentType>,
|
||||
name_ordering: Vec<String>,
|
||||
/// Placeholder slot numbers indexed by argument.
|
||||
arg_types: Vec<Vec<usize>>,
|
||||
/// Unique format specs seen for each argument.
|
||||
arg_unique_types: Vec<Vec<ArgumentType>>,
|
||||
/// Map from named arguments to their resolved indices.
|
||||
names: HashMap<String, usize>,
|
||||
|
||||
/// The latest consecutive literal strings, or empty if there weren't any.
|
||||
literal: String,
|
||||
|
@ -66,10 +75,41 @@ struct Context<'a, 'b:'a> {
|
|||
/// Stays `true` if all formatting parameters are default (as in "{}{}").
|
||||
all_pieces_simple: bool,
|
||||
|
||||
name_positions: HashMap<String, usize>,
|
||||
/// Mapping between positional argument references and indices into the
|
||||
/// final generated static argument array. We record the starting indices
|
||||
/// corresponding to each positional argument, and number of references
|
||||
/// consumed so far for each argument, to facilitate correct `Position`
|
||||
/// mapping in `trans_piece`. In effect this can be seen as a "flattened"
|
||||
/// version of `arg_unique_types`.
|
||||
///
|
||||
/// Again with the example described above in docstring for `args`:
|
||||
///
|
||||
/// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]`
|
||||
arg_index_map: Vec<Vec<usize>>,
|
||||
|
||||
/// Updated as arguments are consumed
|
||||
next_arg: usize,
|
||||
/// Starting offset of count argument slots.
|
||||
count_args_index_offset: usize,
|
||||
|
||||
/// Count argument slots and tracking data structures.
|
||||
/// Count arguments are separately tracked for de-duplication in case
|
||||
/// multiple references are made to one argument. For example, in this
|
||||
/// format string:
|
||||
///
|
||||
/// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"`
|
||||
/// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"`
|
||||
/// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"`
|
||||
/// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}`
|
||||
/// * `count_args`: `vec![Exact(0), Exact(5), Exact(3)]`
|
||||
count_args: Vec<Position>,
|
||||
/// Relative slot numbers for count arguments.
|
||||
count_positions: HashMap<usize, usize>,
|
||||
/// Number of count slots assigned.
|
||||
count_positions_count: usize,
|
||||
|
||||
/// Current position of the implicit positional arg pointer, as if it
|
||||
/// still existed in this phase of processing.
|
||||
/// Used only for `all_pieces_simple` tracking in `trans_piece`.
|
||||
curarg: usize,
|
||||
}
|
||||
|
||||
/// Parses the arguments from the given list of tokens, returning None
|
||||
|
@ -78,15 +118,12 @@ struct Context<'a, 'b:'a> {
|
|||
///
|
||||
/// If parsing succeeds, the return value is:
|
||||
/// ```ignore
|
||||
/// Some((fmtstr, unnamed arguments, ordering of named arguments,
|
||||
/// named arguments))
|
||||
/// Some((fmtstr, parsed arguments, index map for named arguments))
|
||||
/// ```
|
||||
fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[tokenstream::TokenTree])
|
||||
-> Option<(P<ast::Expr>, Vec<P<ast::Expr>>, Vec<String>,
|
||||
HashMap<String, P<ast::Expr>>)> {
|
||||
let mut args = Vec::new();
|
||||
let mut names = HashMap::<String, P<ast::Expr>>::new();
|
||||
let mut order = Vec::new();
|
||||
-> Option<(P<ast::Expr>, Vec<P<ast::Expr>>, HashMap<String, usize>)> {
|
||||
let mut args = Vec::<P<ast::Expr>>::new();
|
||||
let mut names = HashMap::<String, usize>::new();
|
||||
|
||||
let mut p = ecx.new_parser_from_tts(tts);
|
||||
|
||||
|
@ -130,23 +167,50 @@ fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[tokenstream::TokenTree])
|
|||
ecx.struct_span_err(e.span,
|
||||
&format!("duplicate argument named `{}`",
|
||||
name))
|
||||
.span_note(prev.span, "previously here")
|
||||
.span_note(args[*prev].span, "previously here")
|
||||
.emit();
|
||||
continue;
|
||||
}
|
||||
order.push(name.to_string());
|
||||
names.insert(name.to_string(), e);
|
||||
|
||||
// Resolve names into slots early.
|
||||
// Since all the positional args are already seen at this point
|
||||
// if the input is valid, we can simply append to the positional
|
||||
// args. And remember the names.
|
||||
let slot = args.len();
|
||||
names.insert(name.to_string(), slot);
|
||||
args.push(e);
|
||||
} else {
|
||||
args.push(panictry!(p.parse_expr()));
|
||||
}
|
||||
}
|
||||
Some((fmtstr, args, order, names))
|
||||
Some((fmtstr, args, names))
|
||||
}
|
||||
|
||||
impl<'a, 'b> Context<'a, 'b> {
|
||||
/// Verifies one piece of a parse string. All errors are not emitted as
|
||||
/// fatal so we can continue giving errors about this and possibly other
|
||||
/// format strings.
|
||||
fn resolve_name_inplace(&self, p: &mut parse::Piece) {
|
||||
// NOTE: the `unwrap_or` branch is needed in case of invalid format
|
||||
// arguments, e.g. `format_args!("{foo}")`.
|
||||
let lookup = |s| *self.names.get(s).unwrap_or(&0);
|
||||
|
||||
match *p {
|
||||
parse::String(_) => {}
|
||||
parse::NextArgument(ref mut arg) => {
|
||||
if let parse::ArgumentNamed(s) = arg.position {
|
||||
arg.position = parse::ArgumentIs(lookup(s));
|
||||
}
|
||||
if let parse::CountIsName(s) = arg.format.width {
|
||||
arg.format.width = parse::CountIsParam(lookup(s));
|
||||
}
|
||||
if let parse::CountIsName(s) = arg.format.precision {
|
||||
arg.format.precision = parse::CountIsParam(lookup(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies one piece of a parse string, and remembers it if valid.
|
||||
/// All errors are not emitted as fatal so we can continue giving errors
|
||||
/// about this and possibly other format strings.
|
||||
fn verify_piece(&mut self, p: &parse::Piece) {
|
||||
match *p {
|
||||
parse::String(..) => {}
|
||||
|
@ -159,16 +223,11 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
// 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 {
|
||||
parse::ArgumentNext => {
|
||||
let i = self.next_arg;
|
||||
self.next_arg += 1;
|
||||
Exact(i)
|
||||
}
|
||||
parse::ArgumentIs(i) => Exact(i),
|
||||
parse::ArgumentNamed(s) => Named(s.to_string()),
|
||||
};
|
||||
|
||||
let ty = Known(arg.format.ty.to_string());
|
||||
let ty = Placeholder(arg.format.ty.to_string());
|
||||
self.verify_arg_type(pos, ty);
|
||||
}
|
||||
}
|
||||
|
@ -178,15 +237,10 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
match c {
|
||||
parse::CountImplied | parse::CountIs(..) => {}
|
||||
parse::CountIsParam(i) => {
|
||||
self.verify_arg_type(Exact(i), Unsigned);
|
||||
self.verify_arg_type(Exact(i), Count);
|
||||
}
|
||||
parse::CountIsName(s) => {
|
||||
self.verify_arg_type(Named(s.to_string()), Unsigned);
|
||||
}
|
||||
parse::CountIsNextParam => {
|
||||
let next_arg = self.next_arg;
|
||||
self.verify_arg_type(Exact(next_arg), Unsigned);
|
||||
self.next_arg += 1;
|
||||
self.verify_arg_type(Named(s.to_string()), Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,6 +253,8 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Actually verifies and tracks a given format placeholder
|
||||
/// (a.k.a. argument).
|
||||
fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
|
||||
match arg {
|
||||
Exact(arg) => {
|
||||
|
@ -209,84 +265,70 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
self.ecx.span_err(self.fmtsp, &msg[..]);
|
||||
return;
|
||||
}
|
||||
{
|
||||
let arg_type = match self.arg_types[arg] {
|
||||
None => None,
|
||||
Some(ref x) => Some(x)
|
||||
};
|
||||
self.verify_same(self.args[arg].span, &ty, arg_type);
|
||||
}
|
||||
if self.arg_types[arg].is_none() {
|
||||
self.arg_types[arg] = Some(ty);
|
||||
match ty {
|
||||
Placeholder(_) => {
|
||||
// record every (position, type) combination only once
|
||||
let ref mut seen_ty = self.arg_unique_types[arg];
|
||||
let i = match seen_ty.iter().position(|x| *x == ty) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
let i = seen_ty.len();
|
||||
seen_ty.push(ty);
|
||||
i
|
||||
}
|
||||
};
|
||||
self.arg_types[arg].push(i);
|
||||
}
|
||||
Count => {
|
||||
match self.count_positions.entry(arg) {
|
||||
Entry::Vacant(e) => {
|
||||
let i = self.count_positions_count;
|
||||
e.insert(i);
|
||||
self.count_args.push(Exact(arg));
|
||||
self.count_positions_count += 1;
|
||||
}
|
||||
Entry::Occupied(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Named(name) => {
|
||||
let span = match self.names.get(&name) {
|
||||
Some(e) => e.span,
|
||||
let idx = match self.names.get(&name) {
|
||||
Some(e) => *e,
|
||||
None => {
|
||||
let msg = format!("there is no argument named `{}`", name);
|
||||
self.ecx.span_err(self.fmtsp, &msg[..]);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.verify_same(span, &ty, self.name_types.get(&name));
|
||||
if !self.name_types.contains_key(&name) {
|
||||
self.name_types.insert(name.clone(), ty);
|
||||
}
|
||||
// Assign this named argument a slot in the arguments array if
|
||||
// it hasn't already been assigned a slot.
|
||||
if !self.name_positions.contains_key(&name) {
|
||||
let slot = self.name_positions.len();
|
||||
self.name_positions.insert(name, slot);
|
||||
}
|
||||
// Treat as positional arg.
|
||||
self.verify_arg_type(Exact(idx), ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When we're keeping track of the types that are declared for certain
|
||||
/// arguments, we assume that `None` means we haven't seen this argument
|
||||
/// yet, `Some(None)` means that we've seen the argument, but no format was
|
||||
/// specified, and `Some(Some(x))` means that the argument was declared to
|
||||
/// have type `x`.
|
||||
///
|
||||
/// Obviously `Some(Some(x)) != Some(Some(y))`, but we consider it true
|
||||
/// that: `Some(None) == Some(Some(x))`
|
||||
fn verify_same(&self,
|
||||
sp: Span,
|
||||
ty: &ArgumentType,
|
||||
before: Option<&ArgumentType>) {
|
||||
let cur = match before {
|
||||
None => return,
|
||||
Some(t) => t,
|
||||
};
|
||||
if *ty == *cur {
|
||||
return
|
||||
}
|
||||
match (cur, ty) {
|
||||
(&Known(ref cur), &Known(ref ty)) => {
|
||||
self.ecx.span_err(sp,
|
||||
&format!("argument redeclared with type `{}` when \
|
||||
it was previously `{}`",
|
||||
*ty,
|
||||
*cur));
|
||||
}
|
||||
(&Known(ref cur), _) => {
|
||||
self.ecx.span_err(sp,
|
||||
&format!("argument used to format with `{}` was \
|
||||
attempted to not be used for formatting",
|
||||
*cur));
|
||||
}
|
||||
(_, &Known(ref ty)) => {
|
||||
self.ecx.span_err(sp,
|
||||
&format!("argument previously used as a format \
|
||||
argument attempted to be used as `{}`",
|
||||
*ty));
|
||||
}
|
||||
(_, _) => {
|
||||
self.ecx.span_err(sp, "argument declared with multiple formats");
|
||||
}
|
||||
/// Builds the mapping between format placeholders and argument objects.
|
||||
fn build_index_map(&mut self) {
|
||||
// NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
|
||||
let args_len = self.args.len();
|
||||
self.arg_index_map.reserve(args_len);
|
||||
|
||||
let mut sofar = 0usize;
|
||||
|
||||
// Map the arguments
|
||||
for i in 0..args_len {
|
||||
let ref arg_types = self.arg_types[i];
|
||||
let mut arg_offsets = Vec::with_capacity(arg_types.len());
|
||||
for offset in arg_types {
|
||||
arg_offsets.push(sofar + *offset);
|
||||
}
|
||||
self.arg_index_map.push(arg_offsets);
|
||||
sofar += self.arg_unique_types[i].len();
|
||||
}
|
||||
|
||||
// Record starting index for counts, which appear just after arguments
|
||||
self.count_args_index_offset = sofar;
|
||||
}
|
||||
|
||||
fn rtpath(ecx: &ExtCtxt, s: &str) -> Vec<ast::Ident> {
|
||||
|
@ -306,18 +348,18 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
match c {
|
||||
parse::CountIs(i) => count("Is", Some(self.ecx.expr_usize(sp, i))),
|
||||
parse::CountIsParam(i) => {
|
||||
count("Param", Some(self.ecx.expr_usize(sp, i)))
|
||||
}
|
||||
parse::CountImplied => count("Implied", None),
|
||||
parse::CountIsNextParam => count("NextParam", None),
|
||||
parse::CountIsName(n) => {
|
||||
let i = match self.name_positions.get(n) {
|
||||
// This needs mapping too, as `i` is referring to a macro
|
||||
// argument.
|
||||
let i = match self.count_positions.get(&i) {
|
||||
Some(&i) => i,
|
||||
None => 0, // error already emitted elsewhere
|
||||
};
|
||||
let i = i + self.args.len();
|
||||
let i = i + self.count_args_index_offset;
|
||||
count("Param", Some(self.ecx.expr_usize(sp, i)))
|
||||
}
|
||||
parse::CountImplied => count("Implied", None),
|
||||
// should never be the case, names are already resolved
|
||||
parse::CountIsName(_) => panic!("should never happen"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -331,7 +373,10 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
|
||||
/// Translate a `parse::Piece` to a static `rt::Argument` or append
|
||||
/// to the `literal` string.
|
||||
fn trans_piece(&mut self, piece: &parse::Piece) -> Option<P<ast::Expr>> {
|
||||
fn trans_piece(&mut self,
|
||||
piece: &parse::Piece,
|
||||
arg_index_consumed: &mut Vec<usize>)
|
||||
-> Option<P<ast::Expr>> {
|
||||
let sp = self.macsp;
|
||||
match *piece {
|
||||
parse::String(s) => {
|
||||
|
@ -355,25 +400,34 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
}
|
||||
};
|
||||
match arg.position {
|
||||
// These two have a direct mapping
|
||||
parse::ArgumentNext => pos("Next", None),
|
||||
parse::ArgumentIs(i) => pos("At", Some(i)),
|
||||
|
||||
// Named arguments are converted to positional arguments
|
||||
// at the end of the list of arguments
|
||||
parse::ArgumentNamed(n) => {
|
||||
let i = match self.name_positions.get(n) {
|
||||
Some(&i) => i,
|
||||
parse::ArgumentIs(i) => {
|
||||
// Map to index in final generated argument array
|
||||
// in case of multiple types specified
|
||||
let arg_idx = match arg_index_consumed.get_mut(i) {
|
||||
None => 0, // error already emitted elsewhere
|
||||
Some(offset) => {
|
||||
let arg_idx = self.arg_index_map[i][*offset];
|
||||
*offset += 1;
|
||||
arg_idx
|
||||
}
|
||||
};
|
||||
let i = i + self.args.len();
|
||||
pos("At", Some(i))
|
||||
pos("At", Some(arg_idx))
|
||||
}
|
||||
|
||||
// should never be the case, because names are already
|
||||
// resolved.
|
||||
parse::ArgumentNamed(_) => panic!("should never happen"),
|
||||
}
|
||||
};
|
||||
|
||||
let simple_arg = parse::Argument {
|
||||
position: parse::ArgumentNext,
|
||||
position: {
|
||||
// We don't have ArgumentNext any more, so we have to
|
||||
// track the current argument ourselves.
|
||||
let i = self.curarg;
|
||||
self.curarg += 1;
|
||||
parse::ArgumentIs(i)
|
||||
},
|
||||
format: parse::FormatSpec {
|
||||
fill: arg.format.fill,
|
||||
align: parse::AlignUnknown,
|
||||
|
@ -449,11 +503,11 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
ecx.expr_block(ecx.block(sp, vec![stmt, ecx.stmt_expr(ecx.expr_ident(sp, name))]))
|
||||
}
|
||||
|
||||
/// Actually builds the expression which the iformat! block will be expanded
|
||||
/// to
|
||||
/// Actually builds the expression which the format_args! block will be
|
||||
/// expanded to
|
||||
fn into_expr(mut self) -> P<ast::Expr> {
|
||||
let mut locals = Vec::new();
|
||||
let mut names = vec![None; self.name_positions.len()];
|
||||
let mut counts = Vec::new();
|
||||
let mut pats = Vec::new();
|
||||
let mut heads = Vec::new();
|
||||
|
||||
|
@ -470,6 +524,10 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
piece_ty,
|
||||
self.str_pieces);
|
||||
|
||||
// Before consuming the expressions, we have to remember spans for
|
||||
// count arguments as they are now generated separate from other
|
||||
// arguments, hence have no access to the `P<ast::Expr>`'s.
|
||||
let spans_pos: Vec<_> = self.args.iter().map(|e| e.span.clone()).collect();
|
||||
|
||||
// Right now there is a bug such that for the expression:
|
||||
// foo(bar(&1))
|
||||
|
@ -479,38 +537,29 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
// of each variable because we don't want to move out of the arguments
|
||||
// passed to this function.
|
||||
for (i, e) in self.args.into_iter().enumerate() {
|
||||
let arg_ty = match self.arg_types[i].as_ref() {
|
||||
Some(ty) => ty,
|
||||
None => continue // error already generated
|
||||
};
|
||||
|
||||
let name = self.ecx.ident_of(&format!("__arg{}", i));
|
||||
pats.push(self.ecx.pat_ident(DUMMY_SP, name));
|
||||
locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty,
|
||||
self.ecx.expr_ident(e.span, name)));
|
||||
for ref arg_ty in self.arg_unique_types[i].iter() {
|
||||
locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty,
|
||||
self.ecx.expr_ident(e.span, name)));
|
||||
}
|
||||
heads.push(self.ecx.expr_addr_of(e.span, e));
|
||||
}
|
||||
for name in &self.name_ordering {
|
||||
let e = match self.names.remove(name) {
|
||||
Some(e) => e,
|
||||
None => continue
|
||||
for pos in self.count_args {
|
||||
let name = self.ecx.ident_of(&match pos {
|
||||
Exact(i) => format!("__arg{}", i),
|
||||
_ => panic!("should never happen"),
|
||||
});
|
||||
let span = match pos {
|
||||
Exact(i) => spans_pos[i],
|
||||
_ => panic!("should never happen"),
|
||||
};
|
||||
let arg_ty = match self.name_types.get(name) {
|
||||
Some(ty) => ty,
|
||||
None => continue
|
||||
};
|
||||
|
||||
let lname = self.ecx.ident_of(&format!("__arg{}",
|
||||
*name));
|
||||
pats.push(self.ecx.pat_ident(DUMMY_SP, lname));
|
||||
names[*self.name_positions.get(name).unwrap()] =
|
||||
Some(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty,
|
||||
self.ecx.expr_ident(e.span, lname)));
|
||||
heads.push(self.ecx.expr_addr_of(e.span, e));
|
||||
counts.push(Context::format_arg(self.ecx, self.macsp, span, &Count,
|
||||
self.ecx.expr_ident(span, name)));
|
||||
}
|
||||
|
||||
// Now create a vector containing all the arguments
|
||||
let args = locals.into_iter().chain(names.into_iter().map(|a| a.unwrap()));
|
||||
let args = locals.into_iter().chain(counts.into_iter());
|
||||
|
||||
let args_array = self.ecx.expr_vec(self.fmtsp, args.collect());
|
||||
|
||||
|
@ -573,7 +622,7 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
ty: &ArgumentType, arg: P<ast::Expr>)
|
||||
-> P<ast::Expr> {
|
||||
let trait_ = match *ty {
|
||||
Known(ref tyname) => {
|
||||
Placeholder(ref tyname) => {
|
||||
match &tyname[..] {
|
||||
"" => "Display",
|
||||
"?" => "Debug",
|
||||
|
@ -592,7 +641,7 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Unsigned => {
|
||||
Count => {
|
||||
let path = ecx.std_path(&["fmt", "ArgumentV1", "from_usize"]);
|
||||
return ecx.expr_call_global(macsp, path, vec![arg])
|
||||
}
|
||||
|
@ -610,9 +659,9 @@ pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
|
|||
-> Box<base::MacResult+'cx> {
|
||||
|
||||
match parse_args(ecx, sp, tts) {
|
||||
Some((efmt, args, order, names)) => {
|
||||
Some((efmt, args, names)) => {
|
||||
MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt,
|
||||
args, order, names))
|
||||
args, names))
|
||||
}
|
||||
None => DummyResult::expr(sp)
|
||||
}
|
||||
|
@ -623,10 +672,12 @@ pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
|
|||
pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
||||
efmt: P<ast::Expr>,
|
||||
args: Vec<P<ast::Expr>>,
|
||||
name_ordering: Vec<String>,
|
||||
names: HashMap<String, P<ast::Expr>>)
|
||||
names: HashMap<String, usize>)
|
||||
-> P<ast::Expr> {
|
||||
let arg_types: Vec<_> = (0..args.len()).map(|_| None).collect();
|
||||
// NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
|
||||
// `ArgumentType` does not derive `Clone`.
|
||||
let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
|
||||
let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
|
||||
let macsp = ecx.call_site();
|
||||
// Expand the format literal so that efmt.span will have a backtrace. This
|
||||
// is essential for locating a bug when the format literal is generated in
|
||||
|
@ -636,11 +687,14 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
|||
ecx: ecx,
|
||||
args: args,
|
||||
arg_types: arg_types,
|
||||
arg_unique_types: arg_unique_types,
|
||||
names: names,
|
||||
name_positions: HashMap::new(),
|
||||
name_types: HashMap::new(),
|
||||
name_ordering: name_ordering,
|
||||
next_arg: 0,
|
||||
curarg: 0,
|
||||
arg_index_map: Vec::new(),
|
||||
count_args: Vec::new(),
|
||||
count_positions: HashMap::new(),
|
||||
count_positions_count: 0,
|
||||
count_args_index_offset: 0,
|
||||
literal: String::new(),
|
||||
pieces: Vec::new(),
|
||||
str_pieces: Vec::new(),
|
||||
|
@ -656,21 +710,31 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
|||
};
|
||||
|
||||
let mut parser = parse::Parser::new(&fmt);
|
||||
let mut pieces = vec![];
|
||||
|
||||
loop {
|
||||
match parser.next() {
|
||||
Some(piece) => {
|
||||
Some(mut piece) => {
|
||||
if !parser.errors.is_empty() { break }
|
||||
cx.verify_piece(&piece);
|
||||
if let Some(piece) = cx.trans_piece(&piece) {
|
||||
let s = cx.trans_literal_string();
|
||||
cx.str_pieces.push(s);
|
||||
cx.pieces.push(piece);
|
||||
}
|
||||
cx.resolve_name_inplace(&mut piece);
|
||||
pieces.push(piece);
|
||||
}
|
||||
None => break
|
||||
}
|
||||
}
|
||||
|
||||
cx.build_index_map();
|
||||
|
||||
let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()];
|
||||
for piece in pieces {
|
||||
if let Some(piece) = cx.trans_piece(&piece, &mut arg_index_consumed) {
|
||||
let s = cx.trans_literal_string();
|
||||
cx.str_pieces.push(s);
|
||||
cx.pieces.push(piece);
|
||||
}
|
||||
}
|
||||
|
||||
if !parser.errors.is_empty() {
|
||||
cx.ecx.span_err(cx.fmtsp, &format!("invalid format string: {}",
|
||||
parser.errors.remove(0)));
|
||||
|
@ -682,14 +746,20 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
|||
}
|
||||
|
||||
// Make sure that all arguments were used and all arguments have types.
|
||||
let num_pos_args = cx.args.len() - cx.names.len();
|
||||
for (i, ty) in cx.arg_types.iter().enumerate() {
|
||||
if ty.is_none() {
|
||||
cx.ecx.span_err(cx.args[i].span, "argument never used");
|
||||
}
|
||||
}
|
||||
for (name, e) in &cx.names {
|
||||
if !cx.name_types.contains_key(name) {
|
||||
cx.ecx.span_err(e.span, "named argument never used");
|
||||
if ty.len() == 0 {
|
||||
if cx.count_positions.contains_key(&i) {
|
||||
continue;
|
||||
}
|
||||
let msg = if i >= num_pos_args {
|
||||
// named argument
|
||||
"named argument never used"
|
||||
} else {
|
||||
// positional argument
|
||||
"argument never used"
|
||||
};
|
||||
cx.ecx.span_err(cx.args[i].span, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,6 @@ fn main() {
|
|||
format!("{foo}", 1, foo=2); //~ ERROR: argument never used
|
||||
format!("", foo=2); //~ ERROR: named argument never used
|
||||
|
||||
format!("{0:x} {0:X}", 1); //~ ERROR: redeclared with type `X`
|
||||
format!("{foo:x} {foo:X}", foo=1); //~ ERROR: redeclared with type `X`
|
||||
|
||||
format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument
|
||||
format!("", foo=1, 2); //~ ERROR: positional arguments cannot follow
|
||||
|
||||
|
|
|
@ -163,6 +163,24 @@ pub fn main() {
|
|||
t!(format!("{:?}", 0.0), "0");
|
||||
|
||||
|
||||
// Ergonomic format_args!
|
||||
t!(format!("{0:x} {0:X}", 15), "f F");
|
||||
t!(format!("{0:x} {0:X} {}", 15), "f F 15");
|
||||
// NOTE: For now the longer test cases must not be followed immediately by
|
||||
// >1 empty lines, or the pretty printer will break. Since no one wants to
|
||||
// touch the current pretty printer (#751), we have no choice but to work
|
||||
// around it. Some of the following test cases are also affected.
|
||||
t!(format!("{:x}{0:X}{a:x}{:X}{1:x}{a:X}", 13, 14, a=15), "dDfEeF");
|
||||
t!(format!("{a:x} {a:X}", a=15), "f F");
|
||||
|
||||
// And its edge cases
|
||||
t!(format!("{a:.0$} {b:.0$} {0:.0$}\n{a:.c$} {b:.c$} {c:.c$}",
|
||||
4, a="abcdefg", b="hijklmn", c=3),
|
||||
"abcd hijk 4\nabc hij 3");
|
||||
t!(format!("{a:.*} {0} {:.*}", 4, 3, "efgh", a="abcdef"), "abcd 4 efg");
|
||||
t!(format!("{:.a$} {a} {a:#x}", "aaaaaa", a=2), "aa 2 0x2");
|
||||
|
||||
|
||||
// Test that pointers don't get truncated.
|
||||
{
|
||||
let val = usize::MAX;
|
||||
|
@ -177,6 +195,7 @@ pub fn main() {
|
|||
test_write();
|
||||
test_print();
|
||||
test_order();
|
||||
test_once();
|
||||
|
||||
// make sure that format! doesn't move out of local variables
|
||||
let a: Box<_> = box 3;
|
||||
|
@ -260,3 +279,17 @@ fn test_order() {
|
|||
foo(), foo(), foo(), a=foo(), b=foo(), c=foo()),
|
||||
"1 2 4 5 3 6".to_string());
|
||||
}
|
||||
|
||||
fn test_once() {
|
||||
// Make sure each argument are evaluted only once even though it may be
|
||||
// formatted multiple times
|
||||
fn foo() -> isize {
|
||||
static mut FOO: isize = 0;
|
||||
unsafe {
|
||||
FOO += 1;
|
||||
FOO
|
||||
}
|
||||
}
|
||||
assert_eq!(format!("{0} {0} {0} {a} {a} {a}", foo(), a=foo()),
|
||||
"1 1 1 2 2 2".to_string());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue