Skip to content

Commit 5e55a44

Browse files
committed
syntax_ext: format: allow multiple formats for one argument
This commit removed the restriction of only allowing one type per argument. This is achieved by adding mappings between macro arguments and format placeholders, then taking the mapping into consideration when emitting the Arguments expression. syntax_ext: format: fix implicit positional arguments syntax_ext: format: don't panic if no args given for implicit positional args Check the list lengths before use. Fixes regression of `compile-fail/macro-backtrace-println.rs`. syntax_ext: format: also map CountIsParam indices to expanded args syntax_ext: format: fix ICE in case of malformed format args
1 parent eb5417b commit 5e55a44

File tree

2 files changed

+57
-72
lines changed

2 files changed

+57
-72
lines changed

src/libsyntax_ext/format.rs

Lines changed: 57 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct Context<'a, 'b:'a> {
4949
/// Named expressions are resolved early, and are appended to the end of
5050
/// argument expressions.
5151
args: Vec<P<ast::Expr>>,
52-
arg_types: Vec<Option<ArgumentType>>,
52+
arg_types: Vec<Vec<ArgumentType>>,
5353
/// Map from named arguments to their resolved indices.
5454
names: HashMap<String, usize>,
5555

@@ -63,6 +63,13 @@ struct Context<'a, 'b:'a> {
6363
/// Stays `true` if all formatting parameters are default (as in "{}{}").
6464
all_pieces_simple: bool,
6565

66+
/// Mapping between positional argument references and indices into the
67+
/// final generated static argument array. We record the starting indices
68+
/// corresponding to each positional argument, and number of references
69+
/// consumed so far for each argument, to facilitate correct `Position`
70+
/// mapping in `trans_piece`.
71+
arg_index_map: Vec<usize>,
72+
6673
/// Current position of the implicit positional arg pointer, as if it
6774
/// still existed in this phase of processing.
6875
/// Used only for `all_pieces_simple` tracking in `trans_piece`.
@@ -218,16 +225,7 @@ impl<'a, 'b> Context<'a, 'b> {
218225
self.ecx.span_err(self.fmtsp, &msg[..]);
219226
return;
220227
}
221-
{
222-
let arg_type = match self.arg_types[arg] {
223-
None => None,
224-
Some(ref x) => Some(x)
225-
};
226-
self.verify_same(self.args[arg].span, &ty, arg_type);
227-
}
228-
if self.arg_types[arg].is_none() {
229-
self.arg_types[arg] = Some(ty);
230-
}
228+
self.arg_types[arg].push(ty);
231229
}
232230

233231
Named(name) => {
@@ -245,56 +243,27 @@ impl<'a, 'b> Context<'a, 'b> {
245243
}
246244
}
247245

