Skip to content

Commit

Permalink
syntax_ext: format: allow multiple formats for one argument
Browse files Browse the repository at this point in the history
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
  • Loading branch information
xen0n committed Jul 13, 2016
1 parent eb5417b commit 5e55a44
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 72 deletions.
126 changes: 57 additions & 69 deletions src/libsyntax_ext/format.rs
Expand Up @@ -49,7 +49,7 @@ struct Context<'a, 'b:'a> {
/// Named expressions are resolved early, and are appended to the end of
/// argument expressions.
args: Vec<P<ast::Expr>>,
arg_types: Vec<Option<ArgumentType>>,
arg_types: Vec<Vec<ArgumentType>>,
/// Map from named arguments to their resolved indices.
names: HashMap<String, usize>,

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

/// 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`.
arg_index_map: Vec<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`.
Expand Down Expand Up @@ -218,16 +225,7 @@ 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);
}
self.arg_types[arg].push(ty);
}

Named(name) => {
Expand All @@ -245,56 +243,27 @@ impl<'a, 'b> Context<'a, 'b> {
}
}

/// 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");
}
// NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
fn build_index_map(&mut self) {
let args_len = self.args.len();
self.arg_index_map.reserve(args_len);

let mut sofar = 0usize;

// Generate mapping for positional args
for i in 0..args_len {
self.arg_index_map.push(sofar);
sofar += self.arg_types[i].len();
}
}

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

fn trans_count(&self, c: parse::Count) -> P<ast::Expr> {
fn trans_count(&self,
c: parse::Count,
arg_index_consumed: &mut Vec<usize>) -> P<ast::Expr> {
let sp = self.macsp;
let count = |c, arg| {
let mut path = Context::rtpath(self.ecx, "Count");
Expand All @@ -307,7 +276,11 @@ 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)))
// This needs mapping too, as `i` is referring to a macro
// argument.
let arg_idx = self.arg_index_map[i] + arg_index_consumed[i];
arg_index_consumed[i] += 1;
count("Param", Some(self.ecx.expr_usize(sp, arg_idx)))
}
parse::CountImplied => count("Implied", None),
// should never be the case, names are already resolved
Expand All @@ -325,7 +298,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) => {
Expand All @@ -349,7 +325,18 @@ impl<'a, 'b> Context<'a, 'b> {
}
};
match arg.position {
parse::ArgumentIs(i) => pos("At", Some(i)),
parse::ArgumentIs(i) => {
// Map to index in final generated argument array
// in case of multiple types specified
let arg_idx = if self.args.len() > i {
let arg_idx = self.arg_index_map[i] + arg_index_consumed[i];
arg_index_consumed[i] += 1;
arg_idx
} else {
0 // error already emitted elsewhere
};
pos("At", Some(arg_idx))
}

// should never be the case, because names are already
// resolved.
Expand Down Expand Up @@ -396,8 +383,8 @@ impl<'a, 'b> Context<'a, 'b> {
};
let align = self.ecx.expr_path(align);
let flags = self.ecx.expr_u32(sp, arg.format.flags);
let prec = self.trans_count(arg.format.precision);
let width = self.trans_count(arg.format.width);
let prec = self.trans_count(arg.format.precision, arg_index_consumed);
let width = self.trans_count(arg.format.width, arg_index_consumed);
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec"));
let fmt = self.ecx.expr_struct(sp, path, vec!(
self.ecx.field_imm(sp, self.ecx.ident_of("fill"), fill),
Expand Down Expand Up @@ -469,15 +456,12 @@ 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_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));
}

Expand Down Expand Up @@ -597,7 +581,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
args: Vec<P<ast::Expr>>,
names: HashMap<String, usize>)
-> P<ast::Expr> {
let arg_types: Vec<_> = (0..args.len()).map(|_| None).collect();
let arg_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
Expand All @@ -609,6 +593,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
arg_types: arg_types,
names: names,
curarg: 0,
arg_index_map: Vec::new(),
literal: String::new(),
pieces: Vec::new(),
str_pieces: Vec::new(),
Expand Down Expand Up @@ -638,8 +623,11 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
}
}

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) {
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);
Expand All @@ -659,7 +647,7 @@ 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() {
if ty.len() == 0 {
let msg = if i >= num_pos_args {
// named argument
"named argument never used"
Expand Down
3 changes: 0 additions & 3 deletions src/test/compile-fail/ifmt-bad-arg.rs
Expand Up @@ -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

Expand Down

0 comments on commit 5e55a44

Please sign in to comment.