use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::ids::ImportableId;
use cairo_lang_defs::plugin::InlineMacroExprPlugin;
use cairo_lang_doc::db::DocGroup;
use cairo_lang_doc::documentable_item::DocumentableItemId;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_semantic::expr::inference::InferenceId;
use cairo_lang_semantic::items::functions::GenericFunctionId;
use cairo_lang_semantic::resolve::{ResolvedConcreteItem, ResolverData};
use cairo_lang_semantic::substitution::SemanticRewriter;
use cairo_lang_syntax::node::ast::{
FunctionDeclaration, GenericParam, OptionWrappedGenericParamList, TerminalIdentifier,
};
use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode};
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use itertools::Itertools;
use crate::ide::format::types::InferredValue;
use crate::ide::markdown::{RULE, fenced_code_block};
use crate::lang::db::AnalysisDatabase;
use crate::lang::defs::{ResolvedItem, SymbolDef, SymbolSearch};
pub fn definition<'db>(
db: &'db AnalysisDatabase,
identifier: &TerminalIdentifier<'db>,
file_id: FileId<'db>,
importables: &OrderedHashMap<ImportableId<'db>, String>,
) -> Option<String> {
let search = SymbolSearch::find_definition(db, identifier)?;
let md = match &search.def {
SymbolDef::Item(item) => {
let mut md = String::new();
md += &fenced_code_block(&item.definition_path(db));
md += &fenced_code_block(
&concrete_signature(db, search.resolved_item, search.resolver_data, importables)
.map(|signature| item.signature_with_text(db, &signature))
.unwrap_or_else(|| item.signature(db)),
);
if let Some(doc) = item.documentation(db) {
md += RULE;
md += &doc;
}
md
}
SymbolDef::Module(module) => {
let mut md = String::new();
md += &fenced_code_block(&module.definition_path());
md += &fenced_code_block(&module.signature(db));
if let Some(doc) = module.documentation(db) {
md += RULE;
md += &doc;
}
md
}
SymbolDef::Variable(var) => fenced_code_block(&var.signature(db, importables)?),
SymbolDef::PluginInlineMacro(macro_name) => {
let crate_id = db.file_modules(file_id).ok()?.first()?.owning_crate(db);
let mut md = fenced_code_block(macro_name);
if let Some(doc) = db
.crate_inline_macro_plugins(crate_id)
.get(macro_name.as_str())
.map(|&id| id.long(db))?
.documentation()
{
md += RULE;
md += &doc;
}
md
}
SymbolDef::Member(member) => {
let mut md = String::new();
let struct_item = member.struct_item();
md += &fenced_code_block(
format!("{}::{}", struct_item.definition_path(db), struct_item.name(db)).as_str(),
);
let member_signature =
db.get_item_signature(DocumentableItemId::Member(member.member_id()))?;
md += &fenced_code_block(member_signature.as_str());
if let Some(doc) = db.get_item_documentation(member.member_id().into()) {
md += RULE;
md += &doc;
}
md
}
SymbolDef::Variant(variant) => {
let mut md = String::new();
let enum_item = variant.enum_item();
md += &fenced_code_block(
format!("{}::{}", enum_item.definition_path(db), enum_item.name(db)).as_str(),
);
let variant_signature =
db.get_item_signature(DocumentableItemId::Variant(variant.variant_id()))?;
md += &fenced_code_block(variant_signature.as_str());
if let Some(doc) = db.get_item_documentation(variant.variant_id().into()) {
md += RULE;
md += &doc;
}
md
}
SymbolDef::GenericParam(param) => {
let mut md = String::new();
md += &fenced_code_block(¶m.signature(db));
md
}
};
Some(md)
}
fn concrete_signature<'db>(
db: &'db AnalysisDatabase,
resolved_item: ResolvedItem<'db>,
resolver_data: Option<ResolverData<'db>>,
importables: &OrderedHashMap<ImportableId<'db>, String>,
) -> Option<String> {
let resolver_data = resolver_data?;
match resolved_item {
ResolvedItem::Concrete(ResolvedConcreteItem::Function(concrete_func)) => {
let mut inference_data =
resolver_data.inference_data.clone_with_inference_id(db, InferenceId::NoContext);
let mut inference = inference_data.inference(db);
let _ = inference.solve();
let concrete_func = concrete_func.get_concrete(db);
let generics = match concrete_func.generic_function {
GenericFunctionId::Extern(func) => {
get_generics(func.stable_ptr(db).lookup(db).declaration(db), db)
}
GenericFunctionId::Free(func) => {
get_generics(func.stable_ptr(db).lookup(db).declaration(db), db)
}
GenericFunctionId::Impl(impl_id) => {
get_generics(impl_id.function.stable_ptr(db).lookup(db).declaration(db), db)
}
};
if generics.is_empty() {
return None;
}
let generic_args_concrete = concrete_func
.generic_args
.into_iter()
.map(|arg| inference.rewrite(arg))
.collect::<Result<Vec<_>, _>>()
.ok()?;
if generic_args_concrete.iter().any(|arg| !arg.is_fully_concrete(db)) {
return None;
}
let mut result = generics.into_iter().zip(generic_args_concrete).fold(
"\n\n".to_string(),
|mut acc, (generic, concrete)| {
let left = generic.as_syntax_node().get_text_without_trivia(db).to_string(db);
let right = InferredValue::try_from_generic_arg_id(concrete)
.map(|value| value.format(db, importables))
.unwrap_or_else(|| concrete.format(db));
acc.push_str(&left);
acc.push_str(" = ");
acc.push_str(&right);
acc.push('\n');
acc
},
);
result.push('\n');
Some(result)
}
_ => None,
}
}
fn get_generics<'db>(
declaration: FunctionDeclaration<'db>,
db: &'db AnalysisDatabase,
) -> Vec<GenericParam<'db>> {
match declaration.generic_params(db) {
OptionWrappedGenericParamList::Empty(_) => vec![],
OptionWrappedGenericParamList::WrappedGenericParamList(list) => {
list.generic_params(db).elements(db).collect_vec()
}
}
}