]> git.lizzy.rs Git - rust.git/commitdiff
Dump results of analysis phase as CSV
authorNick Cameron <ncameron@mozilla.com>
Wed, 5 Feb 2014 04:31:33 +0000 (17:31 +1300)
committerNick Cameron <ncameron@mozilla.com>
Fri, 13 Jun 2014 09:09:50 +0000 (21:09 +1200)
Adds the option -Zsave-analysis which will dump the results of syntax and type checking into CSV files. These can be interpreted by tools such as DXR to provide semantic information about Rust programs for code search, cross-reference, etc.

Authored by Nick Cameron and Peter Elmers (@pelmers; including enums, type parameters/generics).

src/librustc/driver/config.rs
src/librustc/driver/driver.rs
src/librustc/driver/session.rs
src/librustc/lib.rs
src/librustc/middle/save/mod.rs [new file with mode: 0644]
src/librustc/middle/save/recorder.rs [new file with mode: 0644]
src/librustc/middle/save/span_utils.rs [new file with mode: 0644]
src/librustc/middle/ty.rs
src/libsyntax/codemap.rs
src/test/run-make/save-analysis/Makefile [new file with mode: 0644]
src/test/run-make/save-analysis/foo.rs [new file with mode: 0644]

index 186db839e33d861e6f0f65c76f164de83ae45dec..3326b4e8304cec3246741b26056a2933bbd5fc3c 100644 (file)
@@ -172,7 +172,8 @@ macro_rules! 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
index d703ece307ffb7e412dfabe4773f2b2acc681cf5..880c1d6d5104d5290d015b440d874f52719f20c8 100644 (file)
@@ -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,
index 773b9e6e0aac4022055e64b6cfea8afa95b38009..3efff5eac9e4d92ca0942997b533dc11f51b7a5d 100644 (file)
@@ -28,6 +28,7 @@
 use std::os;
 use std::cell::{Cell, RefCell};
 
+
 pub struct Session {
     pub targ_cfg: config::Config,
     pub opts: config::Options,
index 297d55edec8407430a9856675c82c9f60c5f7964..f79aaa40d213b98f77014a15fcb693702a616677 100644 (file)
@@ -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 (file)
index 0000000..2c73bc8
--- /dev/null
@@ -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 (file)
index 0000000..428f97d
--- /dev/null
@@ -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 (file)
index 0000000..e646827
--- /dev/null
@@ -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");
+        }
+    }
+}
index 99c337946ae3b93467003da348f00f08814c0675..a04f198da368d36f87c56a44efeb46f547cf59e0 100644 (file)
@@ -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`.
index d9e3e4e941d2b208ed64f207c3bb40a827c59257..c917198e7d4714fa31ab8ac82bee0e3687f7daf8 100644 (file)
@@ -421,6 +421,41 @@ pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
         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 @@ fn lookup_pos(&self, pos: BytePos) -> Loc {
             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 (file)
index 0000000..e1cbf75
--- /dev/null
@@ -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 (file)
index 0000000..bf5cc83
--- /dev/null
@@ -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()
+    };
+}