rust/src/libsyntax/ext/format.rs

818 lines
33 KiB
Rust
Raw Normal View History

// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use ast;
use codemap::{Span, respan};
use ext::base::*;
use ext::base;
use ext::build::AstBuilder;
use fmt_macros as parse;
use parse::token::InternedString;
use parse::token;
2014-09-13 11:06:01 -05:00
use ptr::P;
use std::collections::HashMap;
use std::string;
#[deriving(PartialEq)]
enum ArgumentType {
Known(string::String),
2014-09-20 08:37:14 -05:00
Unsigned
}
2013-12-25 23:55:05 -06:00
enum Position {
Exact(uint),
Named(string::String),
2013-12-25 23:55:05 -06:00
}
struct Context<'a, 'b:'a> {
2014-03-09 09:54:34 -05:00
ecx: &'a mut ExtCtxt<'b>,
fmtsp: Span,
2014-06-09 15:12:30 -05:00
/// Parsed argument expressions and the types that we've found so far for
/// them.
2014-09-13 11:06:01 -05:00
args: Vec<P<ast::Expr>>,
arg_types: Vec<Option<ArgumentType>>,
2014-06-09 15:12:30 -05:00
/// 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::String, P<ast::Expr>>,
name_types: HashMap<string::String, ArgumentType>,
name_ordering: Vec<string::String>,
2014-08-21 08:34:00 -05:00
/// The latest consecutive literal strings, or empty if there weren't any.
literal: string::String,
2014-08-21 08:34:00 -05:00
/// Collection of the compiled `rt::Argument` structures
2014-09-13 11:06:01 -05:00
pieces: Vec<P<ast::Expr>>,
2014-08-21 08:34:00 -05:00
/// Collection of string literals
2014-09-13 11:06:01 -05:00
str_pieces: Vec<P<ast::Expr>>,
/// Stays `true` if all formatting parameters are default (as in "{}{}").
all_pieces_simple: bool,
name_positions: HashMap<string::String, uint>,
2014-09-13 11:06:01 -05:00
method_statics: Vec<P<ast::Item>>,
2014-06-09 15:12:30 -05:00
/// Updated as arguments are consumed or methods are entered
nest_level: uint,
next_arg: uint,
}
pub enum Invocation {
2014-09-13 11:06:01 -05:00
Call(P<ast::Expr>),
MethodCall(P<ast::Expr>, ast::Ident),
}
/// Parses the arguments from the given list of tokens, returning None
/// if there's a parse error so we can continue parsing other format!
/// expressions.
///
/// If parsing succeeds, the second return value is:
///
/// Some((fmtstr, unnamed arguments, ordering of named arguments,
/// named arguments))
fn parse_args(ecx: &mut ExtCtxt, sp: Span, allow_method: bool,
tts: &[ast::TokenTree])
-> (Invocation, Option<(P<ast::Expr>, Vec<P<ast::Expr>>, Vec<string::String>,
HashMap<string::String, P<ast::Expr>>)>) {
let mut args = Vec::new();
let mut names = HashMap::<string::String, P<ast::Expr>>::new();
let mut order = Vec::new();
let mut p = ecx.new_parser_from_tts(tts);
// Parse the leading function expression (maybe a block, maybe a path)
let invocation = if allow_method {
let e = p.parse_expr();
if !p.eat(&token::COMMA) {
ecx.span_err(sp, "expected token: `,`");
return (Call(e), None);
}
MethodCall(e, p.parse_ident())
} else {
Call(p.parse_expr())
};
if !p.eat(&token::COMMA) {
ecx.span_err(sp, "expected token: `,`");
return (invocation, None);
}
if p.token == token::EOF {
ecx.span_err(sp, "requires at least a format string argument");
return (invocation, None);
}
let fmtstr = p.parse_expr();
let mut named = false;
while p.token != token::EOF {
if !p.eat(&token::COMMA) {
ecx.span_err(sp, "expected token: `,`");
return (invocation, None);
}
if p.token == token::EOF { break } // accept trailing commas
if named || (token::is_ident(&p.token) &&
p.look_ahead(1, |t| *t == token::EQ)) {
named = true;
let ident = match p.token {
token::IDENT(i, _) => {
p.bump();
i
}
_ if named => {
ecx.span_err(p.span,
"expected ident, positional arguments \
cannot follow named arguments");
return (invocation, None);
}
_ => {
ecx.span_err(p.span,
format!("expected ident for named argument, found `{}`",
p.this_token_to_string()).as_slice());
return (invocation, None);
}
};
let interned_name = token::get_ident(ident);
let name = interned_name.get();
p.expect(&token::EQ);
let e = p.parse_expr();
match names.find_equiv(&name) {
None => {}
Some(prev) => {
ecx.span_err(e.span,
format!("duplicate argument named `{}`",
name).as_slice());
ecx.parse_sess.span_diagnostic.span_note(prev.span, "previously here");
continue
}
}
order.push(name.to_string());
names.insert(name.to_string(), e);
} else {
args.push(p.parse_expr());
}
}
return (invocation, Some((fmtstr, args, order, names)));
}
2014-03-09 09:54:34 -05:00
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 verify_piece(&mut self, p: &parse::Piece) {
match *p {
2013-11-28 14:22:53 -06:00
parse::String(..) => {}
parse::NextArgument(ref arg) => {
// width/precision first, if they have implicit positional
// parameters it makes more sense to consume them first.
self.verify_count(arg.format.width);
self.verify_count(arg.format.precision);
// 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;
if self.check_positional_ok() {
self.next_arg += 1;
}
2013-12-25 23:55:05 -06:00
Exact(i)
}
2013-12-25 23:55:05 -06:00
parse::ArgumentIs(i) => Exact(i),
parse::ArgumentNamed(s) => Named(s.to_string()),
};
let ty = Known(arg.format.ty.to_string());
self.verify_arg_type(pos, ty);
}
}
}
fn verify_count(&mut self, c: parse::Count) {
match c {
2013-11-28 14:22:53 -06:00
parse::CountImplied | parse::CountIs(..) => {}
parse::CountIsParam(i) => {
2013-12-25 23:55:05 -06:00
self.verify_arg_type(Exact(i), Unsigned);
}
parse::CountIsName(s) => {
self.verify_arg_type(Named(s.to_string()), Unsigned);
}
parse::CountIsNextParam => {
if self.check_positional_ok() {
let next_arg = self.next_arg;
self.verify_arg_type(Exact(next_arg), Unsigned);
self.next_arg += 1;
}
}
}
}
fn check_positional_ok(&mut self) -> bool {
if self.nest_level != 0 {
self.ecx.span_err(self.fmtsp, "cannot use implicit positional \
arguments nested inside methods");
false
} else {
true
}
}
fn describe_num_args(&self) -> string::String {
match self.args.len() {
0 => "no arguments given".to_string(),
1 => "there is 1 argument".to_string(),
x => format!("there are {} arguments", x),
}
}
2013-12-25 23:55:05 -06:00
fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
match arg {
2013-12-25 23:55:05 -06:00
Exact(arg) => {
if self.args.len() <= arg {
let msg = format!("invalid reference to argument `{}` ({:s})",
arg, self.describe_num_args());
self.ecx.span_err(self.fmtsp, msg.as_slice());
return;
}
2014-01-21 12:08:10 -06:00
{
let arg_type = match self.arg_types.get(arg) {
&None => None,
&Some(ref x) => Some(x)
2014-01-21 12:08:10 -06:00
};
self.verify_same(self.args.get(arg).span, &ty, arg_type);
2014-01-21 12:08:10 -06:00
}
if self.arg_types.get(arg).is_none() {
*self.arg_types.get_mut(arg) = Some(ty);
}
}
2013-12-25 23:55:05 -06:00
Named(name) => {
let span = match self.names.find(&name) {
Some(e) => e.span,
None => {
2013-09-27 23:01:58 -05:00
let msg = format!("there is no argument named `{}`", name);
self.ecx.span_err(self.fmtsp, msg.as_slice());
return;
}
};
2014-01-21 12:08:10 -06:00
self.verify_same(span, &ty, self.name_types.find(&name));
if !self.name_types.contains_key(&name) {
2014-01-21 12:08:10 -06:00
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);
}
}
}
}
/// 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))`
2014-01-21 12:08:10 -06:00
fn verify_same(&self,
sp: Span,
ty: &ArgumentType,
before: Option<&ArgumentType>) {
let cur = match before {
None => return,
Some(t) => t,
};
2014-01-21 12:08:10 -06:00
if *ty == *cur {
return
}
match (cur, ty) {
2014-01-21 12:08:10 -06:00
(&Known(ref cur), &Known(ref ty)) => {
self.ecx.span_err(sp,
2013-09-27 23:01:58 -05:00
format!("argument redeclared with type `{}` when \
2014-01-21 12:08:10 -06:00
it was previously `{}`",
*ty,
*cur).as_slice());
}
2014-01-21 12:08:10 -06:00
(&Known(ref cur), _) => {
self.ecx.span_err(sp,
2013-09-27 23:01:58 -05:00
format!("argument used to format with `{}` was \
attempted to not be used for formatting",
*cur).as_slice());
}
2014-01-21 12:08:10 -06:00
(_, &Known(ref ty)) => {
self.ecx.span_err(sp,
2013-09-27 23:01:58 -05:00
format!("argument previously used as a format \
argument attempted to be used as `{}`",
*ty).as_slice());
}
(_, _) => {
self.ecx.span_err(sp, "argument declared with multiple formats");
}
}
}
/// These attributes are applied to all statics that this syntax extension
/// will generate.
2014-09-13 11:06:01 -05:00
fn static_attrs(ecx: &ExtCtxt, fmtsp: Span) -> Vec<ast::Attribute> {
// Flag statics as `inline` so LLVM can merge duplicate globals as much
// as possible (which we're generating a whole lot of).
2014-09-13 11:06:01 -05:00
let unnamed = ecx.meta_word(fmtsp, InternedString::new("inline"));
let unnamed = ecx.attribute(fmtsp, unnamed);
2013-12-08 01:55:27 -06:00
// Do not warn format string as dead code
2014-09-13 11:06:01 -05:00
let dead_code = ecx.meta_word(fmtsp, InternedString::new("dead_code"));
let allow_dead_code = ecx.meta_list(fmtsp,
InternedString::new("allow"),
vec![dead_code]);
let allow_dead_code = ecx.attribute(fmtsp, allow_dead_code);
vec![unnamed, allow_dead_code]
}
2014-09-13 11:06:01 -05:00
fn rtpath(ecx: &ExtCtxt, s: &str) -> Vec<ast::Ident> {
vec![ecx.ident_of("std"), ecx.ident_of("fmt"), ecx.ident_of("rt"), ecx.ident_of(s)]
}
2014-09-13 11:06:01 -05:00
fn trans_count(&self, c: parse::Count) -> P<ast::Expr> {
let sp = self.fmtsp;
match c {
parse::CountIs(i) => {
2014-09-13 11:06:01 -05:00
self.ecx.expr_call_global(sp, Context::rtpath(self.ecx, "CountIs"),
vec!(self.ecx.expr_uint(sp, i)))
}
parse::CountIsParam(i) => {
2014-09-13 11:06:01 -05:00
self.ecx.expr_call_global(sp, Context::rtpath(self.ecx, "CountIsParam"),
vec!(self.ecx.expr_uint(sp, i)))
}
parse::CountImplied => {
2014-09-13 11:06:01 -05:00
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx,
"CountImplied"));
self.ecx.expr_path(path)
}
parse::CountIsNextParam => {
2014-09-13 11:06:01 -05:00
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx,
"CountIsNextParam"));
self.ecx.expr_path(path)
}
parse::CountIsName(n) => {
let i = match self.name_positions.find_equiv(&n) {
Some(&i) => i,
None => 0, // error already emitted elsewhere
};
let i = i + self.args.len();
2014-09-13 11:06:01 -05:00
self.ecx.expr_call_global(sp, Context::rtpath(self.ecx, "CountIsParam"),
vec!(self.ecx.expr_uint(sp, i)))
}
}
}
2014-08-21 08:34:00 -05:00
/// Translate the accumulated string literals to a literal expression
2014-09-13 11:06:01 -05:00
fn trans_literal_string(&mut self) -> P<ast::Expr> {
let sp = self.fmtsp;
2014-08-21 08:34:00 -05:00
let s = token::intern_and_get_ident(self.literal.as_slice());
self.literal.clear();
self.ecx.expr_str(sp, s)
}
2014-08-21 08:34:00 -05:00
/// Translate a `parse::Piece` to a static `rt::Argument` or append
/// to the `literal` string.
2014-09-13 11:06:01 -05:00
fn trans_piece(&mut self, piece: &parse::Piece) -> Option<P<ast::Expr>> {
let sp = self.fmtsp;
match *piece {
parse::String(s) => {
2014-08-21 08:34:00 -05:00
self.literal.push_str(s);
None
}
parse::NextArgument(ref arg) => {
// Translate the position
let pos = match arg.position {
// These two have a direct mapping
parse::ArgumentNext => {
2014-09-13 11:06:01 -05:00
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx,
"ArgumentNext"));
self.ecx.expr_path(path)
}
parse::ArgumentIs(i) => {
2014-09-13 11:06:01 -05:00
self.ecx.expr_call_global(sp, Context::rtpath(self.ecx, "ArgumentIs"),
vec!(self.ecx.expr_uint(sp, i)))
}
// Named arguments are converted to positional arguments at
// the end of the list of arguments
parse::ArgumentNamed(n) => {
2014-01-21 12:08:10 -06:00
let i = match self.name_positions.find_equiv(&n) {
Some(&i) => i,
None => 0, // error already emitted elsewhere
};
let i = i + self.args.len();
2014-09-13 11:06:01 -05:00
self.ecx.expr_call_global(sp, Context::rtpath(self.ecx, "ArgumentIs"),
vec!(self.ecx.expr_uint(sp, i)))
}
};
let simple_arg = parse::Argument {
position: parse::ArgumentNext,
format: parse::FormatSpec {
fill: arg.format.fill,
align: parse::AlignUnknown,
flags: 0,
precision: parse::CountImplied,
width: parse::CountImplied,
ty: arg.format.ty
}
};
let fill = match arg.format.fill { Some(c) => c, None => ' ' };
if *arg != simple_arg || fill != ' ' {
self.all_pieces_simple = false;
}
// Translate the format
let fill = self.ecx.expr_lit(sp, ast::LitChar(fill));
let align = match arg.format.align {
parse::AlignLeft => {
2014-09-13 11:06:01 -05:00
self.ecx.path_global(sp, Context::rtpath(self.ecx, "AlignLeft"))
}
parse::AlignRight => {
2014-09-13 11:06:01 -05:00
self.ecx.path_global(sp, Context::rtpath(self.ecx, "AlignRight"))
}
parse::AlignCenter => {
2014-09-13 11:06:01 -05:00
self.ecx.path_global(sp, Context::rtpath(self.ecx, "AlignCenter"))
}
parse::AlignUnknown => {
2014-09-13 11:06:01 -05:00
self.ecx.path_global(sp, Context::rtpath(self.ecx, "AlignUnknown"))
}
};
let align = self.ecx.expr_path(align);
let flags = self.ecx.expr_uint(sp, arg.format.flags);
let prec = self.trans_count(arg.format.precision);
let width = self.trans_count(arg.format.width);
2014-09-13 11:06:01 -05:00
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),
self.ecx.field_imm(sp, self.ecx.ident_of("align"), align),
self.ecx.field_imm(sp, self.ecx.ident_of("flags"), flags),
self.ecx.field_imm(sp, self.ecx.ident_of("precision"), prec),
self.ecx.field_imm(sp, self.ecx.ident_of("width"), width)));
2014-09-13 11:06:01 -05:00
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "Argument"));
2014-08-21 08:34:00 -05:00
Some(self.ecx.expr_struct(sp, path, vec!(
self.ecx.field_imm(sp, self.ecx.ident_of("position"), pos),
2014-08-21 08:34:00 -05:00
self.ecx.field_imm(sp, self.ecx.ident_of("format"), fmt))))
}
}
}
2014-09-13 11:06:01 -05:00
fn item_static_array(ecx: &mut ExtCtxt,
name: ast::Ident,
2014-09-13 11:06:01 -05:00
piece_ty: P<ast::Ty>,
pieces: Vec<P<ast::Expr>>)
-> P<ast::Stmt> {
let fmtsp = piece_ty.span;
let pieces_len = ecx.expr_uint(fmtsp, pieces.len());
let fmt = ecx.expr_vec(fmtsp, pieces);
let ty = ast::TyFixedLengthVec(
piece_ty,
pieces_len
);
2014-09-13 11:06:01 -05:00
let ty = ecx.ty(fmtsp, ty);
let st = ast::ItemStatic(ty, ast::MutImmutable, fmt);
2014-09-13 11:06:01 -05:00
let item = ecx.item(fmtsp, name, Context::static_attrs(ecx, fmtsp), st);
let decl = respan(fmtsp, ast::DeclItem(item));
P(respan(fmtsp, ast::StmtDecl(P(decl), ast::DUMMY_NODE_ID)))
}
2013-09-27 23:01:58 -05:00
/// Actually builds the expression which the iformat! block will be expanded
/// to
2014-09-13 11:06:01 -05:00
fn to_expr(mut self, invocation: Invocation) -> P<ast::Expr> {
let mut lets = Vec::new();
let mut locals = Vec::new();
let mut names = Vec::from_fn(self.name_positions.len(), |_| None);
let mut pats = Vec::new();
let mut heads = Vec::new();
// First, declare all of our methods that are statics
2014-09-14 22:27:36 -05:00
for method in self.method_statics.into_iter() {
let decl = respan(self.fmtsp, ast::DeclItem(method));
2014-09-13 11:06:01 -05:00
lets.push(P(respan(self.fmtsp,
ast::StmtDecl(P(decl), ast::DUMMY_NODE_ID))));
}
// Next, build up the static array which will become our precompiled
// format "string"
2014-08-21 08:34:00 -05:00
let static_str_name = self.ecx.ident_of("__STATIC_FMTSTR");
let static_lifetime = self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("'static").name);
let piece_ty = self.ecx.ty_rptr(
self.fmtsp,
self.ecx.ty_ident(self.fmtsp, self.ecx.ident_of("str")),
Some(static_lifetime),
ast::MutImmutable);
2014-09-13 11:06:01 -05:00
lets.push(Context::item_static_array(self.ecx,
static_str_name,
piece_ty,
self.str_pieces));
// Then, build up the static array which will store our precompiled
// nonstandard placeholders, if there are any.
2014-08-21 08:34:00 -05:00
let static_args_name = self.ecx.ident_of("__STATIC_FMTARGS");
if !self.all_pieces_simple {
let piece_ty = self.ecx.ty_path(self.ecx.path_all(
self.fmtsp,
2014-09-13 11:06:01 -05:00
true, Context::rtpath(self.ecx, "Argument"),
vec![static_lifetime],
vec![]
), None);
2014-09-13 11:06:01 -05:00
lets.push(Context::item_static_array(self.ecx,
static_args_name,
piece_ty,
self.pieces));
}
// 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
2013-09-27 23:01:58 -05:00
// 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.
2014-09-14 22:27:36 -05:00
for (i, e) in self.args.into_iter().enumerate() {
2014-09-13 11:06:01 -05:00
let arg_ty = match self.arg_types.get(i).as_ref() {
Some(ty) => ty,
None => continue // error already generated
};
let name = self.ecx.ident_of(format!("__arg{}", i).as_slice());
pats.push(self.ecx.pat_ident(e.span, name));
2014-09-13 11:06:01 -05:00
locals.push(Context::format_arg(self.ecx, 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.iter() {
2014-09-13 11:06:01 -05:00
let e = match self.names.pop(name) {
Some(e) => e,
None => continue
};
let arg_ty = match self.name_types.find(name) {
Some(ty) => ty,
None => continue
};
let lname = self.ecx.ident_of(format!("__arg{}",
*name).as_slice());
pats.push(self.ecx.pat_ident(e.span, lname));
*names.get_mut(*self.name_positions.get(name)) =
2014-09-13 11:06:01 -05:00
Some(Context::format_arg(self.ecx, e.span, arg_ty,
self.ecx.expr_ident(e.span, lname)));
heads.push(self.ecx.expr_addr_of(e.span, e));
}
// Now create a vector containing all the arguments
let slicename = self.ecx.ident_of("__args_vec");
{
2014-09-14 22:27:36 -05:00
let args = names.into_iter().map(|a| a.unwrap());
let mut args = locals.into_iter().chain(args);
let args = self.ecx.expr_vec_slice(self.fmtsp, args.collect());
lets.push(self.ecx.stmt_let(self.fmtsp, false, slicename, args));
}
// Now create the fmt::Arguments struct with all our locals we created.
2014-08-21 08:34:00 -05:00
let pieces = self.ecx.expr_ident(self.fmtsp, static_str_name);
let args_slice = self.ecx.expr_ident(self.fmtsp, slicename);
let (fn_name, fn_args) = if self.all_pieces_simple {
("new", vec![pieces, args_slice])
} else {
let fmt = self.ecx.expr_ident(self.fmtsp, static_args_name);
("with_placeholders", vec![pieces, fmt, args_slice])
};
let result = self.ecx.expr_call_global(self.fmtsp, vec!(
self.ecx.ident_of("std"),
self.ecx.ident_of("fmt"),
self.ecx.ident_of("Arguments"),
self.ecx.ident_of(fn_name)), fn_args);
// We did all the work of making sure that the arguments
// structure is safe, so we can safely have an unsafe block.
let result = self.ecx.expr_block(P(ast::Block {
view_items: Vec::new(),
stmts: Vec::new(),
expr: Some(result),
id: ast::DUMMY_NODE_ID,
rules: ast::UnsafeBlock(ast::CompilerGenerated),
span: self.fmtsp,
}));
let resname = self.ecx.ident_of("__args");
lets.push(self.ecx.stmt_let(self.fmtsp, false, resname, result));
let res = self.ecx.expr_ident(self.fmtsp, resname);
let result = match invocation {
Call(e) => {
2014-09-13 11:06:01 -05:00
let span = e.span;
self.ecx.expr_call(span, e,
vec!(self.ecx.expr_addr_of(span, res)))
}
MethodCall(e, m) => {
2014-09-13 11:06:01 -05:00
let span = e.span;
self.ecx.expr_method_call(span, e, m,
vec!(self.ecx.expr_addr_of(span, res)))
}
};
let body = self.ecx.expr_block(self.ecx.block(self.fmtsp, lets,
Some(result)));
// Constructs an AST equivalent to:
//
// match (&arg0, &arg1) {
// (tmp0, tmp1) => body
// }
//
// It was:
//
// let tmp0 = &arg0;
// let tmp1 = &arg1;
// body
//
// 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 body to use them. The
// match expression solves the scope problem.
//
// Note, it may also very well be transformed to:
//
// match arg0 {
// ref tmp0 => {
// match arg1 => {
// ref tmp1 => body } } }
//
// But the nested match expression is proved to perform not as well
// as series of let's; the first approach does.
let pat = self.ecx.pat(self.fmtsp, ast::PatTup(pats));
let arm = self.ecx.arm(self.fmtsp, vec!(pat), body);
let head = self.ecx.expr(self.fmtsp, ast::ExprTup(heads));
self.ecx.expr_match(self.fmtsp, head, vec!(arm))
}
2014-09-13 11:06:01 -05:00
fn format_arg(ecx: &ExtCtxt, sp: Span,
ty: &ArgumentType, arg: P<ast::Expr>)
-> P<ast::Expr> {
let (krate, fmt_fn) = match *ty {
2014-01-21 12:08:10 -06:00
Known(ref tyname) => {
match tyname.as_slice() {
"" => ("std", "secret_show"),
"b" => ("std", "secret_bool"),
"c" => ("std", "secret_char"),
"d" | "i" => ("std", "secret_signed"),
"e" => ("std", "secret_lower_exp"),
"E" => ("std", "secret_upper_exp"),
"f" => ("std", "secret_float"),
"o" => ("std", "secret_octal"),
"p" => ("std", "secret_pointer"),
"s" => ("std", "secret_string"),
"t" => ("std", "secret_binary"),
"u" => ("std", "secret_unsigned"),
"x" => ("std", "secret_lower_hex"),
"X" => ("std", "secret_upper_hex"),
_ => {
2014-09-13 11:06:01 -05:00
ecx.span_err(sp,
format!("unknown format trait `{}`",
*tyname).as_slice());
("std", "dummy")
}
}
}
Unsigned => {
2014-09-13 11:06:01 -05:00
return ecx.expr_call_global(sp, vec![
ecx.ident_of("std"),
ecx.ident_of("fmt"),
ecx.ident_of("argumentuint")], vec![arg])
}
};
2014-09-13 11:06:01 -05:00
let format_fn = ecx.path_global(sp, vec![
ecx.ident_of(krate),
ecx.ident_of("fmt"),
ecx.ident_of(fmt_fn)]);
ecx.expr_call_global(sp, vec![
ecx.ident_of("std"),
ecx.ident_of("fmt"),
ecx.ident_of("argument")], vec![ecx.expr_path(format_fn), arg])
}
}
pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
tts: &[ast::TokenTree])
-> Box<base::MacResult+'cx> {
match parse_args(ecx, sp, false, tts) {
(invocation, Some((efmt, args, order, names))) => {
MacExpr::new(expand_preparsed_format_args(ecx, sp, invocation, efmt,
args, order, names))
}
(_, None) => MacExpr::new(ecx.expr_uint(sp, 2))
}
}
pub fn expand_format_args_method<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
tts: &[ast::TokenTree]) -> Box<base::MacResult+'cx> {
match parse_args(ecx, sp, true, tts) {
(invocation, Some((efmt, args, order, names))) => {
MacExpr::new(expand_preparsed_format_args(ecx, sp, invocation, efmt,
args, order, names))
}
(_, None) => MacExpr::new(ecx.expr_uint(sp, 2))
}
}
/// Take the various parts of `format_args!(extra, efmt, args...,
/// name=names...)` and construct the appropriate formatting
/// expression.
pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
invocation: Invocation,
2014-09-13 11:06:01 -05:00
efmt: P<ast::Expr>,
args: Vec<P<ast::Expr>>,
name_ordering: Vec<string::String>,
names: HashMap<string::String, P<ast::Expr>>)
2014-09-13 11:06:01 -05:00
-> P<ast::Expr> {
let arg_types = Vec::from_fn(args.len(), |_| None);
let mut cx = Context {
ecx: ecx,
args: args,
arg_types: arg_types,
names: names,
name_positions: HashMap::new(),
name_types: HashMap::new(),
name_ordering: name_ordering,
nest_level: 0,
next_arg: 0,
literal: string::String::new(),
pieces: Vec::new(),
2014-08-21 08:34:00 -05:00
str_pieces: Vec::new(),
all_pieces_simple: true,
method_statics: Vec::new(),
fmtsp: sp,
};
cx.fmtsp = efmt.span;
let fmt = match expr_to_string(cx.ecx,
efmt,
"format argument must be a string literal.") {
Some((fmt, _)) => fmt,
None => return DummyResult::raw_expr(sp)
};
Remove std::condition This has been a long time coming. Conditions in rust were initially envisioned as being a good alternative to error code return pattern. The idea is that all errors are fatal-by-default, and you can opt-in to handling the error by registering an error handler. While sounding nice, conditions ended up having some unforseen shortcomings: * Actually handling an error has some very awkward syntax: let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } This pattern can certainly use functions like io::result, but at its core actually handling conditions is fairly difficult * The "zero value" of a function is often confusing. One of the main ideas behind using conditions was to change the signature of I/O functions. Instead of read_be_u32() returning a result, it returned a u32. Errors were notified via a condition, and if you caught the condition you understood that the "zero value" returned is actually a garbage value. These zero values are often difficult to understand, however. One case of this is the read_bytes() function. The function takes an integer length of the amount of bytes to read, and returns an array of that size. The array may actually be shorter, however, if an error occurred. Another case is fs::stat(). The theoretical "zero value" is a blank stat struct, but it's a little awkward to create and return a zero'd out stat struct on a call to stat(). In general, the return value of functions that can raise error are much more natural when using a Result as opposed to an always-usable zero-value. * Conditions impose a necessary runtime requirement on *all* I/O. In theory I/O is as simple as calling read() and write(), but using conditions imposed the restriction that a rust local task was required if you wanted to catch errors with I/O. While certainly an surmountable difficulty, this was always a bit of a thorn in the side of conditions. * Functions raising conditions are not always clear that they are raising conditions. This suffers a similar problem to exceptions where you don't actually know whether a function raises a condition or not. The documentation likely explains, but if someone retroactively adds a condition to a function there's nothing forcing upstream users to acknowledge a new point of task failure. * Libaries using I/O are not guaranteed to correctly raise on conditions when an error occurs. In developing various I/O libraries, it's much easier to just return `None` from a read rather than raising an error. The silent contract of "don't raise on EOF" was a little difficult to understand and threw a wrench into the answer of the question "when do I raise a condition?" Many of these difficulties can be overcome through documentation, examples, and general practice. In the end, all of these difficulties added together ended up being too overwhelming and improving various aspects didn't end up helping that much. A result-based I/O error handling strategy also has shortcomings, but the cognitive burden is much smaller. The tooling necessary to make this strategy as usable as conditions were is much smaller than the tooling necessary for conditions. Perhaps conditions may manifest themselves as a future entity, but for now we're going to remove them from the standard library. Closes #9795 Closes #8968
2014-02-04 21:02:10 -06:00
let mut parser = parse::Parser::new(fmt.get());
loop {
match parser.next() {
Some(piece) => {
if parser.errors.len() > 0 { break }
cx.verify_piece(&piece);
match cx.trans_piece(&piece) {
Some(piece) => {
2014-08-21 08:34:00 -05:00
let s = cx.trans_literal_string();
cx.str_pieces.push(s);
cx.pieces.push(piece);
}
None => {}
}
}
Remove std::condition This has been a long time coming. Conditions in rust were initially envisioned as being a good alternative to error code return pattern. The idea is that all errors are fatal-by-default, and you can opt-in to handling the error by registering an error handler. While sounding nice, conditions ended up having some unforseen shortcomings: * Actually handling an error has some very awkward syntax: let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } This pattern can certainly use functions like io::result, but at its core actually handling conditions is fairly difficult * The "zero value" of a function is often confusing. One of the main ideas behind using conditions was to change the signature of I/O functions. Instead of read_be_u32() returning a result, it returned a u32. Errors were notified via a condition, and if you caught the condition you understood that the "zero value" returned is actually a garbage value. These zero values are often difficult to understand, however. One case of this is the read_bytes() function. The function takes an integer length of the amount of bytes to read, and returns an array of that size. The array may actually be shorter, however, if an error occurred. Another case is fs::stat(). The theoretical "zero value" is a blank stat struct, but it's a little awkward to create and return a zero'd out stat struct on a call to stat(). In general, the return value of functions that can raise error are much more natural when using a Result as opposed to an always-usable zero-value. * Conditions impose a necessary runtime requirement on *all* I/O. In theory I/O is as simple as calling read() and write(), but using conditions imposed the restriction that a rust local task was required if you wanted to catch errors with I/O. While certainly an surmountable difficulty, this was always a bit of a thorn in the side of conditions. * Functions raising conditions are not always clear that they are raising conditions. This suffers a similar problem to exceptions where you don't actually know whether a function raises a condition or not. The documentation likely explains, but if someone retroactively adds a condition to a function there's nothing forcing upstream users to acknowledge a new point of task failure. * Libaries using I/O are not guaranteed to correctly raise on conditions when an error occurs. In developing various I/O libraries, it's much easier to just return `None` from a read rather than raising an error. The silent contract of "don't raise on EOF" was a little difficult to understand and threw a wrench into the answer of the question "when do I raise a condition?" Many of these difficulties can be overcome through documentation, examples, and general practice. In the end, all of these difficulties added together ended up being too overwhelming and improving various aspects didn't end up helping that much. A result-based I/O error handling strategy also has shortcomings, but the cognitive burden is much smaller. The tooling necessary to make this strategy as usable as conditions were is much smaller than the tooling necessary for conditions. Perhaps conditions may manifest themselves as a future entity, but for now we're going to remove them from the standard library. Closes #9795 Closes #8968
2014-02-04 21:02:10 -06:00
None => break
}
}
match parser.errors.shift() {
Some(error) => {
2014-09-13 11:06:01 -05:00
cx.ecx.span_err(cx.fmtsp,
format!("invalid format string: {}",
error).as_slice());
return DummyResult::raw_expr(sp);
}
Remove std::condition This has been a long time coming. Conditions in rust were initially envisioned as being a good alternative to error code return pattern. The idea is that all errors are fatal-by-default, and you can opt-in to handling the error by registering an error handler. While sounding nice, conditions ended up having some unforseen shortcomings: * Actually handling an error has some very awkward syntax: let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } This pattern can certainly use functions like io::result, but at its core actually handling conditions is fairly difficult * The "zero value" of a function is often confusing. One of the main ideas behind using conditions was to change the signature of I/O functions. Instead of read_be_u32() returning a result, it returned a u32. Errors were notified via a condition, and if you caught the condition you understood that the "zero value" returned is actually a garbage value. These zero values are often difficult to understand, however. One case of this is the read_bytes() function. The function takes an integer length of the amount of bytes to read, and returns an array of that size. The array may actually be shorter, however, if an error occurred. Another case is fs::stat(). The theoretical "zero value" is a blank stat struct, but it's a little awkward to create and return a zero'd out stat struct on a call to stat(). In general, the return value of functions that can raise error are much more natural when using a Result as opposed to an always-usable zero-value. * Conditions impose a necessary runtime requirement on *all* I/O. In theory I/O is as simple as calling read() and write(), but using conditions imposed the restriction that a rust local task was required if you wanted to catch errors with I/O. While certainly an surmountable difficulty, this was always a bit of a thorn in the side of conditions. * Functions raising conditions are not always clear that they are raising conditions. This suffers a similar problem to exceptions where you don't actually know whether a function raises a condition or not. The documentation likely explains, but if someone retroactively adds a condition to a function there's nothing forcing upstream users to acknowledge a new point of task failure. * Libaries using I/O are not guaranteed to correctly raise on conditions when an error occurs. In developing various I/O libraries, it's much easier to just return `None` from a read rather than raising an error. The silent contract of "don't raise on EOF" was a little difficult to understand and threw a wrench into the answer of the question "when do I raise a condition?" Many of these difficulties can be overcome through documentation, examples, and general practice. In the end, all of these difficulties added together ended up being too overwhelming and improving various aspects didn't end up helping that much. A result-based I/O error handling strategy also has shortcomings, but the cognitive burden is much smaller. The tooling necessary to make this strategy as usable as conditions were is much smaller than the tooling necessary for conditions. Perhaps conditions may manifest themselves as a future entity, but for now we're going to remove them from the standard library. Closes #9795 Closes #8968
2014-02-04 21:02:10 -06:00
None => {}
}
2014-08-21 08:34:00 -05:00
if !cx.literal.is_empty() {
let s = cx.trans_literal_string();
cx.str_pieces.push(s);
}
// Make sure that all arguments were used and all arguments have types.
for (i, ty) in cx.arg_types.iter().enumerate() {
if ty.is_none() {
cx.ecx.span_err(cx.args.get(i).span, "argument never used");
}
}
for (name, e) in cx.names.iter() {
if !cx.name_types.contains_key(name) {
2013-12-28 23:06:22 -06:00
cx.ecx.span_err(e.span, "named argument never used");
}
}
cx.to_expr(invocation)
}