Expand target for autocompletion

This commit is contained in:
Tavo Annus 2024-01-06 15:17:16 +02:00
parent a946970e2d
commit 0b838e3e23
14 changed files with 450 additions and 466 deletions

View file

@ -12,7 +12,7 @@ authors = ["rust-analyzer team"]
[profile.dev]
# Disabling debug info speeds up builds a bunch,
# and we don't rely on it for debugging that much.
debug = 2
debug = 0
[profile.dev.package]
# These speed up local tests.

View file

@ -106,10 +106,6 @@ pub fn could_unify_deeply(
let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
let ty1_with_vars = table.normalize_associated_types_in(ty1_with_vars);
let ty2_with_vars = table.normalize_associated_types_in(ty2_with_vars);
// table.resolve_obligations_as_possible();
// table.propagate_diverging_flag();
// let ty1_with_vars = table.resolve_completely(ty1_with_vars);
// let ty2_with_vars = table.resolve_completely(ty2_with_vars);
table.unify_deeply(&ty1_with_vars, &ty2_with_vars)
}

View file

@ -1085,20 +1085,21 @@ impl Field {
Type::new(db, var_id, ty)
}
pub fn ty_with_generics(
&self,
db: &dyn HirDatabase,
mut generics: impl Iterator<Item = Type>,
) -> Type {
pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
let var_id = self.parent.into();
let def_id: AdtId = match self.parent {
VariantDef::Struct(it) => it.id.into(),
VariantDef::Union(it) => it.id.into(),
VariantDef::Variant(it) => it.parent_enum(db).id.into(),
};
let mut generics = generics.map(|it| it.ty.clone());
let substs = TyBuilder::subst_for_def(db, def_id, None)
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
.fill(|x| {
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
match x {
ParamKind::Type => ty.cast(Interner),
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
}
})
.build();
let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs);
@ -1158,14 +1159,15 @@ impl Struct {
Type::from_def(db, self.id)
}
pub fn ty_with_generics(
self,
db: &dyn HirDatabase,
mut generics: impl Iterator<Item = Type>,
) -> Type {
pub fn ty_with_args(self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
let mut generics = generics.map(|it| it.ty.clone());
let substs = TyBuilder::subst_for_def(db, self.id, None)
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
.fill(|x| {
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
match x {
ParamKind::Type => ty.cast(Interner),
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
}
})
.build();
let ty = db.ty(self.id.into()).substitute(Interner, &substs);
@ -1271,16 +1273,18 @@ impl Enum {
Type::from_def(db, self.id)
}
pub fn ty_with_generics(
&self,
db: &dyn HirDatabase,
mut generics: impl Iterator<Item = Type>,
) -> Type {
pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
let mut generics = generics.map(|it| it.ty.clone());
let substs = TyBuilder::subst_for_def(db, self.id, None)
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
.fill(|x| {
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
match x {
ParamKind::Type => ty.cast(Interner),
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
}
})
.build();
let ty = db.ty(self.id.into()).substitute(Interner, &substs);
Type::new(db, self.id, ty)
}
@ -1854,10 +1858,10 @@ impl Function {
Type::new_with_resolver_inner(db, &resolver, ty)
}
pub fn ret_type_with_generics(
pub fn ret_type_with_args(
self,
db: &dyn HirDatabase,
mut generics: impl Iterator<Item = Type>,
generics: impl Iterator<Item = Type>,
) -> Type {
let resolver = self.id.resolver(db.upcast());
let parent_id: Option<GenericDefId> = match self.id.lookup(db.upcast()).container {
@ -1865,22 +1869,18 @@ impl Function {
ItemContainerId::TraitId(it) => Some(it.into()),
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
};
let parent_substs = parent_id.map(|id| {
TyBuilder::subst_for_def(db, id, None)
.fill(|_| {
GenericArg::new(
Interner,
GenericArgData::Ty(generics.next().unwrap().ty.clone()),
)
})
.build()
});
let mut generics = generics.map(|it| it.ty.clone());
let mut filler = |x: &_| {
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
match x {
ParamKind::Type => ty.cast(Interner),
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
}
};
let substs = TyBuilder::subst_for_def(db, self.id, parent_substs)
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
})
.build();
let parent_substs =
parent_id.map(|id| TyBuilder::subst_for_def(db, id, None).fill(&mut filler).build());
let substs = TyBuilder::subst_for_def(db, self.id, parent_substs).fill(&mut filler).build();
let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
let ty = callable_sig.ret().clone();
@ -2197,11 +2197,7 @@ impl SelfParam {
Type { env: environment, ty }
}
pub fn ty_with_generics(
&self,
db: &dyn HirDatabase,
mut generics: impl Iterator<Item = Type>,
) -> Type {
pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
let parent_id: GenericDefId = match self.func.lookup(db.upcast()).container {
ItemContainerId::ImplId(it) => it.into(),
ItemContainerId::TraitId(it) => it.into(),
@ -2210,16 +2206,18 @@ impl SelfParam {
}
};
let parent_substs = TyBuilder::subst_for_def(db, parent_id, None)
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
})
.build();
let substs = TyBuilder::subst_for_def(db, self.func, Some(parent_substs))
.fill(|_| {
GenericArg::new(Interner, GenericArgData::Ty(generics.next().unwrap().ty.clone()))
})
.build();
let mut generics = generics.map(|it| it.ty.clone());
let mut filler = |x: &_| {
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
match x {
ParamKind::Type => ty.cast(Interner),
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
}
};
let parent_substs = TyBuilder::subst_for_def(db, parent_id, None).fill(&mut filler).build();
let substs =
TyBuilder::subst_for_def(db, self.func, Some(parent_substs)).fill(&mut filler).build();
let callable_sig =
db.callable_item_signature(self.func.into()).substitute(Interner, &substs);
let environment = db.trait_environment(self.func.into());

View file

@ -88,7 +88,7 @@ fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]
/// So in short it pretty much gives us a way to get type `Option<i32>` using the items we have in
/// scope.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum TypeTree {
pub enum Expr {
/// Constant
Const(Const),
/// Static variable
@ -99,21 +99,23 @@ pub enum TypeTree {
ConstParam(ConstParam),
/// Well known type (such as `true` for bool)
FamousType { ty: Type, value: &'static str },
/// Function or method call
Function { func: Function, generics: Vec<Type>, params: Vec<TypeTree> },
/// Function call (does not take self param)
Function { func: Function, generics: Vec<Type>, params: Vec<Expr> },
/// Method call (has self param)
Method { func: Function, generics: Vec<Type>, target: Box<Expr>, params: Vec<Expr> },
/// Enum variant construction
Variant { variant: Variant, generics: Vec<Type>, params: Vec<TypeTree> },
Variant { variant: Variant, generics: Vec<Type>, params: Vec<Expr> },
/// Struct construction
Struct { strukt: Struct, generics: Vec<Type>, params: Vec<TypeTree> },
Struct { strukt: Struct, generics: Vec<Type>, params: Vec<Expr> },
/// Struct field access
Field { type_tree: Box<TypeTree>, field: Field },
Field { expr: Box<Expr>, field: Field },
/// Passing type as reference (with `&`)
Reference(Box<TypeTree>),
Reference(Box<Expr>),
/// Indicates possibility of many different options that all evaluate to `ty`
Many(Type),
}
impl TypeTree {
impl Expr {
/// Generate source code for type tree.
///
/// Note that trait imports are not added to generated code.
@ -126,45 +128,14 @@ impl TypeTree {
) -> String {
let db = sema_scope.db;
match self {
TypeTree::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
TypeTree::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
TypeTree::Local(it) => return it.name(db).display(db.upcast()).to_string(),
TypeTree::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(),
TypeTree::FamousType { value, .. } => return value.to_string(),
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, 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_) => {
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() {
true => format!("{trait_name}::{func_name}({target})",),
false => format!("{trait_name}::{func_name}({target}, {args})",),
}
}
None => format!("{target}.{func_name}({args})"),
}
} else {
let args = params
.iter()
.map(|f| f.gen_source_code(sema_scope, many_formatter))
.join(", ");
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::Function { func, params, .. } => {
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) => {
@ -179,9 +150,7 @@ impl TypeTree {
.as_adt()
.and_then(|adt| mod_item_path(sema_scope, &adt.into()))
{
Some(path) => {
path.display(sema_scope.db.upcast()).to_string()
}
Some(path) => path.display(sema_scope.db.upcast()).to_string(),
None => self_ty.display(db).to_string(),
}
}
@ -190,14 +159,39 @@ impl TypeTree {
format!("{container_name}::{fn_name}({args})",)
}
None => {
let fn_name =
mod_item_path_str(sema_scope, &ModuleDef::Function(*func));
let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func));
format!("{fn_name}({args})",)
}
}
}
Expr::Method { func, target, params, .. } => {
if target.contains_many_in_illegal_pos() {
return many_formatter(&target.ty(db));
}
TypeTree::Variant { variant, generics, params } => {
let func_name = func.name(db).display(db.upcast()).to_string();
let self_param = func.self_param(db).unwrap();
let target = target.gen_source_code(sema_scope, many_formatter);
let args =
params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", ");
match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) {
Some(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() {
true => format!("{trait_name}::{func_name}({target})",),
false => format!("{trait_name}::{func_name}({target}, {args})",),
}
}
None => format!("{target}.{func_name}({args})"),
}
}
Expr::Variant { variant, generics, params } => {
let generics = non_default_generics(db, (*variant).into(), generics);
let generics_str = match generics.is_empty() {
true => String::new(),
@ -236,7 +230,7 @@ impl TypeTree {
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant));
format!("{prefix}{inner}")
}
TypeTree::Struct { strukt, generics, params } => {
Expr::Struct { strukt, generics, params } => {
let generics = non_default_generics(db, (*strukt).into(), generics);
let inner = match strukt.kind(db) {
StructKind::Tuple => {
@ -274,16 +268,24 @@ impl TypeTree {
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)));
format!("{prefix}{inner}")
}
TypeTree::Field { type_tree, field } => {
let strukt = type_tree.gen_source_code(sema_scope, many_formatter);
Expr::Field { expr, field } => {
if expr.contains_many_in_illegal_pos() {
return many_formatter(&expr.ty(db));
}
let strukt = expr.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, many_formatter);
Expr::Reference(expr) => {
if expr.contains_many_in_illegal_pos() {
return many_formatter(&expr.ty(db));
}
let inner = expr.gen_source_code(sema_scope, many_formatter);
format!("&{inner}")
}
TypeTree::Many(ty) => many_formatter(ty),
Expr::Many(ty) => many_formatter(ty),
}
}
@ -292,29 +294,27 @@ impl TypeTree {
/// Same as getting the type of root node
pub fn ty(&self, db: &dyn HirDatabase) -> Type {
match self {
TypeTree::Const(it) => it.ty(db),
TypeTree::Static(it) => it.ty(db),
TypeTree::Local(it) => it.ty(db),
TypeTree::ConstParam(it) => it.ty(db),
TypeTree::FamousType { ty, .. } => ty.clone(),
TypeTree::Function { func, generics, params } => match func.has_self_param(db) {
true => func.ret_type_with_generics(
Expr::Const(it) => it.ty(db),
Expr::Static(it) => it.ty(db),
Expr::Local(it) => it.ty(db),
Expr::ConstParam(it) => it.ty(db),
Expr::FamousType { ty, .. } => ty.clone(),
Expr::Function { func, generics, .. } => {
func.ret_type_with_args(db, generics.iter().cloned())
}
Expr::Method { func, generics, target, .. } => func.ret_type_with_args(
db,
params[0].ty(db).type_arguments().chain(generics.iter().cloned()),
target.ty(db).type_arguments().chain(generics.iter().cloned()),
),
false => func.ret_type_with_generics(db, generics.iter().cloned()),
},
TypeTree::Variant { variant, generics, .. } => {
variant.parent_enum(db).ty_with_generics(db, generics.iter().cloned())
Expr::Variant { variant, generics, .. } => {
variant.parent_enum(db).ty_with_args(db, generics.iter().cloned())
}
TypeTree::Struct { strukt, generics, .. } => {
strukt.ty_with_generics(db, generics.iter().cloned())
Expr::Struct { strukt, generics, .. } => {
strukt.ty_with_args(db, generics.iter().cloned())
}
TypeTree::Field { type_tree, field } => {
field.ty_with_generics(db, type_tree.ty(db).type_arguments())
}
TypeTree::Reference(it) => it.ty(db),
TypeTree::Many(ty) => ty.clone(),
Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()),
Expr::Reference(it) => it.ty(db),
Expr::Many(ty) => ty.clone(),
}
}
@ -323,7 +323,7 @@ impl TypeTree {
let mut res = Vec::new();
match self {
TypeTree::Function { func, params, .. } => {
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) {
@ -336,4 +336,28 @@ impl TypeTree {
res
}
/// Check in the tree contains `Expr::Many` variant in illegal place to insert `todo`,
/// `unimplemented` or similar macro
///
/// Some examples are following
/// ```no_compile
/// macro!().foo
/// macro!().bar()
/// &macro!()
/// ```
fn contains_many_in_illegal_pos(&self) -> bool {
match self {
Expr::Method { target, .. } => target.contains_many_in_illegal_pos(),
Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(),
Expr::Reference(target) => target.is_many(),
Expr::Many(_) => true,
_ => false,
}
}
/// Helper function to check if outermost type tree is `Expr::Many` variant
pub fn is_many(&self) -> bool {
matches!(self, Expr::Many(_))
}
}

View file

@ -7,8 +7,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type};
pub mod type_tree;
pub use type_tree::TypeTree;
mod expr;
pub use expr::Expr;
mod tactics;
@ -19,48 +19,57 @@ enum NewTypesKey {
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 AlternativeTrees {
Few(FxHashSet<TypeTree>),
Many(Type),
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 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);
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
}
pub fn trees(&self) -> Vec<TypeTree> {
/// 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 {
AlternativeTrees::Few(trees) => trees.iter().cloned().collect(),
AlternativeTrees::Many(ty) => vec![TypeTree::Many(ty.clone())],
AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(),
AlternativeExprs::Many => vec![Expr::Many(ty.clone())],
}
}
pub fn extend_with_threshold(
&mut self,
threshold: usize,
ty: Type,
mut trees: impl Iterator<Item = TypeTree>,
) {
/// 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 {
AlternativeTrees::Few(tts) => {
while let Some(it) = trees.next() {
AlternativeExprs::Few(tts) => {
while let Some(it) = exprs.next() {
if tts.len() > threshold {
*self = AlternativeTrees::Many(ty);
*self = AlternativeExprs::Many;
break;
}
tts.insert(it);
}
}
AlternativeTrees::Many(_) => (),
AlternativeExprs::Many => (),
}
}
}
@ -76,8 +85,8 @@ impl AlternativeTrees {
/// not produce any new results.
#[derive(Default, Debug)]
struct LookupTable {
/// All the `TypeTree`s in "value" produce the type of "key"
data: FxHashMap<Type, AlternativeTrees>,
/// 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
@ -94,40 +103,40 @@ struct LookupTable {
impl LookupTable {
/// Initialize lookup table
fn new() -> Self {
let mut res: Self = Default::default();
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 `TypeTree`s that unify with the `ty`
fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<TypeTree>> {
/// 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(|(_, tts)| tts.trees())
.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 `TypeTree::Reference` and returns them.
fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<TypeTree>> {
/// 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(|(_, tts)| tts.trees())
.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(|(_, tts)| {
tts.trees()
.map(|(t, it)| {
it.exprs(t)
.into_iter()
.map(|tt| TypeTree::Reference(Box::new(tt)))
.map(|expr| Expr::Reference(Box::new(expr)))
.collect()
})
})
@ -138,14 +147,11 @@ impl LookupTable {
/// 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, trees: impl Iterator<Item = TypeTree>) {
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, ty, trees),
Some(it) => it.extend_with_threshold(self.many_threshold, exprs),
None => {
self.data.insert(
ty.clone(),
AlternativeTrees::new(self.many_threshold, ty.clone(), trees),
);
self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
for it in self.new_types.values_mut() {
it.push(ty.clone());
}
@ -206,6 +212,7 @@ impl LookupTable {
}
/// Context for the `term_search` function
#[derive(Debug)]
pub struct TermSearchCtx<'a, DB: HirDatabase> {
/// Semantics for the program
pub sema: &'a Semantics<'a, DB>,
@ -230,7 +237,7 @@ pub struct TermSearchConfig {
impl Default for TermSearchConfig {
fn default() -> Self {
Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 5 }
Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 }
}
}
@ -239,9 +246,7 @@ impl Default for TermSearchConfig {
/// Search for terms (expressions) that unify with the `goal` type.
///
/// # Arguments
/// * `sema` - Semantics for the program
/// * `scope` - Semantic scope, captures context for the term search
/// * `goal` - Target / expected output type
/// * `ctx` - Context for term search
///
/// Internally this function uses Breadth First Search to find path to `goal` type.
/// The general idea is following:
@ -258,7 +263,7 @@ impl Default for TermSearchConfig {
/// 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<TypeTree> {
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)));
@ -267,30 +272,21 @@ pub fn term_search<DB: HirDatabase>(ctx: TermSearchCtx<'_, DB>) -> Vec<TypeTree>
defs.insert(def);
});
let mut lookup = LookupTable::new();
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold);
// Try trivial tactic first, also populates lookup table
let mut solutions: Vec<TypeTree> = tactics::trivial(&ctx, &defs, &mut lookup).collect();
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));
let mut solution_found = !solutions.is_empty();
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));
// Break after 1 round after successful solution
if solution_found {
break;
}
solution_found = !solutions.is_empty();
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() {
@ -298,5 +294,5 @@ pub fn term_search<DB: HirDatabase>(ctx: TermSearchCtx<'_, DB>) -> Vec<TypeTree>
}
}
solutions.into_iter().unique().collect()
solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
}

View file

@ -19,7 +19,7 @@ use crate::{
Variant,
};
use crate::term_search::{TermSearchConfig, TypeTree};
use crate::term_search::{Expr, TermSearchConfig};
use super::{LookupTable, NewTypesKey, TermSearchCtx};
@ -41,13 +41,13 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
defs.iter().filter_map(|def| {
let tt = match def {
ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(TypeTree::Const(*it)),
ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(TypeTree::Static(*it)),
ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(TypeTree::ConstParam(*it)),
let expr = match def {
ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(Expr::Const(*it)),
ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(Expr::Static(*it)),
ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(Expr::ConstParam(*it)),
ScopeDef::Local(it) => {
if ctx.config.enable_borrowcheck {
let borrowck = db.borrowck(it.parent).ok()?;
@ -67,22 +67,22 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
}
}
Some(TypeTree::Local(*it))
Some(Expr::Local(*it))
}
_ => None,
}?;
lookup.mark_exhausted(*def);
let ty = tt.ty(db);
lookup.insert(ty.clone(), std::iter::once(tt.clone()));
let ty = expr.ty(db);
lookup.insert(ty.clone(), std::iter::once(expr.clone()));
// Don't suggest local references as they are not valid for return
if matches!(tt, TypeTree::Local(_)) && ty.contains_reference(db) {
if matches!(expr, Expr::Local(_)) && ty.contains_reference(db) {
return None;
}
ty.could_unify_with_deeply(db, &ctx.goal).then(|| tt)
ty.could_unify_with_deeply(db, &ctx.goal).then(|| expr)
})
}
@ -101,7 +101,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
fn variant_helper(
@ -111,14 +111,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
variant: Variant,
goal: &Type,
config: &TermSearchConfig,
) -> Vec<(Type, Vec<TypeTree>)> {
let generics = GenericDef::from(variant.parent_enum(db));
// Ignore unstable variants
) -> Vec<(Type, Vec<Expr>)> {
// Ignore unstable
if variant.is_unstable(db) {
return Vec::new();
}
let generics = GenericDef::from(variant.parent_enum(db));
// Ignore enums with const generics
if !generics.const_params(db).is_empty() {
return Vec::new();
@ -160,7 +160,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
})
.collect();
let enum_ty = parent_enum.ty_with_generics(db, generics.iter().cloned());
let enum_ty = parent_enum.ty_with_args(db, generics.iter().cloned());
// Allow types with generics only if they take us straight to goal for
// performance reasons
@ -174,52 +174,42 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
}
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = variant
let param_exprs: Vec<Vec<Expr>> = variant
.fields(db)
.into_iter()
.map(|field| {
lookup.find(db, &field.ty_with_generics(db, generics.iter().cloned()))
})
.map(|field| lookup.find(db, &field.ty_with_args(db, generics.iter().cloned())))
.collect::<Option<_>>()?;
// Note that we need special case for 0 param constructors because of multi cartesian
// product
let variant_trees: Vec<TypeTree> = if param_trees.is_empty() {
vec![TypeTree::Variant {
variant,
generics: generics.clone(),
params: Vec::new(),
}]
let variant_exprs: Vec<Expr> = if param_exprs.is_empty() {
vec![Expr::Variant { variant, generics: generics.clone(), params: Vec::new() }]
} else {
param_trees
param_exprs
.into_iter()
.multi_cartesian_product()
.map(|params| TypeTree::Variant {
variant,
generics: generics.clone(),
params,
})
.map(|params| Expr::Variant { variant, generics: generics.clone(), params })
.collect()
};
lookup.insert(enum_ty.clone(), variant_trees.iter().cloned());
lookup.insert(enum_ty.clone(), variant_exprs.iter().cloned());
Some((enum_ty, variant_trees))
Some((enum_ty, variant_exprs))
})
.collect()
}
defs.iter()
.filter_map(move |def| match def {
ScopeDef::ModuleDef(ModuleDef::Variant(it)) => {
let variant_trees =
let variant_exprs =
variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.goal, &ctx.config);
if variant_trees.is_empty() {
if variant_exprs.is_empty() {
return None;
}
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it)));
Some(variant_trees)
Some(variant_exprs)
}
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => {
let trees: Vec<(Type, Vec<TypeTree>)> = enum_
let exprs: Vec<(Type, Vec<Expr>)> = enum_
.variants(db)
.into_iter()
.flat_map(|it| {
@ -227,11 +217,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
})
.collect();
if !trees.is_empty() {
if !exprs.is_empty() {
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(*enum_))));
}
Some(trees)
Some(exprs)
}
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => {
// Ignore unstable and not visible
@ -269,7 +259,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
.into_iter()
.permutations(non_default_type_params_len);
let trees = generic_params
let exprs = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
@ -280,7 +270,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
None => g.next().expect("Missing type param"),
})
.collect();
let struct_ty = it.ty_with_generics(db, generics.iter().cloned());
let struct_ty = it.ty_with_args(db, generics.iter().cloned());
// Allow types with generics only if they take us straight to goal for
// performance reasons
@ -301,20 +291,20 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
}
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = fileds
let param_exprs: Vec<Vec<Expr>> = fileds
.into_iter()
.map(|field| lookup.find(db, &field.ty(db)))
.collect::<Option<_>>()?;
// Note that we need special case for 0 param constructors because of multi cartesian
// product
let struct_trees: Vec<TypeTree> = if param_trees.is_empty() {
vec![TypeTree::Struct { strukt: *it, generics, params: Vec::new() }]
let struct_exprs: Vec<Expr> = if param_exprs.is_empty() {
vec![Expr::Struct { strukt: *it, generics, params: Vec::new() }]
} else {
param_trees
param_exprs
.into_iter()
.multi_cartesian_product()
.map(|params| TypeTree::Struct {
.map(|params| Expr::Struct {
strukt: *it,
generics: generics.clone(),
params,
@ -324,17 +314,17 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
lookup
.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it))));
lookup.insert(struct_ty.clone(), struct_trees.iter().cloned());
lookup.insert(struct_ty.clone(), struct_exprs.iter().cloned());
Some((struct_ty, struct_trees))
Some((struct_ty, struct_exprs))
})
.collect();
Some(trees)
Some(exprs)
}
_ => None,
})
.flatten()
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees))
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
.flatten()
}
@ -354,7 +344,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
defs.iter()
@ -394,7 +384,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
.into_iter()
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
let exprs: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
@ -406,7 +396,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
})
.collect();
let ret_ty = it.ret_type_with_generics(db, generics.iter().cloned());
let ret_ty = it.ret_type_with_args(db, generics.iter().cloned());
// Filter out private and unsafe functions
if !it.is_visible_from(db, module)
|| it.is_unsafe_to_call(db)
@ -418,7 +408,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
}
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = it
let param_exprs: Vec<Vec<Expr>> = it
.params_without_self_with_generics(db, generics.iter().cloned())
.into_iter()
.map(|field| {
@ -432,13 +422,13 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
// Note that we need special case for 0 param constructors because of multi cartesian
// product
let fn_trees: Vec<TypeTree> = if param_trees.is_empty() {
vec![TypeTree::Function { func: *it, generics, params: Vec::new() }]
let fn_exprs: Vec<Expr> = if param_exprs.is_empty() {
vec![Expr::Function { func: *it, generics, params: Vec::new() }]
} else {
param_trees
param_exprs
.into_iter()
.multi_cartesian_product()
.map(|params| TypeTree::Function {
.map(|params| Expr::Function {
func: *it,
generics: generics.clone(),
@ -448,16 +438,16 @@ pub(super) fn free_function<'a, DB: HirDatabase>(
};
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Function(*it)));
lookup.insert(ret_ty.clone(), fn_trees.iter().cloned());
Some((ret_ty, fn_trees))
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
Some((ret_ty, fn_exprs))
})
.collect();
Some(trees)
Some(exprs)
}
_ => None,
})
.flatten()
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees))
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
.flatten()
}
@ -479,7 +469,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
lookup
@ -546,7 +536,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
.into_iter()
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
let exprs: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
@ -559,7 +549,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
})
.collect();
let ret_ty = it.ret_type_with_generics(
let ret_ty = it.ret_type_with_args(
db,
ty.type_arguments().chain(generics.iter().cloned()),
);
@ -578,17 +568,17 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
let self_ty = it
.self_param(db)
.expect("No self param")
.ty_with_generics(db, ty.type_arguments().chain(generics.iter().cloned()));
.ty_with_args(db, ty.type_arguments().chain(generics.iter().cloned()));
// Ignore functions that have different self type
if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) {
return None;
}
let target_type_trees = lookup.find(db, &ty).expect("Type not in lookup");
let target_type_exprs = lookup.find(db, &ty).expect("Type not in lookup");
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = it
let param_exprs: Vec<Vec<Expr>> = it
.params_without_self_with_generics(
db,
ty.type_arguments().chain(generics.iter().cloned()),
@ -597,20 +587,29 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
.map(|field| lookup.find_autoref(db, &field.ty()))
.collect::<Option<_>>()?;
let fn_trees: Vec<TypeTree> = std::iter::once(target_type_trees)
.chain(param_trees.into_iter())
let fn_exprs: Vec<Expr> = std::iter::once(target_type_exprs)
.chain(param_exprs.into_iter())
.multi_cartesian_product()
.map(|params| TypeTree::Function { func: it, generics: Vec::new(), params })
.map(|params| {
let mut params = params.into_iter();
let target = Box::new(params.next().unwrap());
Expr::Method {
func: it,
generics: generics.clone(),
target,
params: params.collect(),
}
})
.collect();
lookup.insert(ret_ty.clone(), fn_trees.iter().cloned());
Some((ret_ty, fn_trees))
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
Some((ret_ty, fn_exprs))
})
.collect();
Some(trees)
Some(exprs)
})
.flatten()
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees))
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
.flatten()
}
@ -629,26 +628,26 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
lookup
.new_types(NewTypesKey::StructProjection)
.into_iter()
.map(|ty| (ty.clone(), lookup.find(db, &ty).expect("TypeTree not in lookup")))
.map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup")))
.flat_map(move |(ty, targets)| {
ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| {
if !field.is_visible_from(db, module) {
return None;
}
let trees = targets
let exprs = targets
.clone()
.into_iter()
.map(move |target| TypeTree::Field { field, type_tree: Box::new(target) });
Some((filed_ty, trees))
.map(move |target| Expr::Field { field, expr: Box::new(target) });
Some((filed_ty, exprs))
})
})
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees))
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
.flatten()
}
@ -669,20 +668,20 @@ pub(super) fn famous_types<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
[
TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" },
TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" },
TypeTree::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" },
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" },
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" },
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" },
]
.into_iter()
.map(|tt| {
lookup.insert(tt.ty(db), std::iter::once(tt.clone()));
tt
.map(|exprs| {
lookup.insert(exprs.ty(db), std::iter::once(exprs.clone()));
exprs
})
.filter(|tt| tt.ty(db).could_unify_with_deeply(db, &ctx.goal))
.filter(|expr| expr.ty(db).could_unify_with_deeply(db, &ctx.goal))
}
/// # Impl static method (without self type) tactic
@ -700,7 +699,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = TypeTree> + 'a {
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
lookup
@ -771,7 +770,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
.into_iter()
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
let exprs: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
@ -784,7 +783,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
})
.collect();
let ret_ty = it.ret_type_with_generics(
let ret_ty = it.ret_type_with_args(
db,
ty.type_arguments().chain(generics.iter().cloned()),
);
@ -801,7 +800,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
// }
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = it
let param_exprs: Vec<Vec<Expr>> = it
.params_without_self_with_generics(
db,
ty.type_arguments().chain(generics.iter().cloned()),
@ -812,28 +811,27 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
// Note that we need special case for 0 param constructors because of multi cartesian
// product
let fn_trees: Vec<TypeTree> = if param_trees.is_empty() {
vec![TypeTree::Function { func: it, generics, params: Vec::new() }]
let fn_exprs: Vec<Expr> = if param_exprs.is_empty() {
vec![Expr::Function { func: it, generics, params: Vec::new() }]
} else {
param_trees
param_exprs
.into_iter()
.multi_cartesian_product()
.map(|params| TypeTree::Function {
.map(|params| Expr::Function {
func: it,
generics: generics.clone(),
params,
})
.collect()
};
lookup.insert(ret_ty.clone(), fn_trees.iter().cloned());
Some((ret_ty, fn_trees))
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
Some((ret_ty, fn_exprs))
})
.collect();
Some(trees)
Some(exprs)
})
.flatten()
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| trees))
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then(|| exprs))
.flatten()
}

