Skip to content

Commit

Permalink
load analysis for stdlib crates if available
Browse files Browse the repository at this point in the history
This allows resolving implementations of stdlib traits. Viewing source
code of them isn't supported yet though.

The stdlib crates are filtered out of the crate listing.

Also improve handling of missing crate analysis: make get_def() less
panicky by allowing it to handle missing crates (but not missing defs
within a found crate, that is still invalid).
  • Loading branch information
wfraser committed Jul 7, 2020
1 parent 3f33d78 commit e3892d4
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 55 deletions.
77 changes: 66 additions & 11 deletions src/analysis.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use rls_analysis::{AnalysisHost, AnalysisLoader, SearchDirectory};
use std::collections::btree_map::*;
use std::convert::TryFrom;
use std::io::{stderr, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

/// Write the analysis data to a subdirectory under target/ with this name.
const SUBDIR: &str = "rsbrowse";

pub struct Analysis {
pub crates: Vec<Crate>,
pub stdlib_crates: Vec<CrateId>,
}

impl Analysis {
Expand All @@ -24,7 +27,7 @@ impl Analysis {
})
.expect("failed to json-serialize rust analysis configuration");

let cargo_status = std::process::Command::new("cargo")
let cargo_status = Command::new("cargo")
.arg("check")
.arg("--target-dir")
.arg(Path::new("target").join(SUBDIR))
Expand All @@ -51,8 +54,16 @@ impl Analysis {
&loader, Default::default(), &[] as &[&str])
.into_iter()
.map(|c| Crate::try_from(c).expect("unable to read crate analysis"))
.collect();
Self { crates }
.collect::<Vec<Crate>>();
let mut stdlib_crates = vec![];
if let Some(ref stdlib_base) = loader.stdlib_dir {
for krate in &crates {
if krate.inner.path.as_ref().unwrap().starts_with(stdlib_base) {
stdlib_crates.push(krate.id());
}
}
}
Self { crates, stdlib_crates }
}

pub fn crate_ids<'a>(&'a self) -> impl Iterator<Item=CrateId> + 'a {
Expand Down Expand Up @@ -110,12 +121,13 @@ impl Analysis {
index: id.index,
})
}
self.get_crate(crate_id)
Some(self.try_get_crate(crate_id)?
.inner
.analysis
.defs
.iter()
.find(|def| def.id == id)
.unwrap_or_else(|| panic!("invalid def ID {:?} for crate {:?}", id, crate_id)))
}

pub fn impls<'a>(&'a self, crate_id: &CrateId, parent_id: rls_data::Id)
Expand Down Expand Up @@ -256,6 +268,7 @@ pub enum CrateType {
Lib,
ProcMacro,
CDylib,
Dylib,
}

