#[cfg(test)]
mod test {
use crate::{DiagnosticCode, LuaType, VirtualWorkspace};
const STACKED_PCALL_ALIAS_GUARDS: usize = 180;
#[test]
fn test_issue_263() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias aaa fun(a: string, b: integer): integer
---@type aaa
local a
d, b = pcall(a, "", 1)
"#,
);
let aaa_ty = ws.expr_ty("b");
let expected = ws.ty("integer|string");
assert_eq!(aaa_ty, expected);
}
#[test]
fn test_issue_280() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
assert!(ws.has_no_diagnostic(
DiagnosticCode::ParamTypeMismatch,
r#"
---@class D11.AAA
local AAA = {}
---@param a string
---@param b number
function AAA:name(a, b)
end
---@param a string
---@param b number
function AAA:t(a, b)
local ok, err = pcall(self.name, self, a, b)
end
"#
));
}
#[test]
fn test_nested_pcall_higher_order_return_shape() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@return integer
local function f()
return 1
end
ok, status, payload = pcall(pcall, f)
"#,
);
assert_eq!(ws.expr_ty("status"), ws.ty("true|false|string"));
assert_eq!(ws.expr_ty("payload"), ws.ty("string|integer|nil"));
}
#[test]
fn test_pcall_return_overload_narrow_after_error_guard() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@return integer
local function foo()
return 2
end
local ok, result = pcall(foo)
if not ok then
error(result)
end
a = result
"#,
);
assert_eq!(ws.expr_ty("a"), ws.ty("integer"));
}
#[test]
fn test_pcall_array_return_narrow_after_error_guard() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@class Runner
---@class File
---@param specs string[]
---@param runner Runner
---@return File[] files
local function startTests(specs, runner)
local ok, result = pcall(function ()
---@type File[]
local files
return files
end)
outside = result
if not ok then
error(result)
end
---@cast result - string
narrowed = result
return result
end
"#,
);
assert_eq!(ws.expr_ty("outside"), ws.ty("File[]|string"));
assert_eq!(ws.expr_ty("narrowed"), ws.ty("File[]"));
}
#[test]
fn test_nested_pcall_like_without_return_overload() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic T, R
---@param f fun(...: T...): R...
---@param ... T...
---@return boolean, R...
local function safe_call(f, ...)
return true, f(...)
end
---@return integer
local function produce()
return 1
end
ok, status, payload = safe_call(safe_call, produce)
"#,
);
assert_eq!(ws.expr_ty("status"), ws.ty("boolean"));
assert_eq!(ws.expr_ty("payload"), ws.ty("integer"));
}
#[test]
fn test_nested_pcall_like_without_return_overload2() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic T, R, R1
---@param f sync fun(...: T...): R1, R...
---@param ... T...
---@return boolean, R1|string, R...
local function pcall_like(f, ...) end
---@return integer
local function produce()
return 1
end
ok, status, payload = pcall_like(pcall_like, produce)
"#,
);
assert_eq!(ws.expr_ty("ok"), ws.ty("boolean"));
assert_eq!(ws.expr_ty("status"), ws.ty("boolean|string"));
assert_eq!(ws.expr_ty("payload"), ws.ty("integer|string"));
}
#[test]
fn test_pcall_stacked_alias_guards_build_semantic_model() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
let repeated_guards =
"if failed then error(result) end\n".repeat(STACKED_PCALL_ALIAS_GUARDS);
let block = format!(
r#"
---@return integer
local function foo()
return 1
end
local ok, result = pcall(foo)
local failed = ok == false
{repeated_guards}
narrowed = result
"#,
);
let file_id = ws.def(&block);
assert!(
ws.analysis
.compilation
.get_semantic_model(file_id)
.is_some(),
"expected semantic model for stacked pcall alias guard repro"
);
assert_eq!(ws.expr_ty("narrowed"), ws.ty("integer"));
}
#[test]
fn test_pcall_alias_callee_narrows_return_after_error_guard() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@return integer
local function foo()
return 1
end
local runner = pcall
local ok, result = runner(foo)
if not ok then
error(result)
end
narrowed = result
"#,
);
assert_eq!(ws.expr_ty("narrowed"), ws.ty("integer"));
}
#[test]
fn test_pcall_narrows_type_guarded_callable_return_after_error_guard() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@param a string|fun(): integer
local function foo(a)
if type(a) == "string" then
return
end
local ok, result = pcall(a)
if not ok then
return
end
narrowed = result
end
"#,
);
let narrowed = ws.expr_ty("narrowed");
assert_eq!(ws.humanize_type(narrowed), "integer");
}
#[test]
fn test_pcall_narrows_type_guarded_callable_return_with_forwarded_arg() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@param a string|fun(value: integer): string
local function foo(a)
if type(a) == "string" then
return
end
local ok, result = pcall(a, 1)
if not ok then
return
end
narrowed = result
end
"#,
);
let narrowed = ws.expr_ty("narrowed");
assert_eq!(ws.humanize_type(narrowed), "string");
}
#[test]
fn test_pcall_narrows_type_guarded_callable_return_with_table_arg() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@param cb string|fun(a: {}): integer
local function foo(cb)
if type(cb) == "string" then
return
end
local ok, result = pcall(cb, {})
if not ok then
return
end
narrowed = result
end
"#,
);
let narrowed = ws.expr_ty("narrowed");
assert_eq!(ws.humanize_type(narrowed), "integer");
}
#[test]
fn test_pcall_any_callable_splits_success_unknown_and_failure_string() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@type any
local x
local ok, result = pcall(x)
outside = result
if ok then
success = result
else
failure = result
end
"#,
);
assert_eq!(ws.expr_ty("outside"), ws.ty("unknown|string"));
assert_eq!(ws.expr_ty("success"), ws.ty("unknown"));
assert_eq!(ws.expr_ty("failure"), ws.ty("string"));
}
#[test]
fn test_issue_1020_pcall_preserves_pairs_value_function_return() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
local t = {
{ id = 1, func = function() return a > 0 end },
{ id = 2, func = function() return a > 0 end },
{ id = 3, func = function() return a > 0 end },
}
for _, v in pairs(t) do
local f = v.func
captured_f = f
local success, result = pcall(f)
outside = result
if success then
success_result = result
else
failure_result = result
end
end
"#,
);
let captured_f = ws.expr_ty("captured_f");
let outside = ws.expr_ty("outside");
let success_result = ws.expr_ty("success_result");
let failure_result = ws.expr_ty("failure_result");
assert_eq!(ws.humanize_type(captured_f), "fun() -> boolean");
assert_eq!(ws.humanize_type(outside), "(boolean|string)");
assert_eq!(ws.humanize_type(success_result), "boolean");
assert_eq!(ws.humanize_type(failure_result), "string");
}
#[test]
fn test_return_overload_unions_callable_union_members_with_same_domain() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias FnA fun(x: integer): integer
---@alias FnB fun(x: integer): boolean
---@type FnA | FnB
local run
_, a = pcall(run, 1)
"#,
);
assert_eq!(
ws.expr_ty("a"),
LuaType::from_vec(vec![LuaType::Integer, LuaType::Boolean, LuaType::String])
);
}
#[test]
fn test_return_overload_callable_union_is_not_order_dependent() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias FnA fun(x: integer): integer
---@alias FnB fun(x: integer): boolean
---@type FnB | FnA
local run
_, a = pcall(run, 1)
"#,
);
assert_eq!(
ws.expr_ty("a"),
LuaType::from_vec(vec![LuaType::Integer, LuaType::Boolean, LuaType::String])
);
}
#[test]
fn test_return_overload_unions_callable_intersection_members_with_same_domain() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias FnA fun(x: integer): integer
---@alias FnB fun(x: integer): boolean
---@type FnA & FnB
local run
_, a = pcall(run, 1)
"#,
);
assert_eq!(
ws.expr_ty("a"),
LuaType::from_vec(vec![LuaType::Integer, LuaType::Boolean, LuaType::String])
);
}
#[test]
fn test_return_overload_narrows_callable_union_member_by_arg_shape() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias FnA fun(x: integer): integer
---@alias FnB fun(x: string): integer
---@type FnA | FnB
local run
_, a = pcall(run, 1)
"#,
);
assert_eq!(ws.expr_ty("a"), ws.ty("integer|string"));
}
#[test]
fn test_return_overload_narrows_callable_intersection_member_by_arg_shape() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
---@alias FnA fun(x: integer): integer
---@alias FnB fun(x: string): boolean
---@type FnA & FnB
local run
_, a = pcall(run, 1)
"#,
);
assert_eq!(ws.expr_ty("a"), ws.ty("integer|string"));
}
}