diff --git a/src/comp/driver/diagnostic.rs b/src/comp/driver/diagnostic.rs
index 5c0661fe51e..427bac77197 100644
--- a/src/comp/driver/diagnostic.rs
+++ b/src/comp/driver/diagnostic.rs
@@ -179,6 +179,7 @@ fn emit(cmsp: option<(codemap::codemap, span)>,
         let lines = codemap::span_to_lines(sp, cm);
         print_diagnostic(ss, lvl, msg);
         highlight_lines(cm, sp, lines);
+        print_macro_backtrace(cm, sp);
       }
       none {
         print_diagnostic("", lvl, msg);
@@ -241,3 +242,15 @@ fn highlight_lines(cm: codemap::codemap, sp: span,
         io::stderr().write_str(s + "\n");
     }
 }
+
+fn print_macro_backtrace(cm: codemap::codemap, sp: span) {
+    option::may (sp.expn_info) {|ei|
+        let ss = option::maybe("", ei.callie.span,
+                               bind codemap::span_to_str(_, cm));
+        print_diagnostic(ss, note,
+                         #fmt("in expansion of #%s", ei.callie.name));
+        let ss = codemap::span_to_str(ei.call_site, cm);
+        print_diagnostic(ss, note, "expansion site");
+        print_macro_backtrace(cm, ei.call_site);
+    }
+}
diff --git a/src/comp/front/core_inject.rs b/src/comp/front/core_inject.rs
index f285d4bf67b..07bb48b8b3e 100644
--- a/src/comp/front/core_inject.rs
+++ b/src/comp/front/core_inject.rs
@@ -24,7 +24,7 @@ fn inject_libcore_ref(sess: session,
     fn spanned<T: copy>(x: T) -> @ast::spanned<T> {
         ret @{node: x,
               span: {lo: 0u, hi: 0u,
-                     expanded_from: codemap::os_none}};
+                     expn_info: option::none}};
     }
 
     let n1 = sess.next_node_id();
diff --git a/src/comp/syntax/ast_util.rs b/src/comp/syntax/ast_util.rs
index bdfaee5d4c2..767a2431bd3 100644
--- a/src/comp/syntax/ast_util.rs
+++ b/src/comp/syntax/ast_util.rs
@@ -7,7 +7,7 @@ fn respan<T: copy>(sp: span, t: T) -> spanned<T> {
 
 /* assuming that we're not in macro expansion */
 fn mk_sp(lo: uint, hi: uint) -> span {
-    ret {lo: lo, hi: hi, expanded_from: codemap::os_none};
+    ret {lo: lo, hi: hi, expn_info: none};
 }
 
 // make this a const, once the compiler supports it
diff --git a/src/comp/syntax/codemap.rs b/src/comp/syntax/codemap.rs
index 74b9bb091b4..7af9efe5984 100644
--- a/src/comp/syntax/codemap.rs
+++ b/src/comp/syntax/codemap.rs
@@ -92,38 +92,18 @@ fn lookup_byte_pos(map: codemap, pos: uint) -> loc {
     ret lookup_pos(map, pos, lookup);
 }
 
-enum opt_span {
-
-    //hack (as opposed to option), to make `span` compile
-    os_none,
-    os_some(@span),
+enum expn_info_ {
+    expanded_from({call_site: span,
+                   callie: {name: str, span: option<span>}})
 }
-type span = {lo: uint, hi: uint, expanded_from: opt_span};
+type expn_info = option<@expn_info_>;
+type span = {lo: uint, hi: uint, expn_info: expn_info};
 
 fn span_to_str(sp: span, cm: codemap) -> str {
-    let cur = sp;
-    let res = "";
-    // FIXME: Should probably be doing pointer comparison on filemap
-    let prev_file = none;
-    while true {
-        let lo = lookup_char_pos(cm, cur.lo);
-        let hi = lookup_char_pos(cm, cur.hi);
-        res +=
-            #fmt["%s:%u:%u: %u:%u",
-                 if some(lo.file.name) == prev_file {
-                     "-"
-                 } else { lo.file.name }, lo.line, lo.col, hi.line, hi.col];
-        alt cur.expanded_from {
-          os_none { break; }
-          os_some(new_sp) {
-            cur = *new_sp;
-            prev_file = some(lo.file.name);
-            res += "<<";
-          }
-        }
-    }
-
-    ret res;
+    let lo = lookup_char_pos(cm, sp.lo);
+    let hi = lookup_char_pos(cm, sp.hi);
+    ret #fmt("%s:%u:%u: %u:%u", lo.file.name,
+             lo.line, lo.col, hi.line, hi.col)
 }
 
 type file_lines = {file: filemap, lines: [uint]};
diff --git a/src/comp/syntax/ext/base.rs b/src/comp/syntax/ext/base.rs
index 42ec73077f6..6ab1c8446cf 100644
--- a/src/comp/syntax/ext/base.rs
+++ b/src/comp/syntax/ext/base.rs
@@ -1,12 +1,15 @@
 import core::{vec, option};
 import std::map::hashmap;
 import driver::session::session;
-import codemap::span;
+import codemap::{span, expn_info, expanded_from};
 import std::map::new_str_hash;
 import codemap;
 
-type syntax_expander =
+type syntax_expander_ =
     fn@(ext_ctxt, span, ast::mac_arg, ast::mac_body) -> @ast::expr;
+type syntax_expander = {
+    expander: syntax_expander_,
+    span: option<span>};
 type macro_def = {ident: str, ext: syntax_extension};
 type macro_definer =
     fn@(ext_ctxt, span, ast::mac_arg, ast::mac_body) -> macro_def;
@@ -19,27 +22,29 @@ enum syntax_extension {
 // A temporary hard-coded map of methods for expanding syntax extension
 // AST nodes into full ASTs
 fn syntax_expander_table() -> hashmap<str, syntax_extension> {
+    fn builtin(f: syntax_expander_) -> syntax_extension
+        {normal({expander: f, span: none})}
     let syntax_expanders = new_str_hash::<syntax_extension>();
-    syntax_expanders.insert("fmt", normal(ext::fmt::expand_syntax_ext));
-    syntax_expanders.insert("env", normal(ext::env::expand_syntax_ext));
+    syntax_expanders.insert("fmt", builtin(ext::fmt::expand_syntax_ext));
+    syntax_expanders.insert("env", builtin(ext::env::expand_syntax_ext));
     syntax_expanders.insert("macro",
                             macro_defining(ext::simplext::add_new_extension));
     syntax_expanders.insert("concat_idents",
-                            normal(ext::concat_idents::expand_syntax_ext));
+                            builtin(ext::concat_idents::expand_syntax_ext));
     syntax_expanders.insert("ident_to_str",
-                            normal(ext::ident_to_str::expand_syntax_ext));
+                            builtin(ext::ident_to_str::expand_syntax_ext));
     syntax_expanders.insert("log_syntax",
-                            normal(ext::log_syntax::expand_syntax_ext));
+                            builtin(ext::log_syntax::expand_syntax_ext));
     syntax_expanders.insert("ast",
-                            normal(ext::qquote::expand_ast));
+                            builtin(ext::qquote::expand_ast));
     ret syntax_expanders;
 }
 
 iface ext_ctxt {
     fn session() -> session;
     fn print_backtrace();
-    fn backtrace() -> codemap::opt_span;
-    fn bt_push(sp: span);
+    fn backtrace() -> expn_info;
+    fn bt_push(ei: codemap::expn_info_);
     fn bt_pop();
     fn span_fatal(sp: span, msg: str) -> !;
     fn span_err(sp: span, msg: str);
@@ -51,20 +56,26 @@ iface ext_ctxt {
 
 fn mk_ctxt(sess: session) -> ext_ctxt {
     type ctxt_repr = {sess: session,
-                      mutable backtrace: codemap::opt_span};
+                      mutable backtrace: expn_info};
     impl of ext_ctxt for ctxt_repr {
         fn session() -> session { self.sess }
         fn print_backtrace() { }
-        fn backtrace() -> codemap::opt_span { self.backtrace }
-        fn bt_push(sp: span) {
-            self.backtrace = codemap::os_some(
-                @{lo: sp.lo, hi: sp.hi, expanded_from: self.backtrace});
+        fn backtrace() -> expn_info { self.backtrace }
+        fn bt_push(ei: codemap::expn_info_) {
+            alt ei {
+              expanded_from({call_site: cs, callie: callie}) {
+                self.backtrace =
+                    some(@expanded_from({
+                        call_site: {lo: cs.lo, hi: cs.hi,
+                                    expn_info: self.backtrace},
+                        callie: callie}));
+              }
+            }
         }
         fn bt_pop() {
             alt self.backtrace {
-              codemap::os_some(@{expanded_from: pre, _}) {
-                let tmp = pre;
-                self.backtrace = tmp;
+              some(@expanded_from({call_site: {expn_info: prev, _}, _})) {
+                self.backtrace = prev
               }
               _ { self.bug("tried to pop without a push"); }
             }
@@ -88,7 +99,8 @@ fn mk_ctxt(sess: session) -> ext_ctxt {
         fn bug(msg: str) -> ! { self.print_backtrace(); self.sess.bug(msg); }
         fn next_id() -> ast::node_id { ret self.sess.next_node_id(); }
     }
-    {sess: sess, mutable backtrace: codemap::os_none} as ext_ctxt
+    let imp : ctxt_repr = {sess: sess, mutable backtrace: none};
+    ret imp as ext_ctxt
 }
 
 fn expr_to_str(cx: ext_ctxt, expr: @ast::expr, error: str) -> str {
diff --git a/src/comp/syntax/ext/expand.rs b/src/comp/syntax/ext/expand.rs
index 201e44232bb..b91b794571f 100644
--- a/src/comp/syntax/ext/expand.rs
+++ b/src/comp/syntax/ext/expand.rs
@@ -11,7 +11,7 @@ import syntax::ext::base::*;
 import syntax::ext::qquote::{expand_qquote,qq_helper};
 import syntax::parse::parser::parse_expr_from_source_str;
 
-import codemap::span;
+import codemap::{span, expanded_from};
 
 fn expand_expr(exts: hashmap<str, syntax_extension>, cx: ext_ctxt,
                e: expr_, s: span, fld: ast_fold,
@@ -29,10 +29,12 @@ fn expand_expr(exts: hashmap<str, syntax_extension>, cx: ext_ctxt,
                     cx.span_fatal(pth.span,
                                   #fmt["macro undefined: '%s'", extname])
                   }
-                  some(normal(ext)) {
-                    let expanded = ext(cx, pth.span, args, body);
+                  some(normal({expander: exp, span: exp_sp})) {
+                    let expanded = exp(cx, pth.span, args, body);
 
-                    cx.bt_push(mac.span);
+                    let info = {call_site: s,
+                                callie: {name: extname, span: exp_sp}};
+                    cx.bt_push(expanded_from(info));
                     //keep going, outside-in
                     let fully_expanded = fld.fold_expr(expanded).node;
                     cx.bt_pop();
@@ -53,6 +55,11 @@ fn expand_expr(exts: hashmap<str, syntax_extension>, cx: ext_ctxt,
         };
 }
 
+fn new_span(cx: ext_ctxt, sp: span) -> span {
+    /* this discards information in the case of macro-defining macros */
+    ret {lo: sp.lo, hi: sp.hi, expn_info: cx.backtrace()};
+}
+
 // FIXME: this is a terrible kludge to inject some macros into the default
 // compilation environment. When the macro-definition system is substantially
 // more mature, these should move from here, into a compiled part of libcore
@@ -73,7 +80,8 @@ fn expand_crate(sess: session::session, c: @crate) -> @crate {
     let afp = default_ast_fold();
     let cx: ext_ctxt = mk_ctxt(sess);
     let f_pre =
-        {fold_expr: bind expand_expr(exts, cx, _, _, _, afp.fold_expr)
+        {fold_expr: bind expand_expr(exts, cx, _, _, _, afp.fold_expr),
+         new_span: bind new_span(cx, _)
             with *afp};
     let f = make_fold(f_pre);
     let cm = parse_expr_from_source_str("<core-macros>",
diff --git a/src/comp/syntax/ext/simplext.rs b/src/comp/syntax/ext/simplext.rs
index 3473ae1dbe1..6304df29e26 100644
--- a/src/comp/syntax/ext/simplext.rs
+++ b/src/comp/syntax/ext/simplext.rs
@@ -190,7 +190,7 @@ fn transcribe(cx: ext_ctxt, b: bindings, body: @expr) -> @expr {
     fn new_id(_old: node_id, cx: ext_ctxt) -> node_id { ret cx.next_id(); }
     fn new_span(cx: ext_ctxt, sp: span) -> span {
         /* this discards information in the case of macro-defining macros */
-        ret {lo: sp.lo, hi: sp.hi, expanded_from: cx.backtrace()};
+        ret {lo: sp.lo, hi: sp.hi, expn_info: cx.backtrace()};
     }
     let afp = default_ast_fold();
     let f_pre =
@@ -202,8 +202,8 @@ fn transcribe(cx: ext_ctxt, b: bindings, body: @expr) -> @expr {
          fold_block:
              bind transcribe_block(cx, b, idx_path, _, _, _, afp.fold_block),
          map_exprs: bind transcribe_exprs(cx, b, idx_path, _, _),
-         new_id: bind new_id(_, cx),
-         new_span: bind new_span(cx, _) with *afp};
+         new_id: bind new_id(_, cx)
+         with *afp};
     let f = make_fold(f_pre);
     let result = f.fold_expr(body);
     ret result;
@@ -753,7 +753,7 @@ fn add_new_extension(cx: ext_ctxt, sp: span, arg: ast::mac_arg,
                                    "at least one clause")
                }
              },
-         ext: normal(ext)};
+         ext: normal({expander: ext, span: some(arg.span)})};
 
     fn generic_extension(cx: ext_ctxt, sp: span, arg: ast::mac_arg,
                          _body: ast::mac_body, clauses: [@clause]) -> @expr {