mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
Add quantified trees to reduce autocomplete options
This commit is contained in:
parent
bdbdd83ec1
commit
a946970e2d
16 changed files with 300 additions and 94 deletions
|
@ -12,13 +12,6 @@ pub use type_tree::TypeTree;
|
|||
|
||||
mod tactics;
|
||||
|
||||
/// # Maximum amount of variations to take per type
|
||||
///
|
||||
/// This is to speed up term search as there may be huge amount of variations of arguments for
|
||||
/// function, even when the return type is always the same. The idea is to take first n and call it
|
||||
/// a day.
|
||||
const MAX_VARIATIONS: usize = 10;
|
||||
|
||||
/// Key for lookup table to query new types reached.
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
enum NewTypesKey {
|
||||
|
@ -26,6 +19,52 @@ enum NewTypesKey {
|
|||
StructProjection,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AlternativeTrees {
|
||||
Few(FxHashSet<TypeTree>),
|
||||
Many(Type),
|
||||
}
|
||||
|
||||
impl AlternativeTrees {
|
||||
pub fn new(
|
||||
threshold: usize,
|
||||
ty: Type,
|
||||
trees: impl Iterator<Item = TypeTree>,
|
||||
) -> AlternativeTrees {
|
||||
let mut it = AlternativeTrees::Few(Default::default());
|
||||
it.extend_with_threshold(threshold, ty, trees);
|
||||
it
|
||||
}
|
||||
|
||||
pub fn trees(&self) -> Vec<TypeTree> {
|
||||
match self {
|
||||
AlternativeTrees::Few(trees) => trees.iter().cloned().collect(),
|
||||
AlternativeTrees::Many(ty) => vec![TypeTree::Many(ty.clone())],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend_with_threshold(
|
||||
&mut self,
|
||||
threshold: usize,
|
||||
ty: Type,
|
||||
mut trees: impl Iterator<Item = TypeTree>,
|
||||
) {
|
||||
match self {
|
||||
AlternativeTrees::Few(tts) => {
|
||||
while let Some(it) = trees.next() {
|
||||
if tts.len() > threshold {
|
||||
*self = AlternativeTrees::Many(ty);
|
||||
break;
|
||||
}
|
||||
|
||||
tts.insert(it);
|
||||
}
|
||||
}
|
||||
AlternativeTrees::Many(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Lookup table for term search
|
||||
///
|
||||
/// Lookup table keeps all the state during term search.
|
||||
|
@ -38,7 +77,7 @@ enum NewTypesKey {
|
|||
#[derive(Default, Debug)]
|
||||
struct LookupTable {
|
||||
/// All the `TypeTree`s in "value" produce the type of "key"
|
||||
data: FxHashMap<Type, FxHashSet<TypeTree>>,
|
||||
data: FxHashMap<Type, AlternativeTrees>,
|
||||
/// New types reached since last query by the `NewTypesKey`
|
||||
new_types: FxHashMap<NewTypesKey, Vec<Type>>,
|
||||
/// ScopeDefs that are not interesting any more
|
||||
|
@ -49,6 +88,8 @@ struct LookupTable {
|
|||
rounds_since_sopedef_hit: FxHashMap<ScopeDef, u32>,
|
||||
/// Types queried but not present
|
||||
types_wishlist: FxHashSet<Type>,
|
||||
/// Threshold to squash trees to `Many`
|
||||
many_threshold: usize,
|
||||
}
|
||||
|
||||
impl LookupTable {
|
||||
|
@ -65,7 +106,7 @@ impl LookupTable {
|
|||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(_, tts)| tts.iter().cloned().collect())
|
||||
.map(|(_, tts)| tts.trees())
|
||||
}
|
||||
|
||||
/// Same as find but automatically creates shared reference of types in the lookup
|
||||
|
@ -76,7 +117,7 @@ impl LookupTable {
|
|||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(_, tts)| tts.iter().cloned().collect())
|
||||
.map(|(_, tts)| tts.trees())
|
||||
.or_else(|| {
|
||||
self.data
|
||||
.iter()
|
||||
|
@ -84,7 +125,10 @@ impl LookupTable {
|
|||
Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty)
|
||||
})
|
||||
.map(|(_, tts)| {
|
||||
tts.iter().map(|tt| TypeTree::Reference(Box::new(tt.clone()))).collect()
|
||||
tts.trees()
|
||||
.into_iter()
|
||||
.map(|tt| TypeTree::Reference(Box::new(tt)))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -96,9 +140,12 @@ impl LookupTable {
|
|||
/// but they clearly do not unify themselves.
|
||||
fn insert(&mut self, ty: Type, trees: impl Iterator<Item = TypeTree>) {
|
||||
match self.data.get_mut(&ty) {
|
||||
Some(it) => it.extend(trees.take(MAX_VARIATIONS)),
|
||||
Some(it) => it.extend_with_threshold(self.many_threshold, ty, trees),
|
||||
None => {
|
||||
self.data.insert(ty.clone(), trees.take(MAX_VARIATIONS).collect());
|
||||
self.data.insert(
|
||||
ty.clone(),
|
||||
AlternativeTrees::new(self.many_threshold, ty.clone(), trees),
|
||||
);
|
||||
for it in self.new_types.values_mut() {
|
||||
it.push(ty.clone());
|
||||
}
|
||||
|
@ -175,11 +222,15 @@ pub struct TermSearchCtx<'a, DB: HirDatabase> {
|
|||
pub struct TermSearchConfig {
|
||||
/// Enable borrow checking, this guarantees the outputs of the `term_search` to borrow-check
|
||||
pub enable_borrowcheck: bool,
|
||||
/// Indicate when to squash multiple trees to `Many` as there are too many to keep track
|
||||
pub many_alternatives_threshold: usize,
|
||||
/// Depth of the search eg. number of cycles to run
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
impl Default for TermSearchConfig {
|
||||
fn default() -> Self {
|
||||
Self { enable_borrowcheck: true }
|
||||
Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 5 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +276,7 @@ pub fn term_search<DB: HirDatabase>(ctx: TermSearchCtx<'_, DB>) -> Vec<TypeTree>
|
|||
|
||||
let mut solution_found = !solutions.is_empty();
|
||||
|
||||
for _ in 0..5 {
|
||||
for _ in 0..ctx.config.depth {
|
||||
lookup.new_round();
|
||||
|
||||
solutions.extend(tactics::type_constructor(&ctx, &defs, &mut lookup));
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
|||
|
||||
use crate::term_search::{TermSearchConfig, TypeTree};
|
||||
|
||||
use super::{LookupTable, NewTypesKey, TermSearchCtx, MAX_VARIATIONS};
|
||||
use super::{LookupTable, NewTypesKey, TermSearchCtx};
|
||||
|
||||
/// # Trivial tactic
|
||||
///
|
||||
|
@ -194,7 +194,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
param_trees
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.take(MAX_VARIATIONS)
|
||||
.map(|params| TypeTree::Variant {
|
||||
variant,
|
||||
generics: generics.clone(),
|
||||
|
@ -315,7 +314,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
param_trees
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.take(MAX_VARIATIONS)
|
||||
.map(|params| TypeTree::Struct {
|
||||
strukt: *it,
|
||||
generics: generics.clone(),
|
||||
|
@ -440,7 +438,6 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
|
|||
param_trees
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.take(MAX_VARIATIONS)
|
||||
.map(|params| TypeTree::Function {
|
||||
func: *it,
|
||||
generics: generics.clone(),
|
||||
|
@ -603,7 +600,6 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
let fn_trees: Vec<TypeTree> = std::iter::once(target_type_trees)
|
||||
.chain(param_trees.into_iter())
|
||||
.multi_cartesian_product()
|
||||
.take(MAX_VARIATIONS)
|
||||
.map(|params| TypeTree::Function { func: it, generics: Vec::new(), params })
|
||||
.collect();
|
||||
|
||||
|
@ -822,7 +818,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
param_trees
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.take(MAX_VARIATIONS)
|
||||
.map(|params| TypeTree::Function {
|
||||
func: it,
|
||||
generics: generics.clone(),
|
||||
|
|
|
@ -109,6 +109,8 @@ pub enum TypeTree {
|
|||
Field { type_tree: Box<TypeTree>, field: Field },
|
||||
/// Passing type as reference (with `&`)
|
||||
Reference(Box<TypeTree>),
|
||||
/// Indicates possibility of many different options that all evaluate to `ty`
|
||||
Many(Type),
|
||||
}
|
||||
|
||||
impl TypeTree {
|
||||
|
@ -117,7 +119,11 @@ impl TypeTree {
|
|||
/// Note that trait imports are not added to generated code.
|
||||
/// To make sure that the code is valid, callee has to also ensure that all the traits listed
|
||||
/// by `traits_used` method are also imported.
|
||||
pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String {
|
||||
pub fn gen_source_code(
|
||||
&self,
|
||||
sema_scope: &SemanticsScope<'_>,
|
||||
many_formatter: &mut dyn FnMut(&Type) -> String,
|
||||
) -> String {
|
||||
let db = sema_scope.db;
|
||||
match self {
|
||||
TypeTree::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
|
||||
|
@ -128,9 +134,15 @@ impl TypeTree {
|
|||
TypeTree::Function { func, params, .. } => {
|
||||
if let Some(self_param) = func.self_param(db) {
|
||||
let func_name = func.name(db).display(db.upcast()).to_string();
|
||||
let target = params.first().expect("no self param").gen_source_code(sema_scope);
|
||||
let args =
|
||||
params.iter().skip(1).map(|f| f.gen_source_code(sema_scope)).join(", ");
|
||||
let target = params
|
||||
.first()
|
||||
.expect("no self param")
|
||||
.gen_source_code(sema_scope, many_formatter);
|
||||
let args = params
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|f| f.gen_source_code(sema_scope, many_formatter))
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).unwrap().containing_trait_or_trait_impl(db) {
|
||||
Some(trait_) => {
|
||||
|
@ -149,7 +161,10 @@ impl TypeTree {
|
|||
None => format!("{target}.{func_name}({args})"),
|
||||
}
|
||||
} else {
|
||||
let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", ");
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| f.gen_source_code(sema_scope, many_formatter))
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).map(|it| it.container(db)) {
|
||||
Some(container) => {
|
||||
|
@ -194,7 +209,10 @@ impl TypeTree {
|
|||
};
|
||||
let inner = match variant.kind(db) {
|
||||
StructKind::Tuple => {
|
||||
let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", ");
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| f.gen_source_code(sema_scope, many_formatter))
|
||||
.join(", ");
|
||||
format!("{generics_str}({args})")
|
||||
}
|
||||
StructKind::Record => {
|
||||
|
@ -206,7 +224,7 @@ impl TypeTree {
|
|||
format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()).to_string(),
|
||||
a.gen_source_code(sema_scope)
|
||||
a.gen_source_code(sema_scope, many_formatter)
|
||||
)
|
||||
})
|
||||
.join(", ");
|
||||
|
@ -222,7 +240,10 @@ impl TypeTree {
|
|||
let generics = non_default_generics(db, (*strukt).into(), generics);
|
||||
let inner = match strukt.kind(db) {
|
||||
StructKind::Tuple => {
|
||||
let args = params.iter().map(|a| a.gen_source_code(sema_scope)).join(", ");
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|a| a.gen_source_code(sema_scope, many_formatter))
|
||||
.join(", ");
|
||||
format!("({args})")
|
||||
}
|
||||
StructKind::Record => {
|
||||
|
@ -234,7 +255,7 @@ impl TypeTree {
|
|||
format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()).to_string(),
|
||||
a.gen_source_code(sema_scope)
|
||||
a.gen_source_code(sema_scope, many_formatter)
|
||||
)
|
||||
})
|
||||
.join(", ");
|
||||
|
@ -254,14 +275,15 @@ impl TypeTree {
|
|||
format!("{prefix}{inner}")
|
||||
}
|
||||
TypeTree::Field { type_tree, field } => {
|
||||
let strukt = type_tree.gen_source_code(sema_scope);
|
||||
let strukt = type_tree.gen_source_code(sema_scope, many_formatter);
|
||||
let field = field.name(db).display(db.upcast()).to_string();
|
||||
format!("{strukt}.{field}")
|
||||
}
|
||||
TypeTree::Reference(type_tree) => {
|
||||
let inner = type_tree.gen_source_code(sema_scope);
|
||||
let inner = type_tree.gen_source_code(sema_scope, many_formatter);
|
||||
format!("&{inner}")
|
||||
}
|
||||
TypeTree::Many(ty) => many_formatter(ty),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,6 +314,7 @@ impl TypeTree {
|
|||
field.ty_with_generics(db, type_tree.ty(db).type_arguments())
|
||||
}
|
||||
TypeTree::Reference(it) => it.ty(db),
|
||||
TypeTree::Many(ty) => ty.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue