#[cfg(test)]
mod test {
use crate::{DiagnosticCode, VirtualWorkspace};
use lsp_types::NumberOrString;
use tokio_util::sync::CancellationToken;
#[test]
fn test_1() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Component
---@class G.A
---@class G.B: Component
---@generic T: Component
---@param name `T`
---@return T
local function new(name)
return name
end
new("G.A")
"#
));
}
#[test]
fn test_2() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Component
---@class G.A
---@class G.B: Component
---@generic T: Component
---@param name T
---@return T
local function new(name)
return name
end
new("G.A")
"#
));
}
#[test]
fn test_3() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
local nargs = select('#')
"#
));
}
#[test]
fn test_4() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Component
---@class G.A
---@class G.B: Component
---@class G.C: G.B
---@generic T: Component
---@param name `T`
---@return T
local function new(name)
return name
end
new("G.C")
"#
));
}
#[test]
fn test_class_1() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Component
---@class G.A
---@class G.B: Component
---@class GenericTest<T: Component>
local M = {}
---@param a T
function M.new(a)
end
---@type G.A
local a
M.new(a)
"#
));
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type G.B
local b
---@type GenericTest
local gt
gt.new(b)
"#
));
}
#[test]
fn test_class_2() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Component
---@class G.A
---@class G.B: Component
---@class GenericTest<T: Component>
local M = {}
---@param a T
function M.new(a)
end
---@type GenericTest<G.A>
local a
"#
));
}
#[test]
fn test_class_generic_default_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base<T extends number = string>
"#
));
}
#[test]
fn test_bare_type_use_does_not_repeat_generic_default_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
ws.def_file(
"base.lua",
r#"
---@class Base<T extends number = string>
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type Base
Base_A = {}
"#
));
}
#[test]
fn test_explicit_type_arg_still_reports_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
ws.def_file(
"base.lua",
r#"
---@class Base<T extends number = number>
"#,
);
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type Base<string>
Base_A = {}
"#
));
}
#[test]
fn test_alias_multi_generic_keyof_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@alias B<T, K extends keyof A> A[K]
---@type B<A, 'two'>
local tmp
"#
));
}
#[test]
fn test_alias_multi_generic_keyof_constraint_match() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@alias B<T, K extends keyof A> A[K]
---@type B<A, 'one'>
local tmp
"#
));
}
#[test]
fn test_alias_keyof_constraint_accepts_union_of_valid_keys() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@field two 2
---@field three 3
---@alias Pick<T, K extends keyof T> nil
---@type Pick<A, 'one' | 'two'>
local tmp
"#,
));
}
#[test]
fn test_alias_keyof_constraint_accepts_keyof_type() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@field two 2
---@field three 3
---@alias C<K extends keyof A> any
---@type C<keyof A>
local tmp
"#,
));
}
#[test]
fn test_alias_keyof_constraint_rejects_keyof_type_with_invalid_key() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@field two 2
---@class B
---@field one 1
---@field missing 3
---@alias C<K extends keyof A> any
---@type C<keyof B>
local tmp
"#,
));
}
#[test]
fn test_alias_keyof_constraint_rejects_invalid_union_for_keyof_type() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@field two 2
---@alias C<K extends keyof A> any
---@type C<'one' | 'missing'>
local tmp
"#,
));
}
#[test]
fn test_alias_dependent_keyof_constraint_uses_explicit_type_arg() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base
---@field common 1
---@class Exact: Base
---@field one 1
---@field two 2
---@alias PickFrom<T extends Base, K extends keyof T> any
---@type PickFrom<Exact, keyof Exact>
local tmp
"#,
));
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type PickFrom<Exact, 'error'>
local tmp
"#,
));
}
#[test]
fn test_alias_dependent_keyof_constraint_rejects_keyof_other_type() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base
---@field common 1
---@class Exact: Base
---@field one 1
---@class Extra: Base
---@field one 1
---@field missing 2
---@alias PickFrom<T extends Base, K extends keyof T> any
---@type PickFrom<Exact, keyof Extra>
local tmp
"#,
));
}
#[test]
fn test_alias_keyof_constraint_rejects_union_with_invalid_key() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class A
---@field one 1
---@field two 2
---@alias Pick<T, K extends keyof T> nil
---@type Pick<A, 'one' | 'missing'>
local tmp
"#,
));
}
#[test]
fn test_alias_keyof_constraint_reports_invalid_literal_key() {
let mut ws = VirtualWorkspace::new();
ws.enable_check(DiagnosticCode::GenericConstraintMismatch);
let file_id = ws.def(
r#"
---@class A
---@field one 1
---@alias Pick<T, K extends keyof T> nil
---@type Pick<A, 'missing'>
local tmp
"#,
);
let diagnostics = ws
.analysis
.diagnose_file(file_id, CancellationToken::new())
.unwrap();
let code = Some(NumberOrString::String(
DiagnosticCode::GenericConstraintMismatch
.get_name()
.to_string(),
));
let diagnostic = diagnostics
.iter()
.find(|diagnostic| diagnostic.code == code)
.expect("expected generic constraint mismatch diagnostic");
assert!(
diagnostic.message.contains("\"missing\""),
"{}",
diagnostic.message
);
}
#[test]
fn test_class_generic_default_constraint_match() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base<T extends number = number>
"#
));
}
#[test]
fn test_dependent_generic_default_must_satisfy_rigid_type_param_constraint() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Box<T extends string, U extends T = "x">
"#
));
}
#[test]
fn test_dependent_generic_default_can_reference_same_type_param() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Box<T extends string, U extends T = T>
"#
));
}
#[test]
fn test_dependent_generic_default_uses_type_param_constraint_as_upper_bound() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Box<T extends string, U extends string = T>
"#
));
}
#[test]
fn test_dependent_keyof_default_must_satisfy_any_valid_type_arg() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base
---@field common 1
---@class Exact: Base
---@field one 1
---@alias PickFrom<T extends Base = Exact, K extends keyof T = keyof Exact> any
"#,
));
}
#[test]
fn test_dependent_keyof_default_can_reference_same_type_param() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Base
---@field common 1
---@class Exact: Base
---@field one 1
---@alias PickFrom<T extends Base = Exact, K extends keyof T = keyof T> any
"#,
));
}
#[test]
fn test_alias_generic_default_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@alias Base<T extends number = string> T
"#
));
}
#[test]
fn test_function_generic_default_constraint_mismatch() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@generic T extends number = string
local function f()
end
"#
));
}
#[test]
fn test_call_constraint_uses_uninferred_generic_default() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@generic T = number, U extends T
---@param value U
local function f(value)
end
f("not a number")
"#
));
}
#[test]
fn test_colon_call_constraint_uses_receiver_when_member_is_callable() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Owner
---@field run CallableMethod
---@class CallableMethod
---@overload fun<T: Owner>(owner: T)
---@type Owner
local owner
owner:run()
"#,
));
}
#[test]
fn test_extend_string() {
let mut ws = VirtualWorkspace::new();
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class ABC1
---@generic T: string
---@param t `T`
---@return T
local function test(t)
end
test("ABC1")
"#
));
}
#[test]
fn test_str_tpl_ref_param() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@generic T
---@param a `T`
local function bar(a)
end
---@generic T
---@param a `T`
local function foo(a)
bar(a)
end
"#
));
}
#[test]
fn test_issue_516() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@generic T: table
---@param t T
---@return T
local function wrap(t)
return t
end
local a --- @type string[]?
wrap(assert(a))
"#
));
}
#[test]
fn test_union() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class ab
---@generic T
---@param a `T`|T
---@return T
function name(a)
return a
end
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type ab
local a
name(a)
"#
));
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
name("ab")
"#
));
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
name("a")
"#
));
}
#[test]
fn test_union_2() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic T: table
---@param obj T
function add(obj)
end
---@class GCNode
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@generic T: table
---@param obj T | string
---@return T?
function bindGC(obj)
if type(obj) == "string" then
---@type GCNode
obj = {}
end
return add(obj)
end
"#
));
}
#[test]
fn test_union_3() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic T: table
---@param obj T
function add(obj)
end
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class GCNode<T: table>
GCNode = {}
---@param obj T
---@return T?
function GCNode:bindGC(obj)
return add(obj)
end
"#
));
}
#[test]
fn test_object_constraint_with_class_duck_typing() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class MyPos
---@field x number
---@field y number
---@field z number
---@overload fun(x: number, y: number, z: number): MyPos
MyPos = {}
MyPos.__index = MyPos
setmetatable(MyPos, {
__call = function(x, y, z)
return setmetatable({ x = x, y = y, z = z }, MyPos)
end
})
function MyPos:next()
self.x = self.x + 1
self.y = self.y + 1
return self
end
---@generic T: { x: number, y: number, z: number }
---@param pos T
local function getTile(pos) end
local p = MyPos(0, 0, 0):next()
getTile(p)
local p2 = MyPos(1, 1, 1)
getTile(p2:next())
"#
));
}
#[test]
fn test_generic_keyof_param_scope() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic T, K extends keyof T
---@param object T
---@param key K
---@return std.RawGet<T, K>
function pick(object, key)
end
---@class Person
---@field name string
"#,
);
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type Person
local person
pick(person, "abc")
"#
));
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@type Person
local person
pick(person, "name")
"#
));
}
#[test]
fn test_generic_constraint_can_use_conditional_type() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@generic U, T extends U extends string and number or boolean
---@param val T
function process(val)
return val
end
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
process--[[@<string, number>]](123)
"#,
));
assert!(!ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
process--[[@<string, boolean>]](true)
"#,
));
}
#[test]
fn test_overload_generic_constraint_merged_params() {
let mut ws = VirtualWorkspace::new();
ws.def_file(
"mylib.lua",
r#"
---@meta mylib
local M = {}
---@generic T: table
---@overload fun(a: string, b: T?): T
---@overload fun(a: string, b: integer, c: T?): T
function M.decode(a, b_or_c, c) end
return M
"#,
);
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
local m = require("mylib")
local ret = m.decode("x", 42)
"#
));
}
}