syntax: Add a macro, format_args_method!()
Currently, the format_args!() macro takes as its first argument an expression which is the callee of an ExprCall. This means that if format_args!() is used with calling a method a closure must be used. Consider this code, however: format_args!(|args| { foo.writer.write_fmt(args) }, "{}", foo.field) The closure borrows the entire `foo` structure, disallowing the later borrow of `foo.field`. To preserve the semantics of the `write!` macro, it is also impossible to borrow specifically the `writer` field of the `foo` structure because it must be borrowed mutably, but the `foo` structure is not guaranteed to be mutable itself. This new macro is invoked like: format_args_method!(foo.writer, write_fmt, "{}", foo.field) This macro will generate an ExprMethodCall which allows the borrow checker to understand that `writer` and `field` should be borrowed separately. This macro is not strictly necessary, with DST or possibly UFCS other workarounds could be used. For now, though, it looks like this is required to implement the `write!` macro.
This commit is contained in:
parent
00f9263914
commit
854d95f9ff
@ -281,7 +281,10 @@ pub fn syntax_expander_table() -> SyntaxEnv {
|
|||||||
ext::fmt::expand_syntax_ext));
|
ext::fmt::expand_syntax_ext));
|
||||||
syntax_expanders.insert(intern("format_args"),
|
syntax_expanders.insert(intern("format_args"),
|
||||||
builtin_normal_expander(
|
builtin_normal_expander(
|
||||||
ext::format::expand_args));
|
ext::format::expand_format_args));
|
||||||
|
syntax_expanders.insert(intern("format_args_method"),
|
||||||
|
builtin_normal_expander(
|
||||||
|
ext::format::expand_format_args_method));
|
||||||
syntax_expanders.insert(intern("env"),
|
syntax_expanders.insert(intern("env"),
|
||||||
builtin_normal_expander(
|
builtin_normal_expander(
|
||||||
ext::env::expand_env));
|
ext::env::expand_env));
|
||||||
|
@ -120,23 +120,18 @@ fn show_substructure(cx: &mut ExtCtxt, span: Span,
|
|||||||
// AST construction!
|
// AST construction!
|
||||||
// we're basically calling
|
// we're basically calling
|
||||||
//
|
//
|
||||||
// format_arg!(|__args| ::std::fmt::write(fmt.buf, __args), "<format_string>", exprs...)
|
// format_arg_method!(fmt, write_fmt, "<format_string>", exprs...)
|
||||||
//
|
//
|
||||||
// but doing it directly via ext::format.
|
// but doing it directly via ext::format.
|
||||||
let formatter = substr.nonself_args[0];
|
let formatter = substr.nonself_args[0];
|
||||||
let buf = cx.expr_field_access(span, formatter, cx.ident_of("buf"));
|
|
||||||
|
|
||||||
let std_write = vec!(cx.ident_of("std"), cx.ident_of("fmt"), cx.ident_of("write"));
|
|
||||||
let args = cx.ident_of("__args");
|
|
||||||
let write_call = cx.expr_call_global(span, std_write, vec!(buf, cx.expr_ident(span, args)));
|
|
||||||
let format_closure = cx.lambda_expr(span, vec!(args), write_call);
|
|
||||||
|
|
||||||
|
let meth = cx.ident_of("write_fmt");
|
||||||
let s = token::intern_and_get_ident(format_string.as_slice());
|
let s = token::intern_and_get_ident(format_string.as_slice());
|
||||||
let format_string = cx.expr_str(span, s);
|
let format_string = cx.expr_str(span, s);
|
||||||
|
|
||||||
// phew, not our responsibility any more!
|
// phew, not our responsibility any more!
|
||||||
format::expand_preparsed_format_args(cx, span,
|
format::expand_preparsed_format_args(cx, span,
|
||||||
format_closure,
|
format::MethodCall(formatter, meth),
|
||||||
format_string, exprs, Vec::new(),
|
format_string, exprs, Vec::new(),
|
||||||
HashMap::new())
|
HashMap::new())
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,11 @@ struct Context<'a, 'b> {
|
|||||||
next_arg: uint,
|
next_arg: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Invocation {
|
||||||
|
Call(@ast::Expr),
|
||||||
|
MethodCall(@ast::Expr, ast::Ident),
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses the arguments from the given list of tokens, returning None
|
/// Parses the arguments from the given list of tokens, returning None
|
||||||
/// if there's a parse error so we can continue parsing other format!
|
/// if there's a parse error so we can continue parsing other format!
|
||||||
/// expressions.
|
/// expressions.
|
||||||
@ -67,8 +72,9 @@ struct Context<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// Some((fmtstr, unnamed arguments, ordering of named arguments,
|
/// Some((fmtstr, unnamed arguments, ordering of named arguments,
|
||||||
/// named arguments))
|
/// named arguments))
|
||||||
fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
|
fn parse_args(ecx: &mut ExtCtxt, sp: Span, allow_method: bool,
|
||||||
-> (@ast::Expr, Option<(@ast::Expr, Vec<@ast::Expr>, Vec<StrBuf>,
|
tts: &[ast::TokenTree])
|
||||||
|
-> (Invocation, Option<(@ast::Expr, Vec<@ast::Expr>, Vec<StrBuf>,
|
||||||
HashMap<StrBuf, @ast::Expr>)>) {
|
HashMap<StrBuf, @ast::Expr>)>) {
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut names = HashMap::<StrBuf, @ast::Expr>::new();
|
let mut names = HashMap::<StrBuf, @ast::Expr>::new();
|
||||||
@ -80,22 +86,31 @@ fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
|
|||||||
.map(|x| (*x).clone())
|
.map(|x| (*x).clone())
|
||||||
.collect());
|
.collect());
|
||||||
// Parse the leading function expression (maybe a block, maybe a path)
|
// Parse the leading function expression (maybe a block, maybe a path)
|
||||||
let extra = p.parse_expr();
|
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) {
|
if !p.eat(&token::COMMA) {
|
||||||
ecx.span_err(sp, "expected token: `,`");
|
ecx.span_err(sp, "expected token: `,`");
|
||||||
return (extra, None);
|
return (invocation, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.token == token::EOF {
|
if p.token == token::EOF {
|
||||||
ecx.span_err(sp, "requires at least a format string argument");
|
ecx.span_err(sp, "requires at least a format string argument");
|
||||||
return (extra, None);
|
return (invocation, None);
|
||||||
}
|
}
|
||||||
let fmtstr = p.parse_expr();
|
let fmtstr = p.parse_expr();
|
||||||
let mut named = false;
|
let mut named = false;
|
||||||
while p.token != token::EOF {
|
while p.token != token::EOF {
|
||||||
if !p.eat(&token::COMMA) {
|
if !p.eat(&token::COMMA) {
|
||||||
ecx.span_err(sp, "expected token: `,`");
|
ecx.span_err(sp, "expected token: `,`");
|
||||||
return (extra, None);
|
return (invocation, None);
|
||||||
}
|
}
|
||||||
if p.token == token::EOF { break } // accept trailing commas
|
if p.token == token::EOF { break } // accept trailing commas
|
||||||
if named || (token::is_ident(&p.token) &&
|
if named || (token::is_ident(&p.token) &&
|
||||||
@ -110,13 +125,13 @@ fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
|
|||||||
ecx.span_err(p.span,
|
ecx.span_err(p.span,
|
||||||
"expected ident, positional arguments \
|
"expected ident, positional arguments \
|
||||||
cannot follow named arguments");
|
cannot follow named arguments");
|
||||||
return (extra, None);
|
return (invocation, None);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ecx.span_err(p.span,
|
ecx.span_err(p.span,
|
||||||
format!("expected ident for named argument, but found `{}`",
|
format!("expected ident for named argument, but found `{}`",
|
||||||
p.this_token_to_str()));
|
p.this_token_to_str()));
|
||||||
return (extra, None);
|
return (invocation, None);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let interned_name = token::get_ident(ident);
|
let interned_name = token::get_ident(ident);
|
||||||
@ -137,7 +152,7 @@ fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
|
|||||||
args.push(p.parse_expr());
|
args.push(p.parse_expr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (extra, Some((fmtstr, args, order, names)));
|
return (invocation, Some((fmtstr, args, order, names)));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Context<'a, 'b> {
|
impl<'a, 'b> Context<'a, 'b> {
|
||||||
@ -595,7 +610,7 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||||||
|
|
||||||
/// Actually builds the expression which the iformat! block will be expanded
|
/// Actually builds the expression which the iformat! block will be expanded
|
||||||
/// to
|
/// to
|
||||||
fn to_expr(&self, extra: @ast::Expr) -> @ast::Expr {
|
fn to_expr(&self, invocation: Invocation) -> @ast::Expr {
|
||||||
let mut lets = Vec::new();
|
let mut lets = Vec::new();
|
||||||
let mut locals = Vec::new();
|
let mut locals = Vec::new();
|
||||||
let mut names = Vec::from_fn(self.name_positions.len(), |_| None);
|
let mut names = Vec::from_fn(self.name_positions.len(), |_| None);
|
||||||
@ -699,8 +714,16 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||||||
let resname = self.ecx.ident_of("__args");
|
let resname = self.ecx.ident_of("__args");
|
||||||
lets.push(self.ecx.stmt_let(self.fmtsp, false, resname, result));
|
lets.push(self.ecx.stmt_let(self.fmtsp, false, resname, result));
|
||||||
let res = self.ecx.expr_ident(self.fmtsp, resname);
|
let res = self.ecx.expr_ident(self.fmtsp, resname);
|
||||||
let result = self.ecx.expr_call(extra.span, extra, vec!(
|
let result = match invocation {
|
||||||
self.ecx.expr_addr_of(extra.span, res)));
|
Call(e) => {
|
||||||
|
self.ecx.expr_call(e.span, e,
|
||||||
|
vec!(self.ecx.expr_addr_of(e.span, res)))
|
||||||
|
}
|
||||||
|
MethodCall(e, m) => {
|
||||||
|
self.ecx.expr_method_call(e.span, e, m,
|
||||||
|
vec!(self.ecx.expr_addr_of(e.span, res)))
|
||||||
|
}
|
||||||
|
};
|
||||||
let body = self.ecx.expr_block(self.ecx.block(self.fmtsp, lets,
|
let body = self.ecx.expr_block(self.ecx.block(self.fmtsp, lets,
|
||||||
Some(result)));
|
Some(result)));
|
||||||
|
|
||||||
@ -794,13 +817,25 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
|
pub fn expand_format_args(ecx: &mut ExtCtxt, sp: Span,
|
||||||
tts: &[ast::TokenTree]) -> Box<base::MacResult> {
|
tts: &[ast::TokenTree]) -> Box<base::MacResult> {
|
||||||
|
|
||||||
match parse_args(ecx, sp, tts) {
|
match parse_args(ecx, sp, false, tts) {
|
||||||
(extra, Some((efmt, args, order, names))) => {
|
(invocation, Some((efmt, args, order, names))) => {
|
||||||
MacExpr::new(expand_preparsed_format_args(ecx, sp, extra, efmt, args,
|
MacExpr::new(expand_preparsed_format_args(ecx, sp, invocation, efmt,
|
||||||
order, names))
|
args, order, names))
|
||||||
|
}
|
||||||
|
(_, None) => MacExpr::new(ecx.expr_uint(sp, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_format_args_method(ecx: &mut ExtCtxt, sp: Span,
|
||||||
|
tts: &[ast::TokenTree]) -> Box<base::MacResult> {
|
||||||
|
|
||||||
|
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))
|
(_, None) => MacExpr::new(ecx.expr_uint(sp, 2))
|
||||||
}
|
}
|
||||||
@ -810,7 +845,7 @@ pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
|
|||||||
/// name=names...)` and construct the appropriate formatting
|
/// name=names...)` and construct the appropriate formatting
|
||||||
/// expression.
|
/// expression.
|
||||||
pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
||||||
extra: @ast::Expr,
|
invocation: Invocation,
|
||||||
efmt: @ast::Expr, args: Vec<@ast::Expr>,
|
efmt: @ast::Expr, args: Vec<@ast::Expr>,
|
||||||
name_ordering: Vec<StrBuf>,
|
name_ordering: Vec<StrBuf>,
|
||||||
names: HashMap<StrBuf, @ast::Expr>) -> @ast::Expr {
|
names: HashMap<StrBuf, @ast::Expr>) -> @ast::Expr {
|
||||||
@ -869,5 +904,5 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.to_expr(extra)
|
cx.to_expr(invocation)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user