use std::cmp::Ordering;
use std::sync::Arc;
use crate::{
InferFailReason, check_type_compact,
db_index::{DbIndex, LuaFunctionType, LuaType},
semantic::infer::InferCallFuncResult,
};
pub(crate) fn callable_accepts_args(
db: &DbIndex,
func: &LuaFunctionType,
expr_types: &[LuaType],
is_colon_call: bool,
arg_count: Option<usize>,
) -> bool {
let arg_count = arg_count.unwrap_or(expr_types.len());
if func.get_params().len() < arg_count && !is_func_last_param_variadic(func) {
return false;
}
for (arg_index, expr_type) in expr_types.iter().enumerate() {
let Some(param_index) = get_call_param_index(func, arg_index, is_colon_call) else {
continue;
};
let Some(param_type) = get_func_param_type(func, param_index) else {
return false;
};
if !param_type.is_any() && check_type_compact(db, ¶m_type, expr_type).is_err() {
return false;
}
}
true
}
pub fn resolve_signature_by_args(
db: &DbIndex,
overloads: &[Arc<LuaFunctionType>],
expr_types: &[LuaType],
is_colon_call: bool,
arg_count: Option<usize>,
declined_no_flow_args: &[bool],
) -> InferCallFuncResult {
let expr_len = expr_types.len();
let arg_count = arg_count.unwrap_or(expr_len);
let has_declined_no_flow_arg = declined_no_flow_args.iter().any(|declined| *declined);
let mut need_resolve_funcs = match overloads.len() {
0 => return Err(InferFailReason::None),
1 => return Ok(Arc::clone(&overloads[0])),
_ => overloads
.iter()
.map(|it| Some(it.clone()))
.collect::<Vec<_>>(),
};
if expr_len == 0 {
for overload in overloads {
let param_len = overload.get_params().len();
if param_len == 0 {
return Ok(overload.clone());
}
}
}
let mut best_match_result = need_resolve_funcs[0]
.clone()
.expect("Match result should exist");
for (arg_index, expr_type) in expr_types.iter().enumerate() {
let mut current_match_result = ParamMatchResult::Not;
let declined_no_flow_arg = declined_no_flow_args
.get(arg_index)
.copied()
.unwrap_or(false);
for opt_func in &mut need_resolve_funcs {
let func = match opt_func.as_ref() {
None => continue,
Some(func) => func,
};
let param_len = func.get_params().len();
if param_len < arg_count && !is_func_last_param_variadic(func) {
*opt_func = None;
continue;
}
let Some(param_index) = get_call_param_index(func, arg_index, is_colon_call) else {
continue;
};
let Some(param_type) = get_func_param_type(func, param_index) else {
*opt_func = None;
continue;
};
let match_result = if declined_no_flow_arg && expr_type.is_unknown() {
ParamMatchResult::Any
} else if param_type.is_any() {
ParamMatchResult::Any
} else if check_type_compact(db, ¶m_type, expr_type).is_ok() {
ParamMatchResult::Type
} else {
ParamMatchResult::Not
};
if match_result > current_match_result {
current_match_result = match_result;
best_match_result = func.clone();
}
if match_result == ParamMatchResult::Not {
*opt_func = None;
continue;
}
}
if current_match_result == ParamMatchResult::Not {
break;
}
}
let mut rest_need_resolve_funcs = need_resolve_funcs
.iter()
.filter_map(|it| it.clone())
.map(Some)
.collect::<Vec<_>>();
let rest_len = rest_need_resolve_funcs.len();
match rest_len {
0 => {
if has_declined_no_flow_arg {
return Err(InferFailReason::None);
}
return Ok(best_match_result);
}
1 => {
return Ok(rest_need_resolve_funcs[0]
.clone()
.expect("Resolve function should exist"));
}
_ => {}
}
if !has_declined_no_flow_arg
&& let Some(func) = choose_more_specific_callable(
db,
&rest_need_resolve_funcs,
expr_types,
is_colon_call,
declined_no_flow_args,
)
{
return Ok(func);
}
let start_param_index = expr_len;
let mut max_param_len = 0;
for func in rest_need_resolve_funcs.iter().flatten() {
let mut param_len = func.get_params().len();
if func
.get_params()
.last()
.is_some_and(|last_param| last_param.0 == "...")
{
param_len = param_len.saturating_sub(1);
}
if param_len > max_param_len {
max_param_len = param_len;
}
}
for param_index in start_param_index..max_param_len {
let mut current_match_result = ParamMatchResult::Not;
for (i, opt_func) in rest_need_resolve_funcs.iter_mut().enumerate() {
let func = match opt_func.as_ref() {
None => continue,
Some(func) => func,
};
let param_len = func.get_params().len();
let Some(param_index) = get_call_param_index(func, param_index, is_colon_call) else {
continue;
};
let match_result = if param_index >= param_len {
if func
.get_params()
.last()
.is_some_and(|last_param_info| last_param_info.0 == "...")
{
ParamMatchResult::Any
} else if has_declined_no_flow_arg {
ParamMatchResult::Type
} else {
return Ok(func.clone());
}
} else {
let param_info = func
.get_params()
.get(param_index)
.expect("Param index should exist");
let param_type = param_info.1.clone().unwrap_or(LuaType::Any);
if param_type.is_any() {
ParamMatchResult::Any
} else if param_type.is_nullable() {
ParamMatchResult::Type
} else {
ParamMatchResult::Not
}
};
if match_result > current_match_result {
current_match_result = match_result;
best_match_result = func.clone();
}
if match_result == ParamMatchResult::Not {
*opt_func = None;
continue;
}
if !has_declined_no_flow_arg
&& match_result >= ParamMatchResult::Any
&& i + 1 == rest_len
&& param_index + 1 == func.get_params().len()
{
return Ok(func.clone());
}
}
if current_match_result == ParamMatchResult::Not {
break;
}
}
if !has_declined_no_flow_arg {
return Ok(best_match_result);
}
let mut remaining_funcs = rest_need_resolve_funcs.into_iter().flatten();
let Some(first) = remaining_funcs.next() else {
return Err(InferFailReason::None);
};
if remaining_funcs.all(|func| func.get_ret() == first.get_ret()) {
Ok(first)
} else {
Err(InferFailReason::None)
}
}
fn choose_more_specific_callable(
db: &DbIndex,
funcs: &[Option<Arc<LuaFunctionType>>],
expr_types: &[LuaType],
is_colon_call: bool,
declined_no_flow_args: &[bool],
) -> Option<Arc<LuaFunctionType>> {
if expr_types.is_empty()
|| expr_types.iter().enumerate().all(|(i, expr_type)| {
declined_no_flow_args.get(i).copied().unwrap_or(false)
|| expr_type.is_any()
|| expr_type.is_unknown()
})
{
return None;
}
let mut best: Option<Arc<LuaFunctionType>> = None;
let mut has_strict_better = false;
for func in funcs.iter().flatten() {
let Some(best_func) = best.as_ref() else {
best = Some(func.clone());
continue;
};
match compare_callable_specificity(
db,
func,
best_func,
expr_types,
is_colon_call,
declined_no_flow_args,
) {
Some(Ordering::Greater) => {
best = Some(func.clone());
has_strict_better = true;
}
Some(Ordering::Less) => {
has_strict_better = true;
}
Some(Ordering::Equal) => {}
None => return None,
}
}
if has_strict_better { best } else { None }
}
fn compare_callable_specificity(
db: &DbIndex,
a: &LuaFunctionType,
b: &LuaFunctionType,
expr_types: &[LuaType],
is_colon_call: bool,
declined_no_flow_args: &[bool],
) -> Option<Ordering> {
let mut result = Ordering::Equal;
for (arg_index, expr_type) in expr_types.iter().enumerate() {
if declined_no_flow_args
.get(arg_index)
.copied()
.unwrap_or(false)
|| expr_type.is_any()
|| expr_type.is_unknown()
{
continue;
}
let param_index = get_call_param_index(a, arg_index, is_colon_call)?;
let a_param = get_func_param_type(a, param_index)?;
let b_param = get_func_param_type(b, param_index)?;
let param_order = compare_param_specificity(db, &a_param, &b_param, expr_type);
match (result, param_order) {
(Ordering::Equal, order) => result = order,
(Ordering::Greater, Ordering::Less) | (Ordering::Less, Ordering::Greater) => {
return None;
}
_ => {}
}
}
Some(result)
}
fn compare_param_specificity(
db: &DbIndex,
a: &LuaType,
b: &LuaType,
expr_type: &LuaType,
) -> Ordering {
if a == b {
return Ordering::Equal;
}
match (expr_type, a, b) {
(
LuaType::IntegerConst(expr) | LuaType::DocIntegerConst(expr),
LuaType::DocIntegerConst(a),
LuaType::Integer | LuaType::Number,
) if expr == a => return Ordering::Greater,
(
LuaType::IntegerConst(expr) | LuaType::DocIntegerConst(expr),
LuaType::Integer | LuaType::Number,
LuaType::DocIntegerConst(b),
) if expr == b => return Ordering::Less,
(
LuaType::StringConst(expr) | LuaType::DocStringConst(expr),
LuaType::DocStringConst(a),
LuaType::String,
) if expr == a => return Ordering::Greater,
(
LuaType::StringConst(expr) | LuaType::DocStringConst(expr),
LuaType::String,
LuaType::DocStringConst(b),
) if expr == b => return Ordering::Less,
(
LuaType::BooleanConst(expr) | LuaType::DocBooleanConst(expr),
LuaType::DocBooleanConst(a),
LuaType::Boolean,
) if expr == a => return Ordering::Greater,
(
LuaType::BooleanConst(expr) | LuaType::DocBooleanConst(expr),
LuaType::Boolean,
LuaType::DocBooleanConst(b),
) if expr == b => return Ordering::Less,
_ => {}
}
match (a.is_any() || a.is_unknown(), b.is_any() || b.is_unknown()) {
(true, false) => return Ordering::Less,
(false, true) => return Ordering::Greater,
_ => {}
}
let a_sub_b = check_type_compact(db, b, a).is_ok();
let b_sub_a = check_type_compact(db, a, b).is_ok();
match (a_sub_b, b_sub_a) {
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
_ => Ordering::Equal,
}
}
pub(crate) fn is_func_last_param_variadic(func: &LuaFunctionType) -> bool {
if let Some(last_param) = func.get_params().last() {
last_param.0 == "..."
} else {
false
}
}
pub(crate) fn get_call_param_index(
func: &LuaFunctionType,
arg_index: usize,
is_colon_call: bool,
) -> Option<usize> {
let mut param_index = arg_index;
match (func.is_colon_define(), is_colon_call) {
(true, false) => {
if param_index == 0 {
return None;
}
param_index -= 1;
}
(false, true) => {
param_index += 1;
}
_ => {}
}
Some(param_index)
}
pub(crate) fn get_func_param_type(func: &LuaFunctionType, param_index: usize) -> Option<LuaType> {
if let Some(param_info) = func.get_params().get(param_index) {
return Some(param_info.1.clone().unwrap_or(LuaType::Any));
}
let last_param_info = func.get_params().last()?;
if last_param_info.0 == "..." {
Some(last_param_info.1.clone().unwrap_or(LuaType::Any))
} else {
None
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
enum ParamMatchResult {
Not,
Any,
Type,
}