mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-16 21:15:18 +03:00
df9df19507
Previously calculating glob map was *opt-in*, however it did record node id -> ident use for every use directive. This aims to see if we can unconditionally calculate the glob map and not regress performance.
1213 lines
44 KiB
Rust
1213 lines
44 KiB
Rust
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
|
|
html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
|
|
html_root_url = "https://doc.rust-lang.org/nightly/")]
|
|
#![feature(custom_attribute)]
|
|
#![feature(nll)]
|
|
#![allow(unused_attributes)]
|
|
|
|
#![recursion_limit="256"]
|
|
|
|
#[macro_use]
|
|
extern crate rustc;
|
|
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate rustc_data_structures;
|
|
extern crate rustc_codegen_utils;
|
|
extern crate rustc_serialize;
|
|
extern crate rustc_target;
|
|
extern crate rustc_typeck;
|
|
#[macro_use]
|
|
extern crate syntax;
|
|
extern crate syntax_pos;
|
|
|
|
extern crate rls_data;
|
|
extern crate rls_span;
|
|
|
|
|
|
mod json_dumper;
|
|
mod dump_visitor;
|
|
#[macro_use]
|
|
mod span_utils;
|
|
mod sig;
|
|
|
|
use rustc::hir;
|
|
use rustc::hir::def::Def as HirDef;
|
|
use rustc::hir::Node;
|
|
use rustc::hir::def_id::{DefId, LOCAL_CRATE};
|
|
use rustc::middle::cstore::ExternCrate;
|
|
use rustc::session::config::{CrateType, Input, OutputType};
|
|
use rustc::ty::{self, TyCtxt};
|
|
use rustc_typeck::hir_ty_to_ty;
|
|
use rustc_codegen_utils::link::{filename_for_metadata, out_filename};
|
|
|
|
use std::cell::Cell;
|
|
use std::default::Default;
|
|
use std::env;
|
|
use std::fs::File;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use syntax::ast::{self, Attribute, DUMMY_NODE_ID, NodeId, PatKind};
|
|
use syntax::source_map::Spanned;
|
|
use syntax::parse::lexer::comments::strip_doc_comment_decoration;
|
|
use syntax::print::pprust;
|
|
use syntax::visit::{self, Visitor};
|
|
use syntax::print::pprust::{arg_to_string, ty_to_string};
|
|
use syntax::source_map::MacroAttribute;
|
|
use syntax_pos::*;
|
|
|
|
use json_dumper::JsonDumper;
|
|
use dump_visitor::DumpVisitor;
|
|
use span_utils::SpanUtils;
|
|
|
|
use rls_data::{Def, DefKind, ExternalCrateData, GlobalCrateId, MacroRef, Ref, RefKind, Relation,
|
|
RelationKind, SpanData, Impl, ImplKind};
|
|
use rls_data::config::Config;
|
|
|
|
|
|
pub struct SaveContext<'l, 'tcx: 'l> {
|
|
tcx: TyCtxt<'l, 'tcx, 'tcx>,
|
|
tables: &'l ty::TypeckTables<'tcx>,
|
|
analysis: &'l ty::CrateAnalysis,
|
|
span_utils: SpanUtils<'tcx>,
|
|
config: Config,
|
|
impl_counter: Cell<u32>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Data {
|
|
RefData(Ref),
|
|
DefData(Def),
|
|
RelationData(Relation, Impl),
|
|
}
|
|
|
|
impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
|
|
fn span_from_span(&self, span: Span) -> SpanData {
|
|
use rls_span::{Column, Row};
|
|
|
|
let cm = self.tcx.sess.source_map();
|
|
let start = cm.lookup_char_pos(span.lo());
|
|
let end = cm.lookup_char_pos(span.hi());
|
|
|
|
SpanData {
|
|
file_name: start.file.name.to_string().into(),
|
|
byte_start: span.lo().0,
|
|
byte_end: span.hi().0,
|
|
line_start: Row::new_one_indexed(start.line as u32),
|
|
line_end: Row::new_one_indexed(end.line as u32),
|
|
column_start: Column::new_one_indexed(start.col.0 as u32 + 1),
|
|
column_end: Column::new_one_indexed(end.col.0 as u32 + 1),
|
|
}
|
|
}
|
|
|
|
// Returns path to the compilation output (e.g., libfoo-12345678.rmeta)
|
|
pub fn compilation_output(&self, crate_name: &str) -> PathBuf {
|
|
let sess = &self.tcx.sess;
|
|
// Save-analysis is emitted per whole session, not per each crate type
|
|
let crate_type = sess.crate_types.borrow()[0];
|
|
let outputs = &*self.tcx.output_filenames(LOCAL_CRATE);
|
|
|
|
if outputs.outputs.contains_key(&OutputType::Metadata) {
|
|
filename_for_metadata(sess, crate_name, outputs)
|
|
} else if outputs.outputs.should_codegen() {
|
|
out_filename(sess, crate_type, outputs, crate_name)
|
|
} else {
|
|
// Otherwise it's only a DepInfo, in which case we return early and
|
|
// not even reach the analysis stage.
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
// List external crates used by the current crate.
|
|
pub fn get_external_crates(&self) -> Vec<ExternalCrateData> {
|
|
let mut result = Vec::with_capacity(self.tcx.crates().len());
|
|
|
|
for &n in self.tcx.crates().iter() {
|
|
let span = match *self.tcx.extern_crate(n.as_def_id()) {
|
|
Some(ExternCrate { span, .. }) => span,
|
|
None => {
|
|
debug!("Skipping crate {}, no data", n);
|
|
continue;
|
|
}
|
|
};
|
|
let lo_loc = self.span_utils.sess.source_map().lookup_char_pos(span.lo());
|
|
result.push(ExternalCrateData {
|
|
// FIXME: change file_name field to PathBuf in rls-data
|
|
// https://github.com/nrc/rls-data/issues/7
|
|
file_name: self.span_utils.make_filename_string(&lo_loc.file),
|
|
num: n.as_u32(),
|
|
id: GlobalCrateId {
|
|
name: self.tcx.crate_name(n).to_string(),
|
|
disambiguator: self.tcx.crate_disambiguator(n).to_fingerprint().as_value(),
|
|
},
|
|
});
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn get_extern_item_data(&self, item: &ast::ForeignItem) -> Option<Data> {
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
match item.node {
|
|
ast::ForeignItemKind::Fn(ref decl, ref generics) => {
|
|
filter!(self.span_utils, item.ident.span);
|
|
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::ForeignFunction,
|
|
id: id_from_node_id(item.id, self),
|
|
span: self.span_from_span(item.ident.span),
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
value: make_signature(decl, generics),
|
|
parent: None,
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::foreign_item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ForeignItemKind::Static(ref ty, _) => {
|
|
filter!(self.span_utils, item.ident.span);
|
|
|
|
let id = ::id_from_node_id(item.id, self);
|
|
let span = self.span_from_span(item.ident.span);
|
|
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::ForeignStatic,
|
|
id,
|
|
span,
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
value: ty_to_string(ty),
|
|
parent: None,
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::foreign_item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
// FIXME(plietar): needs a new DefKind in rls-data
|
|
ast::ForeignItemKind::Ty => None,
|
|
ast::ForeignItemKind::Macro(..) => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_item_data(&self, item: &ast::Item) -> Option<Data> {
|
|
match item.node {
|
|
ast::ItemKind::Fn(ref decl, .., ref generics, _) => {
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
filter!(self.span_utils, item.ident.span);
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::Function,
|
|
id: id_from_node_id(item.id, self),
|
|
span: self.span_from_span(item.ident.span),
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
value: make_signature(decl, generics),
|
|
parent: None,
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ItemKind::Static(ref typ, ..) => {
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
|
|
filter!(self.span_utils, item.ident.span);
|
|
|
|
let id = id_from_node_id(item.id, self);
|
|
let span = self.span_from_span(item.ident.span);
|
|
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::Static,
|
|
id,
|
|
span,
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
value: ty_to_string(&typ),
|
|
parent: None,
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ItemKind::Const(ref typ, _) => {
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
filter!(self.span_utils, item.ident.span);
|
|
|
|
let id = id_from_node_id(item.id, self);
|
|
let span = self.span_from_span(item.ident.span);
|
|
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::Const,
|
|
id,
|
|
span,
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
value: ty_to_string(typ),
|
|
parent: None,
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ItemKind::Mod(ref m) => {
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
|
|
let cm = self.tcx.sess.source_map();
|
|
let filename = cm.span_to_filename(m.inner);
|
|
|
|
filter!(self.span_utils, item.ident.span);
|
|
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::Mod,
|
|
id: id_from_node_id(item.id, self),
|
|
name: item.ident.to_string(),
|
|
qualname,
|
|
span: self.span_from_span(item.ident.span),
|
|
value: filename.to_string(),
|
|
parent: None,
|
|
children: m.items
|
|
.iter()
|
|
.map(|i| id_from_node_id(i.id, self))
|
|
.collect(),
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ItemKind::Enum(ref def, _) => {
|
|
let name = item.ident.to_string();
|
|
let qualname = format!("::{}", self.tcx.node_path_str(item.id));
|
|
filter!(self.span_utils, item.ident.span);
|
|
let variants_str = def.variants
|
|
.iter()
|
|
.map(|v| v.node.ident.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
let value = format!("{}::{{{}}}", name, variants_str);
|
|
Some(Data::DefData(Def {
|
|
kind: DefKind::Enum,
|
|
id: id_from_node_id(item.id, self),
|
|
span: self.span_from_span(item.ident.span),
|
|
name,
|
|
qualname,
|
|
value,
|
|
parent: None,
|
|
children: def.variants
|
|
.iter()
|
|
.map(|v| id_from_node_id(v.node.data.id(), self))
|
|
.collect(),
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&item.attrs),
|
|
sig: sig::item_signature(item, self),
|
|
attributes: lower_attributes(item.attrs.clone(), self),
|
|
}))
|
|
}
|
|
ast::ItemKind::Impl(.., ref trait_ref, ref typ, ref impls) => {
|
|
if let ast::TyKind::Path(None, ref path) = typ.node {
|
|
// Common case impl for a struct or something basic.
|
|
if generated_code(path.span) {
|
|
return None;
|
|
}
|
|
let sub_span = path.segments.last().unwrap().ident.span;
|
|
filter!(self.span_utils, sub_span);
|
|
|
|
let impl_id = self.next_impl_id();
|
|
let span = self.span_from_span(sub_span);
|
|
|
|
let type_data = self.lookup_ref_id(typ.id);
|
|
type_data.map(|type_data| {
|
|
Data::RelationData(Relation {
|
|
kind: RelationKind::Impl {
|
|
id: impl_id,
|
|
},
|
|
span: span.clone(),
|
|
from: id_from_def_id(type_data),
|
|
to: trait_ref
|
|
.as_ref()
|
|
.and_then(|t| self.lookup_ref_id(t.ref_id))
|
|
.map(id_from_def_id)
|
|
.unwrap_or_else(|| null_id()),
|
|
},
|
|
Impl {
|
|
id: impl_id,
|
|
kind: match *trait_ref {
|
|
Some(_) => ImplKind::Direct,
|
|
None => ImplKind::Inherent,
|
|
},
|
|
span: span,
|
|
value: String::new(),
|
|
parent: None,
|
|
children: impls
|
|
.iter()
|
|
.map(|i| id_from_node_id(i.id, self))
|
|
.collect(),
|
|
docs: String::new(),
|
|
sig: None,
|
|
attributes: vec![],
|
|
})
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => {
|
|
// FIXME
|
|
bug!();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_field_data(&self, field: &ast::StructField, scope: NodeId) -> Option<Def> {
|
|
if let Some(ident) = field.ident {
|
|
let name = ident.to_string();
|
|
let qualname = format!("::{}::{}", self.tcx.node_path_str(scope), ident);
|
|
filter!(self.span_utils, ident.span);
|
|
let def_id = self.tcx.hir().local_def_id(field.id);
|
|
let typ = self.tcx.type_of(def_id).to_string();
|
|
|
|
|
|
let id = id_from_node_id(field.id, self);
|
|
let span = self.span_from_span(ident.span);
|
|
|
|
Some(Def {
|
|
kind: DefKind::Field,
|
|
id,
|
|
span,
|
|
name,
|
|
qualname,
|
|
value: typ,
|
|
parent: Some(id_from_node_id(scope, self)),
|
|
children: vec![],
|
|
decl_id: None,
|
|
docs: self.docs_for_attrs(&field.attrs),
|
|
sig: sig::field_signature(field, self),
|
|
attributes: lower_attributes(field.attrs.clone(), self),
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// FIXME would be nice to take a MethodItem here, but the ast provides both
|
|
// trait and impl flavours, so the caller must do the disassembly.
|
|
pub fn get_method_data(&self, id: ast::NodeId, ident: ast::Ident, span: Span) -> Option<Def> {
|
|
// 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 (qualname, parent_scope, decl_id, docs, attributes) =
|
|
match self.tcx.impl_of_method(self.tcx.hir().local_def_id(id)) {
|
|
Some(impl_id) => match self.tcx.hir().get_if_local(impl_id) {
|
|
Some(Node::Item(item)) => match item.node {
|
|
hir::ItemKind::Impl(.., ref ty, _) => {
|
|
let mut qualname = String::from("<");
|
|
qualname.push_str(&self.tcx.hir().node_to_pretty_string(ty.id));
|
|
|
|
let trait_id = self.tcx.trait_id_of_impl(impl_id);
|
|
let mut decl_id = None;
|
|
let mut docs = String::new();
|
|
let mut attrs = vec![];
|
|
if let Some(Node::ImplItem(item)) = self.tcx.hir().find(id) {
|
|
docs = self.docs_for_attrs(&item.attrs);
|
|
attrs = item.attrs.to_vec();
|
|
}
|
|
|
|
if let Some(def_id) = trait_id {
|
|
// A method in a trait impl.
|
|
qualname.push_str(" as ");
|
|
qualname.push_str(&self.tcx.item_path_str(def_id));
|
|
self.tcx
|
|
.associated_items(def_id)
|
|
.find(|item| item.ident.name == ident.name)
|
|
.map(|item| decl_id = Some(item.def_id));
|
|
}
|
|
qualname.push_str(">");
|
|
|
|
(qualname, trait_id, decl_id, docs, attrs)
|
|
}
|
|
_ => {
|
|
span_bug!(
|
|
span,
|
|
"Container {:?} for method {} not an impl?",
|
|
impl_id,
|
|
id
|
|
);
|
|
}
|
|
},
|
|
r => {
|
|
span_bug!(
|
|
span,
|
|
"Container {:?} for method {} is not a node item {:?}",
|
|
impl_id,
|
|
id,
|
|
r
|
|
);
|
|
}
|
|
},
|
|
None => match self.tcx.trait_of_item(self.tcx.hir().local_def_id(id)) {
|
|
Some(def_id) => {
|
|
let mut docs = String::new();
|
|
let mut attrs = vec![];
|
|
|
|
if let Some(Node::TraitItem(item)) = self.tcx.hir().find(id) {
|
|
docs = self.docs_for_attrs(&item.attrs);
|
|
attrs = item.attrs.to_vec();
|
|
}
|
|
|
|
(
|
|
format!("::{}", self.tcx.item_path_str(def_id)),
|
|
Some(def_id),
|
|
None,
|
|
docs,
|
|
attrs,
|
|
)
|
|
}
|
|
None => {
|
|
debug!("Could not find container for method {} at {:?}", id, span);
|
|
// This is not necessarily a bug, if there was a compilation error,
|
|
// the tables we need might not exist.
|
|
return None;
|
|
}
|
|
},
|
|
};
|
|
|
|
let qualname = format!("{}::{}", qualname, ident.name);
|
|
|
|
filter!(self.span_utils, ident.span);
|
|
|
|
Some(Def {
|
|
kind: DefKind::Method,
|
|
id: id_from_node_id(id, self),
|
|
span: self.span_from_span(ident.span),
|
|
name: ident.name.to_string(),
|
|
qualname,
|
|
// FIXME you get better data here by using the visitor.
|
|
value: String::new(),
|
|
parent: parent_scope.map(|id| id_from_def_id(id)),
|
|
children: vec![],
|
|
decl_id: decl_id.map(|id| id_from_def_id(id)),
|
|
docs,
|
|
sig: None,
|
|
attributes: lower_attributes(attributes, self),
|
|
})
|
|
}
|
|
|
|
pub fn get_trait_ref_data(&self, trait_ref: &ast::TraitRef) -> Option<Ref> {
|
|
self.lookup_ref_id(trait_ref.ref_id).and_then(|def_id| {
|
|
let span = trait_ref.path.span;
|
|
if generated_code(span) {
|
|
return None;
|
|
}
|
|
let sub_span = trait_ref.path.segments.last().unwrap().ident.span;
|
|
filter!(self.span_utils, sub_span);
|
|
let span = self.span_from_span(sub_span);
|
|
Some(Ref {
|
|
kind: RefKind::Type,
|
|
span,
|
|
ref_id: id_from_def_id(def_id),
|
|
})
|
|
})
|
|
}
|
|
|
|
pub fn get_expr_data(&self, expr: &ast::Expr) -> Option<Data> {
|
|
let hir_node = self.tcx.hir().expect_expr(expr.id);
|
|
let ty = self.tables.expr_ty_adjusted_opt(&hir_node);
|
|
if ty.is_none() || ty.unwrap().sty == ty::Error {
|
|
return None;
|
|
}
|
|
match expr.node {
|
|
ast::ExprKind::Field(ref sub_ex, ident) => {
|
|
let hir_node = match self.tcx.hir().find(sub_ex.id) {
|
|
Some(Node::Expr(expr)) => expr,
|
|
_ => {
|
|
debug!(
|
|
"Missing or weird node for sub-expression {} in {:?}",
|
|
sub_ex.id,
|
|
expr
|
|
);
|
|
return None;
|
|
}
|
|
};
|
|
match self.tables.expr_ty_adjusted(&hir_node).sty {
|
|
ty::Adt(def, _) if !def.is_enum() => {
|
|
let variant = &def.non_enum_variant();
|
|
let index = self.tcx.find_field_index(ident, variant).unwrap();
|
|
filter!(self.span_utils, ident.span);
|
|
let span = self.span_from_span(ident.span);
|
|
return Some(Data::RefData(Ref {
|
|
kind: RefKind::Variable,
|
|
span,
|
|
ref_id: id_from_def_id(variant.fields[index].did),
|
|
}));
|
|
}
|
|
ty::Tuple(..) => None,
|
|
_ => {
|
|
debug!("Expected struct or union type, found {:?}", ty);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
ast::ExprKind::Struct(ref path, ..) => {
|
|
match self.tables.expr_ty_adjusted(&hir_node).sty {
|
|
ty::Adt(def, _) if !def.is_enum() => {
|
|
let sub_span = path.segments.last().unwrap().ident.span;
|
|
filter!(self.span_utils, sub_span);
|
|
let span = self.span_from_span(sub_span);
|
|
Some(Data::RefData(Ref {
|
|
kind: RefKind::Type,
|
|
span,
|
|
ref_id: id_from_def_id(def.did),
|
|
}))
|
|
}
|
|
_ => {
|
|
// FIXME ty could legitimately be an enum, but then we will fail
|
|
// later if we try to look up the fields.
|
|
debug!("expected struct or union, found {:?}", ty);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
ast::ExprKind::MethodCall(ref seg, ..) => {
|
|
let expr_hir_id = self.tcx.hir().definitions().node_to_hir_id(expr.id);
|
|
let method_id = match self.tables.type_dependent_defs().get(expr_hir_id) {
|
|
Some(id) => id.def_id(),
|
|
None => {
|
|
debug!("Could not resolve method id for {:?}", expr);
|
|
return None;
|
|
}
|
|
};
|
|
let (def_id, decl_id) = match self.tcx.associated_item(method_id).container {
|
|
ty::ImplContainer(_) => (Some(method_id), None),
|
|
ty::TraitContainer(_) => (None, Some(method_id)),
|
|
};
|
|
let sub_span = seg.ident.span;
|
|
filter!(self.span_utils, sub_span);
|
|
let span = self.span_from_span(sub_span);
|
|
Some(Data::RefData(Ref {
|
|
kind: RefKind::Function,
|
|
span,
|
|
ref_id: def_id
|
|
.or(decl_id)
|
|
.map(|id| id_from_def_id(id))
|
|
.unwrap_or_else(|| null_id()),
|
|
}))
|
|
}
|
|
ast::ExprKind::Path(_, ref path) => {
|
|
self.get_path_data(expr.id, path).map(|d| Data::RefData(d))
|
|
}
|
|
_ => {
|
|
// FIXME
|
|
bug!();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_path_def(&self, id: NodeId) -> HirDef {
|
|
match self.tcx.hir().get(id) {
|
|
Node::TraitRef(tr) => tr.path.def,
|
|
|
|
Node::Item(&hir::Item {
|
|
node: hir::ItemKind::Use(ref path, _),
|
|
..
|
|
}) |
|
|
Node::Visibility(&Spanned {
|
|
node: hir::VisibilityKind::Restricted { ref path, .. }, .. }) => path.def,
|
|
|
|
Node::PathSegment(seg) => match seg.def {
|
|
Some(def) => def,
|
|
None => HirDef::Err,
|
|
},
|
|
Node::Expr(&hir::Expr {
|
|
node: hir::ExprKind::Struct(ref qpath, ..),
|
|
..
|
|
}) |
|
|
Node::Expr(&hir::Expr {
|
|
node: hir::ExprKind::Path(ref qpath),
|
|
..
|
|
}) |
|
|
Node::Pat(&hir::Pat {
|
|
node: hir::PatKind::Path(ref qpath),
|
|
..
|
|
}) |
|
|
Node::Pat(&hir::Pat {
|
|
node: hir::PatKind::Struct(ref qpath, ..),
|
|
..
|
|
}) |
|
|
Node::Pat(&hir::Pat {
|
|
node: hir::PatKind::TupleStruct(ref qpath, ..),
|
|
..
|
|
}) => {
|
|
let hir_id = self.tcx.hir().node_to_hir_id(id);
|
|
self.tables.qpath_def(qpath, hir_id)
|
|
}
|
|
|
|
Node::Binding(&hir::Pat {
|
|
node: hir::PatKind::Binding(_, canonical_id, ..),
|
|
..
|
|
}) => HirDef::Local(canonical_id),
|
|
|
|
Node::Ty(ty) => if let hir::Ty {
|
|
node: hir::TyKind::Path(ref qpath),
|
|
..
|
|
} = *ty
|
|
{
|
|
match *qpath {
|
|
hir::QPath::Resolved(_, ref path) => path.def,
|
|
hir::QPath::TypeRelative(..) => {
|
|
let ty = hir_ty_to_ty(self.tcx, ty);
|
|
if let ty::Projection(proj) = ty.sty {
|
|
return HirDef::AssociatedTy(proj.item_def_id);
|
|
}
|
|
HirDef::Err
|
|
}
|
|
}
|
|
} else {
|
|
HirDef::Err
|
|
},
|
|
|
|
_ => HirDef::Err,
|
|
}
|
|
}
|
|
|
|
pub fn get_path_data(&self, id: NodeId, path: &ast::Path) -> Option<Ref> {
|
|
path.segments
|
|
.last()
|
|
.and_then(|seg| {
|
|
self.get_path_segment_data(seg)
|
|
.or_else(|| self.get_path_segment_data_with_id(seg, id))
|
|
})
|
|
}
|
|
|
|
pub fn get_path_segment_data(&self, path_seg: &ast::PathSegment) -> Option<Ref> {
|
|
self.get_path_segment_data_with_id(path_seg, path_seg.id)
|
|
}
|
|
|
|
fn get_path_segment_data_with_id(
|
|
&self,
|
|
path_seg: &ast::PathSegment,
|
|
id: NodeId,
|
|
) -> Option<Ref> {
|
|
// Returns true if the path is function type sugar, e.g., `Fn(A) -> B`.
|
|
fn fn_type(seg: &ast::PathSegment) -> bool {
|
|
if let Some(ref generic_args) = seg.args {
|
|
if let ast::GenericArgs::Parenthesized(_) = **generic_args {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
if id == DUMMY_NODE_ID {
|
|
return None;
|
|
}
|
|
|
|
let def = self.get_path_def(id);
|
|
let span = path_seg.ident.span;
|
|
filter!(self.span_utils, span);
|
|
let span = self.span_from_span(span);
|
|
|
|
match def {
|
|
HirDef::Upvar(id, ..) | HirDef::Local(id) => {
|
|
Some(Ref {
|
|
kind: RefKind::Variable,
|
|
span,
|
|
ref_id: id_from_node_id(id, self),
|
|
})
|
|
}
|
|
HirDef::Static(..) |
|
|
HirDef::Const(..) |
|
|
HirDef::AssociatedConst(..) |
|
|
HirDef::VariantCtor(..) => {
|
|
Some(Ref {
|
|
kind: RefKind::Variable,
|
|
span,
|
|
ref_id: id_from_def_id(def.def_id()),
|
|
})
|
|
}
|
|
HirDef::Trait(def_id) if fn_type(path_seg) => {
|
|
Some(Ref {
|
|
kind: RefKind::Type,
|
|
span,
|
|
ref_id: id_from_def_id(def_id),
|
|
})
|
|
}
|
|
HirDef::Struct(def_id) |
|
|
HirDef::Variant(def_id, ..) |
|
|
HirDef::Union(def_id) |
|
|
HirDef::Enum(def_id) |
|
|
HirDef::TyAlias(def_id) |
|
|
HirDef::ForeignTy(def_id) |
|
|
HirDef::TraitAlias(def_id) |
|
|
HirDef::AssociatedExistential(def_id) |
|
|
HirDef::AssociatedTy(def_id) |
|
|
HirDef::Trait(def_id) |
|
|
HirDef::Existential(def_id) |
|
|
HirDef::TyParam(def_id) => {
|
|
Some(Ref {
|
|
kind: RefKind::Type,
|
|
span,
|
|
ref_id: id_from_def_id(def_id),
|
|
})
|
|
}
|
|
HirDef::StructCtor(def_id, _) => {
|
|
// This is a reference to a tuple struct where the def_id points
|
|
// to an invisible constructor function. That is not a very useful
|
|
// def, so adjust to point to the tuple struct itself.
|
|
let parent_def_id = self.tcx.parent_def_id(def_id).unwrap();
|
|
Some(Ref {
|
|
kind: RefKind::Type,
|
|
span,
|
|
ref_id: id_from_def_id(parent_def_id),
|
|
})
|
|
}
|
|
HirDef::Method(decl_id) => {
|
|
let def_id = if decl_id.is_local() {
|
|
let ti = self.tcx.associated_item(decl_id);
|
|
self.tcx
|
|
.associated_items(ti.container.id())
|
|
.find(|item| item.ident.name == ti.ident.name &&
|
|
item.defaultness.has_value())
|
|
.map(|item| item.def_id)
|
|
} else {
|
|
None
|
|
};
|
|
Some(Ref {
|
|
kind: RefKind::Function,
|
|
span,
|
|
ref_id: id_from_def_id(def_id.unwrap_or(decl_id)),
|
|
})
|
|
}
|
|
HirDef::Fn(def_id) => {
|
|
Some(Ref {
|
|
kind: RefKind::Function,
|
|
span,
|
|
ref_id: id_from_def_id(def_id),
|
|
})
|
|
}
|
|
HirDef::Mod(def_id) => {
|
|
Some(Ref {
|
|
kind: RefKind::Mod,
|
|
span,
|
|
ref_id: id_from_def_id(def_id),
|
|
})
|
|
}
|
|
HirDef::PrimTy(..) |
|
|
HirDef::SelfTy(..) |
|
|
HirDef::Label(..) |
|
|
HirDef::Macro(..) |
|
|
HirDef::ToolMod |
|
|
HirDef::NonMacroAttr(..) |
|
|
HirDef::SelfCtor(..) |
|
|
HirDef::Err => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_field_ref_data(
|
|
&self,
|
|
field_ref: &ast::Field,
|
|
variant: &ty::VariantDef,
|
|
) -> Option<Ref> {
|
|
filter!(self.span_utils, field_ref.ident.span);
|
|
self.tcx.find_field_index(field_ref.ident, variant).map(|index| {
|
|
let span = self.span_from_span(field_ref.ident.span);
|
|
Ref {
|
|
kind: RefKind::Variable,
|
|
span,
|
|
ref_id: id_from_def_id(variant.fields[index].did),
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Attempt to return MacroRef for any AST node.
|
|
///
|
|
/// For a given piece of AST defined by the supplied Span and NodeId,
|
|
/// returns None if the node is not macro-generated or the span is malformed,
|
|
/// else uses the expansion callsite and callee to return some MacroRef.
|
|
pub fn get_macro_use_data(&self, span: Span) -> Option<MacroRef> {
|
|
if !generated_code(span) {
|
|
return None;
|
|
}
|
|
// Note we take care to use the source callsite/callee, to handle
|
|
// nested expansions and ensure we only generate data for source-visible
|
|
// macro uses.
|
|
let callsite = span.source_callsite();
|
|
let callsite_span = self.span_from_span(callsite);
|
|
let callee = span.source_callee()?;
|
|
let callee_span = callee.def_site?;
|
|
|
|
// Ignore attribute macros, their spans are usually mangled
|
|
if let MacroAttribute(_) = callee.format {
|
|
return None;
|
|
}
|
|
|
|
// If the callee is an imported macro from an external crate, need to get
|
|
// the source span and name from the session, as their spans are localized
|
|
// when read in, and no longer correspond to the source.
|
|
if let Some(mac) = self.tcx
|
|
.sess
|
|
.imported_macro_spans
|
|
.borrow()
|
|
.get(&callee_span)
|
|
{
|
|
let &(ref mac_name, mac_span) = mac;
|
|
let mac_span = self.span_from_span(mac_span);
|
|
return Some(MacroRef {
|
|
span: callsite_span,
|
|
qualname: mac_name.clone(), // FIXME: generate the real qualname
|
|
callee_span: mac_span,
|
|
});
|
|
}
|
|
|
|
let callee_span = self.span_from_span(callee_span);
|
|
Some(MacroRef {
|
|
span: callsite_span,
|
|
qualname: callee.format.name().to_string(), // FIXME: generate the real qualname
|
|
callee_span,
|
|
})
|
|
}
|
|
|
|
fn lookup_ref_id(&self, ref_id: NodeId) -> Option<DefId> {
|
|
match self.get_path_def(ref_id) {
|
|
HirDef::PrimTy(_) | HirDef::SelfTy(..) | HirDef::Err => None,
|
|
def => Some(def.def_id()),
|
|
}
|
|
}
|
|
|
|
fn docs_for_attrs(&self, attrs: &[Attribute]) -> String {
|
|
let mut result = String::new();
|
|
|
|
for attr in attrs {
|
|
if attr.check_name("doc") {
|
|
if let Some(val) = attr.value_str() {
|
|
if attr.is_sugared_doc {
|
|
result.push_str(&strip_doc_comment_decoration(&val.as_str()));
|
|
} else {
|
|
result.push_str(&val.as_str());
|
|
}
|
|
result.push('\n');
|
|
} else if let Some(meta_list) = attr.meta_item_list() {
|
|
meta_list.into_iter()
|
|
.filter(|it| it.check_name("include"))
|
|
.filter_map(|it| it.meta_item_list().map(|l| l.to_owned()))
|
|
.flat_map(|it| it)
|
|
.filter(|meta| meta.check_name("contents"))
|
|
.filter_map(|meta| meta.value_str())
|
|
.for_each(|val| {
|
|
result.push_str(&val.as_str());
|
|
result.push('\n');
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if !self.config.full_docs {
|
|
if let Some(index) = result.find("\n\n") {
|
|
result.truncate(index);
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn next_impl_id(&self) -> u32 {
|
|
let next = self.impl_counter.get();
|
|
self.impl_counter.set(next + 1);
|
|
next
|
|
}
|
|
}
|
|
|
|
fn make_signature(decl: &ast::FnDecl, generics: &ast::Generics) -> String {
|
|
let mut sig = "fn ".to_owned();
|
|
if !generics.params.is_empty() {
|
|
sig.push('<');
|
|
sig.push_str(&generics
|
|
.params
|
|
.iter()
|
|
.map(|param| param.ident.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", "));
|
|
sig.push_str("> ");
|
|
}
|
|
sig.push('(');
|
|
sig.push_str(&decl.inputs
|
|
.iter()
|
|
.map(arg_to_string)
|
|
.collect::<Vec<_>>()
|
|
.join(", "));
|
|
sig.push(')');
|
|
match decl.output {
|
|
ast::FunctionRetTy::Default(_) => sig.push_str(" -> ()"),
|
|
ast::FunctionRetTy::Ty(ref t) => sig.push_str(&format!(" -> {}", ty_to_string(t))),
|
|
}
|
|
|
|
sig
|
|
}
|
|
|
|
// An AST visitor for collecting paths (e.g., the names of structs) and formal
|
|
// variables (idents) from patterns.
|
|
struct PathCollector<'l> {
|
|
collected_paths: Vec<(NodeId, &'l ast::Path)>,
|
|
collected_idents: Vec<(NodeId, ast::Ident, ast::Mutability)>,
|
|
}
|
|
|
|
impl<'l> PathCollector<'l> {
|
|
fn new() -> PathCollector<'l> {
|
|
PathCollector {
|
|
collected_paths: vec![],
|
|
collected_idents: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'l, 'a: 'l> Visitor<'a> for PathCollector<'l> {
|
|
fn visit_pat(&mut self, p: &'a ast::Pat) {
|
|
match p.node {
|
|
PatKind::Struct(ref path, ..) => {
|
|
self.collected_paths.push((p.id, path));
|
|
}
|
|
PatKind::TupleStruct(ref path, ..) | PatKind::Path(_, ref path) => {
|
|
self.collected_paths.push((p.id, path));
|
|
}
|
|
PatKind::Ident(bm, ident, _) => {
|
|
debug!(
|
|
"PathCollector, visit ident in pat {}: {:?} {:?}",
|
|
ident,
|
|
p.span,
|
|
ident.span
|
|
);
|
|
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::BindingMode::ByRef(_) => ast::Mutability::Immutable,
|
|
ast::BindingMode::ByValue(mt) => mt,
|
|
};
|
|
self.collected_idents
|
|
.push((p.id, ident, immut));
|
|
}
|
|
_ => {}
|
|
}
|
|
visit::walk_pat(self, p);
|
|
}
|
|
}
|
|
|
|
/// Defines what to do with the results of saving the analysis.
|
|
pub trait SaveHandler {
|
|
fn save<'l, 'tcx>(
|
|
&mut self,
|
|
save_ctxt: SaveContext<'l, 'tcx>,
|
|
krate: &ast::Crate,
|
|
cratename: &str,
|
|
input: &'l Input,
|
|
);
|
|
}
|
|
|
|
/// Dump the save-analysis results to a file.
|
|
pub struct DumpHandler<'a> {
|
|
odir: Option<&'a Path>,
|
|
cratename: String,
|
|
}
|
|
|
|
impl<'a> DumpHandler<'a> {
|
|
pub fn new(odir: Option<&'a Path>, cratename: &str) -> DumpHandler<'a> {
|
|
DumpHandler {
|
|
odir,
|
|
cratename: cratename.to_owned(),
|
|
}
|
|
}
|
|
|
|
fn output_file(&self, ctx: &SaveContext) -> File {
|
|
let sess = &ctx.tcx.sess;
|
|
let file_name = match ctx.config.output_file {
|
|
Some(ref s) => PathBuf::from(s),
|
|
None => {
|
|
let mut root_path = match self.odir {
|
|
Some(val) => val.join("save-analysis"),
|
|
None => PathBuf::from("save-analysis-temp"),
|
|
};
|
|
|
|
if let Err(e) = std::fs::create_dir_all(&root_path) {
|
|
error!("Could not create directory {}: {}", root_path.display(), e);
|
|
}
|
|
|
|
let executable = sess.crate_types
|
|
.borrow()
|
|
.iter()
|
|
.any(|ct| *ct == CrateType::Executable);
|
|
let mut out_name = if executable {
|
|
String::new()
|
|
} else {
|
|
"lib".to_owned()
|
|
};
|
|
out_name.push_str(&self.cratename);
|
|
out_name.push_str(&sess.opts.cg.extra_filename);
|
|
out_name.push_str(".json");
|
|
root_path.push(&out_name);
|
|
|
|
root_path
|
|
}
|
|
};
|
|
|
|
info!("Writing output to {}", file_name.display());
|
|
|
|
let output_file = File::create(&file_name).unwrap_or_else(
|
|
|e| sess.fatal(&format!("Could not open {}: {}", file_name.display(), e)),
|
|
);
|
|
|
|
output_file
|
|
}
|
|
}
|
|
|
|
impl<'a> SaveHandler for DumpHandler<'a> {
|
|
fn save<'l, 'tcx>(
|
|
&mut self,
|
|
save_ctxt: SaveContext<'l, 'tcx>,
|
|
krate: &ast::Crate,
|
|
cratename: &str,
|
|
input: &'l Input,
|
|
) {
|
|
let output = &mut self.output_file(&save_ctxt);
|
|
let mut dumper = JsonDumper::new(output, save_ctxt.config.clone());
|
|
let mut visitor = DumpVisitor::new(save_ctxt, &mut dumper);
|
|
|
|
visitor.dump_crate_info(cratename, krate);
|
|
visitor.dump_compilation_options(input, cratename);
|
|
visit::walk_crate(&mut visitor, krate);
|
|
}
|
|
}
|
|
|
|
/// Call a callback with the results of save-analysis.
|
|
pub struct CallbackHandler<'b> {
|
|
pub callback: &'b mut dyn FnMut(&rls_data::Analysis),
|
|
}
|
|
|
|
impl<'b> SaveHandler for CallbackHandler<'b> {
|
|
fn save<'l, 'tcx>(
|
|
&mut self,
|
|
save_ctxt: SaveContext<'l, 'tcx>,
|
|
krate: &ast::Crate,
|
|
cratename: &str,
|
|
input: &'l Input,
|
|
) {
|
|
// We're using the JsonDumper here because it has the format of the
|
|
// save-analysis results that we will pass to the callback. IOW, we are
|
|
// using the JsonDumper to collect the save-analysis results, but not
|
|
// actually to dump them to a file. This is all a bit convoluted and
|
|
// there is certainly a simpler design here trying to get out (FIXME).
|
|
let mut dumper = JsonDumper::with_callback(self.callback, save_ctxt.config.clone());
|
|
let mut visitor = DumpVisitor::new(save_ctxt, &mut dumper);
|
|
|
|
visitor.dump_crate_info(cratename, krate);
|
|
visitor.dump_compilation_options(input, cratename);
|
|
visit::walk_crate(&mut visitor, krate);
|
|
}
|
|
}
|
|
|
|
pub fn process_crate<'l, 'tcx, H: SaveHandler>(
|
|
tcx: TyCtxt<'l, 'tcx, 'tcx>,
|
|
krate: &ast::Crate,
|
|
analysis: &'l ty::CrateAnalysis,
|
|
cratename: &str,
|
|
input: &'l Input,
|
|
config: Option<Config>,
|
|
mut handler: H,
|
|
) {
|
|
tcx.dep_graph.with_ignore(|| {
|
|
info!("Dumping crate {}", cratename);
|
|
|
|
let save_ctxt = SaveContext {
|
|
tcx,
|
|
tables: &ty::TypeckTables::empty(None),
|
|
analysis,
|
|
span_utils: SpanUtils::new(&tcx.sess),
|
|
config: find_config(config),
|
|
impl_counter: Cell::new(0),
|
|
};
|
|
|
|
handler.save(save_ctxt, krate, cratename, input)
|
|
})
|
|
}
|
|
|
|
fn find_config(supplied: Option<Config>) -> Config {
|
|
if let Some(config) = supplied {
|
|
return config;
|
|
}
|
|
match env::var_os("RUST_SAVE_ANALYSIS_CONFIG") {
|
|
Some(config_string) => rustc_serialize::json::decode(config_string.to_str().unwrap())
|
|
.expect("Could not deserialize save-analysis config"),
|
|
None => Config::default(),
|
|
}
|
|
}
|
|
|
|
// Utility functions for the module.
|
|
|
|
// Helper function to escape quotes in a string
|
|
fn escape(s: String) -> String {
|
|
s.replace("\"", "\"\"")
|
|
}
|
|
|
|
// Helper function to determine if a span came from a
|
|
// macro expansion or syntax extension.
|
|
fn generated_code(span: Span) -> bool {
|
|
span.ctxt() != NO_EXPANSION || span.is_dummy()
|
|
}
|
|
|
|
// DefId::index is a newtype and so the JSON serialisation is ugly. Therefore
|
|
// we use our own Id which is the same, but without the newtype.
|
|
fn id_from_def_id(id: DefId) -> rls_data::Id {
|
|
rls_data::Id {
|
|
krate: id.krate.as_u32(),
|
|
index: id.index.as_raw_u32(),
|
|
}
|
|
}
|
|
|
|
fn id_from_node_id(id: NodeId, scx: &SaveContext) -> rls_data::Id {
|
|
let def_id = scx.tcx.hir().opt_local_def_id(id);
|
|
def_id.map(|id| id_from_def_id(id)).unwrap_or_else(|| {
|
|
// Create a *fake* `DefId` out of a `NodeId` by subtracting the `NodeId`
|
|
// out of the maximum u32 value. This will work unless you have *billions*
|
|
// of definitions in a single crate (very unlikely to actually happen).
|
|
rls_data::Id {
|
|
krate: LOCAL_CRATE.as_u32(),
|
|
index: !id.as_u32(),
|
|
}
|
|
})
|
|
}
|
|
|
|
fn null_id() -> rls_data::Id {
|
|
rls_data::Id {
|
|
krate: u32::max_value(),
|
|
index: u32::max_value(),
|
|
}
|
|
}
|
|
|
|
fn lower_attributes(attrs: Vec<Attribute>, scx: &SaveContext) -> Vec<rls_data::Attribute> {
|
|
attrs.into_iter()
|
|
// Only retain real attributes. Doc comments are lowered separately.
|
|
.filter(|attr| attr.path != "doc")
|
|
.map(|mut attr| {
|
|
// Remove the surrounding '#[..]' or '#![..]' of the pretty printed
|
|
// attribute. First normalize all inner attribute (#![..]) to outer
|
|
// ones (#[..]), then remove the two leading and the one trailing character.
|
|
attr.style = ast::AttrStyle::Outer;
|
|
let value = pprust::attribute_to_string(&attr);
|
|
// This str slicing works correctly, because the leading and trailing characters
|
|
// are in the ASCII range and thus exactly one byte each.
|
|
let value = value[2..value.len()-1].to_string();
|
|
|
|
rls_data::Attribute {
|
|
value,
|
|
span: scx.span_from_span(attr.span),
|
|
}
|
|
}).collect()
|
|
}
|