about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-06-13 09:57:06 +0000
committerbors <bors@rust-lang.org>2014-06-13 09:57:06 +0000
commitc119903f621a11643d5f299423a2c72eefffec4c (patch)
treeff02659ad639ce78a447ef2ce6cea7c9c29b82e8
parent5d9bceb1440b6bb5759bc3c1e5ad2170a199d0ce (diff)
parent984e9afae5098192a789dc39d44cec8225067896 (diff)
downloadrust-c119903f621a11643d5f299423a2c72eefffec4c.tar.gz
rust-c119903f621a11643d5f299423a2c72eefffec4c.zip
auto merge of #13222 : nick29581/rust/dxr4, r=brson
Adds a -Z flag `save-analysis` which runs after the analysis phase of the compiler and saves a bunch of info into a CSV file for the crate. This is designed to work with the DXR code browser, but is frontend-independent, that is this info should be useful for all kinds of code browsers, IDEs, or other tools.

I need to squash commits before landing (there will probably be a fair few to come), please ignore them for now and just comment on the changes.
-rw-r--r--src/librustc/driver/config.rs7
-rw-r--r--src/librustc/driver/driver.rs12
-rw-r--r--src/librustc/driver/session.rs1
-rw-r--r--src/librustc/lib.rs1
-rw-r--r--src/librustc/middle/save/mod.rs1439
-rw-r--r--src/librustc/middle/save/recorder.rs575
-rw-r--r--src/librustc/middle/save/span_utils.rs381
-rw-r--r--src/librustc/middle/ty.rs21
-rw-r--r--src/libsyntax/codemap.rs70
-rw-r--r--src/test/run-make/save-analysis/Makefile3
-rw-r--r--src/test/run-make/save-analysis/foo.rs58
11 files changed, 2531 insertions, 37 deletions
diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs
index 186db839e33..3326b4e8304 100644
--- a/src/librustc/driver/config.rs
+++ b/src/librustc/driver/config.rs
@@ -172,7 +172,8 @@ debugging_opts!(
         LTO,
         AST_JSON,
         AST_JSON_NOEXPAND,
-        LS
+        LS,
+        SAVE_ANALYSIS
     ]
     0
 )
@@ -206,7 +207,9 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
      ("lto", "Perform LLVM link-time optimizations", LTO),
      ("ast-json", "Print the AST as JSON and halt", AST_JSON),
      ("ast-json-noexpand", "Print the pre-expansion AST as JSON and halt", AST_JSON_NOEXPAND),
-     ("ls", "List the symbols defined by a library crate", LS))
+     ("ls", "List the symbols defined by a library crate", LS),
+     ("save-analysis", "Write syntax and type analysis information \
+                        in addition to normal output", SAVE_ANALYSIS))
 }
 
 /// Declare a macro that will define all CodegenOptions fields and parsers all
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index d703ece307f..880c1d6d510 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -87,6 +87,7 @@ pub fn compile_input(sess: Session,
         if stop_after_phase_2(&sess) { return; }
 
         let analysis = phase_3_run_analysis_passes(sess, &expanded_crate, ast_map);
+        phase_save_analysis(&analysis.ty_cx.sess, &expanded_crate, &analysis, outdir);
         if stop_after_phase_3(&analysis.ty_cx.sess) { return; }
         let (tcx, trans) = phase_4_translate_to_llvm(expanded_crate,
                                                      analysis, &outputs);
@@ -370,6 +371,17 @@ pub fn phase_3_run_analysis_passes(sess: Session,
     }
 }
 
