Skip to content

Commit 5fead60

Browse files
committed
Auto merge of rust-lang#16555 - davidbarsky:david/speedup-completions-by-exploiting-orphan-rules, r=Veykril
performance: Speed up Method Completions By Taking Advantage of Orphan Rules (Continues rust-lang/rust-analyzer#16498) This PR speeds up method completions by doing two things without regressing `analysis-stats`[^1]: - Filter candidate traits prior to calling `iterate_path_candidates` by relying on orphan rules (see below for a slightly more in-depth explanation). When generating completions [on `slog::Logger`](https://github.com/oxidecomputer/omicron/blob/5e9e59c312cd787ba50cf6776f220951917dfa14/common/src/ledger.rs#L78) in `oxidecomputer/omicron` as a test, this PR halved my completion times—it's now 454ms cold and 281ms warm. Before this PR, it was 808ms cold and 579ms warm. - Inline some of the method candidate checks into `is_valid_method_candidate` and remove some unnecessary visibility checks. This was suggested by `@Veykril` in [this comment](rust-lang/rust-analyzer#16498 (comment)). We filter candidate traits by taking advantage of orphan rules. For additional details, I'll rely on `@WaffleLapkin's` explanation [from Zulip](https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/Trait.20Checking/near/420942417): > A type `A` can only implements traits which > 1. Have a blanket implementation (`impl<T> Trait for T {}`) > 2. Have implementation for `A` (`impl Trait for A {}`) > > Blanket implementation can only exist in `Trait`'s crate. Implementation for `A` can only exist in `A`'s or `Trait`'s crate. Big thanks to Waffle for its keen observation! --- I think some additional improvements are possible: - `for_trait_and_self_ty` seemingly does not distinguish between `&T`, `&mut T`, or `T`, resulting in seemingly irrelevant traits like `tokio::io::AsyncWrite` being being included for, e.g., `&slog::Logger`. I don't know they're being considered due to the [autoref/autoderef behavior](https://github.com/rust-lang/rust-analyzer/blob/a02a219773629686bd8ff123ca1aa995fa50d976/crates/hir-ty/src/method_resolution.rs#L945-L962), but I wonder if it'd make sense to filter by mutability earlier and not consider trait implementations that require `&mut T` when we only have a `&T`. - The method completions [spend a _lot_ of time in unification](https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/Trait.20Checking/near/421072356), and while there might be low-hanging fruit there, it might make more sense to wait for the new trait solver in `rustc`. I dunno. [^1]: The filtering occurs outside of typechecking, after all.
2 parents 1d3558b + c246a93 commit 5fead60

File tree

6 files changed

+243
-4
lines changed

6 files changed

+243
-4
lines changed

crates/hir-ty/src/infer/unify.rs

+1
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ impl<'a> InferenceTable<'a> {
457457
}
458458

459459
/// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that.
460+
#[tracing::instrument(skip_all)]
460461
pub(crate) fn unify<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &T) -> bool {
461462
let result = match self.try_unify(ty1, ty2) {
462463
Ok(r) => r,

crates/hir-ty/src/method_resolution.rs

+78-1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ impl TraitImpls {
254254
.flat_map(|v| v.iter().copied())
255255
}
256256

257+
/// Queries whether `self_ty` has potentially applicable implementations of `trait_`.
258+
pub fn has_impls_for_trait_and_self_ty(&self, trait_: TraitId, self_ty: TyFingerprint) -> bool {
259+
self.for_trait_and_self_ty(trait_, self_ty).next().is_some()
260+
}
261+
257262
pub fn all_impls(&self) -> impl Iterator<Item = ImplId> + '_ {
258263
self.map.values().flat_map(|map| map.values().flat_map(|v| v.iter().copied()))
259264
}
@@ -1170,7 +1175,7 @@ fn iterate_trait_method_candidates(
11701175
for &(_, item) in data.items.iter() {
11711176
// Don't pass a `visible_from_module` down to `is_valid_candidate`,
11721177
// since only inherent methods should be included into visibility checking.
1173-
let visible = match is_valid_candidate(table, name, receiver_ty, item, self_ty, None) {
1178+
let visible = match is_valid_method_candidate(table, name, receiver_ty, item, self_ty) {
11741179
IsValidCandidate::Yes => true,
11751180
IsValidCandidate::NotVisible => false,
11761181
IsValidCandidate::No => continue,
@@ -1414,6 +1419,74 @@ fn is_valid_candidate(
14141419
}
14151420
}
14161421

1422+
/// Checks whether a given `AssocItemId` is applicable for `receiver_ty`.
1423+
///
1424+
/// This method should *only* be called by [`iterate_trait_method_candidates`],
1425+
/// as it is responsible for determining applicability in completions.
1426+
#[tracing::instrument(skip_all, fields(name))]
1427+
fn is_valid_method_candidate(
1428+
table: &mut InferenceTable<'_>,
1429+
name: Option<&Name>,
1430+
receiver_ty: Option<&Ty>,
1431+
item: AssocItemId,
1432+
self_ty: &Ty,
1433+
) -> IsValidCandidate {
1434+
let db = table.db;
1435+
match item {
1436+
AssocItemId::FunctionId(fn_id) => {
1437+
let data = db.function_data(fn_id);
1438+
1439+
check_that!(name.map_or(true, |n| n == &data.name));
1440+
1441+
table.run_in_snapshot(|table| {
1442+
let container = fn_id.lookup(db.upcast()).container;
1443+
let (impl_subst, expect_self_ty) = match container {
1444+
ItemContainerId::ImplId(it) => {
1445+
let subst = TyBuilder::subst_for_def(db, it, None)
1446+
.fill_with_inference_vars(table)
1447+
.build();
1448+
let self_ty = db.impl_self_ty(it).substitute(Interner, &subst);
1449+
(subst, self_ty)
1450+
}
1451+
ItemContainerId::TraitId(it) => {
1452+
let subst = TyBuilder::subst_for_def(db, it, None)
1453+
.fill_with_inference_vars(table)
1454+
.build();
1455+
let self_ty = subst.at(Interner, 0).assert_ty_ref(Interner).clone();
1456+
(subst, self_ty)
1457+
}
1458+
_ => unreachable!(),
1459+
};
1460+
1461+
check_that!(table.unify(&expect_self_ty, self_ty));
1462+
1463+
if let Some(receiver_ty) = receiver_ty {
1464+
check_that!(data.has_self_param());
1465+
1466+
let fn_subst = TyBuilder::subst_for_def(db, fn_id, Some(impl_subst.clone()))
1467+
.fill_with_inference_vars(table)
1468+
.build();
1469+
1470+
let sig = db.callable_item_signature(fn_id.into());
1471+
let expected_receiver =
1472+
sig.map(|s| s.params()[0].clone()).substitute(Interner, &fn_subst);
1473+
1474+
check_that!(table.unify(receiver_ty, &expected_receiver));
1475+
}
1476+
1477+
IsValidCandidate::Yes
1478+
})
1479+
}
1480+
AssocItemId::ConstId(c) => {
1481+
check_that!(receiver_ty.is_none());
1482+
check_that!(name.map_or(true, |n| db.const_data(c).name.as_ref() == Some(n)));
1483+
1484+
IsValidCandidate::Yes
1485+
}
1486+
_ => IsValidCandidate::No,
1487+
}
1488+
}
1489+
14171490
enum IsValidCandidate {
14181491
Yes,
14191492
No,
@@ -1441,6 +1514,8 @@ fn is_valid_fn_candidate(
14411514
}
14421515
table.run_in_snapshot(|table| {
14431516
let container = fn_id.lookup(db.upcast()).container;
1517+
1518+
let _p = tracing::span!(tracing::Level::INFO, "subst_for_def").entered();
14441519
let (impl_subst, expect_self_ty) = match container {
14451520
ItemContainerId::ImplId(it) => {
14461521
let subst =
@@ -1460,6 +1535,7 @@ fn is_valid_fn_candidate(
14601535
check_that!(table.unify(&expect_self_ty, self_ty));
14611536

14621537
if let Some(receiver_ty) = receiver_ty {
1538+
let _p = tracing::span!(tracing::Level::INFO, "check_receiver_ty").entered();
14631539
check_that!(data.has_self_param());
14641540

14651541
let fn_subst = TyBuilder::subst_for_def(db, fn_id, Some(impl_subst.clone()))
@@ -1474,6 +1550,7 @@ fn is_valid_fn_candidate(
14741550
}
14751551

14761552
if let ItemContainerId::ImplId(impl_id) = container {
1553+
let _p = tracing::span!(tracing::Level::INFO, "check_item_container").entered();
14771554
// We need to consider the bounds on the impl to distinguish functions of the same name
14781555
// for a type.
14791556
let predicates = db.generic_predicates(impl_id.into());

crates/hir-ty/src/traits.rs

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ fn solve(
139139
block: Option<BlockId>,
140140
goal: &chalk_ir::UCanonical<chalk_ir::InEnvironment<chalk_ir::Goal<Interner>>>,
141141
) -> Option<chalk_solve::Solution<Interner>> {
142+
let _p = tracing::span!(tracing::Level::INFO, "solve", ?krate, ?block).entered();
142143
let context = ChalkContext { db, krate, block };
143144
tracing::debug!("solve goal: {:?}", goal);
144145
let mut solver = create_chalk_solver();

crates/hir/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4239,6 +4239,10 @@ impl Type {
42394239
}
42404240
}
42414241

4242+
pub fn fingerprint_for_trait_impl(&self) -> Option<TyFingerprint> {
4243+
TyFingerprint::for_trait_impl(&self.ty)
4244+
}
4245+
42424246
pub(crate) fn canonical(&self) -> Canonical<Ty> {
42434247
hir_ty::replace_errors_with_variables(&self.ty)
42444248
}

crates/ide-completion/src/tests/flyimport.rs

+129
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,135 @@ fn main() {
374374
);
375375
}
376376

377+
#[test]
378+
fn trait_method_fuzzy_completion_aware_of_fundamental_boxes() {
379+
let fixture = r#"
380+
//- /fundamental.rs crate:fundamental
381+
#[lang = "owned_box"]
382+
#[fundamental]
383+
pub struct Box<T>(T);
384+
//- /foo.rs crate:foo
385+
pub trait TestTrait {
386+
fn some_method(&self);
387+
}
388+
//- /main.rs crate:main deps:foo,fundamental
389+
struct TestStruct;
390+
391+
impl foo::TestTrait for fundamental::Box<TestStruct> {
392+
fn some_method(&self) {}
393+
}
394+
395+
fn main() {
396+
let t = fundamental::Box(TestStruct);
397+
t.$0
398+
}
399+
"#;
400+
401+
check(
402+
fixture,
403+
expect![[r#"
404+
me some_method() (use foo::TestTrait) fn(&self)
405+
"#]],
406+
);
407+
408+
check_edit(
409+
"some_method",
410+
fixture,
411+
r#"
412+
use foo::TestTrait;
413+
414+
struct TestStruct;
415+
416+
impl foo::TestTrait for fundamental::Box<TestStruct> {
417+
fn some_method(&self) {}
418+
}
419+
420+
fn main() {
421+
let t = fundamental::Box(TestStruct);
422+
t.some_method()$0
423+
}
424+
"#,
425+
);
426+
}
427+
428+
#[test]
429+
fn trait_method_fuzzy_completion_aware_of_fundamental_references() {
430+
let fixture = r#"
431+
//- /foo.rs crate:foo
432+
pub trait TestTrait {
433+
fn some_method(&self);
434+
}
435+
//- /main.rs crate:main deps:foo
436+
struct TestStruct;
437+
438+
impl foo::TestTrait for &TestStruct {
439+
fn some_method(&self) {}
440+
}
441+
442+
fn main() {
443+
let t = &TestStruct;
444+
t.$0
445+
}
446+
"#;
447+
448+
check(
449+
fixture,
450+
expect![[r#"
451+
me some_method() (use foo::TestTrait) fn(&self)
452+
"#]],
453+
);
454+
455+
check_edit(
456+
"some_method",
457+
fixture,
458+
r#"
459+
use foo::TestTrait;
460+
461+
struct TestStruct;
462+
463+
impl foo::TestTrait for &TestStruct {
464+
fn some_method(&self) {}
465+
}
466+
467+
fn main() {
468+
let t = &TestStruct;
469+
t.some_method()$0
470+
}
471+
"#,
472+
);
473+
}
474+
475+
#[test]
476+
fn trait_method_fuzzy_completion_aware_of_unit_type() {
477+
let fixture = r#"
478+
//- /test_trait.rs crate:test_trait
479+
pub trait TestInto<T> {
480+
fn into(self) -> T;
481+
}
482+
483+
//- /main.rs crate:main deps:test_trait
484+
struct A;
485+
486+
impl test_trait::TestInto<A> for () {
487+
fn into(self) -> A {
488+
A
489+
}
490+
}
491+
492+
fn main() {
493+
let a = ();
494+
a.$0
495+
}
496+
"#;
497+
498+
check(
499+
fixture,
500+
expect![[r#"
501+
me into() (use test_trait::TestInto) fn(self) -> T
502+
"#]],
503+
);
504+
}
505+
377506
#[test]
378507
fn trait_method_from_alias() {
379508
let fixture = r#"

crates/ide-db/src/imports/import_assets.rs

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Look up accessible paths for items.
22
33
use hir::{
4-
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef, Name,
5-
PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
4+
db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasCrate, ItemInNs,
5+
ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
6+
SemanticsScope, Trait, Type,
67
};
78
use itertools::{EitherOrBoth, Itertools};
89
use rustc_hash::{FxHashMap, FxHashSet};
@@ -517,7 +518,7 @@ fn trait_applicable_items(
517518
let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
518519

519520
let mut required_assoc_items = FxHashSet::default();
520-
let trait_candidates: FxHashSet<_> = items_locator::items_with_name(
521+
let mut trait_candidates: FxHashSet<_> = items_locator::items_with_name(
521522
sema,
522523
current_crate,
523524
trait_candidate.assoc_item_name.clone(),
@@ -538,6 +539,32 @@ fn trait_applicable_items(
538539
})
539540
.collect();
540541

542+
trait_candidates.retain(|&candidate_trait_id| {
543+
// we care about the following cases:
544+
// 1. Trait's definition crate
545+
// 2. Definition crates for all trait's generic arguments
546+
// a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
547+
// `Into<Vec<A>> for ()`` is *not*.
548+
// 3. Receiver type definition crate
549+
// a. This is recursive for fundamental types
550+
let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
551+
let Some(receiver) = trait_candidate.receiver_ty.fingerprint_for_trait_impl() else {
552+
return false;
553+
};
554+
let definitions_exist_in_trait_crate = db
555+
.trait_impls_in_crate(defining_crate_for_trait.into())
556+
.has_impls_for_trait_and_self_ty(candidate_trait_id, receiver);
557+
558+
// this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
559+
// we can avoid a second db lookup.
560+
let definitions_exist_in_receiver_crate = || {
561+
db.trait_impls_in_crate(trait_candidate.receiver_ty.krate(db).into())
562+
.has_impls_for_trait_and_self_ty(candidate_trait_id, receiver)
563+
};
564+
565+
definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
566+
});
567+
541568
let mut located_imports = FxHashSet::default();
542569
let mut trait_import_paths = FxHashMap::default();
543570

0 commit comments

Comments
 (0)