mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-28 01:34:21 +00:00
Implement a format_args!() macro
The purpose of this macro is to further reduce the number of allocations which occur when dealing with formatting strings. This macro will perform all of the static analysis necessary to validate that a format string is safe, and then it will wrap up the "format string" into an opaque struct which can then be passed around. Two safe functions are added (write/format) which take this opaque argument structure, unwrap it, and then call the unsafe version of write/format (in an unsafe block). Other than these two functions, it is not intended for anyone to ever look inside this opaque struct. The macro looks a bit odd, but mostly because of rvalue lifetimes this is the only way for it to be safe that I know of. Example use-cases of this are: * third-party libraries can use the default formatting syntax without any forced allocations * the fail!() macro can avoid allocating the format string * the logging macros can avoid allocation any strings
This commit is contained in:
parent
62166611e7
commit
9a5f95a82c
@ -36,7 +36,7 @@ Some examples of the `format!` extension are:
|
||||
format!("Hello") // => ~"Hello"
|
||||
format!("Hello, {:s}!", "world") // => ~"Hello, world!"
|
||||
format!("The number is {:d}", 1) // => ~"The number is 1"
|
||||
format!("{}", ~[3, 4]) // => ~"~[3, 4]"
|
||||
format!("{:?}", ~[3, 4]) // => ~"~[3, 4]"
|
||||
format!("{value}", value=4) // => ~"4"
|
||||
format!("{} {}", 1, 2) // => ~"1 2"
|
||||
~~~
|
||||
@ -363,6 +363,32 @@ pub struct Argument<'self> {
|
||||
priv value: &'self util::Void,
|
||||
}
|
||||
|
||||
impl<'self> Arguments<'self> {
|
||||
/// When using the format_args!() macro, this function is used to generate the
|
||||
/// Arguments structure. The compiler inserts an `unsafe` block to call this,
|
||||
/// which is valid because the compiler performs all necessary validation to
|
||||
/// ensure that the resulting call to format/write would be safe.
|
||||
#[doc(hidden)] #[inline]
|
||||
pub unsafe fn new<'a>(fmt: &'static [rt::Piece<'static>],
|
||||
args: &'a [Argument<'a>]) -> Arguments<'a> {
|
||||
Arguments{ fmt: cast::transmute(fmt), args: args }
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure represents a safely precompiled version of a format string
|
||||
/// and its arguments. This cannot be generated at runtime because it cannot
|
||||
/// safely be done so, so no constructors are given and the fields are private
|
||||
/// to prevent modification.
|
||||
///
|
||||
/// The `format_args!` macro will safely create an instance of this structure
|
||||
/// and pass it to a user-supplied function. The macro validates the format
|
||||
/// string at compile-time so usage of the `write` and `format` functions can
|
||||
/// be safely performed.
|
||||
pub struct Arguments<'self> {
|
||||
priv fmt: &'self [rt::Piece<'self>],
|
||||
priv args: &'self [Argument<'self>],
|
||||
}
|
||||
|
||||
/// When a format is not otherwise specified, types are formatted by ascribing
|
||||
/// to this trait. There is not an explicit way of selecting this trait to be
|
||||
/// used for formatting, it is only if no other format is specified.
|
||||
@ -410,6 +436,26 @@ pub trait Float { fn fmt(&Self, &mut Formatter); }
|
||||
/// and a list of arguments. The arguments will be formatted according to the
|
||||
/// specified format string into the output stream provided.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * output - the buffer to write output to
|
||||
/// * args - the precompiled arguments generated by `format_args!`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ~~~{.rust}
|
||||
/// use std::fmt;
|
||||
/// let w: &mut io::Writer = ...;
|
||||
/// format_args!(|args| { fmt::write(w, args) }, "Hello, {}!", "world");
|
||||
/// ~~~
|
||||
pub fn write(output: &mut io::Writer, args: &Arguments) {
|
||||
unsafe { write_unsafe(output, args.fmt, args.args) }
|
||||
}
|
||||
|
||||
/// The `write_unsafe` function takes an output stream, a precompiled format
|
||||
/// string, and a list of arguments. The arguments will be formatted according
|
||||
/// to the specified format string into the output stream provided.
|
||||
///
|
||||
/// See the documentation for `format` for why this function is unsafe and care
|
||||
/// should be taken if calling it manually.
|
||||
///
|
||||
@ -426,8 +472,9 @@ pub trait Float { fn fmt(&Self, &mut Formatter); }
|
||||
///
|
||||
/// Note that this function assumes that there are enough arguments for the
|
||||
/// format string.
|
||||
pub unsafe fn write(output: &mut io::Writer,
|
||||
fmt: &[rt::Piece], args: &[Argument]) {
|
||||
pub unsafe fn write_unsafe(output: &mut io::Writer,
|
||||
fmt: &[rt::Piece],
|
||||
args: &[Argument]) {
|
||||
let mut formatter = Formatter {
|
||||
flags: 0,
|
||||
width: None,
|
||||
@ -446,6 +493,25 @@ pub unsafe fn write(output: &mut io::Writer,
|
||||
/// The format function takes a precompiled format string and a list of
|
||||
/// arguments, to return the resulting formatted string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * args - a structure of arguments generated via the `format_args!` macro.
|
||||
/// Because this structure can only be safely generated at
|
||||
/// compile-time, this function is safe.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ~~~{.rust}
|
||||
/// use std::fmt;
|
||||
/// let s = format_args!(fmt::format, "Hello, {}!", "world");
|
||||
/// assert_eq!(s, "Hello, world!");
|
||||
/// ~~~
|
||||
pub fn format(args: &Arguments) -> ~str {
|
||||
unsafe { format_unsafe(args.fmt, args.args) }
|
||||
}
|
||||
|
||||
/// The unsafe version of the formatting function.
|
||||
///
|
||||
/// This is currently an unsafe function because the types of all arguments
|
||||
/// aren't verified by immediate callers of this function. This currently does
|
||||
/// not validate that the correct types of arguments are specified for each
|
||||
@ -465,9 +531,9 @@ pub unsafe fn write(output: &mut io::Writer,
|
||||
///
|
||||
/// Note that this function assumes that there are enough arguments for the
|
||||
/// format string.
|
||||
pub unsafe fn format(fmt: &[rt::Piece], args: &[Argument]) -> ~str {
|
||||
pub unsafe fn format_unsafe(fmt: &[rt::Piece], args: &[Argument]) -> ~str {
|
||||
let mut output = MemWriter::new();
|
||||
write(&mut output as &mut io::Writer, fmt, args);
|
||||
write_unsafe(&mut output as &mut io::Writer, fmt, args);
|
||||
return str::from_utf8_owned(output.inner());
|
||||
}
|
||||
|
||||
@ -740,7 +806,7 @@ impl<'self> Formatter<'self> {
|
||||
|
||||
/// This is a function which calls are emitted to by the compiler itself to
|
||||
/// create the Argument structures that are passed into the `format` function.
|
||||
#[doc(hidden)]
|
||||
#[doc(hidden)] #[inline]
|
||||
pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter),
|
||||
t: &'a T) -> Argument<'a> {
|
||||
unsafe {
|
||||
@ -753,14 +819,14 @@ pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter),
|
||||
|
||||
/// When the compiler determines that the type of an argument *must* be a string
|
||||
/// (such as for select), then it invokes this method.
|
||||
#[doc(hidden)]
|
||||
#[doc(hidden)] #[inline]
|
||||
pub fn argumentstr<'a>(s: &'a &str) -> Argument<'a> {
|
||||
argument(String::fmt, s)
|
||||
}
|
||||
|
||||
/// When the compiler determines that the type of an argument *must* be a uint
|
||||
/// (such as for plural), then it invokes this method.
|
||||
#[doc(hidden)]
|
||||
#[doc(hidden)] #[inline]
|
||||
pub fn argumentuint<'a>(s: &'a uint) -> Argument<'a> {
|
||||
argument(Unsigned::fmt, s)
|
||||
}
|
||||
@ -899,14 +965,8 @@ impl<T> Pointer for *T {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Pointer for *mut T {
|
||||
fn fmt(t: &*mut T, f: &mut Formatter) {
|
||||
f.flags |= 1 << (parse::FlagAlternate as uint);
|
||||
do ::uint::to_str_bytes(*t as uint, 16) |buf| {
|
||||
f.pad_integral(buf, "0x", true);
|
||||
}
|
||||
}
|
||||
fn fmt(t: &*mut T, f: &mut Formatter) { Pointer::fmt(&(*t as *T), f) }
|
||||
}
|
||||
|
||||
// Implementation of Default for various core types
|
||||
@ -940,7 +1000,6 @@ delegate!(f64 to Float)
|
||||
impl<T> Default for *T {
|
||||
fn fmt(me: &*T, f: &mut Formatter) { Pointer::fmt(me, f) }
|
||||
}
|
||||
|
||||
impl<T> Default for *mut T {
|
||||
fn fmt(me: &*mut T, f: &mut Formatter) { Pointer::fmt(me, f) }
|
||||
}
|
||||
|
@ -161,6 +161,8 @@ pub fn syntax_expander_table() -> SyntaxEnv {
|
||||
builtin_normal_tt_no_ctxt(ext::ifmt::expand_write));
|
||||
syntax_expanders.insert(intern(&"writeln"),
|
||||
builtin_normal_tt_no_ctxt(ext::ifmt::expand_writeln));
|
||||
syntax_expanders.insert(intern(&"format_args"),
|
||||
builtin_normal_tt_no_ctxt(ext::ifmt::expand_format_args));
|
||||
syntax_expanders.insert(
|
||||
intern(&"auto_encode"),
|
||||
@SE(ItemDecorator(ext::auto_encode::expand_auto_encode)));
|
||||
|
@ -60,7 +60,7 @@ impl Context {
|
||||
let p = rsparse::new_parser_from_tts(self.ecx.parse_sess(),
|
||||
self.ecx.cfg(),
|
||||
tts.to_owned());
|
||||
// If we want a leading expression (for ifmtf), parse it here
|
||||
// If we want a leading expression, parse it here
|
||||
let extra = if leading_expr {
|
||||
let e = Some(p.parse_expr());
|
||||
if !p.eat(&token::COMMA) {
|
||||
@ -341,12 +341,18 @@ impl Context {
|
||||
~[self.ecx.ident_of("std"), self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("parse"), self.ecx.ident_of(s)]
|
||||
};
|
||||
let none = || {
|
||||
let p = self.ecx.path(sp, ~[self.ecx.ident_of("None")]);
|
||||
self.ecx.expr_path(p)
|
||||
};
|
||||
let none = self.ecx.path_global(sp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("option"),
|
||||
self.ecx.ident_of("None")]);
|
||||
let none = self.ecx.expr_path(none);
|
||||
let some = |e: @ast::Expr| {
|
||||
self.ecx.expr_call_ident(sp, self.ecx.ident_of("Some"), ~[e])
|
||||
let p = self.ecx.path_global(sp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("option"),
|
||||
self.ecx.ident_of("Some")]);
|
||||
let p = self.ecx.expr_path(p);
|
||||
self.ecx.expr_call(sp, p, ~[e])
|
||||
};
|
||||
let trans_count = |c: parse::Count| {
|
||||
match c {
|
||||
@ -397,7 +403,7 @@ impl Context {
|
||||
parse::Plural(offset, ref arms, ref default) => {
|
||||
let offset = match offset {
|
||||
Some(i) => { some(self.ecx.expr_uint(sp, i)) }
|
||||
None => { none() }
|
||||
None => { none.clone() }
|
||||
};
|
||||
let arms = arms.iter().map(|arm| {
|
||||
let p = self.ecx.path_global(sp, rtpath("PluralArm"));
|
||||
@ -522,7 +528,7 @@ impl Context {
|
||||
|
||||
// Translate the method (if any)
|
||||
let method = match arg.method {
|
||||
None => { none() }
|
||||
None => { none.clone() }
|
||||
Some(ref m) => {
|
||||
let m = trans_method(*m);
|
||||
some(self.ecx.expr_addr_of(sp, m))
|
||||
@ -541,7 +547,7 @@ impl Context {
|
||||
|
||||
/// Actually builds the expression which the ifmt! block will be expanded
|
||||
/// to
|
||||
fn to_expr(&self, extra: Option<@ast::Expr>, f: &str) -> @ast::Expr {
|
||||
fn to_expr(&self, extra: Option<@ast::Expr>, f: Option<&str>) -> @ast::Expr {
|
||||
let mut lets = ~[];
|
||||
let mut locals = ~[];
|
||||
let mut names = vec::from_fn(self.name_positions.len(), |_| None);
|
||||
@ -556,21 +562,19 @@ impl Context {
|
||||
// Next, build up the static array which will become our precompiled
|
||||
// format "string"
|
||||
let fmt = self.ecx.expr_vec(self.fmtsp, self.pieces.clone());
|
||||
let piece_ty = self.ecx.ty_path(self.ecx.path_all(
|
||||
self.fmtsp,
|
||||
true, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("rt"),
|
||||
self.ecx.ident_of("Piece"),
|
||||
],
|
||||
Some(self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("static"))),
|
||||
~[]
|
||||
), None);
|
||||
let ty = ast::ty_fixed_length_vec(
|
||||
self.ecx.ty_mt(
|
||||
self.ecx.ty_path(self.ecx.path_all(
|
||||
self.fmtsp,
|
||||
true, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("rt"),
|
||||
self.ecx.ident_of("Piece"),
|
||||
],
|
||||
Some(self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("static"))),
|
||||
~[]
|
||||
), None),
|
||||
ast::MutImmutable
|
||||
),
|
||||
self.ecx.ty_mt(piece_ty.clone(), ast::MutImmutable),
|
||||
self.ecx.expr_uint(self.fmtsp, self.pieces.len())
|
||||
);
|
||||
let ty = self.ecx.ty(self.fmtsp, ty);
|
||||
@ -596,7 +600,8 @@ impl Context {
|
||||
let name = self.ecx.ident_of(fmt!("__arg%u", i));
|
||||
let e = self.ecx.expr_addr_of(e.span, e);
|
||||
lets.push(self.ecx.stmt_let(e.span, false, name, e));
|
||||
locals.push(self.format_arg(e.span, Left(i), name));
|
||||
locals.push(self.format_arg(e.span, Left(i),
|
||||
self.ecx.expr_ident(e.span, name)));
|
||||
}
|
||||
for (&name, &e) in self.names.iter() {
|
||||
if !self.name_types.contains_key(&name) { loop }
|
||||
@ -605,48 +610,83 @@ impl Context {
|
||||
let e = self.ecx.expr_addr_of(e.span, e);
|
||||
lets.push(self.ecx.stmt_let(e.span, false, lname, e));
|
||||
names[*self.name_positions.get(&name)] =
|
||||
Some(self.format_arg(e.span, Right(name), lname));
|
||||
Some(self.format_arg(e.span, Right(name),
|
||||
self.ecx.expr_ident(e.span, lname)));
|
||||
}
|
||||
|
||||
let args = names.move_iter().map(|a| a.unwrap());
|
||||
let mut args = locals.move_iter().chain(args);
|
||||
|
||||
let mut fmt_args = match extra {
|
||||
Some(e) => ~[e], None => ~[]
|
||||
let result = match f {
|
||||
// Invocation of write!()/format!(), call the function and we're
|
||||
// done.
|
||||
Some(f) => {
|
||||
let mut fmt_args = match extra {
|
||||
Some(e) => ~[e], None => ~[]
|
||||
};
|
||||
fmt_args.push(self.ecx.expr_ident(self.fmtsp, static_name));
|
||||
fmt_args.push(self.ecx.expr_vec_slice(self.fmtsp,
|
||||
args.collect()));
|
||||
|
||||
let result = self.ecx.expr_call_global(self.fmtsp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of(f),
|
||||
], fmt_args);
|
||||
|
||||
// sprintf is unsafe, but we just went through a lot of work to
|
||||
// validate that our call is save, so inject the unsafe block
|
||||
// for the user.
|
||||
self.ecx.expr_block(ast::Block {
|
||||
view_items: ~[],
|
||||
stmts: ~[],
|
||||
expr: Some(result),
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
rules: ast::UnsafeBlock(ast::CompilerGenerated),
|
||||
span: self.fmtsp,
|
||||
})
|
||||
}
|
||||
|
||||
// Invocation of format_args!()
|
||||
None => {
|
||||
let fmt = self.ecx.expr_ident(self.fmtsp, static_name);
|
||||
let args = self.ecx.expr_vec_slice(self.fmtsp, args.collect());
|
||||
let result = self.ecx.expr_call_global(self.fmtsp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("Arguments"),
|
||||
self.ecx.ident_of("new"),
|
||||
], ~[fmt, 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(ast::Block {
|
||||
view_items: ~[],
|
||||
stmts: ~[],
|
||||
expr: Some(result),
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
rules: ast::UnsafeBlock(ast::CompilerGenerated),
|
||||
span: self.fmtsp,
|
||||
});
|
||||
let extra = extra.unwrap();
|
||||
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);
|
||||
self.ecx.expr_call(extra.span, extra, ~[
|
||||
self.ecx.expr_addr_of(extra.span, res)])
|
||||
}
|
||||
};
|
||||
fmt_args.push(self.ecx.expr_ident(self.fmtsp, static_name));
|
||||
fmt_args.push(self.ecx.expr_vec(self.fmtsp, args.collect()));
|
||||
|
||||
// Next, build up the actual call to the {s,f}printf function.
|
||||
let result = self.ecx.expr_call_global(self.fmtsp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of(f),
|
||||
], fmt_args);
|
||||
|
||||
// sprintf is unsafe, but we just went through a lot of work to
|
||||
// validate that our call is save, so inject the unsafe block for the
|
||||
// user.
|
||||
let result = self.ecx.expr_block(ast::Block {
|
||||
view_items: ~[],
|
||||
stmts: ~[],
|
||||
expr: Some(result),
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
rules: ast::UnsafeBlock(ast::CompilerGenerated),
|
||||
span: self.fmtsp,
|
||||
});
|
||||
|
||||
self.ecx.expr_block(self.ecx.block(self.fmtsp, lets, Some(result)))
|
||||
self.ecx.expr_block(self.ecx.block(self.fmtsp, lets,
|
||||
Some(result)))
|
||||
}
|
||||
|
||||
fn format_arg(&self, sp: Span, arg: Either<uint, @str>,
|
||||
ident: ast::Ident) -> @ast::Expr {
|
||||
let ty = match arg {
|
||||
fn format_arg(&self, sp: Span, argno: Either<uint, @str>,
|
||||
arg: @ast::Expr) -> @ast::Expr {
|
||||
let ty = match argno {
|
||||
Left(i) => self.arg_types[i].unwrap(),
|
||||
Right(s) => *self.name_types.get(&s)
|
||||
};
|
||||
|
||||
let argptr = self.ecx.expr_ident(sp, ident);
|
||||
let fmt_trait = match ty {
|
||||
Unknown => "Default",
|
||||
Known(tyname) => {
|
||||
@ -675,14 +715,14 @@ impl Context {
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("argumentstr"),
|
||||
], ~[argptr])
|
||||
], ~[arg])
|
||||
}
|
||||
Unsigned => {
|
||||
return self.ecx.expr_call_global(sp, ~[
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("argumentuint"),
|
||||
], ~[argptr])
|
||||
], ~[arg])
|
||||
}
|
||||
};
|
||||
|
||||
@ -696,28 +736,33 @@ impl Context {
|
||||
self.ecx.ident_of("std"),
|
||||
self.ecx.ident_of("fmt"),
|
||||
self.ecx.ident_of("argument"),
|
||||
], ~[self.ecx.expr_path(format_fn), argptr])
|
||||
], ~[self.ecx.expr_path(format_fn), arg])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_format(ecx: @ExtCtxt, sp: Span,
|
||||
tts: &[ast::token_tree]) -> base::MacResult {
|
||||
expand_ifmt(ecx, sp, tts, false, false, "format")
|
||||
expand_ifmt(ecx, sp, tts, false, false, Some("format_unsafe"))
|
||||
}
|
||||
|
||||
pub fn expand_write(ecx: @ExtCtxt, sp: Span,
|
||||
tts: &[ast::token_tree]) -> base::MacResult {
|
||||
expand_ifmt(ecx, sp, tts, true, false, "write")
|
||||
expand_ifmt(ecx, sp, tts, true, false, Some("write_unsafe"))
|
||||
}
|
||||
|
||||
pub fn expand_writeln(ecx: @ExtCtxt, sp: Span,
|
||||
tts: &[ast::token_tree]) -> base::MacResult {
|
||||
expand_ifmt(ecx, sp, tts, true, true, "write")
|
||||
expand_ifmt(ecx, sp, tts, true, true, Some("write_unsafe"))
|
||||
}
|
||||
|
||||
pub fn expand_format_args(ecx: @ExtCtxt, sp: Span,
|
||||
tts: &[ast::token_tree]) -> base::MacResult {
|
||||
expand_ifmt(ecx, sp, tts, true, false, None)
|
||||
}
|
||||
|
||||
fn expand_ifmt(ecx: @ExtCtxt, sp: Span, tts: &[ast::token_tree],
|
||||
leading_arg: bool, append_newline: bool,
|
||||
function: &str) -> base::MacResult {
|
||||
function: Option<&str>) -> base::MacResult {
|
||||
let mut cx = Context {
|
||||
ecx: ecx,
|
||||
args: ~[],
|
||||
|
14
src/test/compile-fail/ifmt-bad-format-args.rs
Normal file
14
src/test/compile-fail/ifmt-bad-format-args.rs
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
fn main() {
|
||||
format_args!("test"); //~ ERROR: expected token
|
||||
format_args!("", || {}); //~ ERROR: must be a string literal
|
||||
}
|
13
src/test/compile-fail/ifmt-bad-format-args2.rs
Normal file
13
src/test/compile-fail/ifmt-bad-format-args2.rs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
fn main() {
|
||||
format_args!("{}", ""); //~ ERROR: expected function
|
||||
}
|
@ -13,6 +13,11 @@
|
||||
#[deny(warnings)];
|
||||
|
||||
use std::fmt;
|
||||
use std::rt::io::Decorator;
|
||||
use std::rt::io::mem::MemWriter;
|
||||
use std::rt::io;
|
||||
use std::rt::io::Writer;
|
||||
use std::str;
|
||||
|
||||
struct A;
|
||||
struct B;
|
||||
@ -235,16 +240,13 @@ pub fn main() {
|
||||
let a: int = ::std::cast::transmute(3u);
|
||||
format!("{}", a);
|
||||
}
|
||||
|
||||
test_format_args();
|
||||
}
|
||||
|
||||
// Basic test to make sure that we can invoke the `write!` macro with an
|
||||
// io::Writer instance.
|
||||
fn test_write() {
|
||||
use std::rt::io::Decorator;
|
||||
use std::rt::io::mem::MemWriter;
|
||||
use std::rt::io;
|
||||
use std::str;
|
||||
|
||||
let mut buf = MemWriter::new();
|
||||
write!(&mut buf as &mut io::Writer, "{}", 3);
|
||||
{
|
||||
@ -268,3 +270,20 @@ fn test_print() {
|
||||
println!("this is a {}", "test");
|
||||
println!("{foo}", foo="bar");
|
||||
}
|
||||
|
||||
// Just make sure that the macros are defined, there's not really a lot that we
|
||||
// can do with them just yet (to test the output)
|
||||
fn test_format_args() {
|
||||
let mut buf = MemWriter::new();
|
||||
{
|
||||
let w = &mut buf as &mut io::Writer;
|
||||
format_args!(|args| { fmt::write(w, args) }, "{}", 1);
|
||||
format_args!(|args| { fmt::write(w, args) }, "test");
|
||||
format_args!(|args| { fmt::write(w, args) }, "{test}", test=3);
|
||||
}
|
||||
let s = str::from_utf8_owned(buf.inner());
|
||||
t!(s, "1test3");
|
||||
|
||||
let s = format_args!(fmt::format, "hello {}", "world");
|
||||
t!(s, "hello world");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user