use std::collections::HashMap;
use glua_code_analysis::{
LuaCompilation, LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaType, LuaTypeDeclId,
SemanticDeclLevel, SemanticModel,
};
use glua_parser::{
LuaAstNode, LuaDocTagField, LuaExpr, LuaIndexExpr, LuaStat, LuaSyntaxNode, LuaSyntaxToken,
LuaTableField,
};
use lsp_types::Location;
use tokio_util::sync::CancellationToken;
use crate::handlers::definition::goto_inferred_dynamic_field_definition;
use crate::handlers::hover::find_member_origin_owners;
pub fn search_implementations(
semantic_model: &SemanticModel,
compilation: &LuaCompilation,
token: LuaSyntaxToken,
cancel_token: &CancellationToken,
) -> Option<Vec<Location>> {
let mut result = Vec::new();
if let Some(semantic_decl) =
semantic_model.find_decl(token.clone().into(), SemanticDeclLevel::NoTrace)
{
match semantic_decl {
LuaSemanticDeclId::TypeDecl(type_decl_id) => {
search_type_implementations(semantic_model, compilation, type_decl_id, &mut result);
}
LuaSemanticDeclId::Member(member_id) => {
search_member_implementations(
semantic_model,
compilation,
member_id,
&mut result,
cancel_token,
);
}
LuaSemanticDeclId::LuaDecl(decl_id) => {
search_decl_implementations(
semantic_model,
compilation,
decl_id,
&mut result,
cancel_token,
);
}
_ => {}
}
} else if let Some(dynamic_response) =
goto_inferred_dynamic_field_definition(semantic_model, &token)
{
match dynamic_response {
lsp_types::GotoDefinitionResponse::Scalar(location) => result.push(location),
lsp_types::GotoDefinitionResponse::Array(locations) => result.extend(locations),
lsp_types::GotoDefinitionResponse::Link(_) => {}
}
}
Some(result)
}
pub fn search_member_implementations(
semantic_model: &SemanticModel,
compilation: &LuaCompilation,
member_id: LuaMemberId,
result: &mut Vec<Location>,
cancel_token: &CancellationToken,
) -> Option<()> {
let member = semantic_model
.get_db()
.get_member_index()
.get_member(&member_id)?;
let member_key = member.get_key();
let index_references = semantic_model
.get_db()
.get_reference_index()
.get_index_references(member_key)?;
let mut semantic_cache = HashMap::new();
let property_owners =
match find_member_origin_owners(compilation, semantic_model, member_id, true, None) {
crate::handlers::hover::DeclOriginResult::Multiple(ids) => ids,
crate::handlers::hover::DeclOriginResult::Single(id) => vec![id],
};
let origin_class_id = property_owners.iter().find_map(|id| match id {
LuaSemanticDeclId::Member(mid) => semantic_model
.get_db()
.get_member_index()
.get_current_owner(mid)
.and_then(|owner| match owner {
glua_code_analysis::LuaMemberOwner::Type(type_decl_id) => {
Some(type_decl_id.clone())
}
_ => None,
}),
_ => None,
});
for in_filed_syntax_id in index_references {
if cancel_token.is_cancelled() {
return None;
}
let reference_semantic_model =
if let Some(semantic_model) = semantic_cache.get_mut(&in_filed_syntax_id.file_id) {
semantic_model
} else {
let semantic_model = compilation.get_semantic_model(in_filed_syntax_id.file_id)?;
semantic_cache.insert(in_filed_syntax_id.file_id, semantic_model);
semantic_cache.get_mut(&in_filed_syntax_id.file_id)?
};
let root = reference_semantic_model.get_root();
let node = in_filed_syntax_id.value.to_node_from_root(root.syntax())?;
if let Some(is_signature) = check_member_reference(reference_semantic_model, node.clone()) {
let matches_owner = property_owners.iter().any(|owner| {
reference_semantic_model.is_reference_to(
node.clone(),
owner.clone(),
SemanticDeclLevel::default(),
)
});
let matches_prefix = if !matches_owner && LuaIndexExpr::can_cast(node.kind().into()) {
if let Some(class_id) = &origin_class_id {
let expr = LuaIndexExpr::cast(node.clone());
expr.and_then(|e| e.get_prefix_expr())
.is_some_and(|prefix| {
if let LuaExpr::NameExpr(name_expr) = prefix {
reference_semantic_model
.find_decl(
name_expr.syntax().clone().into(),
SemanticDeclLevel::default(),
)
.is_some_and(|prefix_decl| match prefix_decl {
LuaSemanticDeclId::LuaDecl(prefix_decl_id) => {
let prefix_type = reference_semantic_model
.get_type(prefix_decl_id.into());
match &prefix_type {
LuaType::Ref(tid) | LuaType::Def(tid) => {
tid == class_id
}
LuaType::TableConst(_) => {
false
}
_ => false,
}
}
_ => false,
})
} else {
false
}
})
} else {
false
}
} else {
false
};
if !matches_owner && !matches_prefix {
continue;
}
let document = reference_semantic_model.get_document();
let range = in_filed_syntax_id.value.get_range();
let location = document.to_lsp_location(range)?;
if is_signature {
result.insert(0, location);
} else {
result.push(location);
}
}
}
Some(())
}
fn check_member_reference(semantic_model: &SemanticModel, node: LuaSyntaxNode) -> Option<bool> {
match &node {
expr_node if LuaIndexExpr::can_cast(expr_node.kind().into()) => {
let expr = LuaIndexExpr::cast(expr_node.clone())?;
let _prefix_type = semantic_model.infer_expr(expr.get_prefix_expr()?).ok()?;
let mut is_signature = false;
if let Some(current_type) = semantic_model
.infer_expr(LuaExpr::IndexExpr(expr.clone()))
.ok()
&& current_type.is_signature()
{
is_signature = true;
}
let stat = expr.ancestors::<LuaStat>().next()?;
match stat {
LuaStat::FuncStat(_) => {
return Some(is_signature);
}
LuaStat::AssignStat(assign_stat) => {
let (vars, _) = assign_stat.get_var_and_expr_list();
for var in vars {
if var
.syntax()
.text_range()
.contains(node.text_range().start())
{
return Some(is_signature);
}
}
return None;
}
_ => {
return None;
}
}
}
tag_field_node if LuaDocTagField::can_cast(tag_field_node.kind().into()) => {
return Some(false);
}
table_field_node if LuaTableField::can_cast(table_field_node.kind().into()) => {
let table_field = LuaTableField::cast(table_field_node.clone())?;
if table_field.is_assign_field() {
return Some(false);
} else {
return None;
}
}
_ => {}
}
Some(false)
}
pub fn search_type_implementations(
semantic_model: &SemanticModel,
compilation: &LuaCompilation,
type_decl_id: LuaTypeDeclId,
result: &mut Vec<Location>,
) -> Option<()> {
let db = semantic_model.get_db();
let type_index = db.get_type_index();
let type_decl = type_index.get_type_decl(&type_decl_id)?;
let locations = type_decl.get_locations();
let mut semantic_cache = HashMap::new();
for location in locations {
let semantic_model = if let Some(semantic_model) = semantic_cache.get_mut(&location.file_id)
{
semantic_model
} else {
let semantic_model = compilation.get_semantic_model(location.file_id)?;
semantic_cache.insert(location.file_id, semantic_model);
semantic_cache.get_mut(&location.file_id)?
};
let document = semantic_model.get_document();
let range = location.range;
let location = document.to_lsp_location(range)?;
result.push(location);
}
Some(())
}
pub fn search_decl_implementations(
semantic_model: &SemanticModel,
compilation: &LuaCompilation,
decl_id: LuaDeclId,
result: &mut Vec<Location>,
cancel_token: &CancellationToken,
) -> Option<()> {
let decl = semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)?;
if decl.is_local() {
let document = semantic_model.get_document();
let decl_refs = semantic_model
.get_db()
.get_reference_index()
.get_decl_references(&decl_id.file_id, &decl_id)?;
let range = decl.get_range();
let location = document.to_lsp_location(range)?;
result.push(location);
for decl_ref in &decl_refs.cells {
if decl_ref.is_write
&& let Some(location) = document.to_lsp_location(decl_ref.range)
{
result.push(location);
}
}
return Some(());
} else {
let name = decl.get_name();
let global_decl_ids = semantic_model
.get_db()
.get_global_index()
.get_global_decl_ids(name)?;
let mut semantic_cache = HashMap::new();
for global_decl_id in global_decl_ids {
if cancel_token.is_cancelled() {
return None;
}
let semantic_model =
if let Some(semantic_model) = semantic_cache.get_mut(&global_decl_id.file_id) {
semantic_model
} else {
let semantic_model = compilation.get_semantic_model(global_decl_id.file_id)?;
semantic_cache.insert(global_decl_id.file_id, semantic_model);
semantic_cache.get_mut(&global_decl_id.file_id)?
};
let Some(decl) = semantic_model
.get_db()
.get_decl_index()
.get_decl(global_decl_id)
else {
continue;
};
let document = semantic_model.get_document();
let range = decl.get_range();
let location = document.to_lsp_location(range)?;
result.push(location);
}
}
Some(())
}