use hir_def::ImportPathConfig;
use hir_expand::mod_path::ModPath;
use hir_ty::{
db::HirDatabase,
display::{DisplaySourceCodeError, HirDisplay},
};
use itertools::Itertools;
use span::Edition;
use crate::{
Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, Local, ModuleDef,
SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
};
fn mod_item_path(
sema_scope: &SemanticsScope<'_>,
def: &ModuleDef,
cfg: ImportPathConfig,
) -> Option<ModPath> {
let db = sema_scope.db;
let m = sema_scope.module();
m.find_path(db.upcast(), *def, cfg)
}
fn mod_item_path_str(
sema_scope: &SemanticsScope<'_>,
def: &ModuleDef,
cfg: ImportPathConfig,
edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let path = mod_item_path(sema_scope, def, cfg);
path.map(|it| it.display(sema_scope.db.upcast(), edition).to_string())
.ok_or(DisplaySourceCodeError::PathNotFound)
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum Expr {
Const(Const),
Static(Static),
Local(Local),
ConstParam(ConstParam),
FamousType { ty: Type, value: &'static str },
Function { func: Function, generics: Vec<Type>, params: Vec<Expr> },
Method { func: Function, generics: Vec<Type>, target: Box<Expr>, params: Vec<Expr> },
Variant { variant: Variant, generics: Vec<Type>, params: Vec<Expr> },
Struct { strukt: Struct, generics: Vec<Type>, params: Vec<Expr> },
Tuple { ty: Type, params: Vec<Expr> },
Field { expr: Box<Expr>, field: Field },
Reference(Box<Expr>),
Many(Type),
}
impl Expr {
pub fn gen_source_code(
&self,
sema_scope: &SemanticsScope<'_>,
many_formatter: &mut dyn FnMut(&Type) -> String,
cfg: ImportPathConfig,
edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let db = sema_scope.db;
let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg, edition);
match self {
Expr::Const(it) => match it.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => {
let container_name = container_name(container, sema_scope, cfg, edition)?;
let const_name = it
.name(db)
.map(|c| c.display(db.upcast(), edition).to_string())
.unwrap_or(String::new());
Ok(format!("{container_name}::{const_name}"))
}
None => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
},
Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
Expr::Local(it) => Ok(it.name(db).display(db.upcast(), edition).to_string()),
Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast(), edition).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, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
match func.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => {
let container_name = container_name(container, sema_scope, cfg, edition)?;
let fn_name = func.name(db).display(db.upcast(), edition).to_string();
Ok(format!("{container_name}::{fn_name}({args})"))
}
None => {
let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func))?;
Ok(format!("{fn_name}({args})"))
}
}
}
Expr::Method { func, target, params, .. } => {
if self.contains_many_in_illegal_pos(db) {
return Ok(many_formatter(&target.ty(db)));
}
let func_name = func.name(db).display(db.upcast(), edition).to_string();
let self_param = func.self_param(db).unwrap();
let target_str =
target.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
let args = params
.iter()
.map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
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 target = match self_param.access(db) {
crate::Access::Shared if !target.is_many() => format!("&{target_str}"),
crate::Access::Exclusive if !target.is_many() => {
format!("&mut {target_str}")
}
crate::Access::Owned => target_str,
_ => many_formatter(&target.ty(db)),
};
let res = match args.is_empty() {
true => format!("{trait_name}::{func_name}({target})",),
false => format!("{trait_name}::{func_name}({target}, {args})",),
};
Ok(res)
}
None => Ok(format!("{target_str}.{func_name}({args})")),
}
}
Expr::Variant { variant, params, .. } => {
let inner = match variant.kind(db) {
StructKind::Tuple => {
let args = params
.iter()
.map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
format!("({args})")
}
StructKind::Record => {
let fields = variant.fields(db);
let args = params
.iter()
.zip(fields.iter())
.map(|(a, f)| {
let tmp = format!(
"{}: {}",
f.name(db).display(db.upcast(), edition),
a.gen_source_code(sema_scope, many_formatter, cfg, edition)?
);
Ok(tmp)
})
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
format!("{{ {args} }}")
}
StructKind::Unit => String::new(),
};
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant))?;
Ok(format!("{prefix}{inner}"))
}
Expr::Struct { strukt, params, .. } => {
let inner = match strukt.kind(db) {
StructKind::Tuple => {
let args = params
.iter()
.map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
format!("({args})")
}
StructKind::Record => {
let fields = strukt.fields(db);
let args = params
.iter()
.zip(fields.iter())
.map(|(a, f)| {
let tmp = format!(
"{}: {}",
f.name(db).display(db.upcast(), edition),
a.gen_source_code(sema_scope, many_formatter, cfg, edition)?
);
Ok(tmp)
})
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
format!(" {{ {args} }}")
}
StructKind::Unit => String::new(),
};
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?;
Ok(format!("{prefix}{inner}"))
}
Expr::Tuple { params, .. } => {
let args = params
.iter()
.map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
let res = format!("({args})");
Ok(res)
}
Expr::Field { expr, field } => {
if expr.contains_many_in_illegal_pos(db) {
return Ok(many_formatter(&expr.ty(db)));
}
let strukt = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
let field = field.name(db).display(db.upcast(), edition).to_string();
Ok(format!("{strukt}.{field}"))
}
Expr::Reference(expr) => {
if expr.contains_many_in_illegal_pos(db) {
return Ok(many_formatter(&expr.ty(db)));
}
let inner = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
Ok(format!("&{inner}"))
}
Expr::Many(ty) => Ok(many_formatter(ty)),
}
}
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, .. } => {
Adt::from(variant.parent_enum(db)).ty_with_args(db, generics.iter().cloned())
}
Expr::Struct { strukt, generics, .. } => {
Adt::from(*strukt).ty_with_args(db, generics.iter().cloned())
}
Expr::Tuple { 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(),
}
}
pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec<Trait> {
let mut res = Vec::new();
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
}
fn contains_many_in_illegal_pos(&self, db: &dyn HirDatabase) -> bool {
match self {
Expr::Method { target, func, .. } => {
match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) {
Some(_) => false,
None => target.is_many(),
}
}
Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(db),
Expr::Reference(target) => target.is_many(),
Expr::Many(_) => true,
_ => false,
}
}
pub fn is_many(&self) -> bool {
matches!(self, Expr::Many(_))
}
}
fn container_name(
container: AssocItemContainer,
sema_scope: &SemanticsScope<'_>,
cfg: ImportPathConfig,
edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let container_name = match container {
crate::AssocItemContainer::Trait(trait_) => {
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg, edition)?
}
crate::AssocItemContainer::Impl(imp) => {
let self_ty = imp.self_ty(sema_scope.db);
match self_ty.as_adt().and_then(|adt| mod_item_path(sema_scope, &adt.into(), cfg)) {
Some(path) => path.display(sema_scope.db.upcast(), edition).to_string(),
None => self_ty.display(sema_scope.db, edition).to_string(),
}
}
};
Ok(container_name)
}