mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 04:44:57 +00:00
Cleanup term search related changes
This commit is contained in:
parent
88964c0b6a
commit
125791386d
26 changed files with 590 additions and 516 deletions
|
@ -2,7 +2,10 @@
|
|||
|
||||
use hir_def::find_path::PrefixKind;
|
||||
use hir_expand::mod_path::ModPath;
|
||||
use hir_ty::{db::HirDatabase, display::HirDisplay};
|
||||
use hir_ty::{
|
||||
db::HirDatabase,
|
||||
display::{DisplaySourceCodeError, HirDisplay},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
|
@ -48,9 +51,10 @@ fn mod_item_path_str(
|
|||
def: &ModuleDef,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> String {
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude);
|
||||
path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap()
|
||||
path.map(|it| it.display(sema_scope.db.upcast()).to_string())
|
||||
.ok_or(DisplaySourceCodeError::PathNotFound)
|
||||
}
|
||||
|
||||
/// Helper function to get path to `Type`
|
||||
|
@ -59,30 +63,34 @@ fn type_path(
|
|||
ty: &Type,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> String {
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let db = sema_scope.db;
|
||||
let m = sema_scope.module();
|
||||
|
||||
match ty.as_adt() {
|
||||
Some(adt) => {
|
||||
let ty_name = ty.display(db).to_string();
|
||||
let ty_name = ty.display_source_code(db, m.id, true)?;
|
||||
|
||||
let mut path =
|
||||
mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude)
|
||||
.unwrap();
|
||||
path.pop_segment();
|
||||
let path = path.display(db.upcast()).to_string();
|
||||
match path.is_empty() {
|
||||
let res = match path.is_empty() {
|
||||
true => ty_name,
|
||||
false => format!("{path}::{ty_name}"),
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
None => ty.display(db).to_string(),
|
||||
None => ty.display_source_code(db, m.id, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to filter out generic parameters that are default
|
||||
fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]) -> Vec<Type> {
|
||||
def.type_params(db)
|
||||
def.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.as_type_param(db))
|
||||
.zip(generics)
|
||||
.filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg))
|
||||
.map(|(_, arg)| arg.clone())
|
||||
|
@ -150,28 +158,30 @@ impl Expr {
|
|||
many_formatter: &mut dyn FnMut(&Type) -> String,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> String {
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let db = sema_scope.db;
|
||||
let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude);
|
||||
match self {
|
||||
Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
|
||||
Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
|
||||
Expr::Local(it) => return it.name(db).display(db.upcast()).to_string(),
|
||||
Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(),
|
||||
Expr::FamousType { value, .. } => return value.to_string(),
|
||||
Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()),
|
||||
Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()),
|
||||
Expr::FamousType { value, .. } => Ok(value.to_string()),
|
||||
Expr::Function { func, params, .. } => {
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| {
|
||||
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).map(|it| it.container(db)) {
|
||||
Some(container) => {
|
||||
let container_name = match container {
|
||||
crate::AssocItemContainer::Trait(trait_) => {
|
||||
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))
|
||||
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?
|
||||
}
|
||||
crate::AssocItemContainer::Impl(imp) => {
|
||||
let self_ty = imp.self_ty(db);
|
||||
|
@ -190,17 +200,17 @@ impl Expr {
|
|||
}
|
||||
};
|
||||
let fn_name = func.name(db).display(db.upcast()).to_string();
|
||||
format!("{container_name}::{fn_name}({args})",)
|
||||
Ok(format!("{container_name}::{fn_name}({args})"))
|
||||
}
|
||||
None => {
|
||||
let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func));
|
||||
format!("{fn_name}({args})",)
|
||||
let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func))?;
|
||||
Ok(format!("{fn_name}({args})"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Method { func, target, params, .. } => {
|
||||
if target.contains_many_in_illegal_pos() {
|
||||
return many_formatter(&target.ty(db));
|
||||
return Ok(many_formatter(&target.ty(db)));
|
||||
}
|
||||
|
||||
let func_name = func.name(db).display(db.upcast()).to_string();
|
||||
|
@ -210,28 +220,31 @@ impl Expr {
|
|||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
);
|
||||
)?;
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| {
|
||||
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) {
|
||||
match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) {
|
||||
Some(trait_) => {
|
||||
let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_));
|
||||
let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?;
|
||||
let target = match self_param.access(db) {
|
||||
crate::Access::Shared => format!("&{target}"),
|
||||
crate::Access::Exclusive => format!("&mut {target}"),
|
||||
crate::Access::Owned => target,
|
||||
};
|
||||
match args.is_empty() {
|
||||
let res = match args.is_empty() {
|
||||
true => format!("{trait_name}::{func_name}({target})",),
|
||||
false => format!("{trait_name}::{func_name}({target}, {args})",),
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
None => format!("{target}.{func_name}({args})"),
|
||||
None => Ok(format!("{target}.{func_name}({args})")),
|
||||
}
|
||||
}
|
||||
Expr::Variant { variant, generics, params } => {
|
||||
|
@ -242,6 +255,8 @@ impl Expr {
|
|||
let generics = generics
|
||||
.iter()
|
||||
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("::<{generics}>")
|
||||
}
|
||||
|
@ -258,6 +273,8 @@ impl Expr {
|
|||
prefer_prelude,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("{generics_str}({args})")
|
||||
}
|
||||
|
@ -267,25 +284,28 @@ impl Expr {
|
|||
.iter()
|
||||
.zip(fields.iter())
|
||||
.map(|(a, f)| {
|
||||
format!(
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()).to_string(),
|
||||
f.name(db).display(db.upcast()),
|
||||
a.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude
|
||||
)
|
||||
)
|
||||
)?
|
||||
);
|
||||
Ok(tmp)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("{generics_str}{{ {args} }}")
|
||||
}
|
||||
StructKind::Unit => generics_str,
|
||||
};
|
||||
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant));
|
||||
format!("{prefix}{inner}")
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant))?;
|
||||
Ok(format!("{prefix}{inner}"))
|
||||
}
|
||||
Expr::Struct { strukt, generics, params } => {
|
||||
let generics = non_default_generics(db, (*strukt).into(), generics);
|
||||
|
@ -301,6 +321,8 @@ impl Expr {
|
|||
prefer_prelude,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("({args})")
|
||||
}
|
||||
|
@ -310,17 +332,20 @@ impl Expr {
|
|||
.iter()
|
||||
.zip(fields.iter())
|
||||
.map(|(a, f)| {
|
||||
format!(
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()).to_string(),
|
||||
f.name(db).display(db.upcast()),
|
||||
a.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude
|
||||
)
|
||||
)
|
||||
)?
|
||||
);
|
||||
Ok(tmp)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!(" {{ {args} }}")
|
||||
}
|
||||
|
@ -330,35 +355,45 @@ impl Expr {
|
|||
let generics = generics
|
||||
.iter()
|
||||
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("::<{generics}>")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)));
|
||||
format!("{prefix}{inner}")
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?;
|
||||
Ok(format!("{prefix}{inner}"))
|
||||
}
|
||||
Expr::Field { expr, field } => {
|
||||
if expr.contains_many_in_illegal_pos() {
|
||||
return many_formatter(&expr.ty(db));
|
||||
return Ok(many_formatter(&expr.ty(db)));
|
||||
}
|
||||
|
||||
let strukt =
|
||||
expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude);
|
||||
let strukt = expr.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)?;
|
||||
let field = field.name(db).display(db.upcast()).to_string();
|
||||
format!("{strukt}.{field}")
|
||||
Ok(format!("{strukt}.{field}"))
|
||||
}
|
||||
Expr::Reference(expr) => {
|
||||
if expr.contains_many_in_illegal_pos() {
|
||||
return many_formatter(&expr.ty(db));
|
||||
return Ok(many_formatter(&expr.ty(db)));
|
||||
}
|
||||
|
||||
let inner =
|
||||
expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude);
|
||||
format!("&{inner}")
|
||||
let inner = expr.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)?;
|
||||
Ok(format!("&{inner}"))
|
||||
}
|
||||
Expr::Many(ty) => many_formatter(ty),
|
||||
Expr::Many(ty) => Ok(many_formatter(ty)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,10 +415,10 @@ impl Expr {
|
|||
target.ty(db).type_arguments().chain(generics.iter().cloned()),
|
||||
),
|
||||
Expr::Variant { variant, generics, .. } => {
|
||||
variant.parent_enum(db).ty_with_args(db, generics.iter().cloned())
|
||||
Adt::from(variant.parent_enum(db)).ty_with_args(db, generics.iter().cloned())
|
||||
}
|
||||
Expr::Struct { strukt, generics, .. } => {
|
||||
strukt.ty_with_args(db, generics.iter().cloned())
|
||||
Adt::from(*strukt).ty_with_args(db, generics.iter().cloned())
|
||||
}
|
||||
Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()),
|
||||
Expr::Reference(it) => it.ty(db),
|
||||
|
@ -395,16 +430,13 @@ impl Expr {
|
|||
pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec<Trait> {
|
||||
let mut res = Vec::new();
|
||||
|
||||
match self {
|
||||
Expr::Method { func, params, .. } => {
|
||||
res.extend(params.iter().flat_map(|it| it.traits_used(db)));
|
||||
if let Some(it) = func.as_assoc_item(db) {
|
||||
if let Some(it) = it.containing_trait_or_trait_impl(db) {
|
||||
res.push(it);
|
||||
}
|
||||
if let Expr::Method { func, params, .. } = self {
|
||||
res.extend(params.iter().flat_map(|it| it.traits_used(db)));
|
||||
if let Some(it) = func.as_assoc_item(db) {
|
||||
if let Some(it) = it.container_or_implemented_trait(db) {
|
||||
res.push(it);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
res
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
//! Term search
|
||||
|
||||
use hir_def::type_ref::Mutability;
|
||||
use hir_ty::db::HirDatabase;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type};
|
||||
|
||||
mod expr;
|
||||
pub use expr::Expr;
|
||||
|
||||
mod tactics;
|
||||
|
||||
/// Key for lookup table to query new types reached.
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
enum NewTypesKey {
|
||||
ImplMethod,
|
||||
StructProjection,
|
||||
}
|
||||
|
||||
/// Helper enum to squash big number of alternative trees into `Many` variant as there is too many
|
||||
/// to take into account.
|
||||
#[derive(Debug)]
|
||||
enum AlternativeExprs {
|
||||
/// There are few trees, so we keep track of them all
|
||||
Few(FxHashSet<Expr>),
|
||||
/// There are too many trees to keep track of
|
||||
Many,
|
||||
}
|
||||
|
||||
impl AlternativeExprs {
|
||||
/// Construct alternative trees
|
||||
///
|
||||
/// # Arguments
|
||||
/// `threshold` - threshold value for many trees (more than that is many)
|
||||
/// `exprs` - expressions iterator
|
||||
fn new(threshold: usize, exprs: impl Iterator<Item = Expr>) -> AlternativeExprs {
|
||||
let mut it = AlternativeExprs::Few(Default::default());
|
||||
it.extend_with_threshold(threshold, exprs);
|
||||
it
|
||||
}
|
||||
|
||||
/// Get type trees stored in alternative trees (or `Expr::Many` in case of many)
|
||||
///
|
||||
/// # Arguments
|
||||
/// `ty` - Type of expressions queried (this is used to give type to `Expr::Many`)
|
||||
fn exprs(&self, ty: &Type) -> Vec<Expr> {
|
||||
match self {
|
||||
AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(),
|
||||
AlternativeExprs::Many => vec![Expr::Many(ty.clone())],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend alternative expressions
|
||||
///
|
||||
/// # Arguments
|
||||
/// `threshold` - threshold value for many trees (more than that is many)
|
||||
/// `exprs` - expressions iterator
|
||||
fn extend_with_threshold(&mut self, threshold: usize, mut exprs: impl Iterator<Item = Expr>) {
|
||||
match self {
|
||||
AlternativeExprs::Few(tts) => {
|
||||
while let Some(it) = exprs.next() {
|
||||
if tts.len() > threshold {
|
||||
*self = AlternativeExprs::Many;
|
||||
break;
|
||||
}
|
||||
|
||||
tts.insert(it);
|
||||
}
|
||||
}
|
||||
AlternativeExprs::Many => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Lookup table for term search
|
||||
///
|
||||
/// Lookup table keeps all the state during term search.
|
||||
/// This means it knows what types and how are reachable.
|
||||
///
|
||||
/// The secondary functionality for lookup table is to keep track of new types reached since last
|
||||
/// iteration as well as keeping track of which `ScopeDef` items have been used.
|
||||
/// Both of them are to speed up the term search by leaving out types / ScopeDefs that likely do
|
||||
/// not produce any new results.
|
||||
#[derive(Default, Debug)]
|
||||
struct LookupTable {
|
||||
/// All the `Expr`s in "value" produce the type of "key"
|
||||
data: FxHashMap<Type, AlternativeExprs>,
|
||||
/// New types reached since last query by the `NewTypesKey`
|
||||
new_types: FxHashMap<NewTypesKey, Vec<Type>>,
|
||||
/// ScopeDefs that are not interesting any more
|
||||
exhausted_scopedefs: FxHashSet<ScopeDef>,
|
||||
/// ScopeDefs that were used in current round
|
||||
round_scopedef_hits: FxHashSet<ScopeDef>,
|
||||
/// Amount of rounds since scopedef was first used.
|
||||
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 {
|
||||
/// Initialize lookup table
|
||||
fn new(many_threshold: usize) -> Self {
|
||||
let mut res = Self { many_threshold, ..Default::default() };
|
||||
res.new_types.insert(NewTypesKey::ImplMethod, Vec::new());
|
||||
res.new_types.insert(NewTypesKey::StructProjection, Vec::new());
|
||||
res
|
||||
}
|
||||
|
||||
/// Find all `Expr`s that unify with the `ty`
|
||||
fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(t, tts)| tts.exprs(t))
|
||||
}
|
||||
|
||||
/// Same as find but automatically creates shared reference of types in the lookup
|
||||
///
|
||||
/// For example if we have type `i32` in data and we query for `&i32` it map all the type
|
||||
/// trees we have for `i32` with `Expr::Reference` and returns them.
|
||||
fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(t, it)| it.exprs(t))
|
||||
.or_else(|| {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| {
|
||||
Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty)
|
||||
})
|
||||
.map(|(t, it)| {
|
||||
it.exprs(t)
|
||||
.into_iter()
|
||||
.map(|expr| Expr::Reference(Box::new(expr)))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert new type trees for type
|
||||
///
|
||||
/// Note that the types have to be the same, unification is not enough as unification is not
|
||||
/// transitive. For example Vec<i32> and FxHashSet<i32> both unify with Iterator<Item = i32>,
|
||||
/// but they clearly do not unify themselves.
|
||||
fn insert(&mut self, ty: Type, exprs: impl Iterator<Item = Expr>) {
|
||||
match self.data.get_mut(&ty) {
|
||||
Some(it) => it.extend_with_threshold(self.many_threshold, exprs),
|
||||
None => {
|
||||
self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
|
||||
for it in self.new_types.values_mut() {
|
||||
it.push(ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate all the reachable types
|
||||
fn iter_types(&self) -> impl Iterator<Item = Type> + '_ {
|
||||
self.data.keys().cloned()
|
||||
}
|
||||
|
||||
/// Query new types reached since last query by key
|
||||
///
|
||||
/// Create new key if you wish to query it to avoid conflicting with existing queries.
|
||||
fn new_types(&mut self, key: NewTypesKey) -> Vec<Type> {
|
||||
match self.new_types.get_mut(&key) {
|
||||
Some(it) => std::mem::take(it),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more
|
||||
fn mark_exhausted(&mut self, def: ScopeDef) {
|
||||
self.exhausted_scopedefs.insert(def);
|
||||
}
|
||||
|
||||
/// Mark `ScopeDef` as used meaning we managed to produce something useful from it
|
||||
fn mark_fulfilled(&mut self, def: ScopeDef) {
|
||||
self.round_scopedef_hits.insert(def);
|
||||
}
|
||||
|
||||
/// Start new round (meant to be called at the beginning of iteration in `term_search`)
|
||||
///
|
||||
/// This functions marks some `ScopeDef`s as exhausted if there have been
|
||||
/// `MAX_ROUNDS_AFTER_HIT` rounds after first using a `ScopeDef`.
|
||||
fn new_round(&mut self) {
|
||||
for def in &self.round_scopedef_hits {
|
||||
let hits =
|
||||
self.rounds_since_sopedef_hit.entry(*def).and_modify(|n| *n += 1).or_insert(0);
|
||||
const MAX_ROUNDS_AFTER_HIT: u32 = 2;
|
||||
if *hits > MAX_ROUNDS_AFTER_HIT {
|
||||
self.exhausted_scopedefs.insert(*def);
|
||||
}
|
||||
}
|
||||
self.round_scopedef_hits.clear();
|
||||
}
|
||||
|
||||
/// Get exhausted `ScopeDef`s
|
||||
fn exhausted_scopedefs(&self) -> &FxHashSet<ScopeDef> {
|
||||
&self.exhausted_scopedefs
|
||||
}
|
||||
|
||||
/// Types queried but not found
|
||||
fn take_types_wishlist(&mut self) -> FxHashSet<Type> {
|
||||
std::mem::take(&mut self.types_wishlist)
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for the `term_search` function
|
||||
#[derive(Debug)]
|
||||
pub struct TermSearchCtx<'a, DB: HirDatabase> {
|
||||
/// Semantics for the program
|
||||
pub sema: &'a Semantics<'a, DB>,
|
||||
/// Semantic scope, captures context for the term search
|
||||
pub scope: &'a SemanticsScope<'a>,
|
||||
/// Target / expected output type
|
||||
pub goal: Type,
|
||||
/// Configuration for term search
|
||||
pub config: TermSearchConfig,
|
||||
}
|
||||
|
||||
/// Configuration options for the term search
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
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, many_alternatives_threshold: 1, depth: 6 }
|
||||
}
|
||||
}
|
||||
|
||||
/// # Term search
|
||||
///
|
||||
/// Search for terms (expressions) that unify with the `goal` type.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for term search
|
||||
///
|
||||
/// Internally this function uses Breadth First Search to find path to `goal` type.
|
||||
/// The general idea is following:
|
||||
/// 1. Populate lookup (frontier for BFS) from values (local variables, statics, constants, etc)
|
||||
/// as well as from well knows values (such as `true/false` and `()`)
|
||||
/// 2. Iteratively expand the frontier (or contents of the lookup) by trying different type
|
||||
/// transformation tactics. For example functions take as from set of types (arguments) to some
|
||||
/// type (return type). Other transformations include methods on type, type constructors and
|
||||
/// projections to struct fields (field access).
|
||||
/// 3. Once we manage to find path to type we are interested in we continue for single round to see
|
||||
/// if we can find more paths that take us to the `goal` type.
|
||||
/// 4. Return all the paths (type trees) that take us to the `goal` type.
|
||||
///
|
||||
/// Note that there are usually more ways we can get to the `goal` type but some are discarded to
|
||||
/// reduce the memory consumption. It is also unlikely anyone is willing ti browse through
|
||||
/// thousands of possible responses so we currently take first 10 from every tactic.
|
||||
pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
|
||||
let module = ctx.scope.module();
|
||||
let mut defs = FxHashSet::default();
|
||||
defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module)));
|
||||
|
||||
ctx.scope.process_all_names(&mut |_, def| {
|
||||
defs.insert(def);
|
||||
});
|
||||
|
||||
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold);
|
||||
|
||||
// Try trivial tactic first, also populates lookup table
|
||||
let mut solutions: Vec<Expr> = tactics::trivial(ctx, &defs, &mut lookup).collect();
|
||||
// Use well known types tactic before iterations as it does not depend on other tactics
|
||||
solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
|
||||
|
||||
for _ in 0..ctx.config.depth {
|
||||
lookup.new_round();
|
||||
|
||||
solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::free_function(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup));
|
||||
|
||||
// Discard not interesting `ScopeDef`s for speedup
|
||||
for def in lookup.exhausted_scopedefs() {
|
||||
defs.remove(def);
|
||||
}
|
||||
}
|
||||
|
||||
solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
|
||||
}
|
|
@ -16,7 +16,7 @@ use rustc_hash::FxHashSet;
|
|||
|
||||
use crate::{
|
||||
Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, ModuleDef, ScopeDef, Type,
|
||||
Variant,
|
||||
TypeParam, Variant,
|
||||
};
|
||||
|
||||
use crate::term_search::{Expr, TermSearchConfig};
|
||||
|
@ -82,7 +82,7 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
|
|||
return None;
|
||||
}
|
||||
|
||||
ty.could_unify_with_deeply(db, &ctx.goal).then(|| expr)
|
||||
ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -118,11 +118,15 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
}
|
||||
|
||||
let generics = GenericDef::from(variant.parent_enum(db));
|
||||
|
||||
// Ignore enums with const generics
|
||||
if !generics.const_params(db).is_empty() {
|
||||
let Some(type_params) = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()
|
||||
else {
|
||||
// Ignore enums with const generics
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// We currently do not check lifetime bounds so ignore all types that have something to do
|
||||
// with them
|
||||
|
@ -130,9 +134,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
return Vec::new();
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now
|
||||
let type_params = generics.type_params(db);
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
|
@ -154,13 +155,10 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => ty,
|
||||
None => g.next().expect("Missing type param"),
|
||||
})
|
||||
.map(|it| it.default(db).unwrap_or_else(|| g.next().expect("No generic")))
|
||||
.collect();
|
||||
|
||||
let enum_ty = parent_enum.ty_with_args(db, generics.iter().cloned());
|
||||
let enum_ty = Adt::from(parent_enum).ty_with_args(db, generics.iter().cloned());
|
||||
|
||||
// Allow types with generics only if they take us straight to goal for
|
||||
// performance reasons
|
||||
|
@ -212,9 +210,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
let exprs: Vec<(Type, Vec<Expr>)> = enum_
|
||||
.variants(db)
|
||||
.into_iter()
|
||||
.flat_map(|it| {
|
||||
variant_helper(db, lookup, enum_.clone(), it, &ctx.goal, &ctx.config)
|
||||
})
|
||||
.flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.goal, &ctx.config))
|
||||
.collect();
|
||||
|
||||
if !exprs.is_empty() {
|
||||
|
@ -231,10 +227,12 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
|
||||
let generics = GenericDef::from(*it);
|
||||
|
||||
// Ignore enums with const generics
|
||||
if !generics.const_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Ignore const params for now
|
||||
let type_params = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// We currently do not check lifetime bounds so ignore all types that have something to do
|
||||
// with them
|
||||
|
@ -242,8 +240,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
return None;
|
||||
}
|
||||
|
||||
let type_params = generics.type_params(db);
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
|
@ -265,12 +261,13 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => ty,
|
||||
None => g.next().expect("Missing type param"),
|
||||
.map(|it| {
|
||||
it.default(db)
|
||||
.unwrap_or_else(|| g.next().expect("Missing type param"))
|
||||
})
|
||||
.collect();
|
||||
let struct_ty = it.ty_with_args(db, generics.iter().cloned());
|
||||
|
||||
let struct_ty = Adt::from(*it).ty_with_args(db, generics.iter().cloned());
|
||||
|
||||
// Allow types with generics only if they take us straight to goal for
|
||||
// performance reasons
|
||||
|
@ -324,7 +321,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
|||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -352,18 +349,18 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
|
|||
ScopeDef::ModuleDef(ModuleDef::Function(it)) => {
|
||||
let generics = GenericDef::from(*it);
|
||||
|
||||
// Skip functions that require const generics
|
||||
if !generics.const_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Ignore const params for now
|
||||
let type_params = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore lifetimes as we do not check them
|
||||
if !generics.lifetime_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let type_params = generics.type_params(db);
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
|
@ -391,10 +388,14 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
|
|||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => ty,
|
||||
None => g.next().expect("Missing type param"),
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(db, generics.iter().cloned());
|
||||
// Filter out private and unsafe functions
|
||||
|
@ -409,13 +410,13 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
|
|||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_generics(db, generics.iter().cloned())
|
||||
.params_without_self_with_args(db, generics.iter().cloned())
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
let ty = field.ty();
|
||||
match ty.is_mutable_reference() {
|
||||
true => None,
|
||||
false => lookup.find_autoref(db, &ty),
|
||||
false => lookup.find_autoref(db, ty),
|
||||
}
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
@ -447,7 +448,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
|
|||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -487,11 +488,19 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
let fn_generics = GenericDef::from(it);
|
||||
let imp_generics = GenericDef::from(imp);
|
||||
|
||||
// Ignore impl if it has const type arguments
|
||||
if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// Ignore const params for now
|
||||
let imp_type_params = imp_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore const params for now
|
||||
let fn_type_params = fn_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore all functions that have something to do with lifetimes as we don't check them
|
||||
if !fn_generics.lifetime_params(db).is_empty() {
|
||||
|
@ -508,9 +517,6 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
return None;
|
||||
}
|
||||
|
||||
let imp_type_params = imp_generics.type_params(db);
|
||||
let fn_type_params = fn_generics.type_params(db);
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
|
@ -544,10 +550,14 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => ty,
|
||||
None => g.next().expect("Missing type param"),
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(
|
||||
db,
|
||||
|
@ -579,16 +589,16 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_generics(
|
||||
.params_without_self_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|field| lookup.find_autoref(db, &field.ty()))
|
||||
.map(|field| lookup.find_autoref(db, field.ty()))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let fn_exprs: Vec<Expr> = std::iter::once(target_type_exprs)
|
||||
.chain(param_exprs.into_iter())
|
||||
.chain(param_exprs)
|
||||
.multi_cartesian_product()
|
||||
.map(|params| {
|
||||
let mut params = params.into_iter();
|
||||
|
@ -609,7 +619,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
|
|||
Some(exprs)
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -647,7 +657,7 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>(
|
|||
Some((filed_ty, exprs))
|
||||
})
|
||||
})
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -719,11 +729,19 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
let fn_generics = GenericDef::from(it);
|
||||
let imp_generics = GenericDef::from(imp);
|
||||
|
||||
// Ignore impl if it has const type arguments
|
||||
if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// Ignore const params for now
|
||||
let imp_type_params = imp_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore const params for now
|
||||
let fn_type_params = fn_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore all functions that have something to do with lifetimes as we don't check them
|
||||
if !fn_generics.lifetime_params(db).is_empty()
|
||||
|
@ -742,9 +760,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
return None;
|
||||
}
|
||||
|
||||
let imp_type_params = imp_generics.type_params(db);
|
||||
let fn_type_params = fn_generics.type_params(db);
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
|
@ -778,10 +793,17 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => ty,
|
||||
None => g.next().expect("Missing type param"),
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
it.trait_bounds(db)
|
||||
.into_iter()
|
||||
.all(|bound| generic.impls_trait(db, bound, &[]));
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(
|
||||
db,
|
||||
|
@ -801,12 +823,12 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_generics(
|
||||
.params_without_self_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|field| lookup.find_autoref(db, &field.ty()))
|
||||
.map(|field| lookup.find_autoref(db, field.ty()))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
// Note that we need special case for 0 param constructors because of multi cartesian
|
||||
|
@ -832,6 +854,6 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
|||
Some(exprs)
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue