about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-08-30 10:51:26 +0000
committerbors <bors@rust-lang.org>2014-08-30 10:51:26 +0000
commitd398eb76ae5fdb7c08bbb7c0e3d85dc22d42c5ce (patch)
tree29bb3039a1d09a1f953063e9cd83c755e1c1f5ef
parent43c26e6041b811b322f49933e8d0f9455cb7ea2b (diff)
parent32e437161da5e60c660b86da9003b7f73ebb5676 (diff)
downloadrust-d398eb76ae5fdb7c08bbb7c0e3d85dc22d42c5ce.tar.gz
rust-d398eb76ae5fdb7c08bbb7c0e3d85dc22d42c5ce.zip
auto merge of #16419 : huonw/rust/pretty-expanded-hygiene, r=pnkfelix
Different Identifiers and Names can have identical textual representations, but different internal representations, due to the macro hygiene machinery (syntax contexts and gensyms). This provides a way to see these internals by compiling with `--pretty expanded,hygiene`.

This is useful for debugging & hacking on macros (e.g. diagnosing https://github.com/rust-lang/rust/issues/15750/https://github.com/rust-lang/rust/issues/15962 likely would've been faster with this functionality).

E.g. 

```rust
#![feature(macro_rules)]
// minimal junk
#![no_std]

macro_rules! foo {
    ($x: ident) => { y + $x }
}

fn bar() {
    foo!(x)
}
```
```rust
#![feature(macro_rules)]
// minimal junk
#![no_std]


fn bar /* 61#0 */() { y /* 60#2 */ + x /* 58#3 */ }
```
-rw-r--r--src/librustc/driver/driver.rs517
-rw-r--r--src/librustc/driver/mod.rs42
-rw-r--r--src/librustc/driver/pretty.rs615
-rw-r--r--src/librustc/middle/dataflow.rs1
-rw-r--r--src/libsyntax/print/pprust.rs10
-rw-r--r--src/test/run-make/pretty-expanded-hygiene/Makefile20
-rwxr-xr-xsrc/test/run-make/pretty-expanded-hygiene/input.pp.rs16
-rwxr-xr-xsrc/test/run-make/pretty-expanded-hygiene/input.rs22
8 files changed, 685 insertions, 558 deletions
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 5ba54a7bfc1..3d0678aa0e7 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -11,19 +11,12 @@
 
 use back::link;
 use driver::session::Session;
-use driver::{config, PpMode, PpSourceMode};
-use driver::{PpmFlowGraph, PpmExpanded, PpmExpandedIdentified, PpmTyped};
-use driver::{PpmIdentified, PpmNormal, PpmSource};
+use driver::config;
 use front;
 use lint;
 use llvm::{ContextRef, ModuleRef};
 use metadata::common::LinkMeta;
 use metadata::creader;
-use middle::borrowck::{FnPartsWithCFG};
-use middle::borrowck;
-use middle::borrowck::graphviz as borrowck_dot;
-use middle::cfg;
-use middle::cfg::graphviz::LabelledCFG;
 use middle::{trans, freevars, stability, kind, ty, typeck, reachable};
 use middle::dependency_format;
 use middle;
@@ -32,28 +25,18 @@ use plugin::registry::Registry;
 use plugin;
 
 use util::common::time;
-use util::ppaux;
 use util::nodemap::{NodeSet};
 
-use graphviz as dot;
-
 use serialize::{json, Encodable};
 
-use std::from_str::FromStr;
 use std::io;
 use std::io::fs;
-use std::io::MemReader;
-use std::option;
 use syntax::ast;
-use syntax::ast_map;
-use syntax::ast_map::blocks;
-use syntax::ast_map::NodePrinter;
 use syntax::attr;
 use syntax::attr::{AttrMetaMethods};
 use syntax::diagnostics;
 use syntax::parse;
 use syntax::parse::token;
-use syntax::print::{pp, pprust};
 use syntax;
 
 pub fn host_triple() -> &'static str {
@@ -618,504 +601,6 @@ fn write_out_deps(sess: &Session,
     }
 }
 
-// This slightly awkward construction is to allow for each PpMode to
-// choose whether it needs to do analyses (which can consume the
-// Session) and then pass through the session (now attached to the
-// analysis results) on to the chosen pretty-printer, along with the
-// `&PpAnn` object.
-//
-// Note that since the `&PrinterSupport` is freshly constructed on each
-// call, it would not make sense to try to attach the lifetime of `self`
-// to the lifetime of the `&PrinterObject`.
-//
-// (The `use_once_payload` is working around the current lack of once
-// functions in the compiler.)
-trait CratePrinter {
-    /// Constructs a `PrinterSupport` object and passes it to `f`.
-    fn call_with_pp_support<A,B>(&self,
-                                 sess: Session,
-                                 krate: &ast::Crate,
-                                 ast_map: Option<syntax::ast_map::Map>,
-                                 id: String,
-                                 use_once_payload: B,
-                                 f: |&PrinterSupport, B| -> A) -> A;
-}
-
-trait SessionCarrier {
-    /// Provides a uniform interface for re-extracting a reference to a
-    /// `Session` from a value that now owns it.
-    fn sess<'a>(&'a self) -> &'a Session;
-}
-
-trait AstMapCarrier {
-    /// Provides a uniform interface for re-extracting a reference to an
-    /// `ast_map::Map` from a value that now owns it.
-    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map>;
-}
-
-trait PrinterSupport : SessionCarrier + AstMapCarrier {
-    /// Produces the pretty-print annotation object.
-    ///
-    /// Usually implemented via `self as &pprust::PpAnn`.
-    ///
-    /// (Rust does not yet support upcasting from a trait object to
-    /// an object for one of its super-traits.)
-    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn;
-}
-
-struct NoAnn {
-    sess: Session,
-    ast_map: Option<ast_map::Map>,
-}
-
-impl PrinterSupport for NoAnn {
-    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
-}
-
-impl SessionCarrier for NoAnn {
-    fn sess<'a>(&'a self) -> &'a Session { &self.sess }
-}
-
-impl AstMapCarrier for NoAnn {
-    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
-        self.ast_map.as_ref()
-    }
-}
-
-impl pprust::PpAnn for NoAnn {}
-
-struct IdentifiedAnnotation {
-    sess: Session,
-    ast_map: Option<ast_map::Map>,
-}
-
-impl PrinterSupport for IdentifiedAnnotation {
-    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
-}
-
-impl SessionCarrier for IdentifiedAnnotation {
-    fn sess<'a>(&'a self) -> &'a Session { &self.sess }
-}
-
-impl AstMapCarrier for IdentifiedAnnotation {
-    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
-        self.ast_map.as_ref()
-    }
-}
-
-impl pprust::PpAnn for IdentifiedAnnotation {
-    fn pre(&self,
-           s: &mut pprust::State,
-           node: pprust::AnnNode) -> io::IoResult<()> {
-        match node {
-            pprust::NodeExpr(_) => s.popen(),
-            _ => Ok(())
-        }
-    }
-    fn post(&self,
-            s: &mut pprust::State,
-            node: pprust::AnnNode) -> io::IoResult<()> {
-        match node {
-            pprust::NodeItem(item) => {
-                try!(pp::space(&mut s.s));
-                s.synth_comment(item.id.to_string())
-            }
-            pprust::NodeBlock(blk) => {
-                try!(pp::space(&mut s.s));
-                s.synth_comment(format!("block {}", blk.id))
-            }
-            pprust::NodeExpr(expr) => {
-                try!(pp::space(&mut s.s));
-                try!(s.synth_comment(expr.id.to_string()));
-                s.pclose()
-            }
-            pprust::NodePat(pat) => {
-                try!(pp::space(&mut s.s));
-                s.synth_comment(format!("pat {}", pat.id))
-            }
-        }
-    }
-}
-
-struct TypedAnnotation {
-    analysis: CrateAnalysis,
-}
-
-impl PrinterSupport for TypedAnnotation {
-    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
-}
-
-impl SessionCarrier for TypedAnnotation {
-    fn sess<'a>(&'a self) -> &'a Session { &self.analysis.ty_cx.sess }
-}
-
-impl AstMapCarrier for TypedAnnotation {
-    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
-        Some(&self.analysis.ty_cx.map)
-    }
-}
-
-impl pprust::PpAnn for TypedAnnotation {
-    fn pre(&self,
-           s: &mut pprust::State,
-           node: pprust::AnnNode) -> io::IoResult<()> {
-        match node {
-            pprust::NodeExpr(_) => s.popen(),
-            _ => Ok(())
-        }
-    }
-    fn post(&self,
-            s: &mut pprust::State,
-            node: pprust::AnnNode) -> io::IoResult<()> {
-        let tcx = &self.analysis.ty_cx;
-        match node {
-            pprust::NodeExpr(expr) => {
-                try!(pp::space(&mut s.s));
-                try!(pp::word(&mut s.s, "as"));
-                try!(pp::space(&mut s.s));
-                try!(pp::word(&mut s.s,
-                              ppaux::ty_to_string(
-                                  tcx,
-                                  ty::expr_ty(tcx, expr)).as_slice()));
-                s.pclose()
-            }
-            _ => Ok(())
-        }
-    }
-}
-
-fn gather_flowgraph_variants(sess: &Session) -> Vec<borrowck_dot::Variant> {
-    let print_loans   = config::FLOWGRAPH_PRINT_LOANS;
-    let print_moves   = config::FLOWGRAPH_PRINT_MOVES;
-    let print_assigns = config::FLOWGRAPH_PRINT_ASSIGNS;
-    let print_all     = config::FLOWGRAPH_PRINT_ALL;
-    let opt = |print_which| sess.debugging_opt(print_which);
-    let mut variants = Vec::new();
-    if opt(print_all) || opt(print_loans) {
-        variants.push(borrowck_dot::Loans);
-    }
-    if opt(print_all) || opt(print_moves) {
-        variants.push(borrowck_dot::Moves);
-    }
-    if opt(print_all) || opt(print_assigns) {
-        variants.push(borrowck_dot::Assigns);
-    }
-    variants
-}
-
-#[deriving(Clone, Show)]
-pub enum UserIdentifiedItem {
-    ItemViaNode(ast::NodeId),
-    ItemViaPath(Vec<String>),
-}
-
-impl FromStr for UserIdentifiedItem {
-    fn from_str(s: &str) -> Option<UserIdentifiedItem> {
-        let extract_path_parts = || {
-            let v : Vec<_> = s.split_str("::")
-                .map(|x|x.to_string())
-                .collect();
-            Some(ItemViaPath(v))
-        };
-
-        from_str(s).map(ItemViaNode).or_else(extract_path_parts)
-    }
-}
-
-enum NodesMatchingUII<'a> {
-    NodesMatchingDirect(option::Item<ast::NodeId>),
-    NodesMatchingSuffix(ast_map::NodesMatchingSuffix<'a, String>),
-}
-
-impl<'a> Iterator<ast::NodeId> for NodesMatchingUII<'a> {
-    fn next(&mut self) -> Option<ast::NodeId> {
-        match self {
-            &NodesMatchingDirect(ref mut iter) => iter.next(),
-            &NodesMatchingSuffix(ref mut iter) => iter.next(),
-        }
-    }
-}
-
-impl UserIdentifiedItem {
-    fn reconstructed_input(&self) -> String {
-        match *self {
-            ItemViaNode(node_id) => node_id.to_string(),
-            ItemViaPath(ref parts) => parts.connect("::"),
-        }
-    }
-
-    fn all_matching_node_ids<'a>(&'a self, map: &'a ast_map::Map) -> NodesMatchingUII<'a> {
-        match *self {
-            ItemViaNode(node_id) =>
-                NodesMatchingDirect(Some(node_id).move_iter()),
-            ItemViaPath(ref parts) =>
-                NodesMatchingSuffix(map.nodes_matching_suffix(parts.as_slice())),
-        }
-    }
-
-    fn to_one_node_id(self, user_option: &str, sess: &Session, map: &ast_map::Map) -> ast::NodeId {
-        let fail_because = |is_wrong_because| -> ast::NodeId {
-            let message =
-                format!("{:s} needs NodeId (int) or unique \
-                         path suffix (b::c::d); got {:s}, which {:s}",
-                        user_option,
-                        self.reconstructed_input(),
-                        is_wrong_because);
-            sess.fatal(message.as_slice())
-        };
-
-        let mut saw_node = ast::DUMMY_NODE_ID;
-        let mut seen = 0u;
-        for node in self.all_matching_node_ids(map) {
-            saw_node = node;
-            seen += 1;
-            if seen > 1 {
-                fail_because("does not resolve uniquely");
-            }
-        }
-        if seen == 0 {
-            fail_because("does not resolve to any item");
-        }
-
-        assert!(seen == 1);
-        return saw_node;
-    }
-}
-
-impl CratePrinter for PpSourceMode {
-    fn call_with_pp_support<A,B>(&self,
-                                 sess: Session,
-                                 krate: &ast::Crate,
-                                 ast_map: Option<syntax::ast_map::Map>,
-                                 id: String,
-                                 payload: B,
-                                 f: |&PrinterSupport, B| -> A) -> A {
-        match *self {
-            PpmNormal | PpmExpanded => {
-                let annotation = NoAnn { sess: sess, ast_map: ast_map };
-                f(&annotation, payload)
-            }
-
-            PpmIdentified | PpmExpandedIdentified => {
-                let annotation = IdentifiedAnnotation { sess: sess, ast_map: ast_map };
-                f(&annotation, payload)
-            }
-            PpmTyped => {
-                let ast_map = ast_map.expect("--pretty=typed missing ast_map");
-                let analysis = phase_3_run_analysis_passes(sess, krate, ast_map, id);
-                let annotation = TypedAnnotation { analysis: analysis };
-                f(&annotation, payload)
-            }
-        }
-    }
-}
-
-fn needs_ast_map(ppm: &PpMode, opt_uii: &Option<UserIdentifiedItem>) -> bool {
-    match *ppm {
-        PpmSource(PpmNormal) |
-        PpmSource(PpmIdentified) => opt_uii.is_some(),
-
-        PpmSource(PpmExpanded) |
-        PpmSource(PpmExpandedIdentified) |
-        PpmSource(PpmTyped) |
-        PpmFlowGraph => true
-    }
-}
-
-fn needs_expansion(ppm: &PpMode) -> bool {
-    match *ppm {
-        PpmSource(PpmNormal) |
-        PpmSource(PpmIdentified) => false,
-
-        PpmSource(PpmExpanded) |
-        PpmSource(PpmExpandedIdentified) |
-        PpmSource(PpmTyped) |
-        PpmFlowGraph => true
-    }
-}
-pub fn pretty_print_input(sess: Session,
-                          cfg: ast::CrateConfig,
-                          input: &Input,
-                          ppm: PpMode,
-                          opt_uii: Option<UserIdentifiedItem>,
-                          ofile: Option<Path>) {
-    let krate = phase_1_parse_input(&sess, cfg, input);
-    let id = link::find_crate_name(Some(&sess), krate.attrs.as_slice(), input);
-
-    let is_expanded = needs_expansion(&ppm);
-    let (krate, ast_map) = if needs_ast_map(&ppm, &opt_uii) {
-        let k = phase_2_configure_and_expand(&sess, krate, id.as_slice(), None);
-        let (krate, ast_map) = match k {
-            None => return,
-            Some(p) => p,
-        };
-        (krate, Some(ast_map))
-    } else {
-        (krate, None)
-    };
-
-    let src_name = source_name(input);
-    let src = Vec::from_slice(sess.codemap()
-                                  .get_filemap(src_name.as_slice())
-                                  .src
-                                  .as_bytes());
-    let mut rdr = MemReader::new(src);
-
-    let out = match ofile {
-        None => box io::stdout() as Box<Writer+'static>,
-        Some(p) => {
-            let r = io::File::create(&p);
-            match r {
-                Ok(w) => box w as Box<Writer+'static>,
-                Err(e) => fail!("print-print failed to open {} due to {}",
-                                p.display(), e),
-            }
-        }
-    };
-
-    match (ppm, opt_uii) {
-        (PpmSource(s), None) =>
-            s.call_with_pp_support(
-                sess, &krate, ast_map, id, out, |annotation, out| {
-                    debug!("pretty printing source code {}", s);
-                    let sess = annotation.sess();
-                    pprust::print_crate(sess.codemap(),
-                                        sess.diagnostic(),
-                                        &krate,
-                                        src_name.to_string(),
-                                        &mut rdr,
-                                        out,
-                                        annotation.pp_ann(),
-                                        is_expanded)
-                }),
-
-        (PpmSource(s), Some(uii)) =>
-            s.call_with_pp_support(
-                sess, &krate, ast_map, id, (out,uii), |annotation, (out,uii)| {
-                    debug!("pretty printing source code {}", s);
-                    let sess = annotation.sess();
-                    let ast_map = annotation.ast_map()
-                        .expect("--pretty missing ast_map");
-                    let mut pp_state =
-                        pprust::State::new_from_input(sess.codemap(),
-                                                      sess.diagnostic(),
-                                                      src_name.to_string(),
-                                                      &mut rdr,
-                                                      out,
-                                                      annotation.pp_ann(),
-                                                      is_expanded);
-                    for node_id in uii.all_matching_node_ids(ast_map) {
-                        let node = ast_map.get(node_id);
-                        try!(pp_state.print_node(&node));
-                        try!(pp::space(&mut pp_state.s));
-                        try!(pp_state.synth_comment(ast_map.path_to_string(node_id)));
-                        try!(pp::hardbreak(&mut pp_state.s));
-                    }
-                    pp::eof(&mut pp_state.s)
-                }),
-
-        (PpmFlowGraph, opt_uii) => {
-            debug!("pretty printing flow graph for {}", opt_uii);
-            let uii = opt_uii.unwrap_or_else(|| {
-                sess.fatal(format!("`pretty flowgraph=..` needs NodeId (int) or
-                                     unique path suffix (b::c::d)").as_slice())
-
-            });
-            let ast_map = ast_map.expect("--pretty flowgraph missing ast_map");
-            let nodeid = uii.to_one_node_id("--pretty", &sess, &ast_map);
-
-            let node = ast_map.find(nodeid).unwrap_or_else(|| {
-                sess.fatal(format!("--pretty flowgraph couldn't find id: {}",
-                                   nodeid).as_slice())
-            });
-
-            let code = blocks::Code::from_node(node);
-            match code {
-                Some(code) => {
-                    let variants = gather_flowgraph_variants(&sess);
-                    let analysis = phase_3_run_analysis_passes(sess, &krate,
-                                                               ast_map, id);
-                    print_flowgraph(variants, analysis, code, out)
-                }
-                None => {
-                    let message = format!("--pretty=flowgraph needs \
-                                           block, fn, or method; got {:?}",
-                                          node);
-
-                    // point to what was found, if there's an
-                    // accessible span.
-                    match ast_map.opt_span(nodeid) {
-                        Some(sp) => sess.span_fatal(sp, message.as_slice()),
-                        None => sess.fatal(message.as_slice())
-                    }
-                }
-            }
-        }
-    }.unwrap()
-}
-
-fn print_flowgraph<W:io::Writer>(variants: Vec<borrowck_dot::Variant>,
-                                 analysis: CrateAnalysis,
-                                 code: blocks::Code,
-                                 mut out: W) -> io::IoResult<()> {
-    let ty_cx = &analysis.ty_cx;
-    let cfg = match code {
-        blocks::BlockCode(block) => cfg::CFG::new(ty_cx, &*block),
-        blocks::FnLikeCode(fn_like) => cfg::CFG::new(ty_cx, &*fn_like.body()),
-    };
-    debug!("cfg: {:?}", cfg);
-
-    match code {
-        _ if variants.len() == 0 => {
-            let lcfg = LabelledCFG {
-                ast_map: &ty_cx.map,
-                cfg: &cfg,
-                name: format!("node_{}", code.id()),
-            };
-            let r = dot::render(&lcfg, &mut out);
-            return expand_err_details(r);
-        }
-        blocks::BlockCode(_) => {
-            ty_cx.sess.err("--pretty flowgraph with -Z flowgraph-print \
-                            annotations requires fn-like node id.");
-            return Ok(())
-        }
-        blocks::FnLikeCode(fn_like) => {
-            let fn_parts = FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
-            let (bccx, analysis_data) =
-                borrowck::build_borrowck_dataflow_data_for_fn(ty_cx, fn_parts);
-
-            let lcfg = LabelledCFG {
-                ast_map: &ty_cx.map,
-                cfg: &cfg,
-                name: format!("node_{}", code.id()),
-            };
-            let lcfg = borrowck_dot::DataflowLabeller {
-                inner: lcfg,
-                variants: variants,
-                borrowck_ctxt: &bccx,
-                analysis_data: &analysis_data,
-            };
-            let r = dot::render(&lcfg, &mut out);
-            return expand_err_details(r);
-        }
-    }
-
-    fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
-        r.map_err(|ioerr| {
-            let orig_detail = ioerr.detail.clone();
-            let m = "graphviz::render failed";
-            io::IoError {
-                detail: Some(match orig_detail {
-                    None => m.into_string(),
-                    Some(d) => format!("{}: {}", m, d)
-                }),
-                ..ioerr
-            }
-        })
-    }
-}
-
 pub fn collect_crate_types(session: &Session,
                            attrs: &[ast::Attribute]) -> Vec<config::CrateType> {
     // Unconditionally collect crate types from attributes to make them used
diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs
index e6bdf9badc8..5e00b9e9e0e 100644
--- a/src/librustc/driver/mod.rs
+++ b/src/librustc/driver/mod.rs
@@ -33,6 +33,7 @@ use getopts;
 pub mod driver;
 pub mod session;
 pub mod config;
+pub mod pretty;
 
 
 pub fn main_args(args: &[String]) -> int {
@@ -96,11 +97,11 @@ fn run_compiler(args: &[String]) {
     let ofile = matches.opt_str("o").map(|o| Path::new(o));
 
     let pretty = matches.opt_default("pretty", "normal").map(|a| {
-        parse_pretty(&sess, a.as_slice())
+        pretty::parse_pretty(&sess, a.as_slice())
     });
     match pretty {
         Some((ppm, opt_uii)) => {
-            driver::pretty_print_input(sess, cfg, &input, ppm, opt_uii, ofile);
+            pretty::pretty_print_input(sess, cfg, &input, ppm, opt_uii, ofile);
             return;
         }
         None => {/* continue */ }
@@ -384,43 +385,6 @@ fn print_crate_info(sess: &Session,
     }
 }
 
-#[deriving(PartialEq, Show)]
-pub enum PpSourceMode {
-    PpmNormal,
-    PpmExpanded,
-    PpmTyped,
-    PpmIdentified,
-    PpmExpandedIdentified,
-}
-
-#[deriving(PartialEq, Show)]
-pub enum PpMode {
-    PpmSource(PpSourceMode),
-    PpmFlowGraph,
-}
-
-fn parse_pretty(sess: &Session, name: &str) -> (PpMode, Option<driver::UserIdentifiedItem>) {
-    let mut split = name.splitn(1, '=');
-    let first = split.next().unwrap();
-    let opt_second = split.next();
-    let first = match first {
-        "normal"       => PpmSource(PpmNormal),
-        "expanded"     => PpmSource(PpmExpanded),
-        "typed"        => PpmSource(PpmTyped),
-        "expanded,identified" => PpmSource(PpmExpandedIdentified),
-        "identified"   => PpmSource(PpmIdentified),
-        "flowgraph"    => PpmFlowGraph,
-        _ => {
-            sess.fatal(format!(
-                "argument to `pretty` must be one of `normal`, \
-                 `expanded`, `flowgraph=<nodeid>`, `typed`, `identified`, \
-                 or `expanded,identified`; got {}", name).as_slice());
-        }
-    };
-    let opt_second = opt_second.and_then::<driver::UserIdentifiedItem>(from_str);
-    (first, opt_second)
-}
-
 fn parse_crate_attrs(sess: &Session, input: &Input) ->
                      Vec<ast::Attribute> {
     let result = match *input {
diff --git a/src/librustc/driver/pretty.rs b/src/librustc/driver/pretty.rs
new file mode 100644
index 00000000000..67dbc6f6a30
--- /dev/null
+++ b/src/librustc/driver/pretty.rs
@@ -0,0 +1,615 @@
+// 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.
+
+//! The various pretty print routines.
+
+use back::link;
+
+use driver::config;
+use driver::driver::{mod, CrateAnalysis};
+use driver::session::Session;
+
+use middle::ty;
+use middle::borrowck::{mod, FnPartsWithCFG};
+use middle::borrowck::graphviz as borrowck_dot;
+use middle::cfg;
+use middle::cfg::graphviz::LabelledCFG;
+
+use util::ppaux;
+
+use syntax::ast;
+use syntax::ast_map::{mod, blocks, NodePrinter};
+use syntax::print::{pp, pprust};
+
+use graphviz as dot;
+
+use std::io::{mod, MemReader};
+use std::from_str::FromStr;
+use std::option;
+
+
+#[deriving(PartialEq, Show)]
+pub enum PpSourceMode {
+    PpmNormal,
+    PpmExpanded,
+    PpmTyped,
+    PpmIdentified,
+    PpmExpandedIdentified,
+    PpmExpandedHygiene,
+}
+
+#[deriving(PartialEq, Show)]
+pub enum PpMode {
+    PpmSource(PpSourceMode),
+    PpmFlowGraph,
+}
+
+pub fn parse_pretty(sess: &Session, name: &str) -> (PpMode, Option<UserIdentifiedItem>) {
+    let mut split = name.splitn(1, '=');
+    let first = split.next().unwrap();
+    let opt_second = split.next();
+    let first = match first {
+        "normal"       => PpmSource(PpmNormal),
+        "expanded"     => PpmSource(PpmExpanded),
+        "typed"        => PpmSource(PpmTyped),
+        "expanded,identified" => PpmSource(PpmExpandedIdentified),
+        "expanded,hygiene" => PpmSource(PpmExpandedHygiene),
+        "identified"   => PpmSource(PpmIdentified),
+        "flowgraph"    => PpmFlowGraph,
+        _ => {
+            sess.fatal(format!(
+                "argument to `pretty` must be one of `normal`, \
+                 `expanded`, `flowgraph=<nodeid>`, `typed`, `identified`, \
+                 or `expanded,identified`; got {}", name).as_slice());
+        }
+    };
+    let opt_second = opt_second.and_then::<UserIdentifiedItem>(from_str);
+    (first, opt_second)
+}
+
+
+
+// This slightly awkward construction is to allow for each PpMode to
+// choose whether it needs to do analyses (which can consume the
+// Session) and then pass through the session (now attached to the
+// analysis results) on to the chosen pretty-printer, along with the
+// `&PpAnn` object.
+//
+// Note that since the `&PrinterSupport` is freshly constructed on each
+// call, it would not make sense to try to attach the lifetime of `self`
+// to the lifetime of the `&PrinterObject`.
+//
+// (The `use_once_payload` is working around the current lack of once
+// functions in the compiler.)
+
+impl PpSourceMode {
+    /// Constructs a `PrinterSupport` object and passes it to `f`.
+    fn call_with_pp_support<A,B>(&self,
+                                 sess: Session,
+                                 krate: &ast::Crate,
+                                 ast_map: Option<ast_map::Map>,
+                                 id: String,
+                                 payload: B,
+                                 f: |&PrinterSupport, B| -> A) -> A {
+        match *self {
+            PpmNormal | PpmExpanded => {
+                let annotation = NoAnn { sess: sess, ast_map: ast_map };
+                f(&annotation, payload)
+            }
+
+            PpmIdentified | PpmExpandedIdentified => {
+                let annotation = IdentifiedAnnotation { sess: sess, ast_map: ast_map };
+                f(&annotation, payload)
+            }
+            PpmExpandedHygiene => {
+                let annotation = HygieneAnnotation { sess: sess, ast_map: ast_map };
+                f(&annotation, payload)
+            }
+            PpmTyped => {
+                let ast_map = ast_map.expect("--pretty=typed missing ast_map");
+                let analysis = driver::phase_3_run_analysis_passes(sess, krate, ast_map, id);
+                let annotation = TypedAnnotation { analysis: analysis };
+                f(&annotation, payload)
+            }
+        }
+    }
+}
+
+trait SessionCarrier {
+    /// Provides a uniform interface for re-extracting a reference to a
+    /// `Session` from a value that now owns it.
+    fn sess<'a>(&'a self) -> &'a Session;
+}
+
+trait AstMapCarrier {
+    /// Provides a uniform interface for re-extracting a reference to an
+    /// `ast_map::Map` from a value that now owns it.
+    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map>;
+}
+
+trait PrinterSupport : SessionCarrier + AstMapCarrier {
+    /// Produces the pretty-print annotation object.
+    ///
+    /// Usually implemented via `self as &pprust::PpAnn`.
+    ///
+    /// (Rust does not yet support upcasting from a trait object to
+    /// an object for one of its super-traits.)
+    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn;
+}
+
+struct NoAnn {
+    sess: Session,
+    ast_map: Option<ast_map::Map>,
+}
+
+impl PrinterSupport for NoAnn {
+    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
+}
+
+impl SessionCarrier for NoAnn {
+    fn sess<'a>(&'a self) -> &'a Session { &self.sess }
+}
+
+impl AstMapCarrier for NoAnn {
+    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
+        self.ast_map.as_ref()
+    }
+}
+
+impl pprust::PpAnn for NoAnn {}
+
+struct IdentifiedAnnotation {
+    sess: Session,
+    ast_map: Option<ast_map::Map>,
+}
+
+impl PrinterSupport for IdentifiedAnnotation {
+    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
+}
+
+impl SessionCarrier for IdentifiedAnnotation {
+    fn sess<'a>(&'a self) -> &'a Session { &self.sess }
+}
+
+impl AstMapCarrier for IdentifiedAnnotation {
+    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
+        self.ast_map.as_ref()
+    }
+}
+
+impl pprust::PpAnn for IdentifiedAnnotation {
+    fn pre(&self,
+           s: &mut pprust::State,
+           node: pprust::AnnNode) -> io::IoResult<()> {
+        match node {
+            pprust::NodeExpr(_) => s.popen(),
+            _ => Ok(())
+        }
+    }
+    fn post(&self,
+            s: &mut pprust::State,
+            node: pprust::AnnNode) -> io::IoResult<()> {
+        match node {
+            pprust::NodeIdent(_) | pprust::NodeName(_) => Ok(()),
+
+            pprust::NodeItem(item) => {
+                try!(pp::space(&mut s.s));
+                s.synth_comment(item.id.to_string())
+            }
+            pprust::NodeBlock(blk) => {
+                try!(pp::space(&mut s.s));
+                s.synth_comment(format!("block {}", blk.id))
+            }
+            pprust::NodeExpr(expr) => {
+                try!(pp::space(&mut s.s));
+                try!(s.synth_comment(expr.id.to_string()));
+                s.pclose()
+            }
+            pprust::NodePat(pat) => {
+                try!(pp::space(&mut s.s));
+                s.synth_comment(format!("pat {}", pat.id))
+            }
+        }
+    }
+}
+
+struct HygieneAnnotation {
+    sess: Session,
+    ast_map: Option<ast_map::Map>,
+}
+
+impl PrinterSupport for HygieneAnnotation {
+    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
+}
+
+impl SessionCarrier for HygieneAnnotation {
+    fn sess<'a>(&'a self) -> &'a Session { &self.sess }
+}
+
+impl AstMapCarrier for HygieneAnnotation {
+    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
+        self.ast_map.as_ref()
+    }
+}
+
+impl pprust::PpAnn for HygieneAnnotation {
+    fn post(&self,
+            s: &mut pprust::State,
+            node: pprust::AnnNode) -> io::IoResult<()> {
+        match node {
+            pprust::NodeIdent(&ast::Ident { name: ast::Name(nm), ctxt }) => {
+                try!(pp::space(&mut s.s));
+                // FIXME #16420: this doesn't display the connections
+                // between syntax contexts
+                s.synth_comment(format!("{}#{}", nm, ctxt))
+            }
+            pprust::NodeName(&ast::Name(nm)) => {
+                try!(pp::space(&mut s.s));
+                s.synth_comment(nm.to_string())
+            }
+            _ => Ok(())
+        }
+    }
+}
+
+
+struct TypedAnnotation {
+    analysis: CrateAnalysis,
+}
+
+impl PrinterSupport for TypedAnnotation {
+    fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
+}
+
+impl SessionCarrier for TypedAnnotation {
+    fn sess<'a>(&'a self) -> &'a Session { &self.analysis.ty_cx.sess }
+}
+
+impl AstMapCarrier for TypedAnnotation {
+    fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map> {
+        Some(&self.analysis.ty_cx.map)
+    }
+}
+
+impl pprust::PpAnn for TypedAnnotation {
+    fn pre(&self,
+           s: &mut pprust::State,
+           node: pprust::AnnNode) -> io::IoResult<()> {
+        match node {
+            pprust::NodeExpr(_) => s.popen(),
+            _ => Ok(())
+        }
+    }
+    fn post(&self,
+            s: &mut pprust::State,
+            node: pprust::AnnNode) -> io::IoResult<()> {
+        let tcx = &self.analysis.ty_cx;
+        match node {
+            pprust::NodeExpr(expr) => {
+                try!(pp::space(&mut s.s));
+                try!(pp::word(&mut s.s, "as"));
+                try!(pp::space(&mut s.s));
+                try!(pp::word(&mut s.s,
+                              ppaux::ty_to_string(
+                                  tcx,
+                                  ty::expr_ty(tcx, expr)).as_slice()));
+                s.pclose()
+            }
+            _ => Ok(())
+        }
+    }
+}
+
+fn gather_flowgraph_variants(sess: &Session) -> Vec<borrowck_dot::Variant> {
+    let print_loans   = config::FLOWGRAPH_PRINT_LOANS;
+    let print_moves   = config::FLOWGRAPH_PRINT_MOVES;
+    let print_assigns = config::FLOWGRAPH_PRINT_ASSIGNS;
+    let print_all     = config::FLOWGRAPH_PRINT_ALL;
+    let opt = |print_which| sess.debugging_opt(print_which);
+    let mut variants = Vec::new();
+    if opt(print_all) || opt(print_loans) {
+        variants.push(borrowck_dot::Loans);
+    }
+    if opt(print_all) || opt(print_moves) {
+        variants.push(borrowck_dot::Moves);
+    }
+    if opt(print_all) || opt(print_assigns) {
+        variants.push(borrowck_dot::Assigns);
+    }
+    variants
+}
+
+#[deriving(Clone, Show)]
+pub enum UserIdentifiedItem {
+    ItemViaNode(ast::NodeId),
+    ItemViaPath(Vec<String>),
+}
+
+impl FromStr for UserIdentifiedItem {
+    fn from_str(s: &str) -> Option<UserIdentifiedItem> {
+        let extract_path_parts = || {
+            let v : Vec<_> = s.split_str("::")
+                .map(|x|x.to_string())
+                .collect();
+            Some(ItemViaPath(v))
+        };
+
+        from_str(s).map(ItemViaNode).or_else(extract_path_parts)
+    }
+}
+
+enum NodesMatchingUII<'a> {
+    NodesMatchingDirect(option::Item<ast::NodeId>),
+    NodesMatchingSuffix(ast_map::NodesMatchingSuffix<'a, String>),
+}
+
+impl<'a> Iterator<ast::NodeId> for NodesMatchingUII<'a> {
+    fn next(&mut self) -> Option<ast::NodeId> {
+        match self {
+            &NodesMatchingDirect(ref mut iter) => iter.next(),
+            &NodesMatchingSuffix(ref mut iter) => iter.next(),
+        }
+    }
+}
+
+impl UserIdentifiedItem {
+    fn reconstructed_input(&self) -> String {
+        match *self {
+            ItemViaNode(node_id) => node_id.to_string(),
+            ItemViaPath(ref parts) => parts.connect("::"),
+        }
+    }
+
+    fn all_matching_node_ids<'a>(&'a self, map: &'a ast_map::Map) -> NodesMatchingUII<'a> {
+        match *self {
+            ItemViaNode(node_id) =>
+                NodesMatchingDirect(Some(node_id).move_iter()),
+            ItemViaPath(ref parts) =>
+                NodesMatchingSuffix(map.nodes_matching_suffix(parts.as_slice())),
+        }
+    }
+
+    fn to_one_node_id(self, user_option: &str, sess: &Session, map: &ast_map::Map) -> ast::NodeId {
+        let fail_because = |is_wrong_because| -> ast::NodeId {
+            let message =
+                format!("{:s} needs NodeId (int) or unique \
+                         path suffix (b::c::d); got {:s}, which {:s}",
+                        user_option,
+                        self.reconstructed_input(),
+                        is_wrong_because);
+            sess.fatal(message.as_slice())
+        };
+
+        let mut saw_node = ast::DUMMY_NODE_ID;
+        let mut seen = 0u;
+        for node in self.all_matching_node_ids(map) {
+            saw_node = node;
+            seen += 1;
+            if seen > 1 {
+                fail_because("does not resolve uniquely");
+            }
+        }
+        if seen == 0 {
+            fail_because("does not resolve to any item");
+        }
+
+        assert!(seen == 1);
+        return saw_node;
+    }
+}
+
+fn needs_ast_map(ppm: &PpMode, opt_uii: &Option<UserIdentifiedItem>) -> bool {
+    match *ppm {
+        PpmSource(PpmNormal) |
+        PpmSource(PpmIdentified) => opt_uii.is_some(),
+
+        PpmSource(PpmExpanded) |
+        PpmSource(PpmExpandedIdentified) |
+        PpmSource(PpmExpandedHygiene) |
+        PpmSource(PpmTyped) |
+        PpmFlowGraph => true
+    }
+}
+
+fn needs_expansion(ppm: &PpMode) -> bool {
+    match *ppm {
+        PpmSource(PpmNormal) |
+        PpmSource(PpmIdentified) => false,
+
+        PpmSource(PpmExpanded) |
+        PpmSource(PpmExpandedIdentified) |
+        PpmSource(PpmExpandedHygiene) |
+        PpmSource(PpmTyped) |
+        PpmFlowGraph => true
+    }
+}
+
+pub fn pretty_print_input(sess: Session,
+                          cfg: ast::CrateConfig,
+                          input: &driver::Input,
+                          ppm: PpMode,
+                          opt_uii: Option<UserIdentifiedItem>,
+                          ofile: Option<Path>) {
+    let krate = driver::phase_1_parse_input(&sess, cfg, input);
+    let id = link::find_crate_name(Some(&sess), krate.attrs.as_slice(), input);
+
+    let is_expanded = needs_expansion(&ppm);
+    let (krate, ast_map) = if needs_ast_map(&ppm, &opt_uii) {
+        let k = driver::phase_2_configure_and_expand(&sess, krate, id.as_slice(), None);
+        let (krate, ast_map) = match k {
+            None => return,
+            Some(p) => p,
+        };
+        (krate, Some(ast_map))
+    } else {
+        (krate, None)
+    };
+
+    let src_name = driver::source_name(input);
+    let src = Vec::from_slice(sess.codemap()
+                                  .get_filemap(src_name.as_slice())
+                                  .src
+                                  .as_bytes());
+    let mut rdr = MemReader::new(src);
+
+    let out = match ofile {
+        None => box io::stdout() as Box<Writer+'static>,
+        Some(p) => {
+            let r = io::File::create(&p);
+            match r {
+                Ok(w) => box w as Box<Writer+'static>,
+                Err(e) => fail!("print-print failed to open {} due to {}",
+                                p.display(), e),
+            }
+        }
+    };
+
+    match (ppm, opt_uii) {
+        (PpmSource(s), None) =>
+            s.call_with_pp_support(
+                sess, &krate, ast_map, id, out, |annotation, out| {
+                    debug!("pretty printing source code {}", s);
+                    let sess = annotation.sess();
+                    pprust::print_crate(sess.codemap(),
+                                        sess.diagnostic(),
+                                        &krate,
+                                        src_name.to_string(),
+                                        &mut rdr,
+                                        out,
+                                        annotation.pp_ann(),
+                                        is_expanded)
+                }),
+
+        (PpmSource(s), Some(uii)) =>
+            s.call_with_pp_support(
+                sess, &krate, ast_map, id, (out,uii), |annotation, (out,uii)| {
+                    debug!("pretty printing source code {}", s);
+                    let sess = annotation.sess();
+                    let ast_map = annotation.ast_map()
+                        .expect("--pretty missing ast_map");
+                    let mut pp_state =
+                        pprust::State::new_from_input(sess.codemap(),
+                                                      sess.diagnostic(),
+                                                      src_name.to_string(),
+                                                      &mut rdr,
+                                                      out,
+                                                      annotation.pp_ann(),
+                                                      is_expanded);
+                    for node_id in uii.all_matching_node_ids(ast_map) {
+                        let node = ast_map.get(node_id);
+                        try!(pp_state.print_node(&node));
+                        try!(pp::space(&mut pp_state.s));
+                        try!(pp_state.synth_comment(ast_map.path_to_string(node_id)));
+                        try!(pp::hardbreak(&mut pp_state.s));
+                    }
+                    pp::eof(&mut pp_state.s)
+                }),
+
+        (PpmFlowGraph, opt_uii) => {
+            debug!("pretty printing flow graph for {}", opt_uii);
+            let uii = opt_uii.unwrap_or_else(|| {
+                sess.fatal(format!("`pretty flowgraph=..` needs NodeId (int) or
+                                     unique path suffix (b::c::d)").as_slice())
+
+            });
+            let ast_map = ast_map.expect("--pretty flowgraph missing ast_map");
+            let nodeid = uii.to_one_node_id("--pretty", &sess, &ast_map);
+
+            let node = ast_map.find(nodeid).unwrap_or_else(|| {
+                sess.fatal(format!("--pretty flowgraph couldn't find id: {}",
+                                   nodeid).as_slice())
+            });
+
+            let code = blocks::Code::from_node(node);
+            match code {
+                Some(code) => {
+                    let variants = gather_flowgraph_variants(&sess);
+                    let analysis = driver::phase_3_run_analysis_passes(sess, &krate,
+                                                                       ast_map, id);
+                    print_flowgraph(variants, analysis, code, out)
+                }
+                None => {
+                    let message = format!("--pretty=flowgraph needs \
+                                           block, fn, or method; got {:?}",
+                                          node);
+
+                    // point to what was found, if there's an
+                    // accessible span.
+                    match ast_map.opt_span(nodeid) {
+                        Some(sp) => sess.span_fatal(sp, message.as_slice()),
+                        None => sess.fatal(message.as_slice())
+                    }
+                }
+            }
+        }
+    }.unwrap()
+}
+
+fn print_flowgraph<W:io::Writer>(variants: Vec<borrowck_dot::Variant>,
+                                 analysis: CrateAnalysis,
+                                 code: blocks::Code,
+                                 mut out: W) -> io::IoResult<()> {
+    let ty_cx = &analysis.ty_cx;
+    let cfg = match code {
+        blocks::BlockCode(block) => cfg::CFG::new(ty_cx, &*block),
+        blocks::FnLikeCode(fn_like) => cfg::CFG::new(ty_cx, &*fn_like.body()),
+    };
+    debug!("cfg: {:?}", cfg);
+
+    match code {
+        _ if variants.len() == 0 => {
+            let lcfg = LabelledCFG {
+                ast_map: &ty_cx.map,
+                cfg: &cfg,
+                name: format!("node_{}", code.id()),
+            };
+            let r = dot::render(&lcfg, &mut out);
+            return expand_err_details(r);
+        }
+        blocks::BlockCode(_) => {
+            ty_cx.sess.err("--pretty flowgraph with -Z flowgraph-print \
+                            annotations requires fn-like node id.");
+            return Ok(())
+        }
+        blocks::FnLikeCode(fn_like) => {
+            let fn_parts = FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
+            let (bccx, analysis_data) =
+                borrowck::build_borrowck_dataflow_data_for_fn(ty_cx, fn_parts);
+
+            let lcfg = LabelledCFG {
+                ast_map: &ty_cx.map,
+                cfg: &cfg,
+                name: format!("node_{}", code.id()),
+            };
+            let lcfg = borrowck_dot::DataflowLabeller {
+                inner: lcfg,
+                variants: variants,
+                borrowck_ctxt: &bccx,
+                analysis_data: &analysis_data,
+            };
+            let r = dot::render(&lcfg, &mut out);
+            return expand_err_details(r);
+        }
+    }
+
+    fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
+        r.map_err(|ioerr| {
+            let orig_detail = ioerr.detail.clone();
+            let m = "graphviz::render failed";
+            io::IoError {
+                detail: Some(match orig_detail {
+                    None => m.into_string(),
+                    Some(d) => format!("{}: {}", m, d)
+                }),
+                ..ioerr
+            }
+        })
+    }
+}
diff --git a/src/librustc/middle/dataflow.rs b/src/librustc/middle/dataflow.rs
index 91c227cd5bc..876789f99bc 100644
--- a/src/librustc/middle/dataflow.rs
+++ b/src/librustc/middle/dataflow.rs
@@ -111,6 +111,7 @@ impl<'a, O:DataFlowOperator> pprust::PpAnn for DataFlowContext<'a, O> {
            ps: &mut pprust::State,
            node: pprust::AnnNode) -> io::IoResult<()> {
         let id = match node {
+            pprust::NodeIdent(_) | pprust::NodeName(_) => 0,
             pprust::NodeExpr(expr) => expr.id,
             pprust::NodeBlock(blk) => blk.id,
             pprust::NodeItem(_) => 0,
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 6b94048eab7..afea9e01de9 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -33,6 +33,8 @@ use std::io;
 use std::mem;
 
 pub enum AnnNode<'a> {
+    NodeIdent(&'a ast::Ident),
+    NodeName(&'a ast::Name),
     NodeBlock(&'a ast::Block),
     NodeItem(&'a ast::Item),
     NodeExpr(&'a ast::Expr),
@@ -1729,14 +1731,16 @@ impl<'a> State<'a> {
     pub fn print_ident(&mut self, ident: ast::Ident) -> IoResult<()> {
         if self.encode_idents_with_hygiene {
             let encoded = ident.encode_with_hygiene();
-            word(&mut self.s, encoded.as_slice())
+            try!(word(&mut self.s, encoded.as_slice()))
         } else {
-            word(&mut self.s, token::get_ident(ident).get())
+            try!(word(&mut self.s, token::get_ident(ident).get()))
         }
+        self.ann.post(self, NodeIdent(&ident))
     }
 
     pub fn print_name(&mut self, name: ast::Name) -> IoResult<()> {
-        word(&mut self.s, token::get_name(name).get())
+        try!(word(&mut self.s, token::get_name(name).get()));
+        self.ann.post(self, NodeName(&name))
     }
 
     pub fn print_for_decl(&mut self, loc: &ast::Local,
diff --git a/src/test/run-make/pretty-expanded-hygiene/Makefile b/src/test/run-make/pretty-expanded-hygiene/Makefile
new file mode 100644
index 00000000000..11763446437
--- /dev/null
+++ b/src/test/run-make/pretty-expanded-hygiene/Makefile
@@ -0,0 +1,20 @@
+-include ../tools.mk
+
+REPLACEMENT := s/[0-9][0-9]*\#[0-9][0-9]*/$(shell date)/g
+
+all:
+	$(RUSTC) -o $(TMPDIR)/input.out --pretty expanded,hygiene input.rs
+
+	# the name/ctxt numbers are very internals-dependent and thus
+	# change relatively frequently, and testing for their exact values
+	# them will fail annoyingly, so we just check their positions
+	# (using a non-constant replacement like this will make it less
+	# likely the compiler matches whatever other dummy value we
+	# choose).
+	#
+	# (These need to be out-of-place because OSX/BSD & GNU sed
+	# differ.)
+	sed "$(REPLACEMENT)" input.pp.rs > $(TMPDIR)/input.pp.rs
+	sed "$(REPLACEMENT)" $(TMPDIR)/input.out > $(TMPDIR)/input.out.replaced
+
+	diff -u $(TMPDIR)/input.out.replaced $(TMPDIR)/input.pp.rs
diff --git a/src/test/run-make/pretty-expanded-hygiene/input.pp.rs b/src/test/run-make/pretty-expanded-hygiene/input.pp.rs
new file mode 100755
index 00000000000..bf60784ab58
--- /dev/null
+++ b/src/test/run-make/pretty-expanded-hygiene/input.pp.rs
@@ -0,0 +1,16 @@
+// 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.
+
+#![feature(macro_rules)]
+// minimal junk
+#![no_std]
+
+
+fn bar /* 62#0 */() { let x /* 59#2 */ = 1; y /* 61#4 */ + x /* 59#5 */ }
diff --git a/src/test/run-make/pretty-expanded-hygiene/input.rs b/src/test/run-make/pretty-expanded-hygiene/input.rs
new file mode 100755
index 00000000000..c9d603c2e1c
--- /dev/null
+++ b/src/test/run-make/pretty-expanded-hygiene/input.rs
@@ -0,0 +1,22 @@
+// 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.
+
+#![feature(macro_rules)]
+// minimal junk
+#![no_std]
+
+macro_rules! foo {
+    ($x: ident) => { y + $x }
+}
+
+fn bar() {
+    let x = 1;
+    foo!(x)
+}