+pub fn phase_save_analysis(sess: &Session,
+                           krate: &ast::Crate,
+                           analysis: &CrateAnalysis,
+                           odir: &Option<Path>) {
+    if (sess.opts.debugging_opts & config::SAVE_ANALYSIS) == 0 {
+        return;
+    }
+    time(sess.time_passes(), "save analysis", krate, |krate|
+         middle::save::process_crate(sess, krate, analysis, odir));
+}
+
 pub struct CrateTranslation {
     pub context: ContextRef,
     pub module: ModuleRef,
diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs
index 773b9e6e0aa..3efff5eac9e 100644
--- a/src/librustc/driver/session.rs
+++ b/src/librustc/driver/session.rs
@@ -28,6 +28,7 @@ use syntax::{ast, codemap};
 use std::os;
 use std::cell::{Cell, RefCell};
 
+
 pub struct Session {
     pub targ_cfg: config::Config,
     pub opts: config::Options,
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index 297d55edec8..f79aaa40d21 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -84,6 +84,7 @@ pub mod middle {
     pub mod expr_use_visitor;
     pub mod dependency_format;
     pub mod weak_lang_items;
+    pub mod save;
 }
 
 pub mod front {
diff --git a/src/librustc/middle/save/mod.rs b/src/librustc/middle/save/mod.rs
new file mode 100644
index 00000000000..2c73bc847a3
--- /dev/null
+++ b/src/librustc/middle/save/mod.rs
@@ -0,0 +1,1439 @@
+// Copyright 2012-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.
+
+//! Output a CSV file containing the output from rustc's analysis. The data is
+//! primarily designed to be used as input to the DXR tool, specifically its
+//! Rust plugin. It could also be used by IDEs or other code browsing, search, or
+//! cross-referencing tools.
+//!
+//! Dumping the analysis is implemented by walking the AST and getting a bunch of
+//! info out from all over the place. We use Def IDs to identify objects. The
+//! tricky part is getting syntactic (span, source text) and semantic (reference
+//! Def IDs) information for parts of expressions which the compiler has discarded.
+//! E.g., in a path `foo::bar::baz`, the compiler only keeps a span for the whole
+//! path and a reference to `baz`, but we want spans and references for all three
+//! idents.
+//!
+//! SpanUtils is used to manipulate spans. In particular, to extract sub-spans
+//! from spans (e.g., the span for `bar` from the above example path).
+//! Recorder is used for recording the output in csv format. FmtStrs separates
+//! the format of the output away from extracting it from the compiler.
+//! DxrVisitor walks the AST and processes it.
+
+use driver::driver::CrateAnalysis;
+use driver::session::Session;
+
+use middle::def;
+use middle::ty;
+use middle::typeck;
+
+use std::cell::Cell;
+use std::gc::Gc;
+use std::io;
+use std::io::File;
+use std::io::fs;
+use std::os;
+
+use syntax::ast;
+use syntax::ast_util;
+use syntax::ast::{NodeId,DefId};
+use syntax::ast_map::NodeItem;
+use syntax::attr;
+use syntax::codemap::*;
+use syntax::parse::token;
+use syntax::parse::token::{get_ident,keywords};
+use syntax::visit;
+use syntax::visit::Visitor;
+use syntax::print::pprust::{path_to_str,ty_to_str};
+
+use middle::save::span_utils::SpanUtils;
+use middle::save::recorder::Recorder;
+use middle::save::recorder::FmtStrs;
+
+use util::ppaux;
+
+mod span_utils;
+mod recorder;
+
+// Helper function to escape quotes in a string
+fn escape(s: String) -> String {
+    s.replace("\"", "\"\"")
+}
+
+// If the expression is a macro expansion or other generated code, run screaming and don't index.
+fn generated_code(span: Span) -> bool {
+    span.expn_info.is_some() || span  == DUMMY_SP
+}
+
+struct DxrVisitor<'l> {
+    sess: &'l Session,
+    analysis: &'l CrateAnalysis,
+
+    collected_paths: Vec<(NodeId, ast::Path, bool, recorder::Row)>,
+    collecting: bool,
+
+    span: SpanUtils<'l>,
+    fmt: FmtStrs<'l>,
+}
+
+impl <'l> DxrVisitor<'l> {
+    fn dump_crate_info(&mut self, name: &str, krate: &ast::Crate) {
+        // the current crate
+        self.fmt.crate_str(krate.span, name);
+
+        // dump info about all the external crates referenced from this crate
+        self.sess.cstore.iter_crate_data(|n, cmd| {
+            self.fmt.external_crate_str(krate.span, cmd.name.as_slice(), n);
+        });
+        self.fmt.recorder.record("end_external_crates\n");
+    }
+
+    // Return all non-empty prefixes of a path.
+    // For each prefix, we return the span for the last segment in the prefix and
+    // a str representation of the entire prefix.
+    fn process_path_prefixes(&self, path: &ast::Path) -> Vec<(Span, String)> {
+        let spans = self.span.spans_for_path_segments(path);
+
+        // Paths to enums seem to not match their spans - the span includes all the
+        // variants too. But they seem to always be at the end, so I hope we can cope with
+        // always using the first ones. So, only error out if we don't have enough spans.
+        // What could go wrong...?
+        if spans.len() < path.segments.len() {
+            error!("Mis-calculated spans for path '{}'. \
+                    Found {} spans, expected {}. Found spans:",
+                   path_to_str(path), spans.len(), path.segments.len());
+            for s in spans.iter() {
+                let loc = self.sess.codemap().lookup_char_pos(s.lo);
+                error!("    '{}' in {}, line {}",
+                       self.span.snippet(*s), loc.file.name, loc.line);
+            }
+            return vec!();
+        }
+
+        let mut result: Vec<(Span, String)> = vec!();
+
+
+        let mut segs = vec!();
+        for (seg, span) in path.segments.iter().zip(spans.iter()) {
+            segs.push(seg.clone());
+            let sub_path = ast::Path{span: *span, // span for the last segment
+                                     global: path.global,
+                                     segments: segs};
+            let qualname = path_to_str(&sub_path);
+            result.push((*span, qualname));
+            segs = sub_path.segments;
+        }
+
+        result
+    }
+
+    fn write_sub_paths(&mut self, path: &ast::Path, scope_id: NodeId) {
+        let sub_paths = self.process_path_prefixes(path);
+        for &(ref span, ref qualname) in sub_paths.iter() {
+            self.fmt.sub_mod_ref_str(path.span,
+                                     *span,
+                                     qualname.as_slice(),
+                                     scope_id);
+        }
+    }
+
+    // As write_sub_paths, but does not process the last ident in the path (assuming it
+    // will be processed elsewhere).
+    fn write_sub_paths_truncated(&mut self, path: &ast::Path, scope_id: NodeId) {
+        let sub_paths = self.process_path_prefixes(path);
+        let len = sub_paths.len();
+        if len <= 1 {
+            return;
+        }
+
+        let sub_paths = sub_paths.slice(0, len-1);
+        for &(ref span, ref qualname) in sub_paths.iter() {
+            self.fmt.sub_mod_ref_str(path.span,
+                                     *span,
+                                     qualname.as_slice(),
+                                     scope_id);
+        }
+    }
+
+    // As write_sub_paths, but expects a path of the form module_path::trait::method
+    // Where trait could actually be a struct too.
+    fn write_sub_path_trait_truncated(&mut self, path: &ast::Path, scope_id: NodeId) {
+        let sub_paths = self.process_path_prefixes(path);
+        let len = sub_paths.len();
+        if len <= 1 {
+            return;
+        }
+        let sub_paths = sub_paths.slice_to(len-1);
+
+        // write the trait part of the sub-path
+        let (ref span, ref qualname) = sub_paths[len-2];
+        self.fmt.sub_type_ref_str(path.span,
+                                  *span,
+                                  qualname.as_slice());
+
+        // write the other sub-paths
+        if len <= 2 {
+            return;
+        }
+        let sub_paths = sub_paths.slice(0, len-2);
+        for &(ref span, ref qualname) in sub_paths.iter() {
+            self.fmt.sub_mod_ref_str(path.span,
+                                     *span,
+                                     qualname.as_slice(),
+                                     scope_id);
+        }
+    }
+
+    // looks up anything, not just a type
+    fn lookup_type_ref(&self, ref_id: NodeId) -> Option<DefId> {
+        if !self.analysis.ty_cx.def_map.borrow().contains_key(&ref_id) {
+            self.sess.bug(format!("def_map has no key for {} in lookup_type_ref",
+                                  ref_id).as_slice());
+        }
+        let def = *self.analysis.ty_cx.def_map.borrow().get(&ref_id);
+        match def {
+            def::DefPrimTy(_) => None,
+            _ => Some(def.def_id()),
+        }
+    }
+
+    fn lookup_def_kind(&self, ref_id: NodeId, span: Span) -> Option<recorder::Row> {
+        let def_map = self.analysis.ty_cx.def_map.borrow();
+        if !def_map.contains_key(&ref_id) {
+            self.sess.span_bug(span, format!("def_map has no key for {} in lookup_def_kind",
+                                             ref_id).as_slice());
+        }
+        let def = *def_map.get(&ref_id);
+        match def {
+            def::DefMod(_) |
+            def::DefForeignMod(_) => Some(recorder::ModRef),
+            def::DefStruct(_) => Some(recorder::StructRef),
+            def::DefTy(_) |
+            def::DefTrait(_) => Some(recorder::TypeRef),
+            def::DefStatic(_, _) |
+            def::DefBinding(_, _) |
+            def::DefArg(_, _) |
+            def::DefLocal(_, _) |
+            def::DefVariant(_, _, _) |
+            def::DefUpvar(_, _, _, _) => Some(recorder::VarRef),
+
+            def::DefFn(_, _) => Some(recorder::FnRef),
+
+            def::DefSelfTy(_) |
+            def::DefRegion(_) |
+            def::DefTyParamBinder(_) |
+            def::DefLabel(_) |
+            def::DefStaticMethod(_, _, _) |
+            def::DefTyParam(_, _) |
+            def::DefUse(_) |
+            def::DefMethod(_, _) |
+            def::DefPrimTy(_) => {
+                self.sess.span_bug(span, format!("lookup_def_kind for unexpected item: {:?}",
+                                                 def).as_slice());
+            },
+        }
+    }
+
+    fn process_formals(&mut self, formals: &Vec<ast::Arg>, qualname: &str, e:DxrVisitorEnv) {
+        for arg in formals.iter() {
+            assert!(self.collected_paths.len() == 0 && !self.collecting);
+            self.collecting = true;
+            self.visit_pat(&*arg.pat, e);
+            self.collecting = false;
+            let span_utils = self.span;
+            for &(id, ref p, _, _) in self.collected_paths.iter() {
+                let typ = ppaux::ty_to_str(&self.analysis.ty_cx,
+                    *self.analysis.ty_cx.node_types.borrow().get(&(id as uint)));
+                // get the span only for the name of the variable (I hope the path is only ever a
+                // variable name, but who knows?)
+                self.fmt.formal_str(p.span,
+                                    span_utils.span_for_last_ident(p.span),
+                                    id,
+                                    qualname,
+                                    path_to_str(p).as_slice(),
+                                    typ.as_slice());
+            }
+            self.collected_paths.clear();
+        }
+    }
+
+    fn process_method(&mut self, method: &ast::Method, e:DxrVisitorEnv) {
+        if generated_code(method.span) {
+            return;
+        }
+
+        let mut scope_id;
+        // The qualname for a method is the trait name or name of the struct in an impl in
+        // which the method is declared in followed by the method's name.
+        let mut qualname = match ty::impl_of_method(&self.analysis.ty_cx,
+                                                ast_util::local_def(method.id)) {
+            Some(impl_id) => match self.analysis.ty_cx.map.get(impl_id.node) {
+                NodeItem(item) => {
+                    scope_id = item.id;
+                    match item.node {
+                        ast::ItemImpl(_, _, ty, _) => {
+                            let mut result = String::from_str("<");
+                            result.push_str(ty_to_str(&*ty).as_slice());
+
+                            match ty::trait_of_method(&self.analysis.ty_cx,
+                                                      ast_util::local_def(method.id)) {
+                                Some(def_id) => {
+                                    result.push_str(" as ");
+                                    result.push_str(
+                                        ty::item_path_str(&self.analysis.ty_cx, def_id).as_slice());
+                                },
+                                None => {}
+                            }
+                            result.append(">::")
+                        }
+                        _ => {
+                            self.sess.span_bug(method.span,
+                                               format!("Container {} for method {} not an impl?",
+                                                       impl_id.node, method.id).as_slice());
+                        },
+                    }
+                },
+                _ => {
+                    self.sess.span_bug(method.span,
+                                       format!("Container {} for method {} is not a node item {:?}",
+                                               impl_id.node,
+                                               method.id,
+                                               self.analysis.ty_cx.map.get(impl_id.node)
+                                              ).as_slice());
+                },
+            },
+            None => match ty::trait_of_method(&self.analysis.ty_cx,
+                                              ast_util::local_def(method.id)) {
+                Some(def_id) => {
+                    scope_id = def_id.node;
+                    match self.analysis.ty_cx.map.get(def_id.node) {
+                        NodeItem(_) => {
+                            let result = ty::item_path_str(&self.analysis.ty_cx, def_id);
+                            result.append("::")
+                        }
+                        _ => {
+                            self.sess.span_bug(method.span,
+                                               format!("Could not find container {} for method {}",
+                                                       def_id.node, method.id).as_slice());
+                        }
+                    }
+                },
+                None => {
+                    self.sess.span_bug(method.span,
+                                       format!("Could not find container for method {}",
+                                               method.id).as_slice());
+                },
+            },
+        };
+
+        qualname.push_str(get_ident(method.ident).get());
+        let qualname = qualname.as_slice();
+
+        // record the decl for this def (if it has one)
+        let decl_id = ty::trait_method_of_method(&self.analysis.ty_cx,
+                                                 ast_util::local_def(method.id))
+            .filtered(|def_id| method.id != 0 && def_id.node == 0);
+
+        let sub_span = self.span.sub_span_after_keyword(method.span, keywords::Fn);
+        self.fmt.method_str(method.span,
+                            sub_span,
+                            method.id,
+                            qualname,
+                            decl_id,
+                            scope_id);
+
+        self.process_formals(&method.decl.inputs, qualname, e);
+
+        // walk arg and return types
+        for arg in method.decl.inputs.iter() {
+            self.visit_ty(&*arg.ty, e);
+        }
+        self.visit_ty(&*method.decl.output, e);
+        // walk the fn body
+        self.visit_block(&*method.body, DxrVisitorEnv::new_nested(method.id));
+
+        self.process_generic_params(&method.generics,
+                                    method.span,
+                                    qualname,
+                                    method.id,
+                                    e);
+    }
+
+    fn process_trait_ref(&mut self,
+                         trait_ref: &ast::TraitRef,
+                         e: DxrVisitorEnv,
+                         impl_id: Option<NodeId>) {
+        match self.lookup_type_ref(trait_ref.ref_id) {
+            Some(id) => {
+                let sub_span = self.span.sub_span_for_type_name(trait_ref.path.span);
+                self.fmt.ref_str(recorder::TypeRef,
+                                 trait_ref.path.span,
+                                 sub_span,
+                                 id,
+                                 e.cur_scope);
+                match impl_id {
+                    Some(impl_id) => self.fmt.impl_str(trait_ref.path.span,
+                                                       sub_span,
+                                                       impl_id,
+                                                       id,
+                                                       e.cur_scope),
+                    None => (),
+                }
+                visit::walk_path(self, &trait_ref.path, e);
+            },
+            None => ()
+        }
+    }
+
+    fn process_struct_field_def(&mut self,
+                                field: &ast::StructField,
+                                qualname: &str,
+                                scope_id: NodeId) {
+        match field.node.kind {
+            ast::NamedField(ident, _) => {
+                let name = get_ident(ident);
+                let qualname = format!("{}::{}", qualname, name);
+                let typ = ppaux::ty_to_str(&self.analysis.ty_cx,
+                    *self.analysis.ty_cx.node_types.borrow().get(&(field.node.id as uint)));
+                match self.span.sub_span_before_token(field.span, token::COLON) {
+                    Some(sub_span) => self.fmt.field_str(field.span,
+                                                         Some(sub_span),
+                                                         field.node.id,
+                                                         name.get().as_slice(),
+                                                         qualname.as_slice(),
+                                                         typ.as_slice(),
+                                                         scope_id),
+                    None => self.sess.span_bug(field.span,
+                                               format!("Could not find sub-span for field {}",
+                                                       qualname).as_slice()),
+                }
+            },
+            _ => (),
+        }
+    }
+
+    // Dump generic params bindings, then visit_generics
+    fn process_generic_params(&mut self, generics:&ast::Generics,
+                              full_span: Span,
+                              prefix: &str,
+                              id: NodeId,
+                              e: DxrVisitorEnv) {
+        // We can't only use visit_generics since we don't have spans for param
+        // bindings, so we reparse the full_span to get those sub spans.
+        // However full span is the entire enum/fn/struct block, so we only want
+        // the first few to match the number of generics we're looking for.
+        let param_sub_spans = self.span.spans_for_ty_params(full_span,
+                                                           (generics.ty_params.len() as int));
+        for (param, param_ss) in generics.ty_params.iter().zip(param_sub_spans.iter()) {
+            // Append $id to name to make sure each one is unique
+            let name = format!("{}::{}${}",
+                               prefix,
+                               escape(self.span.snippet(*param_ss)),
+                               id);
+            self.fmt.typedef_str(full_span,
+                                 Some(*param_ss),
+                                 param.id,
+                                 name.as_slice(),
+                                 "");
+        }
+        self.visit_generics(generics, e);
+    }
+
+    fn process_fn(&mut self,
+                  item: &ast::Item,
+                  e: DxrVisitorEnv,
+                  decl: ast::P<ast::FnDecl>,
+                  ty_params: &ast::Generics,
+                  body: ast::P<ast::Block>) {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+
+        let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Fn);
+        self.fmt.fn_str(item.span,
+                        sub_span,
+                        item.id,
+                        qualname.as_slice(),
+                        e.cur_scope);
+
+        self.process_formals(&decl.inputs, qualname.as_slice(), e);
+
+        // walk arg and return types
+        for arg in decl.inputs.iter() {
+            self.visit_ty(&*arg.ty, e);
+        }
+        self.visit_ty(&*decl.output, e);
+
+        // walk the body
+        self.visit_block(&*body, DxrVisitorEnv::new_nested(item.id));
+
+        self.process_generic_params(ty_params, item.span, qualname.as_slice(), item.id, e);
+    }
+
+    fn process_static(&mut self,
+                      item: &ast::Item,
+                      e: DxrVisitorEnv,
+                      typ: ast::P<ast::Ty>,
+                      mt: ast::Mutability,
+                      expr: &ast::Expr)
+    {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+
+        // If the variable is immutable, save the initialising expresion.
+        let value = match mt {
+            ast::MutMutable => String::from_str("<mutable>"),
+            ast::MutImmutable => self.span.snippet(expr.span),
+        };
+
+        let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Static);
+        self.fmt.static_str(item.span,
+                            sub_span,
+                            item.id,
+                            get_ident(item.ident).get(),
+                            qualname.as_slice(),
+                            value.as_slice(),
+                            ty_to_str(&*typ).as_slice(),
+                            e.cur_scope);
+
+        // walk type and init value
+        self.visit_ty(&*typ, e);
+        self.visit_expr(expr, e);
+    }
+
+    fn process_struct(&mut self,
+                      item: &ast::Item,
+                      e: DxrVisitorEnv,
+                      def: &ast::StructDef,
+                      ty_params: &ast::Generics) {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+
+        let ctor_id = match def.ctor_id {
+            Some(node_id) => node_id,
+            None => -1,
+        };
+        let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Struct);
+        self.fmt.struct_str(item.span,
+                            sub_span,
+                            item.id,
+                            ctor_id,
+                            qualname.as_slice(),
+                            e.cur_scope);
+
+        // fields
+        for field in def.fields.iter() {
+            self.process_struct_field_def(field, qualname.as_slice(), item.id);
+            self.visit_ty(&*field.node.ty, e);
+        }
+
+        self.process_generic_params(ty_params, item.span, qualname.as_slice(), item.id, e);
+    }
+
+    fn process_enum(&mut self,
+                    item: &ast::Item,
+                    e: DxrVisitorEnv,
+                    enum_definition: &ast::EnumDef,
+                    ty_params: &ast::Generics) {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+        match self.span.sub_span_after_keyword(item.span, keywords::Enum) {
+            Some(sub_span) => self.fmt.enum_str(item.span,
+                                                Some(sub_span),
+                                                item.id,
+                                                qualname.as_slice(),
+                                                e.cur_scope),
+            None => self.sess.span_bug(item.span,
+                                       format!("Could not find subspan for enum {}",
+                                               qualname).as_slice()),
+        }
+        for variant in enum_definition.variants.iter() {
+            let name = get_ident(variant.node.name);
+            let name = name.get();
+            let qualname = qualname.clone().append("::").append(name);
+            let val = self.span.snippet(variant.span);
+            match variant.node.kind {
+                ast::TupleVariantKind(ref args) => {
+                    // first ident in span is the variant's name
+                    self.fmt.tuple_variant_str(variant.span,
+                                               self.span.span_for_first_ident(variant.span),
+                                               variant.node.id,
+                                               name,
+                                               qualname.as_slice(),
+                                               val.as_slice(),
+                                               item.id);
+                    for arg in args.iter() {
+                        self.visit_ty(&*arg.ty, e);
+                    }
+                }
+                ast::StructVariantKind(ref struct_def) => {
+                    let ctor_id = match struct_def.ctor_id {
+                        Some(node_id) => node_id,
+                        None => -1,
+                    };
+                    self.fmt.struct_variant_str(
+                        variant.span,
+                        self.span.span_for_first_ident(variant.span),
+                        variant.node.id,
+                        ctor_id,
+                        qualname.as_slice(),
+                        val.as_slice(),
+                        item.id);
+
+                    for field in struct_def.fields.iter() {
+                        self.process_struct_field_def(field, qualname.as_slice(), variant.node.id);
+                        self.visit_ty(&*field.node.ty, e);
+                    }
+                }
+            }
+        }
+
+        self.process_generic_params(ty_params, item.span, qualname.as_slice(), item.id, e);
+    }
+
+    fn process_impl(&mut self,
+                    item: &ast::Item,
+                    e: DxrVisitorEnv,
+                    type_parameters: &ast::Generics,
+                    trait_ref: &Option<ast::TraitRef>,
+                    typ: ast::P<ast::Ty>,
+                    methods: &Vec<Gc<ast::Method>>) {
+        match typ.node {
+            ast::TyPath(ref path, _, id) => {
+                match self.lookup_type_ref(id) {
+                    Some(id) => {
+                        let sub_span = self.span.sub_span_for_type_name(path.span);
+                        self.fmt.ref_str(recorder::TypeRef,
+                                         path.span,
+                                         sub_span,
+                                         id,
+                                         e.cur_scope);
+                        self.fmt.impl_str(path.span,
+                                          sub_span,
+                                          item.id,
+                                          id,
+                                          e.cur_scope);
+                    },
+                    None => ()
+                }
+            },
+            _ => self.visit_ty(&*typ, e),
+        }
+
+        match *trait_ref {
+            Some(ref trait_ref) => self.process_trait_ref(trait_ref, e, Some(item.id)),
+            None => (),
+        }
+
+        self.process_generic_params(type_parameters, item.span, "", item.id, e);
+        for method in methods.iter() {
+            visit::walk_method_helper(self, &**method, e)
+        }
+    }
+
+    fn process_trait(&mut self,
+                     item: &ast::Item,
+                     e: DxrVisitorEnv,
+                     generics: &ast::Generics,
+                     trait_refs: &Vec<ast::TraitRef>,
+                     methods: &Vec<ast::TraitMethod>) {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+
+        let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Trait);
+        self.fmt.trait_str(item.span,
+                           sub_span,
+                           item.id,
+                           qualname.as_slice(),
+                           e.cur_scope);
+
+        // super-traits
+        for trait_ref in trait_refs.iter() {
+            match self.lookup_type_ref(trait_ref.ref_id) {
+                Some(id) => {
+                    let sub_span = self.span.sub_span_for_type_name(trait_ref.path.span);
+                    self.fmt.ref_str(recorder::TypeRef,
+                                     trait_ref.path.span,
+                                     sub_span,
+                                     id,
+                                     e.cur_scope);
+                    self.fmt.inherit_str(trait_ref.path.span,
+                                         sub_span,
+                                         id,
+                                         item.id);
+                },
+                None => ()
+            }
+        }
+
+        // walk generics and methods
+        self.process_generic_params(generics, item.span, qualname.as_slice(), item.id, e);
+        for method in methods.iter() {
+            self.visit_trait_method(method, e)
+        }
+    }
+
+    fn process_mod(&mut self,
+                   item: &ast::Item,  // The module in question, represented as an item.
+                   e: DxrVisitorEnv,
+                   m: &ast::Mod) {
+        let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+
+        let cm = self.sess.codemap();
+        let filename = cm.span_to_filename(m.inner);
+
+        let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Mod);
+        self.fmt.mod_str(item.span,
+                         sub_span,
+                         item.id,
+                         qualname.as_slice(),
+                         e.cur_scope,
+                         filename.as_slice());
+
+        visit::walk_mod(self, m, DxrVisitorEnv::new_nested(item.id));
+    }
+
+    fn process_path(&mut self,
+                    ex: &ast::Expr,
+                    e: DxrVisitorEnv,
+                    path: &ast::Path) {
+        if generated_code(path.span) {
+            return
+        }
+
+        let def_map = self.analysis.ty_cx.def_map.borrow();
+        if !def_map.contains_key(&ex.id) {
+            self.sess.span_bug(ex.span,
+                               format!("def_map has no key for {} in visit_expr",
+                                       ex.id).as_slice());
+        }
+        let def = def_map.get(&ex.id);
+        let sub_span = self.span.span_for_last_ident(ex.span);
+        match *def {
+            def::DefLocal(id, _) |
+            def::DefArg(id, _) |
+            def::DefUpvar(id, _, _, _) |
+            def::DefBinding(id, _) => self.fmt.ref_str(recorder::VarRef,
+                                                       ex.span,
+                                                       sub_span,
+                                                       ast_util::local_def(id),
+                                                       e.cur_scope),
+            def::DefStatic(def_id,_) |
+            def::DefVariant(_, def_id, _) => self.fmt.ref_str(recorder::VarRef,
+                                                              ex.span,
+                                                              sub_span,
+                                                              def_id,
+                                                              e.cur_scope),
+            def::DefStruct(def_id) => self.fmt.ref_str(recorder::StructRef,
+                                                       ex.span,
+                                                       sub_span,
+                                                       def_id,
+                                                        e.cur_scope),
+            def::DefStaticMethod(declid, provenence, _) => {
+                let sub_span = self.span.sub_span_for_meth_name(ex.span);
+                let defid = if declid.krate == ast::LOCAL_CRATE {
+                    let m = ty::method(&self.analysis.ty_cx, declid);
+                    match provenence {
+                        def::FromTrait(def_id) =>
+                            Some(ty::trait_methods(&self.analysis.ty_cx, def_id)
+                                .iter().find(|mr| mr.ident.name == m.ident.name).unwrap().def_id),
+                        def::FromImpl(def_id) => {
+                            let impl_methods = self.analysis.ty_cx.impl_methods.borrow();
+                            Some(*impl_methods.get(&def_id)
+                                .iter().find(|mr|
+                                    ty::method(
+                                        &self.analysis.ty_cx, **mr).ident.name == m.ident.name)
+                                .unwrap())
+                        }
+                    }
+                } else {
+                    None
+                };
+                self.fmt.meth_call_str(ex.span,
+                                       sub_span,
+                                       defid,
+                                       Some(declid),
+                                       e.cur_scope);
+            },
+            def::DefFn(def_id, _) => self.fmt.fn_call_str(ex.span,
+                                                          sub_span,
+                                                          def_id,
+                                                          e.cur_scope),
+            _ => self.sess.span_bug(ex.span,
+                                    format!("Unexpected def kind while looking up path in '{}'",
+                                            self.span.snippet(ex.span)).as_slice()),
+        }
+        // modules or types in the path prefix
+        match *def {
+            def::DefStaticMethod(_, _, _) => {
+                self.write_sub_path_trait_truncated(path, e.cur_scope);
+            },
+            def::DefLocal(_, _) |
+            def::DefArg(_, _) |
+            def::DefStatic(_,_) |
+            def::DefStruct(_) |
+            def::DefFn(_, _) => self.write_sub_paths_truncated(path, e.cur_scope),
+            _ => {},
+        }
+
+        visit::walk_path(self, path, e);
+    }
+
+    fn process_struct_lit(&mut self,
+                          ex: &ast::Expr,
+                          e: DxrVisitorEnv,
+                          path: &ast::Path,
+                          fields: &Vec<ast::Field>,
+                          base: Option<Gc<ast::Expr>>) {
+        if generated_code(path.span) {
+            return
+        }
+
+        let mut struct_def: Option<DefId> = None;
+        match self.lookup_type_ref(ex.id) {
+            Some(id) => {
+                struct_def = Some(id);
+                let sub_span = self.span.span_for_last_ident(path.span);
+                self.fmt.ref_str(recorder::StructRef,
+                                 path.span,
+                                 sub_span,
+                                 id,
+                                 e.cur_scope);
+            },
+            None => ()
+        }
+
+        self.write_sub_paths_truncated(path, e.cur_scope);
+
+        for field in fields.iter() {
+            match struct_def {
+                Some(struct_def) => {
+                    let fields = ty::lookup_struct_fields(&self.analysis.ty_cx, struct_def);
+                    for f in fields.iter() {
+                        if generated_code(field.ident.span) {
+                            continue;
+                        }
+                        if f.name == field.ident.node.name {
+                            // We don't really need a sub-span here, but no harm done
+                            let sub_span = self.span.span_for_last_ident(field.ident.span);
+                            self.fmt.ref_str(recorder::VarRef,
+                                             field.ident.span,
+                                             sub_span,
+                                             f.id,
+                                             e.cur_scope);
+                        }
+                    }
+                }
+                None => {}
+            }
+
+            self.visit_expr(&*field.expr, e)
+        }
+        visit::walk_expr_opt(self, base, e)
+    }
+
+    fn process_method_call(&mut self,
+                           ex: &ast::Expr,
+                           e: DxrVisitorEnv,
+                           args: &Vec<Gc<ast::Expr>>) {
+        let method_map = self.analysis.ty_cx.method_map.borrow();
+        let method_callee = method_map.get(&typeck::MethodCall::expr(ex.id));
+        let (def_id, decl_id) = match method_callee.origin {
+            typeck::MethodStatic(def_id) => {
+                // method invoked on an object with a concrete type (not a static method)
+                let decl_id = ty::trait_method_of_method(&self.analysis.ty_cx, def_id);
+
+                // This incantation is required if the method referenced is a trait's
+                // defailt implementation.
+                let def_id = ty::method(&self.analysis.ty_cx, def_id).provided_source
+                                    .unwrap_or(def_id);
+                (Some(def_id), decl_id)
+            }
+            typeck::MethodParam(mp) => {
+                // method invoked on a type parameter
+                let method = ty::trait_method(&self.analysis.ty_cx,
+                                              mp.trait_id,
+                                              mp.method_num);
+                (None, Some(method.def_id))
+            },
+            typeck::MethodObject(mo) => {
+                // method invoked on a trait instance
+                let method = ty::trait_method(&self.analysis.ty_cx,
+                                              mo.trait_id,
+                                              mo.method_num);
+                (None, Some(method.def_id))
+            },
+        };
+        let sub_span = self.span.sub_span_for_meth_name(ex.span);
+        self.fmt.meth_call_str(ex.span,
+                               sub_span,
+                               def_id,
+                               decl_id,
+                               e.cur_scope);
+
+        // walk receiver and args
+        visit::walk_exprs(self, args.as_slice(), e);
+    }
+
+    fn process_pat(&mut self, p:&ast::Pat, e: DxrVisitorEnv) {
+        if generated_code(p.span) {
+            return
+        }
+
+        match p.node {
+            ast::PatStruct(ref path, ref fields, _) => {
+                self.collected_paths.push((p.id, path.clone(), false, recorder::StructRef));
+                visit::walk_path(self, path, e);
+                let struct_def = match self.lookup_type_ref(p.id) {
+                    Some(sd) => sd,
+                    None => {
+                        self.sess.span_bug(p.span,
+                                           format!("Could not find struct_def for `{}`",
+                                                   self.span.snippet(p.span)).as_slice());
+                    }
+                };
+                // The AST doesn't give us a span for the struct field, so we have
+                // to figure out where it is by assuming it's the token before each colon.
+                let field_spans = self.span.sub_spans_before_tokens(p.span,
+                                                                    token::COMMA,
+                                                                    token::COLON);
+                if fields.len() != field_spans.len() {
+                    self.sess.span_bug(p.span,
+                        format!("Mismatched field count in '{}', found {}, expected {}",
+                                self.span.snippet(p.span), field_spans.len(), fields.len()
+                               ).as_slice());
+                }
+                for (field, &span) in fields.iter().zip(field_spans.iter()) {
+                    self.visit_pat(&*field.pat, e);
+                    if span.is_none() {
+                        continue;
+                    }
+                    let fields = ty::lookup_struct_fields(&self.analysis.ty_cx, struct_def);
+                    for f in fields.iter() {
+                        if f.name == field.ident.name {
+                            self.fmt.ref_str(recorder::VarRef,
+                                             p.span,
+                                             span,
+                                             f.id,
+                                             e.cur_scope);
+                            break;
+                        }
+                    }
+                }
+            }
+            ast::PatEnum(ref path, _) => {
+                self.collected_paths.push((p.id, path.clone(), false, recorder::VarRef));
+                visit::walk_pat(self, p, e);
+            }
+            ast::PatIdent(bm, ref path, ref optional_subpattern) => {
+                let immut = match bm {
+                    // Even if the ref is mut, you can't change the ref, only
+                    // the data pointed at, so showing the initialising expression
+                    // is still worthwhile.
+                    ast::BindByRef(_) => true,
+                    ast::BindByValue(mt) => {
+                        match mt {
+                            ast::MutMutable => false,
+                            ast::MutImmutable => true,
+                        }
+                    }
+                };
+                // collect path for either visit_local or visit_arm
+                self.collected_paths.push((p.id, path.clone(), immut, recorder::VarRef));
+                match *optional_subpattern {
+                    None => {}
+                    Some(subpattern) => self.visit_pat(&*subpattern, e),
+                }
+            }
+            _ => visit::walk_pat(self, p, e)
+        }
+    }
+}
+
+impl<'l> Visitor<DxrVisitorEnv> for DxrVisitor<'l> {
+    fn visit_item(&mut self, item:&ast::Item, e: DxrVisitorEnv) {
+        if generated_code(item.span) {
+            return
+        }
+
+        match item.node {
+            ast::ItemFn(decl, _, _, ref ty_params, body) =>
+                self.process_fn(item, e, decl, ty_params, body),
+            ast::ItemStatic(typ, mt, expr) =>
+                self.process_static(item, e, typ, mt, &*expr),
+            ast::ItemStruct(def, ref ty_params) => self.process_struct(item, e, &*def, ty_params),
+            ast::ItemEnum(ref def, ref ty_params) => self.process_enum(item, e, def, ty_params),
+            ast::ItemImpl(ref ty_params, ref trait_ref, typ, ref methods) =>
+                self.process_impl(item, e, ty_params, trait_ref, typ, methods),
+            ast::ItemTrait(ref generics, _, ref trait_refs, ref methods) =>
+                self.process_trait(item, e, generics, trait_refs, methods),
+            ast::ItemMod(ref m) => self.process_mod(item, e, m),
+            ast::ItemTy(ty, ref ty_params) => {
+                let qualname = self.analysis.ty_cx.map.path_to_str(item.id);
+                let value = ty_to_str(&*ty);
+                let sub_span = self.span.sub_span_after_keyword(item.span, keywords::Type);
+                self.fmt.typedef_str(item.span,
+                                     sub_span,
+                                     item.id,
+                                     qualname.as_slice(),
+                                     value.as_slice());
+
+                self.visit_ty(&*ty, e);
+                self.process_generic_params(ty_params, item.span, qualname.as_slice(), item.id, e);
+            },
+            ast::ItemMac(_) => (),
+            _ => visit::walk_item(self, item, e),
+        }
+    }
+
+    fn visit_generics(&mut self, generics: &ast::Generics, e: DxrVisitorEnv) {
+        for param in generics.ty_params.iter() {
+            for bound in param.bounds.iter() {
+                match *bound {
+                    ast::TraitTyParamBound(ref trait_ref) => {
+                        self.process_trait_ref(trait_ref, e, None);
+                    }
+                    _ => {}
+                }
+            }
+            match param.default {
+                Some(ty) => self.visit_ty(&*ty, e),
+                None => (),
+            }
+        }
+    }
+
+    // We don't actually index functions here, that is done in visit_item/ItemFn.
+    // Here we just visit methods.
+    fn visit_fn(&mut self,
+                fk: &visit::FnKind,
+                fd: &ast::FnDecl,
+                b: &ast::Block,
+                s: Span,
+                _: NodeId,
+                e: DxrVisitorEnv) {
+        if generated_code(s) {
+            return;
+        }
+
+        match *fk {
+            visit::FkMethod(_, _, method) => self.process_method(method, e),
+            _ => visit::walk_fn(self, fk, fd, b, s, e),
+        }
+    }
+
+    fn visit_trait_method(&mut self, tm: &ast::TraitMethod, e: DxrVisitorEnv) {
+        match *tm {
+            ast::Required(ref method_type) => {
+                if generated_code(method_type.span) {
+                    return;
+                }
+
+                let mut scope_id ;
+                let mut qualname = match ty::trait_of_method(&self.analysis.ty_cx,
+                                                             ast_util::local_def(method_type.id)) {
+                    Some(def_id) => {
+                        scope_id = def_id.node;
+                        ty::item_path_str(&self.analysis.ty_cx, def_id).append("::")
+                    },
+                    None => {
+                        self.sess.span_bug(method_type.span,
+                                           format!("Could not find trait for method {}",
+                                                   method_type.id).as_slice());
+                    },
+                };
+
+                qualname.push_str(get_ident(method_type.ident).get());
+                let qualname = qualname.as_slice();
+
+                let sub_span = self.span.sub_span_after_keyword(method_type.span, keywords::Fn);
+                self.fmt.method_decl_str(method_type.span,
+                                         sub_span,
+                                         method_type.id,
+                                         qualname,
+                                         scope_id);
+
+                // walk arg and return types
+                for arg in method_type.decl.inputs.iter() {
+                    self.visit_ty(&*arg.ty, e);
+                }
+                self.visit_ty(&*method_type.decl.output, e);
+
+                self.process_generic_params(&method_type.generics,
+                                            method_type.span,
+                                            qualname,
+                                            method_type.id,
+                                            e);
+            }
+            ast::Provided(method) => self.process_method(&*method, e),
+        }
+    }
+
+    fn visit_view_item(&mut self, i:&ast::ViewItem, e:DxrVisitorEnv) {
+        if generated_code(i.span) {
+            return
+        }
+
+        match i.node {
+            ast::ViewItemUse(ref path) => {
+                match path.node {
+                    ast::ViewPathSimple(ident, ref path, id) => {
+                        let sub_span = self.span.span_for_last_ident(path.span);
+                        let mod_id = match self.lookup_type_ref(id) {
+                            Some(def_id) => {
+                                match self.lookup_def_kind(id, path.span) {
+                                    Some(kind) => self.fmt.ref_str(kind,
+                                                                   path.span,
+                                                                   sub_span,
+                                                                   def_id,
+                                                                   e.cur_scope),
+                                    None => {},
+                                }
+                                Some(def_id)
+                            },
+                            None => None,
+                        };
+
+                        // 'use' always introduces an alias, if there is not an explicit
+                        // one, there is an implicit one.
+                        let sub_span =
+                            match self.span.sub_span_before_token(path.span, token::EQ) {
+                                Some(sub_span) => Some(sub_span),
+                                None => sub_span,
+                            };
+
+                        self.fmt.use_alias_str(path.span,
+                                               sub_span,
+                                               id,
+                                               mod_id,
+                                               get_ident(ident).get(),
+                                               e.cur_scope);
+                        self.write_sub_paths_truncated(path, e.cur_scope);
+                    }
+                    ast::ViewPathGlob(ref path, _) => {
+                        self.write_sub_paths(path, e.cur_scope);
+                    }
+                    ast::ViewPathList(ref path, ref list, _) => {
+                        for plid in list.iter() {
+                            match self.lookup_type_ref(plid.node.id) {
+                                Some(id) => match self.lookup_def_kind(plid.node.id, plid.span) {
+                                    Some(kind) => self.fmt.ref_str(kind,
+                                                                   plid.span,
+                                                                   Some(plid.span),
+                                                                   id,
+                                                                   e.cur_scope),
+                                    None => (),
+                                },
+                                None => ()
+                            }
+                        }
+
+                        self.write_sub_paths(path, e.cur_scope);
+                    }
+                }
+            },
+            ast::ViewItemExternCrate(ident, ref s, id) => {
+                let name = get_ident(ident).get().to_owned();
+                let s = match *s {
+                    Some((ref s, _)) => s.get().to_owned(),
+                    None => name.to_owned(),
+                };
+                let sub_span = self.span.sub_span_after_keyword(i.span, keywords::Crate);
+                let cnum = match self.sess.cstore.find_extern_mod_stmt_cnum(id) {
+                    Some(cnum) => cnum,
+                    None => 0,
+                };
+                self.fmt.extern_crate_str(i.span,
+                                          sub_span,
+                                          id,
+                                          cnum,
+                                          name.as_slice(),
+                                          s.as_slice(),
+                                          e.cur_scope);
+            },
+        }
+    }
+
+    fn visit_ty(&mut self, t: &ast::Ty, e: DxrVisitorEnv) {
+        if generated_code(t.span) {
+            return
+        }
+
+        match t.node {
+            ast::TyPath(ref path, _, id) => {
+                match self.lookup_type_ref(id) {
+                    Some(id) => {
+                        let sub_span = self.span.sub_span_for_type_name(t.span);
+                        self.fmt.ref_str(recorder::TypeRef,
+                                         t.span,
+                                         sub_span,
+                                         id,
+                                         e.cur_scope);
+                    },
+                    None => ()
+                }
+
+                self.write_sub_paths_truncated(path, e.cur_scope);
+
+                visit::walk_path(self, path, e);
+            },
+            _ => visit::walk_ty(self, t, e),
+        }
+    }
+
+    fn visit_expr(&mut self, ex: &ast::Expr, e: DxrVisitorEnv) {
+        if generated_code(ex.span) {
+            return
+        }
+
+        match ex.node {
+            ast::ExprCall(_f, ref _args) => {
+                // Don't need to do anything for function calls,
+                // because just walking the callee path does what we want.
+                visit::walk_expr(self, ex, e);
+            },
+            ast::ExprPath(ref path) => self.process_path(ex, e, path),
+            ast::ExprStruct(ref path, ref fields, base) =>
+                self.process_struct_lit(ex, e, path, fields, base),
+            ast::ExprMethodCall(_, _, ref args) => self.process_method_call(ex, e, args),
+            ast::ExprField(sub_ex, ident, _) => {
+                if generated_code(sub_ex.span) {
+                    return
+                }
+
+                self.visit_expr(&*sub_ex, e);
+
+                let t = ty::expr_ty_adjusted(&self.analysis.ty_cx, &*sub_ex);
+                let t_box = ty::get(t);
+                match t_box.sty {
+                    ty::ty_struct(def_id, _) => {
+                        let fields = ty::lookup_struct_fields(&self.analysis.ty_cx, def_id);
+                        for f in fields.iter() {
+                            if f.name == ident.name {
+                                let sub_span = self.span.span_for_last_ident(ex.span);
+                                self.fmt.ref_str(recorder::VarRef,
+                                                 ex.span,
+                                                 sub_span,
+                                                 f.id,
+                                                 e.cur_scope);
+                                break;
+                            }
+                        }
+                    },
+                    _ => self.sess.span_bug(ex.span,
+                                            "Expected struct type, but not ty_struct"),
+                }
+            },
+            ast::ExprFnBlock(decl, body) => {
+                if generated_code(body.span) {
+                    return
+                }
+
+                let id = String::from_str("$").append(ex.id.to_str().as_slice());
+                self.process_formals(&decl.inputs, id.as_slice(), e);
+
+                // walk arg and return types
+                for arg in decl.inputs.iter() {
+                    self.visit_ty(&*arg.ty, e);
+                }
+                self.visit_ty(&*decl.output, e);
+
+                // walk the body
+                self.visit_block(&*body, DxrVisitorEnv::new_nested(ex.id));
+            },
+            _ => {
+                visit::walk_expr(self, ex, e)
+            },
+        }
+    }
+
+    fn visit_mac(&mut self, _: &ast::Mac, _: DxrVisitorEnv) {
+        // Just stop, macros are poison to us.
+    }
+
+    fn visit_pat(&mut self, p: &ast::Pat, e: DxrVisitorEnv) {
+        self.process_pat(p, e);
+        if !self.collecting {
+            self.collected_paths.clear();
+        }
+    }
+
+    fn visit_arm(&mut self, arm: &ast::Arm, e: DxrVisitorEnv) {
+        assert!(self.collected_paths.len() == 0 && !self.collecting);
+        self.collecting = true;
+
+        for pattern in arm.pats.iter() {
+            // collect paths from the arm's patterns
+            self.visit_pat(&**pattern, e);
+        }
+        self.collecting = false;
+        // process collected paths
+        for &(id, ref p, ref immut, ref_kind) in self.collected_paths.iter() {
+            let value = if *immut {
+                self.span.snippet(p.span).into_owned()
+            } else {
+                "<mutable>".to_owned()
+            };
+            let sub_span = self.span.span_for_first_ident(p.span);
+            let def_map = self.analysis.ty_cx.def_map.borrow();
+            if !def_map.contains_key(&id) {
+                self.sess.span_bug(p.span,
+                                   format!("def_map has no key for {} in visit_arm",
+                                           id).as_slice());
+            }
+            let def = def_map.get(&id);
+            match *def {
+                def::DefBinding(id, _)  => self.fmt.variable_str(p.span,
+                                                                 sub_span,
+                                                                 id,
+                                                                 path_to_str(p).as_slice(),
+                                                                 value.as_slice(),
+                                                                 ""),
+                def::DefVariant(_,id,_) => self.fmt.ref_str(ref_kind,
+                                                            p.span,
+                                                            sub_span,
+                                                            id,
+                                                            e.cur_scope),
+                // FIXME(nrc) what is this doing here?
+                def::DefStatic(_, _) => {}
+                _ => error!("unexpected defintion kind when processing collected paths: {:?}", *def)
+            }
+        }
+        self.collected_paths.clear();
+        visit::walk_expr_opt(self, arm.guard, e);
+        self.visit_expr(&*arm.body, e);
+    }
+
+    fn visit_stmt(&mut self, s:&ast::Stmt, e:DxrVisitorEnv) {
+        if generated_code(s.span) {
+            return
+        }
+
+        visit::walk_stmt(self, s, e)
+    }
+
+    fn visit_local(&mut self, l:&ast::Local, e: DxrVisitorEnv) {
+        if generated_code(l.span) {
+            return
+        }
+
+        // The local could declare multiple new vars, we must walk the
+        // pattern and collect them all.
+        assert!(self.collected_paths.len() == 0 && !self.collecting);
+        self.collecting = true;
+        self.visit_pat(&*l.pat, e);
+        self.collecting = false;
+
+        let value = self.span.snippet(l.span);
+
+        for &(id, ref p, ref immut, _) in self.collected_paths.iter() {
+            let value = if *immut { value.to_owned() } else { "<mutable>".to_owned() };
+            let types = self.analysis.ty_cx.node_types.borrow();
+            let typ = ppaux::ty_to_str(&self.analysis.ty_cx, *types.get(&(id as uint)));
+            // Get the span only for the name of the variable (I hope the path
+            // is only ever a variable name, but who knows?).
+            let sub_span = self.span.span_for_last_ident(p.span);
+            // Rust uses the id of the pattern for var lookups, so we'll use it too.
+            self.fmt.variable_str(p.span,
+                                  sub_span,
+                                  id,
+                                  path_to_str(p).as_slice(),
+                                  value.as_slice(),
+                                  typ.as_slice());
+        }
+        self.collected_paths.clear();
+
+        // Just walk the initialiser and type (don't want to walk the pattern again).
+        self.visit_ty(&*l.ty, e);
+        visit::walk_expr_opt(self, l.init, e);
+    }
+}
+
+#[deriving(Clone)]
+struct DxrVisitorEnv {
+    cur_scope: NodeId,
+}
+
+impl DxrVisitorEnv {
+    fn new() -> DxrVisitorEnv {
+        DxrVisitorEnv{cur_scope: 0}
+    }
+    fn new_nested(new_mod: NodeId) -> DxrVisitorEnv {
+        DxrVisitorEnv{cur_scope: new_mod}
+    }
+}
+
+pub fn process_crate(sess: &Session,
+                     krate: &ast::Crate,
+                     analysis: &CrateAnalysis,
+                     odir: &Option<Path>) {
+    if generated_code(krate.span) {
+        return;
+    }
+
+    let (cratename, crateid) = match attr::find_crateid(krate.attrs.as_slice()) {
+        Some(crateid) => (crateid.name.clone(), crateid.to_str()),
+        None => {
+            info!("Could not find crate name, using 'unknown_crate'");
+            (String::from_str("unknown_crate"),"unknown_crate".to_owned())
+        },
+    };
+
+    info!("Dumping crate {} ({})", cratename, crateid);
+
+    // find a path to dump our data to
+    let mut root_path = match os::getenv("DXR_RUST_TEMP_FOLDER") {
+        Some(val) => Path::new(val),
+        None => match *odir {
+            Some(ref val) => val.join("dxr"),
+            None => Path::new("dxr-temp"),
+        },
+    };
+
+    match fs::mkdir_recursive(&root_path, io::UserRWX) {
+        Err(e) => sess.err(format!("Could not create directory {}: {}",
+                           root_path.display(), e).as_slice()),
+        _ => (),
+    }
+
+    {
+        let disp = root_path.display();
+        info!("Writing output to {}", disp);
+    }
+
+    // Create ouput file.
+    let mut out_name = cratename.clone();
+    out_name.push_str(".csv");
+    root_path.push(out_name);
+    let output_file = match File::create(&root_path) {
+        Ok(f) => box f,
+        Err(e) => {
+            let disp = root_path.display();
+            sess.fatal(format!("Could not open {}: {}", disp, e).as_slice());
+        }
+    };
+    root_path.pop();
+
+    let mut visitor = DxrVisitor{ sess: sess,
+                                  analysis: analysis,
+                                  collected_paths: vec!(),
+                                  collecting: false,
+                                  fmt: FmtStrs::new(box Recorder {
+                                                        out: output_file as Box<Writer>,
+                                                        dump_spans: false,
+                                                    },
+                                                    SpanUtils {
+                                                        sess: sess,
+                                                        err_count: Cell::new(0)
+                                                    },
+                                                    cratename.clone()),
+                                  span: SpanUtils {
+                                      sess: sess,
+                                      err_count: Cell::new(0)
+                                  }};
+
+    visitor.dump_crate_info(cratename.as_slice(), krate);
+
+    visit::walk_crate(&mut visitor, krate, DxrVisitorEnv::new());
+}
diff --git a/src/librustc/middle/save/recorder.rs b/src/librustc/middle/save/recorder.rs
new file mode 100644
index 00000000000..428f97d0e53
--- /dev/null
+++ b/src/librustc/middle/save/recorder.rs
@@ -0,0 +1,575 @@
+// Copyright 2012-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.
+
+use middle::save::escape;
+use middle::save::span_utils::SpanUtils;
+
+use std::vec::Vec;
+
+use syntax::ast;
+use syntax::ast::{NodeId,DefId};
+use syntax::codemap::*;
+
+pub struct Recorder {
+    // output file
+    pub out: Box<Writer>,
+    pub dump_spans: bool,
+}
+
+impl Recorder {
+    pub fn record(&mut self, info: &str) {
+        match write!(self.out, "{}", info) {
+            Err(_) => error!("Error writing output '{}'", info),
+            _ => (),
+        }
+    }
+
+    pub fn dump_span(&mut self,
+                     su: SpanUtils,
+                     kind: &str,
+                     span: Span,
+                     _sub_span: Option<Span>) {
+        assert!(self.dump_spans);
+        let result = format!("span,kind,{},{},text,\"{}\"\n",
+                             kind, su.extent_str(span), escape(su.snippet(span)));
+        self.record(result.as_slice());
+    }
+}
+
+pub struct FmtStrs<'a> {
+    pub recorder: Box<Recorder>,
+    span: SpanUtils<'a>,
+    krate: String,
+}
+
+macro_rules! s { ($e:expr) => { format!("{}", $e) }}
+macro_rules! svec {
+    ($($e:expr),*) => ({
+        // leading _ to allow empty construction without a warning.
+        let mut _temp = ::std::vec::Vec::new();
+        $(_temp.push(s!($e));)*
+        _temp
+    })
+}
+
+pub enum Row {
+    Variable,
+    Enum,
+    Variant,
+    VariantStruct,
+    Function,
+    MethodDecl,
+    Struct,
+    Trait,
+    Impl,
+    Module,
+    UseAlias,
+    ExternCrate,
+    Inheritance,
+    MethodCall,
+    Typedef,
+    ExternalCrate,
+    Crate,
+    FnCall,
+    ModRef,
+    VarRef,
+    TypeRef,
+    StructRef,
+    FnRef,
+}
+
+impl<'a> FmtStrs<'a> {
+    pub fn new(rec: Box<Recorder>, span: SpanUtils<'a>, krate: String) -> FmtStrs<'a> {
+        FmtStrs {
+            recorder: rec,
+            span: span,
+            krate: krate,
+        }
+    }
+
+    // A map from kind of item to a tuple of
+    //   a string representation of the name
+    //   a vector of field names
+    //   whether this kind requires a span
+    //   whether dump_spans should dump for this kind
+    fn lookup_row(r: Row) -> (&'static str, Vec<&'static str>, bool, bool) {
+        match r {
+            Variable => ("variable",
+                         vec!("id","name","qualname","value","type","scopeid"),
+                         true, true),
+            Enum => ("enum", vec!("id","qualname","scopeid"), true, true),
+            Variant => ("variant", vec!("id","name","qualname","value","scopeid"), true, true),
+            VariantStruct => ("variant_struct",
+                              vec!("id","ctor_id","qualname","value","scopeid"), true, true),
+            Function => ("function", vec!("id","qualname","declid","declidcrate","scopeid"),
+                         true, true),
+            MethodDecl => ("method_decl", vec!("id","qualname","scopeid"), true, true),
+            Struct => ("struct", vec!("id","ctor_id","qualname","scopeid"), true, true),
+            Trait => ("trait", vec!("id","qualname","scopeid"), true, true),
+            Impl => ("impl", vec!("id","refid","refidcrate","scopeid"), true, true),
+            Module => ("module", vec!("id","qualname","scopeid","def_file"), true, false),
+            UseAlias => ("use_alias",
+                         vec!("id","refid","refidcrate","name","scopeid"),
+                         true, true),
+            ExternCrate => ("extern_crate",
+                            vec!("id","name","location","crate","scopeid"),
+                            true, true),
+            Inheritance => ("inheritance",
+                            vec!("base","basecrate","derived","derivedcrate"),
+                            true, false),
+            MethodCall => ("method_call",
+                           vec!("refid","refidcrate","declid","declidcrate","scopeid"),
+                            true, true),
+            Typedef => ("typedef", vec!("id","qualname","value"), true, true),
+            ExternalCrate => ("external_crate", vec!("name","crate","file_name"), false, false),
+            Crate => ("crate", vec!("name"), true, false),
+            FnCall => ("fn_call", vec!("refid","refidcrate","qualname","scopeid"), true, true),
+            ModRef => ("mod_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true),
+            VarRef => ("var_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true),
+            TypeRef => ("type_ref",
+                        vec!("refid","refidcrate","qualname","scopeid"),
+                        true, true),
+            StructRef => ("struct_ref",
+                          vec!("refid","refidcrate","qualname","scopeid"),
+                           true, true),
+            FnRef => ("fn_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true)
+        }
+    }
+
+    pub fn make_values_str(&self,
+                           kind: &'static str,
+                           fields: &Vec<&'static str>,
+                           values: Vec<String>,
+                           span: Span) -> Option<String> {
+        if values.len() != fields.len() {
+            self.span.sess.span_bug(span, format!(
+                "Mismatch between length of fields for '{}', expected '{}', found '{}'",
+                kind, fields.len(), values.len()).as_slice());
+        }
+
+        let values = values.iter().map(|s| {
+            if s.len() > 1020 {
+                s.as_slice().slice_to(1020)
+            } else {
+                s.as_slice()
+            }
+        });
+
+        let pairs = fields.iter().zip(values);
+        let mut strs = pairs.map(|(f, v)| format!(",{},\"{}\"", f, escape(
+            if *f == "qualname" {
+                self.krate.clone().append("::").append(v)
+            } else {
+                String::from_str(v)
+            }
+        )));
+        Some(strs.fold(String::new(), |s, ss| s.append(ss.as_slice()))).map(|s| s.into_owned())
+    }
+
+    pub fn record_without_span(&mut self,
+                               kind: Row,
+                               values: Vec<String>,
+                               span: Span) {
+        let (label, ref fields, needs_span, dump_spans) = FmtStrs::lookup_row(kind);
+
+        if needs_span {
+            self.span.sess.span_bug(span, format!(
+                "Called record_without_span for '{}' which does requires a span",
+                label).as_slice());
+        }
+        assert!(!dump_spans);
+
+        if self.recorder.dump_spans {
+            return;
+        }
+
+        let values_str = match self.make_values_str(label, fields, values, span) {
+            Some(vs) => vs,
+            None => return,
+        };
+
+        let result = String::from_str(label);
+        self.recorder.record(result.append(values_str.as_slice()).append("\n").as_slice());
+    }
+
+    pub fn record_with_span(&mut self,
+                            kind: Row,
+                            span: Span,
+                            sub_span: Span,
+                            values: Vec<String>) {
+        let (label, ref fields, needs_span, dump_spans) = FmtStrs::lookup_row(kind);
+
+        if self.recorder.dump_spans {
+            if dump_spans {
+                self.recorder.dump_span(self.span, label, span, Some(sub_span));
+            }
+            return;
+        }
+
+        if !needs_span {
+            self.span.sess.span_bug(span,
+                                    format!("Called record_with_span for '{}' \
+                                             which does not require a span", label).as_slice());
+        }
+
+        let values_str = match self.make_values_str(label, fields, values, span) {
+            Some(vs) => vs,
+            None => return,
+        };
+        let result = format!("{},{}{}\n", label, self.span.extent_str(sub_span), values_str);
+        self.recorder.record(result.as_slice());
+    }
+
+    pub fn check_and_record(&mut self,
+                            kind: Row,
+                            span: Span,
+                            sub_span: Option<Span>,
+                            values: Vec<String>) {
+        match sub_span {
+            Some(sub_span) => self.record_with_span(kind, span, sub_span, values),
+            None => {
+                let (label, _, _, _) = FmtStrs::lookup_row(kind);
+                self.span.report_span_err(label, span);
+            }
+        }
+    }
+
+    pub fn variable_str(&mut self,
+                        span: Span,
+                        sub_span: Option<Span>,
+                        id: NodeId,
+                        name: &str,
+                        value: &str,
+                        typ: &str) {
+        // Getting a fully qualified name for a variable is hard because in
+        // the local case they can be overridden in one block and there is no nice way
+        // to refer to such a scope in english, so we just hack it by appending the
+        // variable def's node id
+        let qualname = String::from_str(name).append("$").append(id.to_str().as_slice());
+        self.check_and_record(Variable,
+                              span,
+                              sub_span,
+                              svec!(id, name, qualname, value, typ, 0));
+    }
+
+    // formal parameters
+    pub fn formal_str(&mut self,
+                      span: Span,
+                      sub_span: Option<Span>,
+                      id: NodeId,
+                      fn_name: &str,
+                      name: &str,
+                      typ: &str) {
+        let qualname = String::from_str(fn_name).append("::").append(name);
+        self.check_and_record(Variable,
+                              span,
+                              sub_span,
+                              svec!(id, name, qualname, "", typ, 0));
+    }
+
+    // value is the initialising expression of the static if it is not mut, otherwise "".
+    pub fn static_str(&mut self,
+                      span: Span,
+                      sub_span: Option<Span>,
+                      id: NodeId,
+                      name: &str,
+                      qualname: &str,
+                      value: &str,
+                      typ: &str,
+                      scope_id: NodeId) {
+        self.check_and_record(Variable,
+                              span,
+                              sub_span,
+                              svec!(id, name, qualname, value, typ, scope_id));
+    }
+
+    pub fn field_str(&mut self,
+                     span: Span,
+                     sub_span: Option<Span>,
+                     id: NodeId,
+                     name: &str,
+                     qualname: &str,
+                     typ: &str,
+                     scope_id: NodeId) {
+        self.check_and_record(Variable,
+                              span,
+                              sub_span,
+                              svec!(id, name, qualname, "", typ, scope_id));
+    }
+
+    pub fn enum_str(&mut self,
+                    span: Span,
+                    sub_span: Option<Span>,
+                    id: NodeId,
+                    name: &str,
+                    scope_id: NodeId) {
+        self.check_and_record(Enum,
+                              span,
+                              sub_span,
+                              svec!(id, name, scope_id));
+    }
+
+    pub fn tuple_variant_str(&mut self,
+                             span: Span,
+                             sub_span: Option<Span>,
+                             id: NodeId,
+                             name: &str,
+                             qualname: &str,
+                             val: &str,
+                             scope_id: NodeId) {
+        self.check_and_record(Variant,
+                              span,
+                              sub_span,
+                              svec!(id, name, qualname, val, scope_id));
+    }
+
+    pub fn struct_variant_str(&mut self,
+                              span: Span,
+                              sub_span: Option<Span>,
+                              id: NodeId,
+                              ctor_id: NodeId,
+                              name: &str,
+                              val: &str,
+                              scope_id: NodeId) {
+        self.check_and_record(VariantStruct,
+                              span,
+                              sub_span,
+                              svec!(id, ctor_id, name, val, scope_id));
+    }
+
+    pub fn fn_str(&mut self,
+                  span: Span,
+                  sub_span: Option<Span>,
+                  id: NodeId,
+                  name: &str,
+                  scope_id: NodeId) {
+        self.check_and_record(Function,
+                              span,
+                              sub_span,
+                              svec!(id, name, "", "", scope_id));
+    }
+
+    pub fn method_str(&mut self,
+                      span: Span,
+                      sub_span: Option<Span>,
+                      id: NodeId,
+                      name: &str,
+                      decl_id: Option<DefId>,
+                      scope_id: NodeId) {
+        let values = match decl_id {
+            Some(decl_id) => svec!(id, name, decl_id.node, decl_id.krate, scope_id),
+            None => svec!(id, name, "", "", scope_id)
+        };
+        self.check_and_record(Function,
+                              span,
+                              sub_span,
+                              values);
+    }
+
+    pub fn method_decl_str(&mut self,
+                           span: Span,
+                           sub_span: Option<Span>,
+                           id: NodeId,
+                           name: &str,
+                           scope_id: NodeId) {
+        self.check_and_record(MethodDecl,
+                              span,
+                              sub_span,
+                              svec!(id, name, scope_id));
+    }
+
+    pub fn struct_str(&mut self,
+                      span: Span,
+                      sub_span: Option<Span>,
+                      id: NodeId,
+                      ctor_id: NodeId,
+                      name: &str,
+                      scope_id: NodeId) {
+        self.check_and_record(Struct,
+                              span,
+                              sub_span,
+                              svec!(id, ctor_id, name, scope_id));
+    }
+
+    pub fn trait_str(&mut self,
+                     span: Span,
+                     sub_span: Option<Span>,
+                     id: NodeId,
+                     name: &str,
+                     scope_id: NodeId) {
+        self.check_and_record(Trait,
+                              span,
+                              sub_span,
+                              svec!(id, name, scope_id));
+    }
+
+    pub fn impl_str(&mut self,
+                    span: Span,
+                    sub_span: Option<Span>,
+                    id: NodeId,
+                    ref_id: DefId,
+                    scope_id: NodeId) {
+        self.check_and_record(Impl,
+                              span,
+                              sub_span,
+                              svec!(id, ref_id.node, ref_id.krate, scope_id));
+    }
+
+    pub fn mod_str(&mut self,
+                   span: Span,
+                   sub_span: Option<Span>,
+                   id: NodeId,
+                   name: &str,
+                   parent: NodeId,
+                   filename: &str) {
+        self.check_and_record(Module,
+                              span,
+                              sub_span,
+                              svec!(id, name, parent, filename));
+    }
+
+    pub fn use_alias_str(&mut self,
+                         span: Span,
+                         sub_span: Option<Span>,
+                         id: NodeId,
+                         mod_id: Option<DefId>,
+                         name: &str,
+                         parent: NodeId) {
+        let (mod_node, mod_crate) = match mod_id {
+            Some(mod_id) => (mod_id.node, mod_id.krate),
+            None => (0, 0)
+        };
+        self.check_and_record(UseAlias,
+                              span,
+                              sub_span,
+                              svec!(id, mod_node, mod_crate, name, parent));
+    }
+
+    pub fn extern_crate_str(&mut self,
+                          span: Span,
+                          sub_span: Option<Span>,
+                          id: NodeId,
+                          cnum: ast::CrateNum,
+                          name: &str,
+                          loc: &str,
+                          parent: NodeId) {
+        self.check_and_record(ExternCrate,
+                              span,
+                              sub_span,
+                              svec!(id, name, loc, cnum, parent));
+    }
+
+    pub fn inherit_str(&mut self,
+                       span: Span,
+                       sub_span: Option<Span>,
+                       base_id: DefId,
+                       deriv_id: NodeId) {
+        self.check_and_record(Inheritance,
+                              span,
+                              sub_span,
+                              svec!(base_id.node, base_id.krate, deriv_id, 0));
+    }
+
+    pub fn fn_call_str(&mut self,
+                       span: Span,
+                       sub_span: Option<Span>,
+                       id: DefId,
+                       scope_id:NodeId) {
+        self.check_and_record(FnCall,
+                              span,
+                              sub_span,
+                              svec!(id.node, id.krate, "", scope_id));
+    }
+
+    pub fn meth_call_str(&mut self,
+                         span: Span,
+                         sub_span: Option<Span>,
+                         defid: Option<DefId>,
+                         declid: Option<DefId>,
+                         scope_id: NodeId) {
+        let (dfn, dfk) = match defid {
+            Some(defid) => (defid.node, defid.krate),
+            None => (0, 0)
+        };
+        let (dcn, dck) = match declid {
+            Some(declid) => (s!(declid.node), s!(declid.krate)),
+            None => ("".to_owned(), "".to_owned())
+        };
+        self.check_and_record(MethodCall,
+                              span,
+                              sub_span,
+                              svec!(dfn, dfk, dcn, dck, scope_id));
+    }
+
+    pub fn sub_mod_ref_str(&mut self,
+                           span: Span,
+                           sub_span: Span,
+                           qualname: &str,
+                           parent:NodeId) {
+        self.record_with_span(ModRef,
+                              span,
+                              sub_span,
+                              svec!(0, 0, qualname, parent));
+    }
+
+    pub fn typedef_str(&mut self,
+                       span: Span,
+                       sub_span: Option<Span>,
+                       id: NodeId,
+                       qualname: &str,
+                       value: &str) {
+        self.check_and_record(Typedef,
+                              span,
+                              sub_span,
+                              svec!(id, qualname, value));
+    }
+
+    pub fn crate_str(&mut self,
+                     span: Span,
+                     name: &str) {
+        self.record_with_span(Crate,
+                              span,
+                              span,
+                              svec!(name));
+    }
+
+    pub fn external_crate_str(&mut self,
+                              span: Span,
+                              name: &str,
+                              num: ast::CrateNum) {
+        let lo_loc = self.span.sess.codemap().lookup_char_pos(span.lo);
+        self.record_without_span(ExternalCrate,
+                                 svec!(name, num, lo_loc.file.name),
+                                 span);
+    }
+
+    pub fn sub_type_ref_str(&mut self,
+                            span: Span,
+                            sub_span: Span,
+                            qualname: &str) {
+        self.record_with_span(TypeRef,
+                              span,
+                              sub_span,
+                              svec!(0, 0, qualname, 0));
+    }
+
+    // A slightly generic function for a reference to an item of any kind.
+    pub fn ref_str(&mut self,
+                   kind: Row,
+                   span: Span,
+                   sub_span: Option<Span>,
+                   id: DefId,
+                   scope_id: NodeId) {
+        self.check_and_record(kind,
+                              span,
+                              sub_span,
+                              svec!(id.node, id.krate, "", scope_id));
+    }
+}
diff --git a/src/librustc/middle/save/span_utils.rs b/src/librustc/middle/save/span_utils.rs
new file mode 100644
index 00000000000..e646827fa23
--- /dev/null
+++ b/src/librustc/middle/save/span_utils.rs
@@ -0,0 +1,381 @@
+// Copyright 2012-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.
+
+use driver::session::Session;
+
+use middle::save::generated_code;
+
+use std::cell::Cell;
+
+use syntax::ast;
+use syntax::codemap::*;
+use syntax::parse::lexer;
+use syntax::parse::lexer::{Reader,StringReader};
+use syntax::parse::token;
+use syntax::parse::token::{is_keyword,keywords,is_ident,Token};
+
+pub struct SpanUtils<'a> {
+    pub sess: &'a Session,
+    pub err_count: Cell<int>,
+}
+
+impl<'a> SpanUtils<'a> {
+    // Standard string for extents/location.
+    pub fn extent_str(&self, span: Span) -> String {
+        let lo_loc = self.sess.codemap().lookup_char_pos(span.lo);
+        let hi_loc = self.sess.codemap().lookup_char_pos(span.hi);
+        let lo_pos = self.sess.codemap().lookup_byte_offset(span.lo).pos;
+        let hi_pos = self.sess.codemap().lookup_byte_offset(span.hi).pos;
+
+        format!("file_name,{},file_line,{},file_col,{},extent_start,{},\
+                 file_line_end,{},file_col_end,{},extent_end,{}",
+                lo_loc.file.name, lo_loc.line, lo_loc.col.to_uint(), lo_pos.to_uint(),
+                hi_loc.line, hi_loc.col.to_uint(), hi_pos.to_uint())
+    }
+
+    // sub_span starts at span.lo, so we need to adjust the positions etc.
+    // If sub_span is None, we don't need to adjust.
+    pub fn make_sub_span(&self, span: Span, sub_span: Option<Span>) -> Option<Span> {
+        let loc = self.sess.codemap().lookup_char_pos(span.lo);
+        assert!(!generated_code(span),
+                "generated code; we should not be processing this `{}` in {}, line {}",
+                 self.snippet(span), loc.file.name, loc.line);
+
+        match sub_span {
+            None => None,
+            Some(sub) => {
+                let FileMapAndBytePos {fm, pos} =
+                    self.sess.codemap().lookup_byte_offset(span.lo);
+                let base = pos + fm.start_pos;
+                Some(Span {
+                    lo: base + self.sess.codemap().lookup_byte_offset(sub.lo).pos,
+                    hi: base + self.sess.codemap().lookup_byte_offset(sub.hi).pos,
+                    expn_info: None,
+                })
+            }
+        }
+    }
+
+    pub fn snippet(&self, span: Span) -> String {
+        match self.sess.codemap().span_to_snippet(span) {
+            Some(s) => s,
+            None => String::new(),
+        }
+    }
+
+    pub fn retokenise_span(&self, span: Span) -> StringReader<'a> {
+        // sadness - we don't have spans for sub-expressions nor access to the tokens
+        // so in order to get extents for the function name itself (which dxr expects)
+        // we need to re-tokenise the fn definition
+
+        // Note: this is a bit awful - it adds the contents of span to the end of
+        // the codemap as a new filemap. This is mostly OK, but means we should
+        // not iterate over the codemap. Also, any spans over the new filemap
+        // are incompatible with spans over other filemaps.
+        let filemap = self.sess.codemap().new_filemap(String::from_str("<anon-dxr>"),
+                                                      self.snippet(span));
+        let s = self.sess;
+        lexer::StringReader::new(s.diagnostic(), filemap)
+    }
+
+    // Re-parses a path and returns the span for the last identifier in the path
+    pub fn span_for_last_ident(&self, span: Span) -> Option<Span> {
+        let mut result = None;
+
+        let mut toks = self.retokenise_span(span);
+        let mut bracket_count = 0;
+        loop {
+            let ts = toks.next_token();
+            if ts.tok == token::EOF {
+                return self.make_sub_span(span, result)
+            }
+            if bracket_count == 0 &&
+               (is_ident(&ts.tok) || is_keyword(keywords::Self, &ts.tok)) {
+                result = Some(ts.sp);
+            }
+
+            bracket_count += match ts.tok {
+                token::LT => 1,
+                token::GT => -1,
+                token::BINOP(token::SHR) => -2,
+                _ => 0
+            }
+        }
+    }
+
+    // Return the span for the first identifier in the path.
+    pub fn span_for_first_ident(&self, span: Span) -> Option<Span> {
+        let mut toks = self.retokenise_span(span);
+        let mut bracket_count = 0;
+        loop {
+            let ts = toks.next_token();
+            if ts.tok == token::EOF {
+                return None;
+            }
+            if bracket_count == 0 &&
+               (is_ident(&ts.tok) || is_keyword(keywords::Self, &ts.tok)) {
+                return self.make_sub_span(span, Some(ts.sp));
+            }
+
+            bracket_count += match ts.tok {
+                token::LT => 1,
+                token::GT => -1,
+                token::BINOP(token::SHR) => -2,
+                _ => 0
+            }
+        }
+    }
+
+    // Return the span for the last ident before a `(` or `<` or '::<' and outside any
+    // any brackets, or the last span.
+    pub fn sub_span_for_meth_name(&self, span: Span) -> Option<Span> {
+        let mut toks = self.retokenise_span(span);
+        let mut prev = toks.next_token();
+        let mut result = None;
+        let mut bracket_count = 0;
+        let mut last_span = None;
+        while prev.tok != token::EOF {
+            last_span = None;
+            let mut next = toks.next_token();
+
+            if (next.tok == token::LPAREN ||
+                next.tok == token::LT) &&
+               bracket_count == 0 &&
+               is_ident(&prev.tok) {
+                result = Some(prev.sp);
+            }
+
+            if bracket_count == 0 &&
+                next.tok == token::MOD_SEP {
+                let old = prev;
+                prev = next;
+                next = toks.next_token();
+                if next.tok == token::LT &&
+                   is_ident(&old.tok) {
+                    result = Some(old.sp);
+                }
+            }
+
+            bracket_count += match prev.tok {
+                token::LPAREN | token::LT => 1,
+                token::RPAREN | token::GT => -1,
+                token::BINOP(token::SHR) => -2,
+                _ => 0
+            };
+
+            if is_ident(&prev.tok) && bracket_count == 0 {
+                last_span = Some(prev.sp);
+            }
+            prev = next;
+        }
+        if result.is_none() && last_span.is_some() {
+            return self.make_sub_span(span, last_span);
+        }
+        return self.make_sub_span(span, result);
+    }
+
+    // Return the span for the last ident before a `<` and outside any
+    // brackets, or the last span.
+    pub fn sub_span_for_type_name(&self, span: Span) -> Option<Span> {
+        let mut toks = self.retokenise_span(span);
+        let mut prev = toks.next_token();
+        let mut result = None;
+        let mut bracket_count = 0;
+        loop {
+            let next = toks.next_token();
+
+            if (next.tok == token::LT ||
+                next.tok == token::COLON) &&
+               bracket_count == 0 &&
+               is_ident(&prev.tok) {
+                result = Some(prev.sp);
+            }
+
+            bracket_count += match prev.tok {
+                token::LT => 1,
+                token::GT => -1,
+                token::BINOP(token::SHR) => -2,
+                _ => 0
+            };
+
+            if next.tok == token::EOF {
+                break;
+            }
+            prev = next;
+        }
+        if bracket_count != 0 {
+            let loc = self.sess.codemap().lookup_char_pos(span.lo);
+            self.sess.span_bug(span,
+                format!("Mis-counted brackets when breaking path? Parsing '{}' in {}, line {}",
+                        self.snippet(span), loc.file.name, loc.line).as_slice());
+        }
+        if result.is_none() && is_ident(&prev.tok) && bracket_count == 0 {
+            return self.make_sub_span(span, Some(prev.sp));
+        }
+        self.make_sub_span(span, result)
+    }
+
+    // Reparse span and return an owned vector of sub spans of the first limit
+    // identifier tokens in the given nesting level.
+    // example with Foo<Bar<T,V>, Bar<T,V>>
+    // Nesting = 0: all idents outside of brackets: ~[Foo]
+    // Nesting = 1: idents within one level of brackets: ~[Bar, Bar]
+    pub fn spans_with_brackets(&self, span: Span, nesting: int, limit: int) -> Vec<Span> {
+        let mut result: Vec<Span> = vec!();
+
+        let mut toks = self.retokenise_span(span);
+        // We keep track of how many brackets we're nested in
+        let mut bracket_count = 0;
+        loop {
+            let ts = toks.next_token();
+            if ts.tok == token::EOF {
+                if bracket_count != 0 {
+                    let loc = self.sess.codemap().lookup_char_pos(span.lo);
+                    self.sess.span_bug(span, format!(
+                        "Mis-counted brackets when breaking path? Parsing '{}' in {}, line {}",
+                         self.snippet(span), loc.file.name, loc.line).as_slice());
+                }
+                return result
+            }
+            if (result.len() as int) == limit {
+                return result;
+            }
+            bracket_count += match ts.tok {
+                token::LT => 1,
+                token::GT => -1,
+                token::BINOP(token::SHL) => 2,
+                token::BINOP(token::SHR) => -2,
+                _ => 0
+            };
+            if is_ident(&ts.tok) &&
+               bracket_count == nesting {
+                result.push(self.make_sub_span(span, Some(ts.sp)).unwrap());
+            }
+        }
+    }
+
+    pub fn sub_span_before_token(&self, span: Span, tok: Token) -> Option<Span> {
+        let mut toks = self.retokenise_span(span);
+        let mut prev = toks.next_token();
+        loop {
+            if prev.tok == token::EOF {
+                return None;
+            }
+            let next = toks.next_token();
+            if next.tok == tok {
+                return self.make_sub_span(span, Some(prev.sp));
+            }
+            prev = next;
+        }
+    }
+
+    // Return an owned vector of the subspans of the tokens that come before tok2
+    // which is before tok1. If there is no instance of tok2 before tok1, then that
+    // place in the result is None.
+    // Everything returned must be inside a set of (non-angle) brackets, but no
+    // more deeply nested than that.
+    pub fn sub_spans_before_tokens(&self,
+                               span: Span,
+                               tok1: Token,
+                               tok2: Token) -> Vec<Option<Span>> {
+        let mut sub_spans : Vec<Option<Span>> = vec!();
+        let mut toks = self.retokenise_span(span);
+        let mut prev = toks.next_token();
+        let mut next = toks.next_token();
+        let mut stored_val = false;
+        let mut found_val = false;
+        let mut bracket_count = 0;
+        while next.tok != token::EOF {
+            if bracket_count == 1 {
+                if next.tok == tok2 {
+                    sub_spans.push(self.make_sub_span(span, Some(prev.sp)));
+                    stored_val = true;
+                    found_val = false;
+                }
+                if next.tok == tok1 {
+                    if !stored_val {
+                        sub_spans.push(None);
+                    } else {
+                        stored_val = false;
+                    }
+                    found_val = false;
+                }
+                if !stored_val &&
+                   is_ident(&next.tok) {
+                    found_val = true;
+                }
+            }
+
+            bracket_count += match next.tok {
+                token::LPAREN | token::LBRACE => 1,
+                token::RPAREN | token::RBRACE => -1,
+                _ => 0
+            };
+
+            prev = next;
+            next = toks.next_token();
+        }
+        if found_val {
+            sub_spans.push(None);
+        }
+        return sub_spans;
+    }
+
+    pub fn sub_span_after_keyword(&self,
+                              span: Span,
+                              keyword: keywords::Keyword) -> Option<Span> {
+        let mut toks = self.retokenise_span(span);
+        loop {
+            let ts = toks.next_token();
+            if ts.tok == token::EOF {
+                return None;
+            }
+            if is_keyword(keyword, &ts.tok) {
+                let ts = toks.next_token();
+                if ts.tok == token::EOF {
+                    return None
+                } else {
+                    return self.make_sub_span(span, Some(ts.sp));
+                }
+            }
+        }
+    }
+
+    // Returns a list of the spans of idents in a patch.
+    // E.g., For foo::bar<x,t>::baz, we return [foo, bar, baz] (well, their spans)
+    pub fn spans_for_path_segments(&self, path: &ast::Path) -> Vec<Span> {
+        if generated_code(path.span) {
+            return vec!();
+        }
+
+        self.spans_with_brackets(path.span, 0, -1)
+    }
+
+    // Return an owned vector of the subspans of the param identifier
+    // tokens found in span.
+    pub fn spans_for_ty_params(&self, span: Span, number: int) -> Vec<Span> {
+        if generated_code(span) {
+            return vec!();
+        }
+        // Type params are nested within one level of brackets:
+        // i.e. we want ~[A, B] from Foo<A, B<T,U>>
+        self.spans_with_brackets(span, 1, number)
+    }
+
+    pub fn report_span_err(&self, kind: &str, span: Span) {
+        let loc = self.sess.codemap().lookup_char_pos(span.lo);
+        info!("({}) Could not find sub_span in `{}` in {}, line {}",
+              kind, self.snippet(span), loc.file.name, loc.line);
+        self.err_count.set(self.err_count.get()+1);
+        if self.err_count.get() > 1000 {
+            self.sess.bug("span errors reached 1000, giving up");
+        }
+    }
+}
diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs
index 99c337946ae..a04f198da36 100644
--- a/src/librustc/middle/ty.rs
+++ b/src/librustc/middle/ty.rs
@@ -4375,6 +4375,27 @@ pub fn trait_id_of_impl(tcx: &ctxt,
     }
 }
 
+/// If the given def ID describes a method belonging to an impl, return the
+/// ID of the impl that the method belongs to. Otherwise, return `None`.
+pub fn impl_of_method(tcx: &ctxt, def_id: ast::DefId)
+                       -> Option<ast::DefId> {
+    if def_id.krate != LOCAL_CRATE {
+        return match csearch::get_method(tcx, def_id).container {
+            TraitContainer(_) => None,
+            ImplContainer(def_id) => Some(def_id),
+        };
+    }
+    match tcx.methods.borrow().find_copy(&def_id) {
+        Some(method) => {
+            match method.container {
+                TraitContainer(_) => None,
+                ImplContainer(def_id) => Some(def_id),
+            }
+        }
+        None => None
+    }
+}
+
 /// If the given def ID describes a method belonging to a trait (either a
 /// default method or an implementation of a trait method), return the ID of
 /// the trait that the method belongs to. Otherwise, return `None`.
diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs
index d9e3e4e941d..c917198e7d4 100644
--- a/src/libsyntax/codemap.rs
+++ b/src/libsyntax/codemap.rs
@@ -421,6 +421,41 @@ impl CodeMap {
         fail!("asking for {} which we don't know about", filename);
     }
 
+    pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
+        let idx = self.lookup_filemap_idx(bpos);
+        let fm = self.files.borrow().get(idx).clone();
+        let offset = bpos - fm.start_pos;
+        FileMapAndBytePos {fm: fm, pos: offset}
+    }
+
+    // Converts an absolute BytePos to a CharPos relative to the filemap and above.
+    pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
+        debug!("codemap: converting {:?} to char pos", bpos);
+        let idx = self.lookup_filemap_idx(bpos);
+        let files = self.files.borrow();
+        let map = files.get(idx);
+
+        // The number of extra bytes due to multibyte chars in the FileMap
+        let mut total_extra_bytes = 0;
+
+        for mbc in map.multibyte_chars.borrow().iter() {
+            debug!("codemap: {:?}-byte char at {:?}", mbc.bytes, mbc.pos);
+            if mbc.pos < bpos {
+                // every character is at least one byte, so we only
+                // count the actual extra bytes.
+                total_extra_bytes += mbc.bytes - 1;
+                // We should never see a byte position in the middle of a
+                // character
+                assert!(bpos.to_uint() >= mbc.pos.to_uint() + mbc.bytes);
+            } else {
+                break;
+            }
+        }
+
+        assert!(map.start_pos.to_uint() + total_extra_bytes <= bpos.to_uint());
+        CharPos(bpos.to_uint() - map.start_pos.to_uint() - total_extra_bytes)
+    }
+
     fn lookup_filemap_idx(&self, pos: BytePos) -> uint {
         let files = self.files.borrow();
         let files = files;
@@ -491,41 +526,6 @@ impl CodeMap {
             col: chpos - linechpos
         }
     }
