1251: Chalk integration improvements r=matklad a=flodiebold

A few improvements that came up while working on where clause support:
 - turn `implements` into a query again to improve performance
 - allow skipping to a specific function with `analysis-stats`, e.g. `ra_cli analysis-stats --only world_symbols`
 - deduplicate impls in impls_for_trait -- previously many impls e.g. from std where repeated many times, this should help performance as well...
 - add a `HirDisplay` implementation for TraitRef (not used here anywhere, but useful for debugging)

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2019-05-07 17:37:47 +00:00
commit 70cd5ffbf5
7 changed files with 45 additions and 14 deletions

View file

@ -7,7 +7,7 @@ use ra_syntax::AstNode;
use crate::Result;
pub fn run(verbose: bool) -> Result<()> {
pub fn run(verbose: bool, only: Option<&str>) -> Result<()> {
let db_load_time = Instant::now();
let (db, roots) = BatchDatabase::load_cargo(".")?;
println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
@ -57,14 +57,19 @@ pub fn run(verbose: bool) -> Result<()> {
let mut num_exprs_unknown = 0;
let mut num_exprs_partially_unknown = 0;
for f in funcs {
let name = f.name(&db);
if verbose {
let (file_id, source) = f.source(&db);
let original_file = file_id.original_file(&db);
let path = db.file_relative_path(original_file);
let syntax_range = source.syntax().range();
let name = f.name(&db);
println!("{} ({:?} {})", name, path, syntax_range);
}
if let Some(only_name) = only {
if name.to_string() != only_name {
continue;
}
}
let body = f.body(&db);
let inference_result = f.infer(&db);
for (expr_id, _) in body.exprs() {

View file

@ -23,7 +23,9 @@ fn main() -> Result<()> {
.subcommand(SubCommand::with_name("parse").arg(Arg::with_name("no-dump").long("--no-dump")))
.subcommand(SubCommand::with_name("symbols"))
.subcommand(
SubCommand::with_name("analysis-stats").arg(Arg::with_name("verbose").short("v")),
SubCommand::with_name("analysis-stats")
.arg(Arg::with_name("verbose").short("v"))
.arg(Arg::with_name("only").short("o").takes_value(true)),
)
.get_matches();
match matches.subcommand() {
@ -51,7 +53,8 @@ fn main() -> Result<()> {
}
("analysis-stats", Some(matches)) => {
let verbose = matches.is_present("verbose");
analysis_stats::run(verbose)?;
let only = matches.value_of("only");
analysis_stats::run(verbose, only)?;
}
_ => unreachable!(),
}

View file

@ -161,6 +161,13 @@ pub trait HirDatabase: DefDatabase {
#[salsa::invoke(crate::ty::traits::solver)]
#[salsa::volatile]
fn solver(&self, krate: Crate) -> Arc<Mutex<crate::ty::traits::Solver>>;
#[salsa::invoke(crate::ty::traits::implements)]
fn implements(
&self,
krate: Crate,
goal: crate::ty::Canonical<crate::ty::TraitRef>,
) -> Option<crate::ty::traits::Solution>;
}
#[test]

View file

@ -240,7 +240,7 @@ impl TraitRef {
/// many there are. This is used to erase irrelevant differences between types
/// before using them in queries.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Canonical<T> {
pub struct Canonical<T> {
pub value: T,
pub num_vars: usize,
}
@ -534,3 +534,20 @@ impl HirDisplay for Ty {
Ok(())
}
}
impl HirDisplay for TraitRef {
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
write!(
f,
"{}: {}",
self.substs[0].display(f.db),
self.trait_.name(f.db).unwrap_or_else(Name::missing)
)?;
if self.substs.len() > 1 {
write!(f, "<")?;
f.write_joined(&self.substs[1..], ", ")?;
write!(f, ">")?;
}
Ok(())
}
}

View file

@ -328,8 +328,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
Obligation::Trait(tr) => {
let canonicalized = self.canonicalizer().canonicalize_trait_ref(tr.clone());
(
super::traits::implements(
self.db,
self.db.implements(
self.resolver.krate().unwrap(),
canonicalized.value.clone(),
),

View file

@ -196,8 +196,7 @@ fn iterate_trait_method_candidates<T>(
if name.map_or(true, |name| sig.name() == name) && sig.has_self_param() {
if !known_implemented {
let trait_ref = canonical_trait_ref(db, t, ty.clone());
// FIXME cache this implements check (without solution) in a query?
if super::traits::implements(db, krate, trait_ref).is_none() {
if db.implements(krate, trait_ref).is_none() {
continue 'traits;
}
}

View file

@ -1,6 +1,7 @@
//! Trait solving using Chalk.
use std::sync::{Arc, Mutex};
use rustc_hash::FxHashSet;
use log::debug;
use chalk_ir::cast::Cast;
@ -31,7 +32,7 @@ pub(crate) fn impls_for_trait(
krate: Crate,
trait_: Trait,
) -> Arc<[ImplBlock]> {
let mut impls = Vec::new();
let mut impls = FxHashSet::default();
// We call the query recursively here. On the one hand, this means we can
// reuse results from queries for different crates; on the other hand, this
// will only ever get called for a few crates near the root of the tree (the
@ -42,7 +43,7 @@ pub(crate) fn impls_for_trait(
}
let crate_impl_blocks = db.impls_in_crate(krate);
impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(&trait_));
impls.into()
impls.into_iter().collect::<Vec<_>>().into()
}
fn solve(
@ -125,11 +126,11 @@ fn solution_from_chalk(db: &impl HirDatabase, solution: chalk_solve::Solution) -
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct SolutionVariables(pub Canonical<Vec<Ty>>);
pub struct SolutionVariables(pub Canonical<Vec<Ty>>);
#[derive(Clone, Debug, PartialEq, Eq)]
/// A (possible) solution for a proposed goal.
pub(crate) enum Solution {
pub enum Solution {
/// The goal indeed holds, and there is a unique value for all existential
/// variables.
Unique(SolutionVariables),
@ -144,7 +145,7 @@ pub(crate) enum Solution {
#[derive(Clone, Debug, PartialEq, Eq)]
/// When a goal holds ambiguously (e.g., because there are multiple possible
/// solutions), we issue a set of *guidance* back to type inference.
pub(crate) enum Guidance {
pub enum Guidance {
/// The existential variables *must* have the given values if the goal is
/// ever to hold, but that alone isn't enough to guarantee the goal will
/// actually hold.