use rustc_ast as ast;
use rustc_ast::ptr::P;
use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::visit::{self, Visitor};
use rustc_ast::{token, BlockCheckMode, UnsafeSource};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{pluralize, Applicability, DiagnosticBuilder};
use rustc_expand::base::{self, *};
use rustc_parse_format as parse;
use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{MultiSpan, Span};
+use rustc_span::{InnerSpan, MultiSpan, Span};
+use smallvec::SmallVec;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
enum Position {
Exact(usize),
Capture(usize),
- Named(Symbol),
+ Named(Symbol, InnerSpan),
}
struct Context<'a, 'b> {
match *p {
parse::String(_) => {}
parse::NextArgument(ref mut arg) => {
- if let parse::ArgumentNamed(s) = arg.position {
+ if let parse::ArgumentNamed(s, _) = arg.position {
arg.position = parse::ArgumentIs(lookup(s));
}
- if let parse::CountIsName(s) = arg.format.width {
+ if let parse::CountIsName(s, _) = arg.format.width {
arg.format.width = parse::CountIsParam(lookup(s));
}
- if let parse::CountIsName(s) = arg.format.precision {
+ if let parse::CountIsName(s, _) = arg.format.precision {
arg.format.precision = parse::CountIsParam(lookup(s));
}
}
// it's written second, so it should come after width/precision.
let pos = match arg.position {
parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => Exact(i),
- parse::ArgumentNamed(s) => Named(s),
+ parse::ArgumentNamed(s, span) => Named(s, span),
};
let ty = Placeholder(match arg.format.ty {
parse::CountIsParam(i) => {
self.verify_arg_type(Exact(i), Count);
}
- parse::CountIsName(s) => {
- self.verify_arg_type(Named(s), Count);
+ parse::CountIsName(s, span) => {
+ self.verify_arg_type(Named(s, span), Count);
}
}
}
}
}
- Named(name) => {
+ Named(name, span) => {
match self.names.get(&name) {
Some(&idx) => {
// Treat as positional arg.
self.arg_types.push(Vec::new());
self.arg_unique_types.push(Vec::new());
let span = if self.is_literal {
- *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp)
+ self.fmtsp.from_inner(span)
} else {
self.fmtsp
};
} else {
let msg = format!("there is no argument named `{}`", name);
let sp = if self.is_literal {
- *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp)
+ self.fmtsp.from_inner(span)
} else {
self.fmtsp
};
}
parse::CountImplied => count(sym::Implied, None),
// should never be the case, names are already resolved
- parse::CountIsName(_) => panic!("should never happen"),
+ parse::CountIsName(..) => panic!("should never happen"),
}
}
// should never be the case, because names are already
// resolved.
- parse::ArgumentNamed(_) => panic!("should never happen"),
+ parse::ArgumentNamed(..) => panic!("should never happen"),
}
};
/// Actually builds the expression which the format_args! block will be
/// expanded to.
fn into_expr(self) -> P<ast::Expr> {
- let mut args = Vec::with_capacity(
+ let mut original_args = self.args;
+ let mut fmt_args = Vec::with_capacity(
self.arg_unique_types.iter().map(|v| v.len()).sum::<usize>() + self.count_args.len(),
);
- let mut heads = Vec::with_capacity(self.args.len());
// First, build up the static array which will become our precompiled
// format "string"
let pieces = self.ecx.expr_vec_slice(self.fmtsp, 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).collect();
-
- // Right now there is a bug such that for the expression:
- // foo(bar(&1))
- // the lifetime of `1` doesn't outlast the call to `bar`, so it's not
- // valid for the call to `foo`. To work around this all arguments to the
- // format! string are shoved into locals. Furthermore, we shove the address
- // 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() {
- for arg_ty in self.arg_unique_types[i].iter() {
- args.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty, i));
- }
- // use the arg span for `&arg` so that borrowck errors
- // point to the specific expression passed to the macro
- // (the span is otherwise unavailable in MIR)
- heads.push(self.ecx.expr_addr_of(e.span.with_ctxt(self.macsp.ctxt()), e));
- }
- for index in self.count_args {
- let span = spans_pos[index];
- args.push(Context::format_arg(self.ecx, self.macsp, span, &Count, index));
+ // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments
+ // constructor. In general the expressions in this slice might be
+ // permuted from their order in original_args (such as in the case of
+ // "{1} {0}"), or may have multiple entries referring to the same
+ // element of original_args ("{0} {0}").
+ //
+ // The following vector has one item per element of our output slice,
+ // identifying the index of which element of original_args it's passing,
+ // and that argument's type.
+ let mut fmt_arg_index_and_ty = SmallVec::<[(usize, &ArgumentType); 8]>::new();
+ for (i, unique_types) in self.arg_unique_types.iter().enumerate() {
+ fmt_arg_index_and_ty.extend(unique_types.iter().map(|ty| (i, ty)));
}
+ fmt_arg_index_and_ty.extend(self.count_args.iter().map(|&i| (i, &Count)));
- let args_array = self.ecx.expr_vec(self.macsp, args);
-
- // Constructs an AST equivalent to:
+ // Figure out whether there are permuted or repeated elements. If not,
+ // we can generate simpler code.
//
- // match (&arg0, &arg1) {
- // (tmp0, tmp1) => args_array
- // }
+ // The sequence has no indices out of order or repeated if: for every
+ // adjacent pair of elements, the first one's index is less than the
+ // second one's index.
+ let nicely_ordered =
+ fmt_arg_index_and_ty.array_windows().all(|[(i, _i_ty), (j, _j_ty)]| i < j);
+
+ // We want to emit:
//
- // It was:
+ // [ArgumentV1::new(&$arg0, …), ArgumentV1::new(&$arg1, …), …]
//
- // let tmp0 = &arg0;
- // let tmp1 = &arg1;
- // args_array
+ // However, it's only legal to do so if $arg0, $arg1, … were written in
+ // exactly that order by the programmer. When arguments are permuted, we
+ // want them evaluated in the order written by the programmer, not in
+ // the order provided to fmt::Arguments. When arguments are repeated, we
+ // want the expression evaluated only once.
//
- // Because of #11585 the new temporary lifetime rule, the enclosing
- // statements for these temporaries become the let's themselves.
- // If one or more of them are RefCell's, RefCell borrow() will also
- // end there; they don't last long enough for args_array to use them.
- // The match expression solves the scope problem.
+ // Further, if any arg _after the first one_ contains a yield point such
+ // as `await` or `yield`, the above short form is inconvenient for the
+ // caller because it would keep a temporary of type ArgumentV1 alive
+ // across the yield point. ArgumentV1 can't implement Send since it
+ // holds a type-erased arbitrary type.
//
- // Note, it may also very well be transformed to:
+ // Thus in the not nicely ordered case, and in the yielding case, we
+ // emit the following instead:
//
- // match arg0 {
- // ref tmp0 => {
- // match arg1 => {
- // ref tmp1 => args_array } } }
+ // match (&$arg0, &$arg1, …) {
+ // args => [ArgumentV1::new(args.$i, …), ArgumentV1::new(args.$j, …), …]
+ // }
//
- // But the nested match expression is proved to perform not as well
- // as series of let's; the first approach does.
- let args_match = {
- let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::_args, self.macsp));
- let arm = self.ecx.arm(self.macsp, pat, args_array);
- let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads));
- self.ecx.expr_match(self.macsp, head, vec![arm])
- };
+ // for the sequence of indices $i, $j, … governed by fmt_arg_index_and_ty.
+ // This more verbose representation ensures that all arguments are
+ // evaluated a single time each, in the order written by the programmer,
+ // and that the surrounding future/generator (if any) is Send whenever
+ // possible.
+ let no_need_for_match =
+ nicely_ordered && !original_args.iter().skip(1).any(|e| may_contain_yield_point(e));
+
+ for (arg_index, arg_ty) in fmt_arg_index_and_ty {
+ let e = &mut original_args[arg_index];
+ let span = e.span;
+ let arg = if no_need_for_match {
+ let expansion_span = e.span.with_ctxt(self.macsp.ctxt());
+ // The indices are strictly ordered so e has not been taken yet.
+ self.ecx.expr_addr_of(expansion_span, P(e.take()))
+ } else {
+ let def_site = self.ecx.with_def_site_ctxt(span);
+ let args_tuple = self.ecx.expr_ident(def_site, Ident::new(sym::args, def_site));
+ let member = Ident::new(sym::integer(arg_index), def_site);
+ self.ecx.expr(def_site, ast::ExprKind::Field(args_tuple, member))
+ };
+ fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg));
+ }
- let args_slice = self.ecx.expr_addr_of(self.macsp, args_match);
+ let args_array = self.ecx.expr_vec(self.macsp, fmt_args);
+ let args_slice = self.ecx.expr_addr_of(
+ self.macsp,
+ if no_need_for_match {
+ args_array
+ } else {
+ // In the !no_need_for_match case, none of the exprs were moved
+ // away in the previous loop.
+ //
+ // This uses the arg span for `&arg` so that borrowck errors
+ // point to the specific expression passed to the macro (the
+ // span is otherwise unavailable in the MIR used by borrowck).
+ let heads = original_args
+ .into_iter()
+ .map(|e| self.ecx.expr_addr_of(e.span.with_ctxt(self.macsp.ctxt()), e))
+ .collect();
+
+ let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::args, self.macsp));
+ let arm = self.ecx.arm(self.macsp, pat, args_array);
+ let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads));
+ self.ecx.expr_match(self.macsp, head, vec![arm])
+ },
+ );
// Now create the fmt::Arguments struct with all our locals we created.
let (fn_name, fn_args) = if self.all_pieces_simple {
macsp: Span,
mut sp: Span,
ty: &ArgumentType,
- arg_index: usize,
+ arg: P<ast::Expr>,
) -> P<ast::Expr> {
sp = ecx.with_def_site_ctxt(sp);
- let arg = ecx.expr_ident(sp, Ident::new(sym::_args, sp));
- let arg = ecx.expr(sp, ast::ExprKind::Field(arg, Ident::new(sym::integer(arg_index), sp)));
let trait_ = match *ty {
Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true),
Placeholder(trait_) => trait_,
return ecx.expr_call_global(macsp, path, vec![arg]);
}
};
+ let new_fn_name = match trait_ {
+ "Display" => "new_display",
+ "Debug" => "new_debug",
+ "LowerExp" => "new_lower_exp",
+ "UpperExp" => "new_upper_exp",
+ "Octal" => "new_octal",
+ "Pointer" => "new_pointer",
+ "Binary" => "new_binary",
+ "LowerHex" => "new_lower_hex",
+ "UpperHex" => "new_upper_hex",
+ _ => unreachable!(),
+ };
- let path = ecx.std_path(&[sym::fmt, Symbol::intern(trait_), sym::fmt]);
- let format_fn = ecx.path_global(sp, path);
- let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::new]);
- ecx.expr_call_global(macsp, path, vec![arg, ecx.expr_path(format_fn)])
+ let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, Symbol::intern(new_fn_name)]);
+ ecx.expr_call_global(sp, path, vec![arg])
}
}
cx.into_expr()
}
+
+fn may_contain_yield_point(e: &ast::Expr) -> bool {
+ struct MayContainYieldPoint(bool);
+
+ impl Visitor<'_> for MayContainYieldPoint {
+ fn visit_expr(&mut self, e: &ast::Expr) {
+ if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
+ self.0 = true;
+ } else {
+ visit::walk_expr(self, e);
+ }
+ }
+
+ fn visit_mac_call(&mut self, _: &ast::MacCall) {
+ self.0 = true;
+ }
+
+ fn visit_attribute(&mut self, _: &ast::Attribute) {
+ // Conservatively assume this may be a proc macro attribute in
+ // expression position.
+ self.0 = true;
+ }
+
+ fn visit_item(&mut self, _: &ast::Item) {
+ // Do not recurse into nested items.
+ }
+ }
+
+ let mut visitor = MayContainYieldPoint(false);
+ visitor.visit_expr(e);
+ visitor.0
+}