use glua_code_analysis::{
DbIndex, InferGuard, LuaDeclId, LuaType, get_real_type, infer_table_field_value_should_be,
infer_table_should_be,
};
use glua_parser::{
BinaryOperator, LuaAst, LuaAstNode, LuaAstToken, LuaBinaryExpr, LuaBlock, LuaLiteralExpr,
LuaSyntaxNode, LuaSyntaxToken, LuaTokenKind,
};
use crate::handlers::completion::{
completion_builder::CompletionBuilder, providers::function_provider::dispatch_type,
};
pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
if builder.is_cancelled() {
return None;
}
if !check_can_add_completion(builder) {
return None;
}
let types = get_token_should_type(builder)?;
for typ in &types {
dispatch_type(builder, typ.clone(), &InferGuard::new());
}
if !types.is_empty() && !builder.is_invoked() {
builder.stop_here();
}
Some(())
}
fn check_can_add_completion(builder: &CompletionBuilder) -> bool {
if builder.is_space_trigger_character {
return true;
}
true
}
fn get_token_should_type(builder: &mut CompletionBuilder) -> Option<Vec<LuaType>> {
let token = builder.trigger_token.clone();
let mut parent_node = token.parent()?;
if LuaBlock::cast(parent_node.clone()).is_some() {
if let Some(node) = token.prev_token()?.parent() {
parent_node = node;
}
} else {
if LuaLiteralExpr::can_cast(parent_node.kind().into()) {
parent_node = parent_node.parent()?;
}
}
if let Some(typ) = get_equality_should_type(builder, &token, &parent_node) {
return Some(vec![typ]);
}
match LuaAst::cast(parent_node)? {
LuaAst::LuaLocalStat(local_stat) => {
let locals = local_stat.get_local_name_list().collect::<Vec<_>>();
if locals.len() != 1 {
return None;
}
let position = builder.trigger_token.text_range().start();
let eq = local_stat.token_by_kind(LuaTokenKind::TkAssign)?;
if position < eq.get_position() {
return None;
}
let local = locals.first()?;
let decl_id =
LuaDeclId::new(builder.semantic_model.get_file_id(), local.get_position());
let decl_type = builder
.semantic_model
.get_db()
.get_type_index()
.get_type_cache(&decl_id.into())?;
let typ = decl_type.as_type().clone();
if contain_function_types(builder.semantic_model.get_db(), &typ).is_none() {
return Some(vec![typ]);
}
}
LuaAst::LuaAssignStat(assign_stat) => {
let (vars, _) = assign_stat.get_var_and_expr_list();
if vars.len() != 1 {
return None;
}
let position = builder.trigger_token.text_range().start();
let eq = assign_stat.token_by_kind(LuaTokenKind::TkAssign)?;
if position < eq.get_position() {
return None;
}
let var = vars.first()?;
let var_type = builder.semantic_model.infer_expr(var.to_expr());
if let Ok(typ) = var_type
&& contain_function_types(builder.semantic_model.get_db(), &typ).is_none()
{
return Some(vec![typ]);
}
}
LuaAst::LuaTableExpr(table_expr) => {
let table_type = infer_table_should_be(
builder.semantic_model.get_db(),
&mut builder.semantic_model.get_cache().borrow_mut(),
table_expr,
);
if let Ok(typ) = table_type
&& let LuaType::Array(array_type) = typ
{
return Some(vec![array_type.get_base().clone()]);
}
}
LuaAst::LuaTableField(table_field) => {
if table_field.is_value_field() {
return None;
}
let typ = infer_table_field_value_should_be(
builder.semantic_model.get_db(),
&mut builder.semantic_model.get_cache().borrow_mut(),
table_field,
)
.ok()?;
return Some(vec![typ]);
}
_ => {}
}
None
}
fn get_equality_should_type(
builder: &CompletionBuilder,
token: &LuaSyntaxToken,
parent_node: &LuaSyntaxNode,
) -> Option<LuaType> {
if let Some(binary_expr) = LuaBinaryExpr::cast(parent_node.clone())
&& let Some(typ) = infer_left_type_if_equality(builder, &binary_expr)
{
return Some(typ);
}
if let Some(binary_expr) = token
.parent_ancestors()
.filter_map(LuaBinaryExpr::cast)
.find(|binary_expr| {
is_equality_binary_expr(binary_expr) && cursor_is_in_binary_rhs(builder, binary_expr)
})
&& let Some(typ) = infer_left_type_if_equality(builder, &binary_expr)
{
return Some(typ);
}
let op_token = find_previous_equality_op_token(token.clone())?;
let binary_expr = op_token
.parent_ancestors()
.filter_map(LuaBinaryExpr::cast)
.find(|binary_expr| {
is_equality_binary_expr(binary_expr) && cursor_is_in_binary_rhs(builder, binary_expr)
})?;
infer_left_type_if_equality(builder, &binary_expr)
}
fn cursor_is_in_binary_rhs(builder: &CompletionBuilder, binary_expr: &LuaBinaryExpr) -> bool {
let cursor = builder.position_offset;
let Some(op_token) = binary_expr.get_op_token() else {
return false;
};
let op_end = op_token.get_range().end();
if cursor < op_end {
return false;
}
if let Some((_, right_expr)) = binary_expr.get_exprs() {
let right_range = right_expr.get_range();
return right_range.contains_inclusive(cursor) || cursor <= right_range.start();
}
true
}
fn infer_left_type_if_equality(
builder: &CompletionBuilder,
binary_expr: &LuaBinaryExpr,
) -> Option<LuaType> {
if !is_equality_binary_expr(binary_expr) {
return None;
}
let left = binary_expr.get_left_expr()?;
builder.semantic_model.infer_expr(left).ok()
}
fn is_equality_binary_expr(binary_expr: &LuaBinaryExpr) -> bool {
let Some(op_token) = binary_expr.get_op_token() else {
return false;
};
matches!(
op_token.get_op(),
BinaryOperator::OpEq | BinaryOperator::OpNe
)
}
fn find_previous_equality_op_token(token: LuaSyntaxToken) -> Option<LuaSyntaxToken> {
let mut cursor = token;
for _ in 0..32 {
let prev = cursor.prev_token()?;
cursor = prev.clone();
match prev.kind().into() {
LuaTokenKind::TkWhitespace | LuaTokenKind::TkEndOfLine => continue,
LuaTokenKind::TkEq | LuaTokenKind::TkNe => return Some(prev),
LuaTokenKind::TkSemicolon => return None,
_ => {}
}
}
None
}
pub fn contain_function_types(db: &DbIndex, typ: &LuaType) -> Option<()> {
match typ {
LuaType::Union(union_typ) => {
for member in union_typ.into_vec().iter() {
match member {
_ if member.is_function() => {
return Some(());
}
_ if member.is_custom_type() => {
let real_type = get_real_type(db, member)?;
if real_type.is_function() {
return Some(());
}
}
_ => {
continue;
}
}
}
None
}
_ if typ.is_function() => Some(()),
_ => None,
}
}