#![warn(rust_2018_idioms)]
#[macro_use]
extern crate derive_new;
#[macro_use]
extern crate log;
extern crate rls_data as data;
extern crate rls_span as span;
mod analysis;
mod listings;
mod loader;
mod lowering;
mod raw;
mod symbol_query;
#[cfg(test)]
mod test;
mod util;
use analysis::Analysis;
pub use analysis::{Def, Ident, IdentKind, Ref};
pub use loader::{AnalysisLoader, CargoAnalysisLoader, SearchDirectory, Target};
pub use raw::{name_space_for_def_kind, read_analysis_from_files, Crate, CrateId, DefKind};
pub use symbol_query::SymbolQuery;
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::{Instant, SystemTime};
use std::u64;
pub struct AnalysisHost<L: AnalysisLoader = CargoAnalysisLoader> {
analysis: Mutex<Option<Analysis>>,
master_crate_map: Mutex<HashMap<CrateId, u32>>,
loader: Mutex<L>,
}
pub type AResult<T> = Result<T, AError>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AError {
MutexPoison,
Unclassified,
}
#[derive(Debug, Clone)]
pub struct SymbolResult {
pub id: Id,
pub name: String,
pub kind: raw::DefKind,
pub span: Span,
pub parent: Option<Id>,
}
impl SymbolResult {
fn new(id: Id, def: &Def) -> SymbolResult {
SymbolResult {
id,
name: def.name.clone(),
span: def.span.clone(),
kind: def.kind,
parent: def.parent,
}
}
}
pub type Span = span::Span<span::ZeroIndexed>;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, new)]
pub struct Id(u64);
impl Id {
fn from_crate_and_local(crate_id: u32, local_id: u32) -> Id {
Id((u64::from(crate_id) << 32) | u64::from(local_id))
}
}
pub const NULL: Id = Id(u64::MAX);
macro_rules! clone_field {
($field: ident) => {
|x| x.$field.clone()
};
}
macro_rules! def_span {
($analysis: expr, $id: expr) => {
$analysis.with_defs_and_then($id, |def| Some(def.span.clone()))
};
}
impl AnalysisHost<CargoAnalysisLoader> {
pub fn new(target: Target) -> AnalysisHost {
AnalysisHost {
analysis: Mutex::new(None),
master_crate_map: Mutex::new(HashMap::new()),
loader: Mutex::new(CargoAnalysisLoader::new(target)),
}
}
}
impl<L: AnalysisLoader> AnalysisHost<L> {
pub fn new_with_loader(loader: L) -> Self {
Self {
analysis: Mutex::new(None),
master_crate_map: Mutex::new(HashMap::new()),
loader: Mutex::new(loader),
}
}
pub fn reload_from_analysis(
&self,
analysis: Vec<data::Analysis>,
path_prefix: &Path,
base_dir: &Path,
blacklist: &[impl AsRef<str> + Debug],
) -> AResult<()> {
self.reload_with_blacklist(path_prefix, base_dir, blacklist)?;
let crates: Vec<_> = analysis
.into_iter()
.map(|analysis| raw::Crate::new(analysis, SystemTime::now(), None, None))
.collect();
lowering::lower(crates, base_dir, self, |host, per_crate, id| {
let mut a = host.analysis.lock()?;
a.as_mut().unwrap().update(id, per_crate);
Ok(())
})
}
pub fn reload(&self, path_prefix: &Path, base_dir: &Path) -> AResult<()> {
self.reload_with_blacklist(path_prefix, base_dir, &[] as &[&str])
}
pub fn reload_with_blacklist(
&self,
path_prefix: &Path,
base_dir: &Path,
blacklist: &[impl AsRef<str> + Debug],
) -> AResult<()> {
trace!("reload_with_blacklist {:?} {:?} {:?}", path_prefix, base_dir, blacklist);
let empty = self.analysis.lock()?.is_none();
if empty || self.loader.lock()?.needs_hard_reload(path_prefix) {
return self.hard_reload_with_blacklist(path_prefix, base_dir, blacklist);
}
let timestamps = self.analysis.lock()?.as_ref().unwrap().timestamps();
let raw_analysis = {
let loader = self.loader.lock()?;
read_analysis_from_files(&*loader, timestamps, blacklist)
};
lowering::lower(raw_analysis, base_dir, self, |host, per_crate, id| {
let mut a = host.analysis.lock()?;
a.as_mut().unwrap().update(id, per_crate);
Ok(())
})
}
pub fn hard_reload(&self, path_prefix: &Path, base_dir: &Path) -> AResult<()> {
self.hard_reload_with_blacklist(path_prefix, base_dir, &[] as &[&str])
}
pub fn hard_reload_with_blacklist(
&self,
path_prefix: &Path,
base_dir: &Path,
blacklist: &[impl AsRef<str> + Debug],
) -> AResult<()> {
trace!("hard_reload {:?} {:?}", path_prefix, base_dir);
let mut fresh_host = self.loader.lock()?.fresh_host();
fresh_host.analysis = Mutex::new(Some(Analysis::new()));
{
let mut fresh_loader = fresh_host.loader.lock().unwrap();
fresh_loader.set_path_prefix(path_prefix);
let raw_analysis = read_analysis_from_files(&*fresh_loader, HashMap::new(), blacklist);
lowering::lower(raw_analysis, base_dir, &fresh_host, |host, per_crate, id| {
let mut a = host.analysis.lock()?;
a.as_mut().unwrap().update(id, per_crate);
Ok(())
})?;
}
macro_rules! swap_mutex_fields {
($($name:ident),*) => {
$(let mut $name = self.$name.lock()?;)*
$(*$name = fresh_host.$name.into_inner().unwrap();)*
};
}
swap_mutex_fields!(analysis, master_crate_map, loader);
Ok(())
}
pub fn has_def(&self, id: Id) -> bool {
match self.analysis.lock() {
Ok(a) => a.as_ref().unwrap().has_def(id),
_ => false,
}
}
pub fn get_def(&self, id: Id) -> AResult<Def> {
self.with_analysis(|a| a.with_defs(id, Clone::clone))
}
pub fn goto_def(&self, span: &Span) -> AResult<Span> {
self.with_analysis(|a| a.def_id_for_span(span).and_then(|id| def_span!(a, id)))
}
pub fn for_each_child_def<F, T>(&self, id: Id, f: F) -> AResult<Vec<T>>
where
F: FnMut(Id, &Def) -> T,
{
self.with_analysis(|a| a.for_each_child(id, f))
}
pub fn def_parents(&self, id: Id) -> AResult<Vec<(Id, String)>> {
self.with_analysis(|a| {
let mut result = vec![];
let mut next = id;
loop {
match a.with_defs_and_then(next, |def| {
def.parent.and_then(|p| a.with_defs(p, |def| (p, def.name.clone())))
}) {
Some((id, name)) => {
result.insert(0, (id, name));
next = id;
}
None => {
return Some(result);
}
}
}
})
}
pub fn def_roots(&self) -> AResult<Vec<(Id, String)>> {
self.with_analysis(|a| {
Some(
a.per_crate
.iter()
.filter_map(|(crate_id, data)| {
data.root_id.map(|id| (id, crate_id.name.clone()))
})
.collect(),
)
})
}
pub fn id(&self, span: &Span) -> AResult<Id> {
self.with_analysis(|a| a.def_id_for_span(span))
}
pub fn crate_local_id(&self, span: &Span) -> AResult<Id> {
self.with_analysis(|a| a.local_def_id_for_span(span))
}
pub fn find_all_refs(
&self,
span: &Span,
include_decl: bool,
force_unique_spans: bool,
) -> AResult<Vec<Span>> {
let t_start = Instant::now();
let result = self.with_analysis(|a| {
a.def_id_for_span(span).map(|id| {
if force_unique_spans && a.aliased_imports.contains(&id) {
return vec![];
}
let decl = if include_decl { def_span!(a, id) } else { None };
let refs = a.with_ref_spans(id, |refs| {
if force_unique_spans {
for r in refs.iter() {
match a.ref_for_span(r) {
Some(Ref::Id(_)) => {}
_ => return None,
}
}
}
Some(refs.clone())
});
refs.map(|refs| decl.into_iter().chain(refs.into_iter()).collect::<Vec<_>>())
.unwrap_or_else(|| vec![])
})
});
let time = t_start.elapsed();
info!(
"find_all_refs: {}s",
time.as_secs() as f64 + f64::from(time.subsec_nanos()) / 1_000_000_000.0
);
result
}
pub fn show_type(&self, span: &Span) -> AResult<String> {
self.with_analysis(|a| {
a.def_id_for_span(span)
.and_then(|id| a.with_defs(id, clone_field!(value)))
.or_else(|| a.with_globs(span, clone_field!(value)))
})
}
pub fn docs(&self, span: &Span) -> AResult<String> {
self.with_analysis(|a| {
a.def_id_for_span(span).and_then(|id| a.with_defs(id, clone_field!(docs)))
})
}
pub fn matching_defs(&self, stem: &str) -> AResult<Vec<Def>> {
self.query_defs(SymbolQuery::prefix(stem))
}
pub fn query_defs(&self, query: SymbolQuery) -> AResult<Vec<Def>> {
let t_start = Instant::now();
let result = self.with_analysis(move |a| {
let defs = a.query_defs(query);
info!("query_defs {:?}", &defs);
Some(defs)
});
let time = t_start.elapsed();
info!(
"query_defs: {}",
time.as_secs() as f64 + f64::from(time.subsec_nanos()) / 1_000_000_000.0
);
result
}
pub fn search(&self, name: &str) -> AResult<Vec<Span>> {
let t_start = Instant::now();
let result = self.with_analysis(|a| {
Some(a.with_def_names(name, |defs| {
info!("defs: {:?}", defs);
defs.iter()
.flat_map(|id| {
a.with_ref_spans(*id, |refs| {
Some(
def_span!(a, *id)
.into_iter()
.chain(refs.iter().cloned())
.collect::<Vec<_>>(),
)
})
.or_else(|| def_span!(a, *id).map(|s| vec![s]))
.unwrap_or_else(Vec::new)
.into_iter()
})
.collect::<Vec<Span>>()
}))
});
let time = t_start.elapsed();
info!(
"search: {}s",
time.as_secs() as f64 + f64::from(time.subsec_nanos()) / 1_000_000_000.0
);
result
}
pub fn find_all_refs_by_id(&self, id: Id) -> AResult<Vec<Span>> {
let t_start = Instant::now();
let result = self.with_analysis(|a| {
a.with_ref_spans(id, |refs| {
Some(def_span!(a, id).into_iter().chain(refs.iter().cloned()).collect::<Vec<_>>())
})
.or_else(|| def_span!(a, id).map(|s| vec![s]))
});
let time = t_start.elapsed();
info!(
"find_all_refs_by_id: {}s",
time.as_secs() as f64 + f64::from(time.subsec_nanos()) / 1_000_000_000.0
);
result
}
pub fn find_impls(&self, id: Id) -> AResult<Vec<Span>> {
self.with_analysis(|a| Some(a.for_all_crates(|c| c.impls.get(&id).cloned())))
}
pub fn search_for_id(&self, name: &str) -> AResult<Vec<Id>> {
self.with_analysis(|a| Some(a.with_def_names(name, Clone::clone)))
}
#[cfg(feature = "idents")]
pub fn idents(&self, span: &Span) -> AResult<Vec<Ident>> {
self.with_analysis(|a| Some(a.idents(span)))
}
pub fn symbols(&self, file_name: &Path) -> AResult<Vec<SymbolResult>> {
self.with_analysis(|a| {
a.with_defs_per_file(file_name, |ids| {
ids.iter()
.map(|id| a.with_defs(*id, |def| SymbolResult::new(*id, def)).unwrap())
.collect()
})
})
}
pub fn doc_url(&self, span: &Span) -> AResult<String> {
self.with_analysis(|a| {
a.def_id_for_span(span).and_then(|id| {
a.with_defs_and_then(id, |def| AnalysisHost::<L>::mk_doc_url(def, a))
})
})
}
pub fn src_url(&self, span: &Span) -> AResult<String> {
let path_prefix = self.loader.lock().unwrap().abs_path_prefix();
self.with_analysis(|a| {
a.def_id_for_span(span).and_then(|id| {
a.with_defs_and_then(id, |def| {
AnalysisHost::<L>::mk_src_url(def, path_prefix.as_ref(), a)
})
})
})
}
fn with_analysis<F, T>(&self, f: F) -> AResult<T>
where
F: FnOnce(&Analysis) -> Option<T>,
{
let a = self.analysis.lock()?;
if let Some(ref a) = *a {
f(a).ok_or(AError::Unclassified)
} else {
Err(AError::Unclassified)
}
}
fn mk_doc_url(def: &Def, analysis: &Analysis) -> Option<String> {
if !def.distro_crate {
return None;
}
if def.parent.is_none() && def.qualname.contains('<') {
debug!("mk_doc_url, bailing, found generic qualname: `{}`", def.qualname);
return None;
}
match def.parent {
Some(p) => analysis.with_defs(p, |parent| match def.kind {
DefKind::Field
| DefKind::Method
| DefKind::Tuple
| DefKind::TupleVariant
| DefKind::StructVariant => {
let ns = name_space_for_def_kind(def.kind);
let mut res = AnalysisHost::<L>::mk_doc_url(parent, analysis)
.unwrap_or_else(|| "".into());
res.push_str(&format!("#{}.{}", def.name, ns));
res
}
DefKind::Mod => {
let parent_qualpath = parent.qualname.replace("::", "/");
format!(
"{}/{}/{}/",
analysis.doc_url_base,
parent_qualpath.trim_end_matches('/'),
def.name,
)
}
_ => {
let parent_qualpath = parent.qualname.replace("::", "/");
let ns = name_space_for_def_kind(def.kind);
format!(
"{}/{}/{}.{}.html",
analysis.doc_url_base, parent_qualpath, def.name, ns,
)
}
}),
None => {
let qualpath = def.qualname.replace("::", "/");
let ns = name_space_for_def_kind(def.kind);
Some(format!("{}/{}.{}.html", analysis.doc_url_base, qualpath, ns,))
}
}
}
fn mk_src_url(def: &Def, path_prefix: Option<&PathBuf>, analysis: &Analysis) -> Option<String> {
if !def.distro_crate {
return None;
}
let file_path = &def.span.file;
let file_path = file_path.strip_prefix(path_prefix?).ok()?;
Some(format!(
"{}/{}#L{}-L{}",
analysis.src_url_base,
file_path.to_str().unwrap(),
def.span.range.row_start.one_indexed().0,
def.span.range.row_end.one_indexed().0
))
}
}
impl ::std::fmt::Display for Id {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::error::Error for AError {
fn description(&self) -> &str {
match *self {
AError::MutexPoison => "poison error in a mutex (usually a secondary error)",
AError::Unclassified => "unknown error",
}
}
}
impl ::std::fmt::Display for AError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", ::std::error::Error::description(self))
}
}
impl<T> From<::std::sync::PoisonError<T>> for AError {
fn from(_: ::std::sync::PoisonError<T>) -> AError {
AError::MutexPoison
}
}