View file

@ -30,7 +30,7 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
goal: target_ty,
config: Default::default(),
};
let paths = hir::term_search::term_search(term_search_ctx);
let paths = hir::term_search::term_search(&term_search_ctx);
if paths.is_empty() {
return None;

View file

@ -40,8 +40,8 @@ use crate::{
literal::{render_struct_literal, render_variant_lit},
macro_::render_macro,
pattern::{render_struct_pat, render_variant_pat},
render_field, render_path_resolution, render_pattern_resolution, render_tuple_field,
render_type_tree,
render_expr, render_field, render_path_resolution, render_pattern_resolution,
render_tuple_field,
type_alias::{render_type_alias, render_type_alias_with_eq},
union_literal::render_union_literal,
RenderContext,
@ -158,12 +158,8 @@ impl Completions {
item.add_to(self, ctx.db);
}
pub(crate) fn add_expr(
&mut self,
ctx: &CompletionContext<'_>,
expr: &hir::term_search::TypeTree,
) {
match render_type_tree(ctx, expr) {
pub(crate) fn add_expr(&mut self, ctx: &CompletionContext<'_>, expr: &hir::term_search::Expr) {
match render_expr(ctx, expr) {
Some(item) => item.add_to(self, ctx.db),
None => (),
}
@ -699,7 +695,6 @@ pub(super) fn complete_name_ref(
ctx: &CompletionContext<'_>,
NameRefContext { nameref, kind }: &NameRefContext,
) {
expr::complete_expr(acc, ctx);
match kind {
NameRefKind::Path(path_ctx) => {
flyimport::import_on_the_fly_path(acc, ctx, path_ctx);
@ -707,6 +702,7 @@ pub(super) fn complete_name_ref(
match &path_ctx.kind {
PathKind::Expr { expr_ctx } => {
expr::complete_expr_path(acc, ctx, path_ctx, expr_ctx);
expr::complete_expr(acc, ctx);
dot::complete_undotted_self(acc, ctx, path_ctx, expr_ctx);
item_list::complete_item_list_in_expr(acc, ctx, path_ctx, expr_ctx);
@ -763,6 +759,7 @@ pub(super) fn complete_name_ref(
flyimport::import_on_the_fly_dot(acc, ctx, dot_access);
dot::complete_dot(acc, ctx, dot_access);
postfix::complete_postfix(acc, ctx, dot_access);
expr::complete_expr(acc, ctx);
}
NameRefKind::Keyword(item) => {
keyword::complete_for_and_where(acc, ctx, item);

View file

@ -329,11 +329,8 @@ pub(crate) fn complete_expr_path(
}
}
pub(crate) fn complete_expr(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
) {
let _p = profile::span("complete_expr");
pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) {
let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered();
if !ctx.qualifier_ctx.none() {
return;
}
@ -351,12 +348,34 @@ pub(crate) fn complete_expr(
config: hir::term_search::TermSearchConfig {
enable_borrowcheck: false,
many_alternatives_threshold: 1,
depth: 2,
depth: 6,
},
};
let exprs = hir::term_search::term_search(term_search_ctx);
let exprs = hir::term_search::term_search(&term_search_ctx);
for expr in exprs {
acc.add_expr(ctx, &expr);
// Expand method calls
match expr {
hir::term_search::Expr::Method { func, generics, target, params }
if target.is_many() =>
{
let target_ty = target.ty(ctx.db);
let term_search_ctx =
hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
let target_exprs = hir::term_search::term_search(&term_search_ctx);
for expr in target_exprs {
let expanded_expr = hir::term_search::Expr::Method {
func,
generics: generics.clone(),
target: Box::new(expr),
params: params.clone(),
};
acc.add_expr(ctx, &expanded_expr)
}
}
_ => acc.add_expr(ctx, &expr),
}
}
}
}

View file

@ -17,7 +17,7 @@ use ide_db::{
imports::import_assets::LocatedImport,
RootDatabase, SnippetCap, SymbolKind,
};
use syntax::{ast, AstNode, SmolStr, SyntaxKind, TextRange};
use syntax::{ast, format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange};
use text_edit::TextEdit;
use crate::{
@ -272,9 +272,9 @@ pub(crate) fn render_resolution_with_import_pat(
Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution))
}
pub(crate) fn render_type_tree(
pub(crate) fn render_expr(
ctx: &CompletionContext<'_>,
expr: &hir::term_search::TypeTree,
expr: &hir::term_search::Expr,
) -> Option<Builder> {
let mut i = 1;
let mut snippet_formatter = |ty: &hir::Type| {
@ -292,31 +292,42 @@ pub(crate) fn render_type_tree(
ty.as_adt()
.and_then(|adt| adt.name(ctx.db).as_text())
.map(|s| stdx::to_lower_snake_case(s.as_str()))
.unwrap_or_else(|| String::from("_"))
.unwrap_or_else(|| String::from("..."))
};
let label = expr.gen_source_code(&ctx.scope, &mut label_formatter);
let source_range = match &ctx.expected_name {
Some(name_or_ref) => name_or_ref.syntax().text_range(),
None => match ctx.original_token.parent() {
let source_range = match ctx.original_token.parent() {
Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) {
Some(path) => path.syntax().text_range(),
None => node.text_range(),
},
None => ctx.source_range(),
},
};
let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label);
let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone());
let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter));
let edit = TextEdit::replace(source_range, snippet);
item.snippet_edit(ctx.config.snippet_cap?, edit);
item.documentation(Documentation::new(String::from("Autogenerated expression by term search")));
item.set_relevance(crate::CompletionRelevance {
type_match: Some(crate::item::CompletionRelevanceTypeMatch::CouldUnify),
type_match: compute_type_match(ctx, &expr.ty(ctx.db)),
..Default::default()
});
for trait_ in expr.traits_used(ctx.db) {
let trait_item = hir::ItemInNs::from(hir::ModuleDef::from(trait_));
let Some(path) = ctx.module.find_use_path(
ctx.db,
trait_item,
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
) else {
continue;
};
item.add_import(LocatedImport::new(path, trait_item, trait_item));
}
Some(item)
}
@ -2243,6 +2254,8 @@ fn main() {
&[CompletionItemKind::Snippet, CompletionItemKind::Method],
expect![[r#"
sn not [snippet]
sn true [type]
sn false [type]
me not() (use ops::Not) [type_could_unify+requires_import]
sn if []
sn while []

View file

@ -316,15 +316,6 @@ fn func() {
bn RecordV {} RecordV { field$1 }$0
bn TupleV() TupleV($1)$0
bn UnitV UnitV$0
sn ()
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn func()
sn function()
sn true
"#]],
);
}
@ -571,8 +562,6 @@ fn foo() {
bn B {} B { r#type$1 }$0
bn struct {} r#struct { r#type$1 }$0
bn type r#type$0
sn Enum::A
sn Enum::r#type
"#]],
);
}
@ -597,7 +586,6 @@ fn f(t: Ty) {
"#,
expect![[r#"
ct ABC const ABC: Self
sn t
"#]],
);
@ -620,7 +608,6 @@ fn f(e: MyEnum) {
expect![[r#"
ct A pub const A: i32
ct B pub const B: i32
sn e
"#]],
);
@ -646,7 +633,6 @@ fn f(u: U) {
expect![[r#"
ct C pub const C: i32
ct D pub const D: i32
sn u
"#]],
);
@ -666,7 +652,6 @@ fn f(v: u32) {
"#,
expect![[r#"
ct MIN pub const MIN: Self
sn v
"#]],
);
}
@ -778,7 +763,6 @@ fn f(x: EnumAlias<u8>) {
expect![[r#"
bn Tuple() Tuple($1)$0
bn Unit Unit$0
sn x
"#]],
);
}

View file

@ -82,15 +82,6 @@ fn x<'lt, T, const C: usize>() -> $0
bt u32 u32
kw crate::
kw self::
sn ()
sn C
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn function()
sn true
"#]],
);
}
@ -121,15 +112,6 @@ fn foo() -> B$0 {
it ()
kw crate::
kw self::
sn ()
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn foo()
sn function()
sn true
"#]],
)
}
@ -234,14 +216,6 @@ fn f2(x: u64) -> $0 {
it u64
kw crate::
kw self::
sn ()
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn function()
sn true
"#]],
);
}
@ -357,15 +331,6 @@ fn foo<'lt, T, const C: usize>() {
bt u32 u32
kw crate::
kw self::
sn ()
sn C
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn function()
sn true
"#]],
);
check(
@ -384,15 +349,6 @@ fn foo<'lt, T, const C: usize>() {
st Unit Unit
tt Trait
un Union Union
sn ()
sn C
sn CONST
sn Enum::UnitV
sn STATIC
sn Unit
sn false
sn function()
sn true
"#]],
);
}

View file

@ -46,7 +46,7 @@ fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option<Vec<A
let ctx =
TermSearchCtx { sema, scope: &scope, goal: d.expected.clone(), config: Default::default() };
let paths = term_search(ctx);
let paths = term_search(&ctx);
let mut assists = vec![];
let mut formatter = |_: &hir::Type| String::from("_");

View file

@ -415,7 +415,7 @@ impl flags::AnalysisStats {
..Default::default()
},
};
let found_terms = hir::term_search::term_search(ctx);
let found_terms = hir::term_search::term_search(&ctx);
if found_terms.is_empty() {
acc.tail_expr_no_term += 1;
@ -428,7 +428,8 @@ impl flags::AnalysisStats {
s.chars().into_iter().filter(|c| !c.is_whitespace()).collect()
}
let mut formatter = |_: &hir::Type| syntax::ast::make::ext::expr_todo().to_string();
let todo = syntax::ast::make::ext::expr_todo().to_string();
let mut formatter = |_: &hir::Type| todo.clone();
let mut syntax_hit_found = false;
for term in found_terms {
let generated = term.gen_source_code(&scope, &mut formatter);
@ -449,8 +450,10 @@ impl flags::AnalysisStats {
if let Some(mut err_idx) = err.find("error[E") {
err_idx += 7;
let err_code = &err[err_idx..err_idx + 4];
if err_code == "0282" {
continue; // Byproduct of testing method
match err_code {
"0282" => continue, // Byproduct of testing method
"0277" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882
_ => (),
}
bar.println(err);
bar.println(generated);