auto merge of #11644 : huonw/rust/less-fatality, r=cmr

This means that compilation continues for longer, and so we can see more
errors per compile. This is mildly more user-friendly because it stops
users having to run rustc n times to see n macro errors: just run it
once to see all of them.
This commit is contained in:
bors 2014-01-19 16:56:40 -08:00
commit 7c33df0dbb
15 changed files with 245 additions and 84 deletions

View File

@ -59,9 +59,12 @@ pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
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);
}

View File

@ -136,6 +136,17 @@ pub enum MacResult {
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
@ -364,10 +375,27 @@ impl<'a> ExtCtxt<'a> {
_ => 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);
@ -402,53 +430,69 @@ impl<'a> ExtCtxt<'a> {
}
}
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,

View File

@ -20,7 +20,10 @@ use std::char;
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() {

View File

@ -18,7 +18,10 @@ use ext::build::AstBuilder;
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);

View File

@ -23,12 +23,18 @@ pub fn expand_syntax_ext(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
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();
}
}
}
}

View File

@ -70,8 +70,9 @@ fn default_substructure(cx: &ExtCtxt, span: Span, substr: &Substructure) -> @Exp
}
}
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)`")
};

View File

@ -73,7 +73,9 @@ fn rand_substructure(cx: &ExtCtxt, span: Span, substr: &Substructure) -> @Expr {
}
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());

View File

@ -86,8 +86,9 @@ fn zero_substructure(cx: &ExtCtxt, span: Span, substr: &Substructure) -> @Expr {
}
}
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)`")
};

View File

@ -24,7 +24,10 @@ use std::os;
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>),
@ -35,24 +38,38 @@ pub fn expand_option_env(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
pub fn expand_env(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
let exprs = get_exprs_from_tts(cx, sp, tts);
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
};
if exprs.len() == 0 {
cx.span_fatal(sp, "env! takes 1 or 2 arguments");
}
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)

View File

@ -47,19 +47,24 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr {
// 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 {
@ -88,13 +93,14 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr {
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;
}
};
@ -102,10 +108,11 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr {
mark_expr(expanded,fm)
}
_ => {
fld.cx.span_fatal(
fld.cx.span_err(
pth.span,
format!("'{}' is not a tt-style macro", extnamestr)
)
);
return e;
}
};
@ -294,15 +301,20 @@ pub fn expand_item_mac(it: @ast::Item, fld: &mut MacroExpander)
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,
@ -319,9 +331,9 @@ pub fn expand_item_mac(it: @ast::Item, fld: &mut MacroExpander)
}
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,
@ -336,9 +348,10 @@ pub fn expand_item_mac(it: @ast::Item, fld: &mut MacroExpander)
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 {
@ -348,8 +361,8 @@ pub fn expand_item_mac(it: @ast::Item, fld: &mut MacroExpander)
.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()
@ -412,12 +425,16 @@ fn load_extern_macros(crate: &ast::ViewItem, fld: &mut MacroExpander) {
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| {
@ -448,14 +465,15 @@ pub fn expand_stmt(s: &Stmt, fld: &mut MacroExpander) -> SmallVector<@Stmt> {
_ => 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)) => {
@ -487,26 +505,27 @@ pub fn expand_stmt(s: &Stmt, fld: &mut MacroExpander) -> SmallVector<@Stmt> {
}
}
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()

View File

@ -755,8 +755,11 @@ pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
// 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| {

View File

@ -79,7 +79,10 @@ pub fn expand_mod(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
// 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(),
@ -94,12 +97,15 @@ pub fn expand_include(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
// 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,
};
@ -114,7 +120,8 @@ pub fn expand_include_str(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
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();
}
}
}
@ -124,12 +131,15 @@ pub fn expand_include_bin(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
{
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);

View File

@ -33,7 +33,8 @@ pub fn expand_trace_macros(cx: &mut ExtCtxt,
} 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();

View File

@ -183,8 +183,8 @@ pub fn nameize(p_s: @ParseSess, ms: &[Matcher], res: &[@NamedMatch])
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]);
}

View File

@ -0,0 +1,48 @@
// 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
}