use std::collections::HashSet;
use glua_code_analysis::{
LuaCompilation, LuaDeclExtra, LuaDeclId, LuaMemberId, LuaMemberIndexItem, LuaMemberOwner,
LuaSemanticDeclId, LuaType, LuaTypeDeclId, LuaUnionType, SemanticDeclLevel, SemanticModel,
};
use glua_parser::{LuaAssignStat, LuaAstNode, LuaSyntaxKind, LuaTableExpr, LuaTableField};
use rowan::TextSize;
#[derive(Debug, Clone)]
pub enum DeclOriginResult {
Single(LuaSemanticDeclId),
Multiple(Vec<LuaSemanticDeclId>),
}
impl DeclOriginResult {
pub fn get_first(&self) -> Option<LuaSemanticDeclId> {
match self {
DeclOriginResult::Single(decl) => Some(decl.clone()),
DeclOriginResult::Multiple(decls) => decls.first().cloned(),
}
}
pub fn get_types(&self, semantic_model: &SemanticModel) -> Vec<(LuaSemanticDeclId, LuaType)> {
let get_type = |decl: &LuaSemanticDeclId| -> Option<(LuaSemanticDeclId, LuaType)> {
match decl {
LuaSemanticDeclId::Member(member_id) => {
let typ = semantic_model.get_type((*member_id).into());
Some((decl.clone(), typ))
}
LuaSemanticDeclId::LuaDecl(decl_id) => {
let db = semantic_model.get_db();
let decl_info = db.get_decl_index().get_decl(decl_id)?;
let typ = if let LuaDeclExtra::Param {
idx, signature_id, ..
} = &decl_info.extra
{
db.get_signature_index()
.get(signature_id)?
.get_param_info_by_id(*idx)?
.type_ref
.clone()
} else {
semantic_model.get_type((*decl_id).into())
};
Some((decl.clone(), typ))
}
_ => None,
}
};
match self {
DeclOriginResult::Single(decl) => get_type(decl).into_iter().collect(),
DeclOriginResult::Multiple(decls) => decls.iter().filter_map(get_type).collect(),
}
}
}
pub fn find_decl_origin_owners(
compilation: &LuaCompilation,
semantic_model: &SemanticModel,
decl_id: LuaDeclId,
) -> DeclOriginResult {
let node = semantic_model
.get_db()
.get_vfs()
.get_syntax_tree(&decl_id.file_id)
.and_then(|tree| {
let root = tree.get_red_root();
semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)
.and_then(|decl| decl.get_value_syntax_id())
.and_then(|syntax_id| syntax_id.to_node_from_root(&root))
});
if let Some(node) = node {
let semantic_decl = semantic_model.find_decl(node.into(), SemanticDeclLevel::default());
match semantic_decl {
Some(LuaSemanticDeclId::Member(member_id)) => {
find_member_origin_owners(compilation, semantic_model, member_id, true, None)
}
Some(LuaSemanticDeclId::LuaDecl(decl_id)) => {
DeclOriginResult::Single(LuaSemanticDeclId::LuaDecl(decl_id))
}
_ => DeclOriginResult::Single(LuaSemanticDeclId::LuaDecl(decl_id)),
}
} else {
DeclOriginResult::Single(LuaSemanticDeclId::LuaDecl(decl_id))
}
}
pub fn find_member_origin_owners(
compilation: &LuaCompilation,
semantic_model: &SemanticModel,
member_id: LuaMemberId,
find_all: bool,
usage_position: Option<TextSize>,
) -> DeclOriginResult {
const MAX_ITERATIONS: usize = 50;
let mut visited_members = HashSet::new();
let mut current_owner = resolve_member_owner(compilation, semantic_model, &member_id);
let mut final_owner = current_owner.clone();
let mut iteration_count = 0;
while let Some(LuaSemanticDeclId::Member(current_member_id)) = ¤t_owner {
if visited_members.contains(current_member_id) || iteration_count >= MAX_ITERATIONS {
break;
}
visited_members.insert(*current_member_id);
iteration_count += 1;
match resolve_member_owner(compilation, semantic_model, current_member_id) {
Some(next_owner) => {
final_owner = Some(next_owner.clone());
current_owner = Some(next_owner);
}
None => break,
}
}
if final_owner.is_none() {
final_owner = Some(LuaSemanticDeclId::Member(member_id));
}
if !find_all {
return DeclOriginResult::Single(
final_owner.unwrap_or_else(|| LuaSemanticDeclId::Member(member_id)),
);
}
if let Some(same_named_members) =
find_all_same_named_members(semantic_model, &final_owner, usage_position)
&& same_named_members.len() > 1
{
return DeclOriginResult::Multiple(same_named_members);
}
DeclOriginResult::Single(final_owner.unwrap_or_else(|| LuaSemanticDeclId::Member(member_id)))
}
pub fn find_member_origin_owner(
compilation: &LuaCompilation,
semantic_model: &SemanticModel,
member_id: LuaMemberId,
) -> Option<LuaSemanticDeclId> {
find_member_origin_owners(compilation, semantic_model, member_id, false, None).get_first()
}
pub fn find_all_same_named_members(
semantic_model: &SemanticModel,
final_owner: &Option<LuaSemanticDeclId>,
position_offset: Option<rowan::TextSize>,
) -> Option<Vec<LuaSemanticDeclId>> {
let final_owner = final_owner.as_ref()?;
let member_id = match final_owner {
LuaSemanticDeclId::Member(id) => id,
_ => return None,
};
let original_member = semantic_model
.get_db()
.get_member_index()
.get_member(member_id)?;
let is_scripted_class_assignment_file_define = original_member.get_feature().is_file_define()
&& original_member.get_syntax_id().get_kind() == LuaSyntaxKind::IndexExpr;
let target_key = original_member.get_key().clone();
let current_owner = semantic_model
.get_db()
.get_member_index()
.get_current_owner(member_id)?;
let mut seen_member_ids = HashSet::new();
let mut same_named: Vec<LuaSemanticDeclId> = Vec::new();
let mut push_member = |member_id: LuaMemberId| {
if seen_member_ids.insert(member_id) {
same_named.push(LuaSemanticDeclId::Member(member_id));
}
};
let scoped_class_info = semantic_model
.get_db()
.get_gmod_infer_index()
.get_scoped_class_info(&semantic_model.get_file_id());
if is_scripted_class_assignment_file_define && scoped_class_info.is_some() {
let mut owner_candidates = vec![current_owner.clone()];
if let Some(scoped_class_info) = scoped_class_info {
let scripted_owner =
LuaMemberOwner::Type(LuaTypeDeclId::global(&scoped_class_info.class_name));
if !owner_candidates.contains(&scripted_owner) {
owner_candidates.push(scripted_owner);
}
}
let mut matching_member_ids = owner_candidates
.iter()
.flat_map(|owner| {
semantic_model
.get_db()
.get_member_index()
.get_members_for_owner_key(owner, &target_key)
.into_iter()
.map(|member| member.get_id())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let same_file_member_ids = semantic_model
.get_db()
.get_member_index()
.get_file_members(semantic_model.get_file_id())
.into_iter()
.filter(|member| {
member.get_key() == &target_key
&& member.get_feature().is_file_define()
&& member.get_syntax_id().get_kind() == LuaSyntaxKind::IndexExpr
&& semantic_model
.get_db()
.get_member_index()
.get_current_owner(&member.get_id())
.is_some_and(|owner| {
owner_candidates.iter().any(|candidate| candidate == owner)
})
})
.map(|member| member.get_id())
.collect::<Vec<_>>();
matching_member_ids.extend(same_file_member_ids);
let mut matching_members =
member_ids_to_visible_same_named(semantic_model, matching_member_ids, position_offset);
matching_members.sort_by_key(|member_id| {
(
member_id.file_id != semantic_model.get_file_id(),
member_id.get_position(),
)
});
for member_id in matching_members {
push_member(member_id);
}
} else {
match current_owner {
LuaMemberOwner::Type(type_decl_id) => {
let members = if let Some(position_offset) = position_offset {
semantic_model.get_member_info_with_key_at_offset(
&LuaType::Def(type_decl_id.clone()),
target_key,
true,
position_offset,
)?
} else {
semantic_model.get_member_info_with_key(
&LuaType::Def(type_decl_id.clone()),
target_key,
true,
)?
};
for member_info in members {
if let Some(LuaSemanticDeclId::Member(member_id)) =
member_info.property_owner_id
{
push_member(member_id);
}
}
}
_ => {
if let Some(member_item) = semantic_model
.get_db()
.get_member_index()
.get_member_item(current_owner, &target_key)
{
let visible_member_ids = position_offset.map_or_else(
|| {
member_item.visible_member_ids_with_realm(
semantic_model.get_db(),
&semantic_model.get_file_id(),
)
},
|position_offset| {
member_item.visible_member_ids_with_realm_at_offset(
semantic_model.get_db(),
&semantic_model.get_file_id(),
position_offset,
)
},
);
for member_id in visible_member_ids {
push_member(member_id);
}
} else {
let all_members = semantic_model
.get_db()
.get_member_index()
.get_members(current_owner)?;
for member in all_members {
if member.get_key() == &target_key {
push_member(member.get_id());
}
}
}
}
}
}
if same_named.is_empty() {
None
} else {
Some(same_named)
}
}
fn member_ids_to_visible_same_named(
semantic_model: &SemanticModel,
member_ids: Vec<LuaMemberId>,
position_offset: Option<rowan::TextSize>,
) -> Vec<LuaMemberId> {
let member_item = LuaMemberIndexItem::Many(member_ids);
position_offset.map_or_else(
|| {
member_item.visible_member_ids_with_realm(
semantic_model.get_db(),
&semantic_model.get_file_id(),
)
},
|position_offset| {
member_item.visible_member_ids_with_realm_at_offset(
semantic_model.get_db(),
&semantic_model.get_file_id(),
position_offset,
)
},
)
}
fn resolve_member_owner(
compilation: &LuaCompilation,
semantic_model: &SemanticModel,
member_id: &LuaMemberId,
) -> Option<LuaSemanticDeclId> {
let semantic_model = if member_id.file_id == semantic_model.get_file_id() {
semantic_model
} else {
&compilation.get_semantic_model(member_id.file_id)?
};
let root = semantic_model.get_root().syntax();
let current_node = member_id.get_syntax_id().to_node_from_root(root)?;
let result = match member_id.get_syntax_id().get_kind() {
LuaSyntaxKind::TableFieldAssign => {
if LuaTableField::can_cast(current_node.kind().into()) {
let table_field = LuaTableField::cast(current_node.clone())?;
if let Some(owner_id) =
resolve_table_field_through_type_inference(semantic_model, &table_field)
{
return Some(owner_id);
}
let value_expr = table_field.get_value_expr()?;
let value_node = value_expr.get_syntax_id().to_node_from_root(root)?;
semantic_model.find_decl(value_node.into(), SemanticDeclLevel::default())
} else {
None
}
}
LuaSyntaxKind::IndexExpr => {
let assign_node = current_node.parent()?;
let assign_stat = LuaAssignStat::cast(assign_node)?;
let (vars, exprs) = assign_stat.get_var_and_expr_list();
let mut result = None;
for (var, expr) in vars.iter().zip(exprs.iter()) {
if var.syntax().text_range() == current_node.text_range() {
let expr_node = expr.get_syntax_id().to_node_from_root(root)?;
result =
semantic_model.find_decl(expr_node.into(), SemanticDeclLevel::default());
break;
}
}
result
}
_ => None,
};
match result {
Some(LuaSemanticDeclId::LuaDecl(decl_id)) => {
let decl = semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)?;
if decl.is_param() {
return None;
}
result
}
_ => result,
}
}
fn table_is_class(table_type: &LuaType, depth: usize) -> bool {
if depth > 10 {
return false;
}
match table_type {
LuaType::Ref(_) | LuaType::Def(_) | LuaType::Generic(_) => true,
LuaType::Union(union) => match union.as_ref() {
LuaUnionType::Nullable(t) => table_is_class(t, depth + 1),
LuaUnionType::Multi(ts) => ts.iter().any(|t| table_is_class(t, depth + 1)),
},
_ => false,
}
}
fn resolve_table_field_through_type_inference(
semantic_model: &SemanticModel,
table_field: &LuaTableField,
) -> Option<LuaSemanticDeclId> {
let parent = table_field.syntax().parent()?;
let table_expr = LuaTableExpr::cast(parent)?;
let table_type = semantic_model.infer_table_should_be(table_expr)?;
if !table_is_class(&table_type, 0) {
return None;
}
let field_key = table_field.get_field_key()?;
let key = semantic_model.get_member_key(&field_key)?;
let member_infos = semantic_model.get_member_info_with_key_at_offset(
&table_type,
key,
false,
table_field.syntax().text_range().start(),
)?;
member_infos
.first()
.cloned()
.and_then(|m| m.property_owner_id)
}
#[allow(unused)]
pub fn replace_semantic_type(
semantic_decls: &mut [(LuaSemanticDeclId, LuaType)],
origin_type: &LuaType,
) {
let mut type_vec = Vec::new();
match origin_type {
LuaType::Union(union) => {
for typ in union.into_vec() {
type_vec.push(typ);
}
}
_ => {
type_vec.push(origin_type.clone());
}
}
if type_vec.len() != semantic_decls.len() {
return;
}
let mut has_generic = false;
let type_set: HashSet<_> = type_vec.iter().collect();
for (_, typ) in semantic_decls.iter() {
if !type_set.contains(&typ) {
has_generic = true;
break;
}
}
if !has_generic {
return;
}
for (i, (_, typ)) in semantic_decls.iter_mut().enumerate() {
if i < type_vec.len() {
*typ = type_vec[i].clone();
}
}
}