while continue_ {
match state {
Asm => {
- let (s, style) =
- expr_to_str(cx, p.parse_expr(),
- "inline assembly must be a string literal.");
+ let (s, style) = match expr_to_str(cx, p.parse_expr(),
+ "inline assembly must be a string literal.") {
+ Some((s, st)) => (s, st),
+ // let compilation continue
+ None => return MacResult::dummy_expr(),
+ };
asm = s;
asm_str_style = Some(style);
}
MRAny(@AnyMacro),
MRDef(MacroDef),
}
+impl MacResult {
+ /// Create an empty expression MacResult; useful for satisfying
+ /// type signatures after emitting a non-fatal error (which stop
+ /// compilation well before the validity (or otherwise)) of the
+ /// expression are checked.
+ pub fn dummy_expr() -> MacResult {
+ MRExpr(@ast::Expr {
+ id: ast::DUMMY_NODE_ID, node: ast::ExprLogLevel, span: codemap::DUMMY_SP
+ })
+ }
+}
pub enum SyntaxExtension {
// #[deriving] and such
_ => self.bug("tried to pop without a push")
}
}
+ /// Emit `msg` attached to `sp`, and stop compilation immediately.
+ ///
+ /// `span_err` should be strongly prefered where-ever possible:
+ /// this should *only* be used when
+ /// - continuing has a high risk of flow-on errors (e.g. errors in
+ /// declaring a macro would cause all uses of that macro to
+ /// complain about "undefined macro"), or
+ /// - there is literally nothing else that can be done (however,
+ /// in most cases one can construct a dummy expression/item to
+ /// substitute; we never hit resolve/type-checking so the dummy
+ /// value doesn't have to match anything)
pub fn span_fatal(&self, sp: Span, msg: &str) -> ! {
self.print_backtrace();
self.parse_sess.span_diagnostic.span_fatal(sp, msg);
}
+
+ /// Emit `msg` attached to `sp`, without immediately stopping
+ /// compilation.
+ ///
+ /// Compilation will be stopped in the near future (at the end of
+ /// the macro expansion phase).
pub fn span_err(&self, sp: Span, msg: &str) {
self.print_backtrace();
self.parse_sess.span_diagnostic.span_err(sp, msg);
}
}
-pub fn expr_to_str(cx: &ExtCtxt, expr: @ast::Expr, err_msg: &str) -> (@str, ast::StrStyle) {
+/// Extract a string literal from `expr`, emitting `err_msg` if `expr`
+/// is not a string literal. This does not stop compilation on error,
+/// merely emits a non-fatal error and returns None.
+pub fn expr_to_str(cx: &ExtCtxt, expr: @ast::Expr,
+ err_msg: &str) -> Option<(@str, ast::StrStyle)> {
match expr.node {
ast::ExprLit(l) => match l.node {
- ast::LitStr(s, style) => (s, style),
- _ => cx.span_fatal(l.span, err_msg)
+ ast::LitStr(s, style) => return Some((s, style)),
+ _ => cx.span_err(l.span, err_msg)
},
- _ => cx.span_fatal(expr.span, err_msg)
+ _ => cx.span_err(expr.span, err_msg)
}
+ None
}
+/// Non-fatally assert that `tts` is empty. Note that this function
+/// returns even when `tts` is non-empty, macros that *need* to stop
+/// compilation should call
+/// `cx.parse_sess.span_diagnostic.abort_if_errors()` (this should be
+/// done as rarely as possible).
pub fn check_zero_tts(cx: &ExtCtxt, sp: Span, tts: &[ast::TokenTree],
name: &str) {
if tts.len() != 0 {
- cx.span_fatal(sp, format!("{} takes no arguments", name));
+ cx.span_err(sp, format!("{} takes no arguments", name));
}
}
+/// Extract the string literal from the first token of `tts`. If this
+/// is not a string literal, emit an error and return None.
pub fn get_single_str_from_tts(cx: &ExtCtxt,
sp: Span,
tts: &[ast::TokenTree],
name: &str)
- -> @str {
+ -> Option<@str> {
if tts.len() != 1 {
- cx.span_fatal(sp, format!("{} takes 1 argument.", name));
- }
-
- match tts[0] {
- ast::TTTok(_, token::LIT_STR(ident))
- | ast::TTTok(_, token::LIT_STR_RAW(ident, _)) => cx.str_of(ident),
- _ => cx.span_fatal(sp, format!("{} requires a string.", name)),
+ cx.span_err(sp, format!("{} takes 1 argument.", name));
+ } else {
+ match tts[0] {
+ ast::TTTok(_, token::LIT_STR(ident))
+ | ast::TTTok(_, token::LIT_STR_RAW(ident, _)) => return Some(cx.str_of(ident)),
+ _ => cx.span_err(sp, format!("{} requires a string.", name)),
+ }
}
+ None
}
+/// Extract comma-separated expressions from `tts`. If there is a
+/// parsing error, emit a non-fatal error and return None.
pub fn get_exprs_from_tts(cx: &ExtCtxt,
sp: Span,
- tts: &[ast::TokenTree]) -> ~[@ast::Expr] {
+ tts: &[ast::TokenTree]) -> Option<~[@ast::Expr]> {
let mut p = parse::new_parser_from_tts(cx.parse_sess(),
cx.cfg(),
tts.to_owned());
let mut es = ~[];
while p.token != token::EOF {
if es.len() != 0 && !p.eat(&token::COMMA) {
- cx.span_fatal(sp, "expected token: `,`");
+ cx.span_err(sp, "expected token: `,`");
+ return None;
}
es.push(p.parse_expr());
}
- es
+ Some(es)
}
// in order to have some notion of scoping for macros,
pub fn expand_syntax_ext(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) -> base::MacResult {
// Gather all argument expressions
- let exprs = get_exprs_from_tts(cx, sp, tts);
+ let exprs = match get_exprs_from_tts(cx, sp, tts) {
+ None => return MacResult::dummy_expr(),
+ Some(e) => e,
+ };
let mut bytes = ~[];
for expr in exprs.iter() {
pub fn expand_syntax_ext(cx: &mut base::ExtCtxt,
sp: codemap::Span,
tts: &[ast::TokenTree]) -> base::MacResult {
- let es = base::get_exprs_from_tts(cx, sp, tts);
+ let es = match base::get_exprs_from_tts(cx, sp, tts) {
+ Some(e) => e,
+ None => return base::MacResult::dummy_expr()
+ };
let mut accumulator = ~"";
for e in es.move_iter() {
let e = cx.expand_expr(e);
if i & 1 == 1 {
match *e {
ast::TTTok(_, token::COMMA) => (),
- _ => cx.span_fatal(sp, "concat_idents! expecting comma.")
+ _ => {
+ cx.span_err(sp, "concat_idents! expecting comma.");
+ return MacResult::dummy_expr();
+ }
}
} else {
match *e {
ast::TTTok(_, token::IDENT(ident,_)) => res_str.push_str(cx.str_of(ident)),
- _ => cx.span_fatal(sp, "concat_idents! requires ident args.")
+ _ => {
+ cx.span_err(sp, "concat_idents! requires ident args.");
+ return MacResult::dummy_expr();
+ }
}
}
}
}
}
StaticEnum(..) => {
- cx.span_fatal(span, "`Default` cannot be derived for enums, \
- only structs")
+ cx.span_err(span, "`Default` cannot be derived for enums, only structs");
+ // let compilation continue
+ cx.expr_uint(span, 0)
}
_ => cx.bug("Non-static method in `deriving(Default)`")
};
}
StaticEnum(_, ref variants) => {
if variants.is_empty() {
- cx.span_fatal(span, "`Rand` cannot be derived for enums with no variants");
+ cx.span_err(span, "`Rand` cannot be derived for enums with no variants");
+ // let compilation continue
+ return cx.expr_uint(span, 0);
}
let variant_count = cx.expr_uint(span, variants.len());
}
}
StaticEnum(..) => {
- cx.span_fatal(span, "`Zero` cannot be derived for enums, \
- only structs")
+ cx.span_err(span, "`Zero` cannot be derived for enums, only structs");
+ // let compilation continue
+ cx.expr_uint(span, 0)
}
_ => cx.bug("Non-static method in `deriving(Zero)`")
};
pub fn expand_option_env(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
- let var = get_single_str_from_tts(cx, sp, tts, "option_env!");
+ let var = match get_single_str_from_tts(cx, sp, tts, "option_env!") {
+ None => return MacResult::dummy_expr(),
+ Some(v) => v
+ };
let e = match os::getenv(var) {
None => quote_expr!(cx, ::std::option::None::<&'static str>),
pub fn expand_env(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
- let exprs = get_exprs_from_tts(cx, sp, tts);
-
- if exprs.len() == 0 {
- cx.span_fatal(sp, "env! takes 1 or 2 arguments");
- }
+ let exprs = match get_exprs_from_tts(cx, sp, tts) {
+ Some([]) => {
+ cx.span_err(sp, "env! takes 1 or 2 arguments");
+ return MacResult::dummy_expr();
+ }
+ None => return MacResult::dummy_expr(),
+ Some(exprs) => exprs
+ };
- let (var, _var_str_style) = expr_to_str(cx, exprs[0], "expected string literal");
+ let var = match expr_to_str(cx, exprs[0], "expected string literal") {
+ None => return MacResult::dummy_expr(),
+ Some((v, _style)) => v
+ };
let msg = match exprs.len() {
1 => format!("environment variable `{}` not defined", var).to_managed(),
2 => {
- let (s, _style) = expr_to_str(cx, exprs[1], "expected string literal");
- s
+ match expr_to_str(cx, exprs[1], "expected string literal") {
+ None => return MacResult::dummy_expr(),
+ Some((s, _style)) => s
+ }
+ }
+ _ => {
+ cx.span_err(sp, "env! takes 1 or 2 arguments");
+ return MacResult::dummy_expr();
}
- _ => cx.span_fatal(sp, "env! takes 1 or 2 arguments")
};
let e = match os::getenv(var) {
- None => cx.span_fatal(sp, msg),
+ None => {
+ cx.span_err(sp, msg);
+ cx.expr_uint(sp, 0)
+ }
Some(s) => cx.expr_str(sp, s.to_managed())
};
MRExpr(e)
// Token-tree macros:
MacInvocTT(ref pth, ref tts, ctxt) => {
if (pth.segments.len() > 1u) {
- fld.cx.span_fatal(
+ fld.cx.span_err(
pth.span,
format!("expected macro name without module \
separators"));
+ // let compilation continue
+ return e;
}
let extname = &pth.segments[0].identifier;
let extnamestr = ident_to_str(extname);
// leaving explicit deref here to highlight unbox op:
let marked_after = match fld.extsbox.find(&extname.name) {
None => {
- fld.cx.span_fatal(
+ fld.cx.span_err(
pth.span,
- format!("macro undefined: '{}'", extnamestr))
+ format!("macro undefined: '{}'", extnamestr));
+
+ // let compilation continue
+ return e;
}
Some(&NormalTT(ref expandfun, exp_span)) => {
fld.cx.bt_push(ExpnInfo {
MRExpr(e) => e,
MRAny(any_macro) => any_macro.make_expr(),
_ => {
- fld.cx.span_fatal(
+ fld.cx.span_err(
pth.span,
format!(
"non-expr macro in expr pos: {}",
extnamestr
)
- )
+ );
+ return e;
}
};
mark_expr(expanded,fm)
}
_ => {
- fld.cx.span_fatal(
+ fld.cx.span_err(
pth.span,
format!("'{}' is not a tt-style macro", extnamestr)
- )
+ );
+ return e;
}
};
let extnamestr = ident_to_str(extname);
let fm = fresh_mark();
let expanded = match fld.extsbox.find(&extname.name) {
- None => fld.cx.span_fatal(pth.span,
- format!("macro undefined: '{}!'", extnamestr)),
+ None => {
+ fld.cx.span_err(pth.span,
+ format!("macro undefined: '{}!'", extnamestr));
+ // let compilation continue
+ return SmallVector::zero();
+ }
Some(&NormalTT(ref expander, span)) => {
if it.ident.name != parse::token::special_idents::invalid.name {
- fld.cx.span_fatal(pth.span,
- format!("macro {}! expects no ident argument, \
- given '{}'", extnamestr,
- ident_to_str(&it.ident)));
+ fld.cx.span_err(pth.span,
+ format!("macro {}! expects no ident argument, \
+ given '{}'", extnamestr,
+ ident_to_str(&it.ident)));
+ return SmallVector::zero();
}
fld.cx.bt_push(ExpnInfo {
call_site: it.span,
}
Some(&IdentTT(ref expander, span)) => {
if it.ident.name == parse::token::special_idents::invalid.name {
- fld.cx.span_fatal(pth.span,
- format!("macro {}! expects an ident argument",
- extnamestr));
+ fld.cx.span_err(pth.span,
+ format!("macro {}! expects an ident argument", extnamestr));
+ return SmallVector::zero();
}
fld.cx.bt_push(ExpnInfo {
call_site: it.span,
let marked_ctxt = new_mark(fm,ctxt);
expander.expand(fld.cx, it.span, it.ident, marked_tts, marked_ctxt)
}
- _ => fld.cx.span_fatal(it.span,
- format!("{}! is not legal in item position",
- extnamestr))
+ _ => {
+ fld.cx.span_err(it.span, format!("{}! is not legal in item position", extnamestr));
+ return SmallVector::zero();
+ }
};
let items = match expanded {
.collect()
}
MRExpr(_) => {
- fld.cx.span_fatal(pth.span, format!("expr macro in item position: {}",
- extnamestr))
+ fld.cx.span_err(pth.span, format!("expr macro in item position: {}", extnamestr));
+ return SmallVector::zero();
}
MRAny(any_macro) => {
any_macro.make_items().move_iter()
let lib = match DynamicLibrary::open(Some(&path)) {
Ok(lib) => lib,
+ // this is fatal: there are almost certainly macros we need
+ // inside this crate, so continue would spew "macro undefined"
+ // errors
Err(err) => fld.cx.span_fatal(crate.span, err)
};
unsafe {
let registrar: MacroCrateRegistrationFun = match lib.symbol(registrar) {
Ok(registrar) => registrar,
+ // again fatal if we can't register macros
Err(err) => fld.cx.span_fatal(crate.span, err)
};
registrar(|name, extension| {
_ => return expand_non_macro_stmt(s, fld)
};
if (pth.segments.len() > 1u) {
- fld.cx.span_fatal(pth.span,
- "expected macro name without module separators");
+ fld.cx.span_err(pth.span, "expected macro name without module separators");
+ return SmallVector::zero();
}
let extname = &pth.segments[0].identifier;
let extnamestr = ident_to_str(extname);
let marked_after = match fld.extsbox.find(&extname.name) {
None => {
- fld.cx.span_fatal(pth.span, format!("macro undefined: '{}'", extnamestr))
+ fld.cx.span_err(pth.span, format!("macro undefined: '{}'", extnamestr));
+ return SmallVector::zero();
}
Some(&NormalTT(ref expandfun, exp_span)) => {
}
}
MRAny(any_macro) => any_macro.make_stmt(),
- _ => fld.cx.span_fatal(
- pth.span,
- format!("non-stmt macro in stmt pos: {}", extnamestr))
+ _ => {
+ fld.cx.span_err(pth.span,
+ format!("non-stmt macro in stmt pos: {}", extnamestr));
+ return SmallVector::zero();
+ }
};
mark_stmt(expanded,fm)
}
_ => {
- fld.cx.span_fatal(pth.span,
- format!("'{}' is not a tt-style macro",
- extnamestr))
+ fld.cx.span_err(pth.span, format!("'{}' is not a tt-style macro", extnamestr));
+ return SmallVector::zero();
}
};
// Keep going, outside-in.
let fully_expanded = fld.fold_stmt(marked_after);
if fully_expanded.is_empty() {
- fld.cx.span_fatal(pth.span,
- "macro didn't expand to a statement");
+ fld.cx.span_err(pth.span, "macro didn't expand to a statement");
+ return SmallVector::zero();
}
fld.cx.bt_pop();
let fully_expanded: SmallVector<@Stmt> = fully_expanded.move_iter()
// Be sure to recursively expand macros just in case the format string uses
// a macro to build the format expression.
let expr = cx.ecx.expand_expr(efmt);
- let (fmt, _) = expr_to_str(cx.ecx, expr,
- "format argument must be a string literal.");
+ let fmt = match expr_to_str(cx.ecx, expr,
+ "format argument must be a string literal.") {
+ Some((fmt, _)) => fmt,
+ None => return MacResult::dummy_expr()
+ };
let mut err = false;
parse::parse_error::cond.trap(|m| {
// unhygienically.
pub fn expand_include(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
- let file = get_single_str_from_tts(cx, sp, tts, "include!");
+ let file = match get_single_str_from_tts(cx, sp, tts, "include!") {
+ Some(f) => f,
+ None => return MacResult::dummy_expr(),
+ };
// The file will be added to the code map by the parser
let mut p =
parse::new_sub_parser_from_file(cx.parse_sess(),
// include_str! : read the given file, insert it as a literal string expr
pub fn expand_include_str(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
- let file = get_single_str_from_tts(cx, sp, tts, "include_str!");
+ let file = match get_single_str_from_tts(cx, sp, tts, "include_str!") {
+ Some(f) => f,
+ None => return MacResult::dummy_expr()
+ };
let file = res_rel_file(cx, sp, &Path::new(file));
let bytes = match io::result(|| File::open(&file).read_to_end()) {
Err(e) => {
- cx.span_fatal(sp, format!("couldn't read {}: {}",
- file.display(), e.desc));
+ cx.span_err(sp, format!("couldn't read {}: {}", file.display(), e.desc));
+ return MacResult::dummy_expr();
}
Ok(bytes) => bytes,
};
base::MRExpr(cx.expr_str(sp, src))
}
None => {
- cx.span_fatal(sp, format!("{} wasn't a utf-8 file", file.display()));
+ cx.span_err(sp, format!("{} wasn't a utf-8 file", file.display()));
+ return MacResult::dummy_expr();
}
}
}
{
use std::at_vec;
- let file = get_single_str_from_tts(cx, sp, tts, "include_bin!");
+ let file = match get_single_str_from_tts(cx, sp, tts, "include_bin!") {
+ Some(f) => f,
+ None => return MacResult::dummy_expr()
+ };
let file = res_rel_file(cx, sp, &Path::new(file));
match io::result(|| File::open(&file).read_to_end()) {
Err(e) => {
- cx.span_fatal(sp, format!("couldn't read {}: {}",
- file.display(), e.desc));
+ cx.span_err(sp, format!("couldn't read {}: {}", file.display(), e.desc));
+ return MacResult::dummy_expr();
}
Ok(bytes) => {
let bytes = at_vec::to_managed_move(bytes);
} else if rust_parser.is_keyword(keywords::False) {
cx.set_trace_macros(false);
} else {
- cx.span_fatal(sp, "trace_macros! only accepts `true` or `false`")
+ cx.span_err(sp, "trace_macros! only accepts `true` or `false`");
+ return base::MacResult::dummy_expr();
}
rust_parser.bump();
node: MatchNonterminal(ref bind_name, _, idx), span: sp
} => {
if ret_val.contains_key(bind_name) {
- p_s.span_diagnostic.span_fatal(sp, ~"Duplicated bind name: "+
- ident_to_str(bind_name))
+ p_s.span_diagnostic.span_fatal(sp,
+ "Duplicated bind name: "+ ident_to_str(bind_name))
}
ret_val.insert(*bind_name, res[idx]);
}
--- /dev/null
+// Copyright 2014 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.
+
+// test that errors in a (selection) of macros don't kill compilation
+// immediately, so that we get more errors listed at a time.
+
+#[feature(asm)];
+
+#[deriving(Default, //~ ERROR
+ Rand, //~ ERROR
+ Zero)] //~ ERROR
+enum CantDeriveThose {}
+
+fn main() {
+ doesnt_exist!(); //~ ERROR
+
+ bytes!(invalid); //~ ERROR
+
+ asm!(invalid); //~ ERROR
+
+ concat_idents!("not", "idents"); //~ ERROR
+
+ option_env!(invalid); //~ ERROR
+ env!(invalid); //~ ERROR
+ env!(foo, abr, baz); //~ ERROR
+ env!("RUST_HOPEFULLY_THIS_DOESNT_EXIST"); //~ ERROR
+
+ foo::blah!(); //~ ERROR
+
+ format!(); //~ ERROR
+ format!(invalid); //~ ERROR
+
+ include!(invalid); //~ ERROR
+
+ include_str!(invalid); //~ ERROR
+ include_str!("i'd be quite surprised if a file with this name existed"); //~ ERROR
+ include_bin!(invalid); //~ ERROR
+ include_bin!("i'd be quite surprised if a file with this name existed"); //~ ERROR
+
+ trace_macros!(invalid); //~ ERROR
+}