use std::sync::Arc;
use emmylua_parser::{LuaAstNode, LuaIndexMemberExpr, LuaTableExpr, LuaVarExpr};
use crate::{
DbIndex, InferFailReason, InferGuard, InferGuardRef, LuaDocParamInfo, LuaDocReturnInfo,
LuaFunctionType, LuaInferCache, LuaSignature, LuaType, SignatureReturnStatus, TypeOps,
get_real_type, infer_call_expr_func, infer_expr, infer_table_should_be,
};
use super::{
ResolveResult, UnResolveCallClosureParams, UnResolveClosureReturn, UnResolveParentAst,
UnResolveParentClosureParams, UnResolveReturn, find_decl_function::find_decl_function_type,
resolve::try_resolve_return_point,
};
pub fn try_resolve_call_closure_params(
db: &mut DbIndex,
cache: &mut LuaInferCache,
closure_params: &mut UnResolveCallClosureParams,
) -> ResolveResult {
let call_expr = closure_params.call_expr.clone();
let prefix_expr = call_expr.get_prefix_expr().ok_or(InferFailReason::None)?;
let call_expr_type = infer_expr(db, cache, prefix_expr)?;
let call_doc_func = infer_call_expr_func(
db,
cache,
call_expr.clone(),
call_expr_type,
&InferGuard::new(),
None,
)?;
let colon_call = call_expr.is_colon_call();
let colon_define = call_doc_func.is_colon_define();
let mut param_idx = closure_params.param_idx;
match (colon_call, colon_define) {
(true, false) => {
param_idx += 1;
}
(false, true) => {
if param_idx == 0 {
return Ok(());
}
param_idx -= 1;
}
_ => {}
}
let (async_state, params_to_insert) = if let Some(param_type) =
call_doc_func.get_params().get(param_idx)
{
let Some(param_type) = get_real_type(db, param_type.1.as_ref().unwrap_or(&LuaType::Any))
else {
return Ok(());
};
match param_type {
LuaType::DocFunction(func) => (func.get_async_state(), func.get_params().to_vec()),
LuaType::Union(union_types) => {
if let Some(LuaType::DocFunction(func)) = union_types
.into_vec()
.iter()
.find(|typ| matches!(typ, LuaType::DocFunction(_)))
{
(func.get_async_state(), func.get_params().to_vec())
} else {
return Ok(());
}
}
_ => return Ok(()),
}
} else {
return Ok(());
};
let signature = db
.get_signature_index_mut()
.get_mut(&closure_params.signature_id)
.ok_or(InferFailReason::None)?;
let signature_params = &mut signature.param_docs;
for (idx, (name, type_ref)) in params_to_insert.iter().enumerate() {
if signature_params.contains_key(&idx) {
continue;
}
signature_params.insert(
idx,
LuaDocParamInfo {
name: name.clone(),
type_ref: type_ref.clone().unwrap_or(LuaType::Any),
description: None,
nullable: false,
attributes: None,
},
);
}
signature.async_state = async_state;
Ok(())
}
pub fn try_resolve_closure_return(
db: &mut DbIndex,
cache: &mut LuaInferCache,
closure_return: &mut UnResolveClosureReturn,
) -> ResolveResult {
let call_expr = closure_return.call_expr.clone();
let prefix_expr = call_expr.get_prefix_expr().ok_or(InferFailReason::None)?;
let call_expr_type = infer_expr(db, cache, prefix_expr)?;
let mut param_idx = closure_return.param_idx;
let call_doc_func = infer_call_expr_func(
db,
cache,
call_expr.clone(),
call_expr_type,
&InferGuard::new(),
None,
)?;
let colon_define = call_doc_func.is_colon_define();
let colon_call = call_expr.is_colon_call();
match (colon_define, colon_call) {
(true, false) => {
if param_idx == 0 {
return Ok(());
}
param_idx -= 1
}
(false, true) => {
param_idx += 1;
}
_ => {}
}
let ret_type = if let Some(param_type) = call_doc_func.get_params().get(param_idx) {
let Some(param_type) = get_real_type(db, param_type.1.as_ref().unwrap_or(&LuaType::Any))
else {
return Ok(());
};
if let LuaType::DocFunction(func) = param_type {
func.get_ret().clone()
} else {
return Ok(());
}
} else {
return Ok(());
};
let signature = db
.get_signature_index_mut()
.get_mut(&closure_return.signature_id)
.ok_or(InferFailReason::None)?;
if ret_type.contain_tpl() {
return try_convert_to_func_body_infer(db, cache, closure_return);
}
match signature.resolve_return {
SignatureReturnStatus::UnResolve => {}
SignatureReturnStatus::InferResolve => {
signature.return_docs.clear();
}
_ => return Ok(()),
}
signature.return_docs.push(LuaDocReturnInfo {
name: None,
type_ref: ret_type.clone(),
description: None,
attributes: None,
});
signature.resolve_return = SignatureReturnStatus::DocResolve;
Ok(())
}
fn try_convert_to_func_body_infer(
db: &mut DbIndex,
cache: &mut LuaInferCache,
closure_return: &mut UnResolveClosureReturn,
) -> ResolveResult {
let mut unresolve = UnResolveReturn {
file_id: closure_return.file_id,
signature_id: closure_return.signature_id,
return_points: closure_return.return_points.clone(),
};
try_resolve_return_point(db, cache, &mut unresolve)
}
pub fn try_resolve_closure_parent_params(
db: &mut DbIndex,
cache: &mut LuaInferCache,
closure_params: &mut UnResolveParentClosureParams,
) -> ResolveResult {
let signature = db
.get_signature_index()
.get(&closure_params.signature_id)
.ok_or(InferFailReason::None)?;
if !signature.param_docs.is_empty() {
return Ok(());
}
let self_type;
let member_type = match &closure_params.parent_ast {
UnResolveParentAst::LuaFuncStat(func_stat) => {
let func_name = func_stat.get_func_name().ok_or(InferFailReason::None)?;
match func_name {
LuaVarExpr::IndexExpr(index_expr) => {
let prefix_expr = index_expr.get_prefix_expr().ok_or(InferFailReason::None)?;
let prefix_type = infer_expr(db, cache, prefix_expr)?;
self_type = Some(prefix_type.clone());
find_best_function_type(
db,
cache,
&prefix_type,
LuaIndexMemberExpr::IndexExpr(index_expr),
signature,
)
.ok_or(InferFailReason::None)?
}
_ => return Ok(()),
}
}
UnResolveParentAst::LuaTableField(table_field) => {
let parnet_table_expr = table_field
.get_parent::<LuaTableExpr>()
.ok_or(InferFailReason::None)?;
let parent_table_type = infer_table_should_be(db, cache, parnet_table_expr)?;
self_type = Some(parent_table_type.clone());
find_best_function_type(
db,
cache,
&parent_table_type,
LuaIndexMemberExpr::TableField(table_field.clone()),
signature,
)
.ok_or(InferFailReason::None)?
}
UnResolveParentAst::LuaAssignStat(assign) => {
let (vars, exprs) = assign.get_var_and_expr_list();
let position = closure_params.signature_id.get_position();
let idx = exprs
.iter()
.position(|expr| expr.get_position() == position)
.ok_or(InferFailReason::None)?;
let var = vars.get(idx).ok_or(InferFailReason::None)?;
match var {
LuaVarExpr::IndexExpr(index_expr) => {
let prefix_expr = index_expr.get_prefix_expr().ok_or(InferFailReason::None)?;
let prefix_expr_type = infer_expr(db, cache, prefix_expr)?;
self_type = Some(prefix_expr_type.clone());
find_best_function_type(
db,
cache,
&prefix_expr_type,
LuaIndexMemberExpr::IndexExpr(index_expr.clone()),
signature,
)
.ok_or(InferFailReason::None)?
}
_ => return Ok(()),
}
}
};
resolve_closure_member_type(
db,
closure_params,
&member_type,
self_type,
&InferGuard::new(),
)
}
fn resolve_closure_member_type(
db: &mut DbIndex,
closure_params: &UnResolveParentClosureParams,
member_type: &LuaType,
self_type: Option<LuaType>,
infer_guard: &InferGuardRef,
) -> ResolveResult {
match &member_type {
LuaType::DocFunction(doc_func) => {
resolve_doc_function(db, closure_params, doc_func, self_type)
}
LuaType::Signature(id) => {
if id == &closure_params.signature_id {
return Ok(());
}
let signature = db.get_signature_index().get(id);
if let Some(signature) = signature {
let fake_doc_function = signature.to_doc_func_type();
resolve_doc_function(db, closure_params, &fake_doc_function, self_type)
} else {
Ok(())
}
}
LuaType::Union(union_types) => {
let signature = db
.get_signature_index()
.get(&closure_params.signature_id)
.ok_or(InferFailReason::None)?;
let mut final_params = signature.get_type_params().to_vec();
let mut final_ret = LuaType::Never;
let mut has_final_ret = false;
let mut multi_function_type = Vec::new();
for typ in union_types.into_vec() {
match typ {
LuaType::DocFunction(func) => {
multi_function_type.push(func.clone());
}
LuaType::Ref(ref_id) => {
if infer_guard.check(&ref_id).is_err() {
continue;
}
let type_decl = db
.get_type_index()
.get_type_decl(&ref_id)
.ok_or(InferFailReason::None)?;
if let Some(origin) = type_decl.get_alias_origin(db, None)
&& let LuaType::DocFunction(f) = origin
{
multi_function_type.push(f);
}
}
_ => {}
};
}
let mut variadic_type = LuaType::Unknown;
for doc_func in multi_function_type {
let mut doc_params = doc_func.get_params().to_vec();
match (doc_func.is_colon_define(), signature.is_colon_define) {
(true, false) => {
doc_params.insert(0, ("self".to_string(), Some(LuaType::SelfInfer)));
}
(false, true) => {
if !doc_params.is_empty() {
doc_params.remove(0);
}
}
_ => {}
}
for (idx, param) in doc_params.iter().enumerate() {
if let Some(final_param) = final_params.get(idx) {
if final_param.0 == "..." {
for i in idx..doc_params.len() {
if let Some(param) = doc_params.get(i)
&& let Some(typ) = ¶m.1
{
if variadic_type == LuaType::Unknown {
variadic_type = typ.clone();
} else if variadic_type != *typ {
variadic_type = LuaType::Any;
}
}
}
break;
}
let new_type = match (&final_param.1, ¶m.1) {
(Some(final_type), Some(param_type)) => {
Some(TypeOps::Union.apply(db, final_type, param_type))
}
(Some(final_type), None) => Some(final_type.clone()),
(None, Some(param_type)) => Some(param_type.clone()),
(None, None) => None,
};
final_params[idx] = (final_param.0.clone(), new_type);
} else {
final_params.push((param.0.clone(), param.1.clone()));
}
}
has_final_ret = true;
final_ret = TypeOps::Union.apply(db, &final_ret, doc_func.get_ret());
}
if !variadic_type.is_unknown()
&& let Some(param) = final_params.last_mut()
{
param.1 = Some(variadic_type);
}
let final_ret = if !has_final_ret {
LuaType::Unknown
} else {
final_ret
};
resolve_doc_function(
db,
closure_params,
&LuaFunctionType::new(
signature.async_state,
signature.is_colon_define,
signature.is_vararg,
final_params,
final_ret,
),
self_type,
)
}
LuaType::Ref(ref_id) => {
infer_guard.check(ref_id)?;
let type_decl = db
.get_type_index()
.get_type_decl(ref_id)
.ok_or(InferFailReason::None)?;
if type_decl.is_alias()
&& let Some(origin) = type_decl.get_alias_origin(db, None)
{
return resolve_closure_member_type(
db,
closure_params,
&origin,
self_type,
infer_guard,
);
}
Ok(())
}
_ => Ok(()),
}
}
fn resolve_doc_function(
db: &mut DbIndex,
closure_params: &UnResolveParentClosureParams,
doc_func: &LuaFunctionType,
self_type: Option<LuaType>,
) -> ResolveResult {
let signature = db
.get_signature_index_mut()
.get_mut(&closure_params.signature_id)
.ok_or(InferFailReason::None)?;
signature.async_state = doc_func.get_async_state();
let mut doc_params = doc_func.get_params().to_vec();
match (doc_func.is_colon_define(), signature.is_colon_define) {
(true, false) => {
doc_params.insert(0, ("self".to_string(), Some(LuaType::SelfInfer)));
}
(false, true) => {
if !doc_params.is_empty() {
doc_params.remove(0);
}
}
_ => {}
}
if let Some(self_type) = self_type
&& let Some((_, Some(typ))) = doc_params.first()
&& typ.is_self_infer()
{
doc_params[0].1 = Some(self_type);
}
for (index, param) in doc_params.iter().enumerate() {
let name = signature.params.get(index).unwrap_or(¶m.0);
signature.param_docs.insert(
index,
LuaDocParamInfo {
name: name.clone(),
type_ref: param.1.clone().unwrap_or(LuaType::Any),
description: None,
nullable: false,
attributes: None,
},
);
}
if signature.resolve_return == SignatureReturnStatus::UnResolve
|| signature.resolve_return == SignatureReturnStatus::InferResolve
{
signature.resolve_return = SignatureReturnStatus::DocResolve;
signature.return_docs.clear();
signature.return_docs.push(LuaDocReturnInfo {
name: None,
type_ref: doc_func.get_ret().clone(),
description: None,
attributes: None,
});
}
Ok(())
}
fn filter_signature_type(db: &DbIndex, typ: &LuaType) -> Option<Vec<Arc<LuaFunctionType>>> {
let mut result: Vec<Arc<LuaFunctionType>> = Vec::new();
let mut stack = Vec::new();
stack.push(typ.clone());
let guard = InferGuard::new();
while let Some(typ) = stack.pop() {
match typ {
LuaType::DocFunction(func) => {
result.push(func.clone());
}
LuaType::Union(union) => {
let types = union.into_vec();
for typ in types.into_iter().rev() {
stack.push(typ);
}
}
LuaType::Ref(type_ref_id) => {
guard.check(&type_ref_id).ok()?;
let type_decl = db.get_type_index().get_type_decl(&type_ref_id)?;
if let Some(func) = type_decl.get_alias_origin(db, None) {
match func {
LuaType::DocFunction(f) => {
result.push(f.clone());
}
LuaType::Union(union) => {
let types = union.into_vec();
for typ in types.into_iter().rev() {
stack.push(typ);
}
}
_ => {}
}
}
}
_ => {}
}
}
if result.is_empty() {
None
} else {
Some(result)
}
}
fn find_best_function_type(
db: &DbIndex,
cache: &mut LuaInferCache,
prefix_type: &LuaType,
index_member_expr: LuaIndexMemberExpr,
origin_signature: &LuaSignature,
) -> Option<LuaType> {
if let Ok(result) = find_decl_function_type(db, cache, prefix_type, index_member_expr) {
if result.is_current_owner {
if let Some(filtered_types) = filter_signature_type(db, &result.typ) {
match filtered_types.len() {
0 => {}
1 => return Some(LuaType::DocFunction(filtered_types[0].clone())),
_ => {
return Some(LuaType::from_vec(
filtered_types
.into_iter()
.map(|func| LuaType::DocFunction(func.clone()))
.collect(),
));
}
}
}
} else {
return Some(result.typ);
}
}
match origin_signature.overloads.len() {
0 => None,
1 => origin_signature
.overloads
.clone()
.into_iter()
.next()
.map(LuaType::DocFunction),
_ => Some(LuaType::from_vec(
origin_signature
.overloads
.clone()
.into_iter()
.map(LuaType::DocFunction)
.collect(),
)),
}
}