-
-    fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
-        let idx = self.lookup_filemap_idx(bpos);
-        let fm = self.files.borrow().get(idx).clone();
-        let offset = bpos - fm.start_pos;
-        FileMapAndBytePos {fm: fm, pos: offset}
-    }
-
-    // Converts an absolute BytePos to a CharPos relative to the filemap.
-    fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
-        debug!("codemap: converting {:?} to char pos", bpos);
-        let idx = self.lookup_filemap_idx(bpos);
-        let files = self.files.borrow();
-        let map = files.get(idx);
-
-        // The number of extra bytes due to multibyte chars in the FileMap
-        let mut total_extra_bytes = 0;
-
-        for mbc in map.multibyte_chars.borrow().iter() {
-            debug!("codemap: {:?}-byte char at {:?}", mbc.bytes, mbc.pos);
-            if mbc.pos < bpos {
-                // every character is at least one byte, so we only
-                // count the actual extra bytes.
-                total_extra_bytes += mbc.bytes - 1;
-                // We should never see a byte position in the middle of a
-                // character
-                assert!(bpos.to_uint() >= mbc.pos.to_uint() + mbc.bytes);
-            } else {
-                break;
-            }
-        }
-
-        assert!(map.start_pos.to_uint() + total_extra_bytes <= bpos.to_uint());
-        CharPos(bpos.to_uint() - map.start_pos.to_uint() - total_extra_bytes)
-    }
 }
 
 #[cfg(test)]
diff --git a/src/test/run-make/save-analysis/Makefile b/src/test/run-make/save-analysis/Makefile
new file mode 100644
index 00000000000..e1cbf754946
--- /dev/null
+++ b/src/test/run-make/save-analysis/Makefile
@@ -0,0 +1,3 @@
+-include ../tools.mk
+all:
+	$(RUSTC) foo.rs -Zsave-analysis
diff --git a/src/test/run-make/save-analysis/foo.rs b/src/test/run-make/save-analysis/foo.rs
new file mode 100644
index 00000000000..bf5cc833d32
--- /dev/null
+++ b/src/test/run-make/save-analysis/foo.rs
@@ -0,0 +1,58 @@
+// 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.
+
+struct Foo {
+    f: int
+}
+
+impl Foo {
+    fn bar(&self) -> int {
+        println!("f is {}", self.f);
+        self.f
+    }
+}
+
+trait Tr {
+    fn tar(&self, x: Box<Foo>) -> Foo;
+}
+
+impl Tr for Foo {
+    fn tar(&self, x: Box<Foo>) -> Foo {
+        Foo{ f: self.f + x.f }
+    }
+}
+
+trait Tr2<X, Y: Tr> {
+    fn squid(&self, y: &Y, z: Self) -> Box<X>;
+}
+
+impl Tr2<Foo, Foo> for Foo {
+    fn squid(&self, y: &Foo, z: Foo) -> Box<Foo> {
+        box Foo { f: y.f + z.f + self.f }
+    }
+}
+
+enum En {
+    Var1,
+    Var2,
+    Var3(int, int, Foo)
+}
+
+fn main() {
+    let x = Foo { f: 237 };
+    let _f = x.bar();
+    let en = Var2;
+
+    let _ = match en {
+        Var1 => x.bar(),
+        Var2 => 34,
+        Var3(x, y, f) => f.bar()
+    };
+}