Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/term_search/expr.rs')
-rw-r--r--crates/hir/src/term_search/expr.rs363
1 files changed, 363 insertions, 0 deletions
diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs
new file mode 100644
index 0000000000..b43f10528d
--- /dev/null
+++ b/crates/hir/src/term_search/expr.rs
@@ -0,0 +1,363 @@
+//! Type tree for term search
+
+use hir_def::find_path::PrefixKind;
+use hir_expand::mod_path::ModPath;
+use hir_ty::{db::HirDatabase, display::HirDisplay};
+use itertools::Itertools;
+
+use crate::{
+ Adt, AsAssocItem, Const, ConstParam, Field, Function, GenericDef, Local, ModuleDef,
+ SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
+};
+
+/// Helper function to get path to `ModuleDef`
+fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option<ModPath> {
+ let db = sema_scope.db;
+ // Account for locals shadowing items from module
+ let name_hit_count = def.name(db).map(|def_name| {
+ let mut name_hit_count = 0;
+ sema_scope.process_all_names(&mut |name, _| {
+ if name == def_name {
+ name_hit_count += 1;
+ }
+ });
+ name_hit_count
+ });
+
+ let m = sema_scope.module();
+ match name_hit_count {
+ Some(0..=1) | None => m.find_use_path(db.upcast(), *def, false, true),
+ Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true),
+ }
+}
+
+/// Helper function to get path to `ModuleDef` as string
+fn mod_item_path_str(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String {
+ let path = mod_item_path(sema_scope, def);
+ path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap()
+}
+
+/// Helper function to get path to `Type`
+fn type_path(sema_scope: &SemanticsScope<'_>, ty: &Type) -> String {
+ let db = sema_scope.db;
+ match ty.as_adt() {
+ Some(adt) => {
+ let ty_name = ty.display(db).to_string();
+
+ let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt)).unwrap();
+ path.pop_segment();
+ let path = path.display(db.upcast()).to_string();
+ match path.is_empty() {
+ true => ty_name,
+ false => format!("{path}::{ty_name}"),
+ }
+ }
+ None => ty.display(db).to_string(),
+ }
+}
+
+/// 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)
+ .into_iter()
+ .zip(generics)
+ .filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg))
+ .map(|(_, arg)| arg.clone())
+ .collect()
+}
+
+/// Type tree shows how can we get from set of types to some type.
+///
+/// Consider the following code as an example
+/// ```
+/// fn foo(x: i32, y: bool) -> Option<i32> { None }
+/// fn bar() {
+/// let a = 1;
+/// let b = true;
+/// let c: Option<i32> = _;
+/// }
+/// ```
+/// If we generate type tree in the place of `_` we get
+/// ```txt
+/// Option<i32>
+/// |
+/// foo(i32, bool)
+/// / \
+/// a: i32 b: bool
+/// ```
+/// 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 Expr {
+ /// Constant
+ Const(Const),
+ /// Static variable
+ Static(Static),
+ /// Local variable
+ Local(Local),
+ /// Constant generic parameter
+ ConstParam(ConstParam),
+ /// Well known type (such as `true` for bool)
+ FamousType { ty: Type, value: &'static str },
+ /// 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<Expr> },
+ /// Struct construction
+ Struct { strukt: Struct, generics: Vec<Type>, params: Vec<Expr> },
+ /// Struct field access
+ Field { expr: Box<Expr>, field: Field },
+ /// Passing type as reference (with `&`)
+ Reference(Box<Expr>),
+ /// Indicates possibility of many different options that all evaluate to `ty`
+ Many(Type),
+}
+
+impl Expr {
+ /// Generate source code for type tree.
+ ///
+ /// 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<'_>,
+ many_formatter: &mut dyn FnMut(&Type) -> String,
+ ) -> String {
+ let db = sema_scope.db;
+ 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::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) => {
+ let container_name = match container {
+ crate::AssocItemContainer::Trait(trait_) => {
+ mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))
+ }
+ crate::AssocItemContainer::Impl(imp) => {
+ let self_ty = imp.self_ty(db);
+ // Should it be guaranteed that `mod_item_path` always exists?
+ match self_ty
+ .as_adt()
+ .and_then(|adt| mod_item_path(sema_scope, &adt.into()))
+ {
+ Some(path) => path.display(sema_scope.db.upcast()).to_string(),
+ None => self_ty.display(db).to_string(),
+ }
+ }
+ };
+ let fn_name = func.name(db).display(db.upcast()).to_string();
+ format!("{container_name}::{fn_name}({args})",)
+ }
+ None => {
+ 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));
+ }
+
+ 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(),
+ false => {
+ let generics =
+ generics.iter().map(|it| type_path(sema_scope, it)).join(", ");
+ format!("::<{generics}>")
+ }
+ };
+ let inner = match variant.kind(db) {
+ StructKind::Tuple => {
+ let args = params
+ .iter()
+ .map(|f| f.gen_source_code(sema_scope, many_formatter))
+ .join(", ");
+ format!("{generics_str}({args})")
+ }
+ StructKind::Record => {
+ let fields = variant.fields(db);
+ let args = params
+ .iter()
+ .zip(fields.iter())
+ .map(|(a, f)| {
+ format!(
+ "{}: {}",
+ f.name(db).display(db.upcast()).to_string(),
+ a.gen_source_code(sema_scope, many_formatter)
+ )
+ })
+ .join(", ");
+ format!("{generics_str}{{ {args} }}")
+ }
+ StructKind::Unit => generics_str,
+ };
+
+ let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant));
+ format!("{prefix}{inner}")
+ }
+ Expr::Struct { strukt, generics, params } => {
+ 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, many_formatter))
+ .join(", ");
+ format!("({args})")
+ }
+ StructKind::Record => {
+ let fields = strukt.fields(db);
+ let args = params
+ .iter()
+ .zip(fields.iter())
+ .map(|(a, f)| {
+ format!(
+ "{}: {}",
+ f.name(db).display(db.upcast()).to_string(),
+ a.gen_source_code(sema_scope, many_formatter)
+ )
+ })
+ .join(", ");
+ format!(" {{ {args} }}")
+ }
+ StructKind::Unit => match generics.is_empty() {
+ true => String::new(),
+ false => {
+ let generics =
+ generics.iter().map(|it| type_path(sema_scope, it)).join(", ");
+ format!("::<{generics}>")
+ }
+ },
+ };
+
+ let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)));
+ format!("{prefix}{inner}")
+ }
+ 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}")
+ }
+ 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}")
+ }
+ Expr::Many(ty) => many_formatter(ty),
+ }
+ }
+
+ /// Get type of the type tree.
+ ///
+ /// Same as getting the type of root node
+ pub fn ty(&self, db: &dyn HirDatabase) -> Type {
+ match self {
+ 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,
+ target.ty(db).type_arguments().chain(generics.iter().cloned()),
+ ),
+ Expr::Variant { variant, generics, .. } => {
+ variant.parent_enum(db).ty_with_args(db, generics.iter().cloned())
+ }
+ Expr::Struct { strukt, generics, .. } => {
+ 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),
+ Expr::Many(ty) => ty.clone(),
+ }
+ }
+
+ /// List the traits used in type tree
+ 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);
+ }
+ }
+ }
+ _ => (),
+ }
+
+ 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(_))
+ }
+}