248-
/// When we're keeping track of the types that are declared for certain
249-
/// arguments, we assume that `None` means we haven't seen this argument
250-
/// yet, `Some(None)` means that we've seen the argument, but no format was
251-
/// specified, and `Some(Some(x))` means that the argument was declared to
252-
/// have type `x`.
253-
///
254-
/// Obviously `Some(Some(x)) != Some(Some(y))`, but we consider it true
255-
/// that: `Some(None) == Some(Some(x))`
256-
fn verify_same(&self,
257-
sp: Span,
258-
ty: &ArgumentType,
259-
before: Option<&ArgumentType>) {
260-
let cur = match before {
261-
None => return,
262-
Some(t) => t,
263-
};
264-
if *ty == *cur {
265-
return
266-
}
267-
match (cur, ty) {
268-
(&Known(ref cur), &Known(ref ty)) => {
269-
self.ecx.span_err(sp,
270-
&format!("argument redeclared with type `{}` when \
271-
it was previously `{}`",
272-
*ty,
273-
*cur));
274-
}
275-
(&Known(ref cur), _) => {
276-
self.ecx.span_err(sp,
277-
&format!("argument used to format with `{}` was \
278-
attempted to not be used for formatting",
279-
*cur));
280-
}
281-
(_, &Known(ref ty)) => {
282-
self.ecx.span_err(sp,
283-
&format!("argument previously used as a format \
284-
argument attempted to be used as `{}`",
285-
*ty));
286-
}
287-
(_, _) => {
288-
self.ecx.span_err(sp, "argument declared with multiple formats");
289-
}
246+
// NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
247+
fn build_index_map(&mut self) {
248+
let args_len = self.args.len();
249+
self.arg_index_map.reserve(args_len);
250+
251+
let mut sofar = 0usize;
252+
253+
// Generate mapping for positional args
254+
for i in 0..args_len {
255+
self.arg_index_map.push(sofar);
256+
sofar += self.arg_types[i].len();
290257
}
291258
}
292259

293260
fn rtpath(ecx: &ExtCtxt, s: &str) -> Vec<ast::Ident> {
294261
ecx.std_path(&["fmt", "rt", "v1", s])
295262
}
296263

297-
fn trans_count(&self, c: parse::Count) -> P<ast::Expr> {
264+
fn trans_count(&self,
265+
c: parse::Count,
266+
arg_index_consumed: &mut Vec<usize>) -> P<ast::Expr> {
298267
let sp = self.macsp;
299268
let count = |c, arg| {
300269
let mut path = Context::rtpath(self.ecx, "Count");
@@ -307,7 +276,11 @@ impl<'a, 'b> Context<'a, 'b> {
307276
match c {
308277
parse::CountIs(i) => count("Is", Some(self.ecx.expr_usize(sp, i))),
309278
parse::CountIsParam(i) => {
310-
count("Param", Some(self.ecx.expr_usize(sp, i)))
279+
// This needs mapping too, as `i` is referring to a macro
280+
// argument.
281+
let arg_idx = self.arg_index_map[i] + arg_index_consumed[i];
282+
arg_index_consumed[i] += 1;
283+
count("Param", Some(self.ecx.expr_usize(sp, arg_idx)))
311284
}
312285
parse::CountImplied => count("Implied", None),
313286
// should never be the case, names are already resolved
@@ -325,7 +298,10 @@ impl<'a, 'b> Context<'a, 'b> {
325298

326299
/// Translate a `parse::Piece` to a static `rt::Argument` or append
327300
/// to the `literal` string.
328-
fn trans_piece(&mut self, piece: &parse::Piece) -> Option<P<ast::Expr>> {
301+
fn trans_piece(&mut self,
302+
piece: &parse::Piece,
303+
arg_index_consumed: &mut Vec<usize>)
304+
-> Option<P<ast::Expr>> {
329305
let sp = self.macsp;
330306
match *piece {
331307
parse::String(s) => {
@@ -349,7 +325,18 @@ impl<'a, 'b> Context<'a, 'b> {
349325
}
350326
};
351327
match arg.position {
352-
parse::ArgumentIs(i) => pos("At", Some(i)),
328+
parse::ArgumentIs(i) => {
329+
// Map to index in final generated argument array
330+
// in case of multiple types specified
331+
let arg_idx = if self.args.len() > i {
332+
let arg_idx = self.arg_index_map[i] + arg_index_consumed[i];
333+
arg_index_consumed[i] += 1;
334+
arg_idx
335+
} else {
336+
0 // error already emitted elsewhere
337+
};
338+
pos("At", Some(arg_idx))
339+
}
353340

354341
// should never be the case, because names are already
355342
// resolved.
@@ -396,8 +383,8 @@ impl<'a, 'b> Context<'a, 'b> {
396383
};
397384
let align = self.ecx.expr_path(align);
398385
let flags = self.ecx.expr_u32(sp, arg.format.flags);
399-
let prec = self.trans_count(arg.format.precision);
400-
let width = self.trans_count(arg.format.width);
386+
let prec = self.trans_count(arg.format.precision, arg_index_consumed);
387+
let width = self.trans_count(arg.format.width, arg_index_consumed);
401388
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec"));
402389
let fmt = self.ecx.expr_struct(sp, path, vec!(
403390
self.ecx.field_imm(sp, self.ecx.ident_of("fill"), fill),
@@ -469,15 +456,12 @@ impl<'a, 'b> Context<'a, 'b> {
469456
// of each variable because we don't want to move out of the arguments
470457
// passed to this function.
471458
for (i, e) in self.args.into_iter().enumerate() {
472-
let arg_ty = match self.arg_types[i].as_ref() {
473-
Some(ty) => ty,
474-
None => continue // error already generated
475-
};
476-
477459
let name = self.ecx.ident_of(&format!("__arg{}", i));
478460
pats.push(self.ecx.pat_ident(DUMMY_SP, name));
479-
locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty,
480-
self.ecx.expr_ident(e.span, name)));
461+
for ref arg_ty in self.arg_types[i].iter() {
462+
locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty,
463+
self.ecx.expr_ident(e.span, name)));
464+
}
481465
heads.push(self.ecx.expr_addr_of(e.span, e));
482466
}
483467

@@ -597,7 +581,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
597581
args: Vec<P<ast::Expr>>,
598582
names: HashMap<String, usize>)
599583
-> P<ast::Expr> {
600-
let arg_types: Vec<_> = (0..args.len()).map(|_| None).collect();
584+
let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
601585
let macsp = ecx.call_site();
602586
// Expand the format literal so that efmt.span will have a backtrace. This
603587
// is essential for locating a bug when the format literal is generated in
@@ -609,6 +593,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
609593
arg_types: arg_types,
610594
names: names,
611595
curarg: 0,
596+
arg_index_map: Vec::new(),
612597
literal: String::new(),
613598
pieces: Vec::new(),
614599
str_pieces: Vec::new(),
@@ -638,8 +623,11 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
638623
}
639624
}
640625

626+
cx.build_index_map();
627+
628+
let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()];
641629
for piece in pieces {
642-
if let Some(piece) = cx.trans_piece(&piece) {
630+
if let Some(piece) = cx.trans_piece(&piece, &mut arg_index_consumed) {
643631
let s = cx.trans_literal_string();
644632
cx.str_pieces.push(s);
645633
cx.pieces.push(piece);
@@ -659,7 +647,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
659647
// Make sure that all arguments were used and all arguments have types.
660648
let num_pos_args = cx.args.len() - cx.names.len();
661649
for (i, ty) in cx.arg_types.iter().enumerate() {
662-
if ty.is_none() {
650+
if ty.len() == 0 {
663651
let msg = if i >= num_pos_args {
664652
// named argument
665653
"named argument never used"

src/test/compile-fail/ifmt-bad-arg.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ fn main() {
2323
format!("{foo}", 1, foo=2); //~ ERROR: argument never used
2424
format!("", foo=2); //~ ERROR: named argument never used
2525

26-
format!("{0:x} {0:X}", 1); //~ ERROR: redeclared with type `X`
27-
format!("{foo:x} {foo:X}", foo=1); //~ ERROR: redeclared with type `X`
28-
2926
format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument
3027
format!("", foo=1, 2); //~ ERROR: positional arguments cannot follow
3128

0 commit comments

Comments
 (0)