impl std::str::FromStr for CrateType {
Expand All @@ -266,6 +279,7 @@ impl std::str::FromStr for CrateType {
"lib" => Self::Lib,
"proc-macro" => Self::ProcMacro,
"cdylib" => Self::CDylib,
"dylib" => Self::Dylib,
_ => {
return Err(format!("unknown crate type {:?}", s));
}
Expand All @@ -276,6 +290,7 @@ impl std::str::FromStr for CrateType {
#[derive(Clone)]
struct Loader {
deps_dir: PathBuf,
stdlib_dir: Option<PathBuf>,
}

impl AnalysisLoader for Loader {
Expand All @@ -296,19 +311,59 @@ impl AnalysisLoader for Loader {
}

fn search_directories(&self) -> Vec<SearchDirectory> {
vec![SearchDirectory { path : self.deps_dir.clone(), prefix_rewrite: None }]
let mut paths = vec![
SearchDirectory { path : self.deps_dir.clone(), prefix_rewrite: None }
];
if let Some(path) = self.stdlib_dir.clone() {
paths.push(SearchDirectory { path, prefix_rewrite: None });
}
paths
}
}

impl Loader {
pub fn new(path: impl Into<PathBuf>, target: &str) -> Self {
let deps_dir = path.into()
.join("target")
.join(SUBDIR)
.join(target)
.join("deps")
.join("save-analysis");

Self {
deps_dir: path.into()
.join("target")
.join(SUBDIR)
.join(target)
.join("deps")
.join("save-analysis"),
deps_dir,
stdlib_dir: get_stdlib_analysis_path(),
}
}
}

fn get_stdlib_analysis_path() -> Option<PathBuf> {
Command::new("rustc")
.arg("--print")
.arg("target-libdir")
.output()
.map_err(|e| {
eprintln!("Error running 'rustc --print target-libdir': {}", e);
e
})
.ok()
.and_then(|out| {
if out.status.success() {
let path = String::from_utf8(out.stdout)
.map_err(|e| {
eprintln!("'rustc --print target-libdir' returned invalid utf8: {}", e);
e
})
.ok()?;

Some(PathBuf::from(path.trim_end())
.join("..")
.join("analysis"))
} else {
eprintln!("Error running 'rustc --print target-libdir': {}", out.status);
eprint!("Command stderr: ");
stderr().write_all(&out.stderr).unwrap();
None
}
})
}
93 changes: 51 additions & 42 deletions src/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl Browser {

pub fn list_crates(&self) -> Vec<(String, CrateId)> {
let mut crates = self.analysis.crate_ids()
.filter(|c| !self.analysis.stdlib_crates.contains(c))
.map(|c| (crate_label(&c), c))
.collect::<Vec<_>>();

Expand All @@ -23,6 +24,24 @@ impl Browser {
crates
}

fn get_maybe_external_trait<'a>(&'a self, crate_id: &'a CrateId, trait_id: rls_data::Id)
-> (bool, &'a CrateId, rls_data::Id)
{
if trait_id.krate != 0 {
(
true,
self.analysis.get_external_crate_id(crate_id, trait_id)
.expect("nonexistent external crate"),
rls_data::Id {
krate: 0,
index: trait_id.index,
}
)
} else {
(false, crate_id, trait_id)
}
}

pub fn list_items(&self, crate_id: &CrateId, parent: &Item) -> Vec<(String, Item)> {
let mut items = vec![];
match parent {
Expand All @@ -41,36 +60,27 @@ impl Browser {

if let Some(id) = parent_id {
let mut impls = self.analysis.impls(crate_id, id)
.filter_map(|impl_details| {
.map(|impl_details| {
let trait_name = match impl_details.trait_id {
Some(mut trait_id) => {
let mut trait_crate = crate_id;
let mut is_external_crate = false;
if trait_id.krate != parent_id.unwrap().krate {
let other = self.analysis.get_external_crate_id(crate_id, trait_id)
.expect("nonexistent external crate");
trait_id.krate = 0; // now we're looking inside this one
trait_crate = other;
is_external_crate = true;
}
if self.analysis.try_get_crate(trait_crate).is_none() {
// Probably a reference to a trait in core or std, which we
// currently don't have a way to load analysis data for.
return None;
}
let trait_name = self.analysis.get_def(trait_crate, trait_id)
.expect("invalid trait ID")
.qualname
.clone();
if is_external_crate {
trait_crate.name.clone() + &trait_name
} else {
trait_name
}
Some(trait_id) => {
let (is_external, trait_crate, trait_id)
= self.get_maybe_external_trait(crate_id, trait_id);
self.analysis.get_def(trait_crate, trait_id)
.map(|t| if is_external {
trait_crate.name.clone() + &t.qualname
} else {
t.qualname.clone()
})
.unwrap_or_else(|| {
format!("{}::(unresolved trait at {}:{})",
trait_crate.name,
impl_details.span.file_name.display(),
impl_details.span.line_start.0)
})
}
None => "Self".to_owned(),
};
Some((format!("impl {}", trait_name), Item::Impl(impl_details)))
(format!("impl {}", trait_name), Item::Impl(impl_details))
})
.collect::<Vec<_>>();

Expand All @@ -94,27 +104,22 @@ impl Browser {
}

// Trait methods.
if let Some(mut trait_id) = impl_details.trait_id {
let mut trait_crate_id = crate_id;
let mut is_external = false;
if trait_id.krate != 0 {
trait_crate_id = self.analysis.get_external_crate_id(crate_id, trait_id)
.expect("nonexistent external crate");
trait_id.krate = 0;
is_external = true;
}
if let Some(trait_id) = impl_details.trait_id {
let (is_external, trait_crate, trait_id) = self.get_maybe_external_trait(
crate_id, trait_id);

let def = self.analysis.get_def(&trait_crate_id, trait_id)
.expect("no such trait");
let children = self.analysis.get_def(&trait_crate, trait_id)
.map(|def| &def.children[..])
.unwrap_or(&[]);

for id in &def.children {
if let Some(method) = self.analysis.get_def(&trait_crate_id, *id) {
for id in children {
if let Some(method) = self.analysis.get_def(&trait_crate, *id) {
// Add to the map only if not existing (if it already exists it means
// the method has been overridden).
methods.entry(def_label(method))
.or_insert_with(|| {
if is_external {
Item::ExternalDef(trait_crate_id.to_owned(), method.clone())
Item::ExternalDef(trait_crate.to_owned(), method.clone())
} else {
Item::Def(method.clone())
}
Expand Down Expand Up @@ -159,8 +164,11 @@ impl Browser {
}
Item::Impl(imp) => {
if let Some(t) = imp.trait_id {
let tdef = self.analysis.get_def(crate_id, t).unwrap();
txt += &format!("implementation of trait {}", tdef.qualname);
if let Some(tdef) = self.analysis.get_def(crate_id, t) {
txt += &format!("implementation of trait {}", tdef.qualname);
} else {
txt += "implementation of unresolved trait";
}
} else {
txt += "inherent impl";
// nothing else to show really
Expand Down Expand Up @@ -215,6 +223,7 @@ fn crate_label(c: &CrateId) -> String {
CrateType::ProcMacro => format!("{} (proc-macro)", c.name),
CrateType::Lib => c.name.clone(),
CrateType::CDylib => format!("{} (cdylib)", c.name),
CrateType::Dylib => format!("{} (dylib)", c.name),
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ fn get_source_for_def(def: &rls_data::Def) -> (String, usize) {
(txt, line as usize)
}
Err(e) => {
return (format!("error opening source: {}", e), 0);
(format!("error opening source: {}", e), 0)
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion tests/testlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ fn list_items() {

let x_s = mod_x_items.by_label("struct S");
let x_s_items = BROWSER.list_items(crate_id, x_s);
assert_eq!(x_s_items.labels(), &["impl Self", "impl externcrate::ExternTrait"]);
assert_eq!(x_s_items.labels(), &[
"impl Self",
"impl core::fmt::Display",
"impl externcrate::ExternTrait",
]);

let y_s = mod_y_items.by_label("struct S");
let y_s_items = BROWSER.list_items(crate_id, y_s);
Expand Down

0 comments on commit e3892d4

Please sign in to comment.