Skip to content

Commit 678c001

Browse files
authored
Merge pull request #1150 from xuhuanzy/update
update
2 parents be22cae + 1156b83 commit 678c001

36 files changed

Lines changed: 1105 additions & 255 deletions

File tree

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ repos:
3838
- id: check-symlinks
3939
- id: destroyed-symlinks
4040
- id: check-vcs-permalinks
41+
- repo: https://github.com/crate-ci/typos
42+
rev: master
43+
hooks:
44+
- id: typos

crates/emmylua_code_analysis/src/compilation/test/generic_test.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,131 @@ mod test {
903903
assert_eq!(ws.humanize_type(result_ty), "integer");
904904
}
905905

906+
#[test]
907+
fn test_call_operator_self_infer_on_index() {
908+
let mut ws = VirtualWorkspace::new();
909+
ws.def(
910+
r#"
911+
---@class Factory
912+
---@overload fun(): self
913+
914+
---@class Mod
915+
---@field Factory Factory
916+
---@type Mod
917+
local Mod
918+
919+
result = Mod.Factory()
920+
"#,
921+
);
922+
923+
let result_ty = ws.expr_ty("result");
924+
assert_eq!(ws.humanize_type(result_ty), "Factory");
925+
}
926+
927+
#[test]
928+
fn test_call_operator_self_infer_filters_union_receiver() {
929+
let mut ws = VirtualWorkspace::new();
930+
ws.def(
931+
r#"
932+
---@class Callable
933+
---@overload fun(): self
934+
935+
---@type Callable|string
936+
local value
937+
938+
result = value()
939+
"#,
940+
);
941+
942+
let result_ty = ws.expr_ty("result");
943+
assert_eq!(ws.humanize_type(result_ty), "Callable");
944+
}
945+
946+
#[test]
947+
fn test_call_operator_self_infer_through_generic_alias() {
948+
let mut ws = VirtualWorkspace::new();
949+
ws.def(
950+
r#"
951+
---@class Callable
952+
---@overload fun(): self
953+
954+
---@alias Box<T> T
955+
956+
---@type Box<Callable>
957+
local value
958+
959+
result = value()
960+
"#,
961+
);
962+
963+
let result_ty = ws.expr_ty("result");
964+
assert_eq!(ws.humanize_type(result_ty), "Callable");
965+
}
966+
967+
#[test]
968+
fn test_call_operator_self_infer_through_intersection() {
969+
let mut ws = VirtualWorkspace::new();
970+
ws.def(
971+
r#"
972+
---@class Callable
973+
---@overload fun(): self
974+
975+
---@class Extra
976+
977+
---@type Callable & Extra
978+
local value
979+
980+
result = value()
981+
"#,
982+
);
983+
984+
let result_ty = ws.expr_ty("result");
985+
assert_eq!(ws.humanize_type(result_ty), "(Callable & Extra)");
986+
}
987+
988+
#[test]
989+
fn test_colon_call_generic_uses_receiver_when_member_is_callable() {
990+
let mut ws = VirtualWorkspace::new();
991+
ws.def(
992+
r#"
993+
---@class Owner
994+
---@field run CallableMethod
995+
996+
---@class CallableMethod
997+
---@overload fun<T>(owner: T): T
998+
999+
---@type Owner
1000+
local owner
1001+
1002+
result = owner:run()
1003+
"#,
1004+
);
1005+
1006+
let result_ty = ws.expr_ty("result");
1007+
assert_eq!(ws.humanize_type(result_ty), "Owner");
1008+
}
1009+
1010+
#[test]
1011+
fn test_plain_table_metatable_call_return_self() {
1012+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
1013+
ws.def(
1014+
r#"
1015+
factory = setmetatable({}, {
1016+
---@return self
1017+
__call = function(self)
1018+
return self
1019+
end,
1020+
})
1021+
1022+
result = factory()
1023+
"#,
1024+
);
1025+
1026+
let result_ty = ws.expr_ty("result");
1027+
let humanized = ws.humanize_type(result_ty);
1028+
assert_eq!(humanized, "table");
1029+
}
1030+
9061031
#[test]
9071032
fn test_function_generic_constraint_is_fallback() {
9081033
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use itertools::Itertools;
55

66
use crate::{
77
AsyncState, DbIndex, LuaAliasCallType, LuaConditionalType, LuaFunctionType, LuaGenericType,
8-
LuaIntersectionType, LuaMemberKey, LuaMemberOwner, LuaObjectType, LuaSignatureId,
9-
LuaStringTplType, LuaTupleType, LuaType, LuaTypeDeclId, LuaUnionType, TypeSubstitutor,
10-
VariadicType,
8+
LuaIntersectionType, LuaMappedType, LuaMemberKey, LuaMemberOwner, LuaObjectType,
9+
LuaSignatureId, LuaStringTplType, LuaTupleType, LuaType, LuaTypeDeclId, LuaUnionType,
10+
TypeSubstitutor, VariadicType,
1111
};
1212

1313
use super::{LuaAliasCallKind, LuaMultiLineUnion};
@@ -217,9 +217,9 @@ impl<'a> TypeHumanizer<'a> {
217217
}
218218
LuaType::Language(s) => w.write_str(s),
219219
LuaType::Conditional(c) => self.write_conditional_type(c, w),
220+
LuaType::Mapped(mapped) => self.write_mapped_type(mapped, w),
220221
LuaType::Never => w.write_str("never"),
221222
LuaType::ModuleRef(file_id) => self.write_module_ref(*file_id, w),
222-
_ => w.write_str("unknown"),
223223
}
224224
}
225225

@@ -1082,6 +1082,36 @@ impl<'a> TypeHumanizer<'a> {
10821082
Ok(())
10831083
}
10841084

1085+
// ─── Mapped ─────────────────────────────────────────────────────
1086+
1087+
fn write_mapped_type<W: Write>(&mut self, mapped: &LuaMappedType, w: &mut W) -> fmt::Result {
1088+
w.write_str("{ ")?;
1089+
if mapped.is_readonly {
1090+
w.write_str("readonly ")?;
1091+
}
1092+
1093+
w.write_char('[')?;
1094+
w.write_str(mapped.param.1.name.as_str())?;
1095+
w.write_str(" in ")?;
1096+
1097+
let saved = self.level;
1098+
self.level = self.child_level();
1099+
if let Some(constraint) = &mapped.param.1.constraint {
1100+
self.write_type(constraint, w)?;
1101+
} else {
1102+
w.write_str("unknown")?;
1103+
}
1104+
w.write_char(']')?;
1105+
if mapped.is_optional {
1106+
w.write_char('?')?;
1107+
}
1108+
w.write_str(": ")?;
1109+
self.write_type(&mapped.value, w)?;
1110+
self.level = saved;
1111+
1112+
w.write_str("; }")
1113+
}
1114+
10851115
// ─── ModuleRef ──────────────────────────────────────────────────
10861116

10871117
fn write_module_ref<W: Write>(&mut self, file_id: crate::FileId, w: &mut W) -> fmt::Result {

crates/emmylua_code_analysis/src/diagnostic/checker/generic/call_constraint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(super) fn build_call_constraint_context(
6060
params.insert(0, ("self".into(), Some(LuaType::SelfInfer)));
6161
}
6262
(true, false) => {
63-
let self_type = semantic_model.infer_call_self_type(call_expr)?;
63+
let self_type = semantic_model.resolve_call_self_type(call_expr)?;
6464
args.insert(
6565
0,
6666
CallConstraintArg {

crates/emmylua_code_analysis/src/diagnostic/checker/missing_fields.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use hashbrown::{HashMap, HashSet};
22

3-
use emmylua_parser::{LuaAst, LuaAstNode, LuaSyntaxId, LuaTableExpr};
3+
use emmylua_parser::{LuaAst, LuaAstNode, LuaExpr, LuaSyntaxId, LuaTableExpr};
44
use rowan::NodeOrToken;
55

66
use crate::{
@@ -63,6 +63,13 @@ fn check_table_expr(
6363
let table_type = match semantic_model.infer_table_should_be(expr.clone())? {
6464
LuaType::Union(union) => {
6565
let mut check_type = None;
66+
let array_like_expr_type = if expr.is_array() || expr.is_empty() {
67+
semantic_model
68+
.infer_expr(LuaExpr::TableExpr(expr.clone()))
69+
.ok()
70+
} else {
71+
None
72+
};
6673
for ty in union.into_vec() {
6774
match &ty {
6875
LuaType::Ref(_)
@@ -74,10 +81,14 @@ fn check_table_expr(
7481
}
7582
check_type = Some(ty);
7683
}
77-
LuaType::Table | LuaType::Userdata => {
84+
LuaType::Table | LuaType::Userdata | LuaType::TableGeneric(_) => {
7885
return Some(());
7986
}
80-
LuaType::TableGeneric(_) => {
87+
LuaType::Array(_) | LuaType::Tuple(_)
88+
if array_like_expr_type.as_ref().is_some_and(|expr_type| {
89+
semantic_model.type_check(&ty, expr_type).is_ok()
90+
}) =>
91+
{
8192
return Some(());
8293
}
8394
_ => {}

crates/emmylua_code_analysis/src/diagnostic/checker/param_check/type_mismatch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(super) fn check_param_types(
3030
.into_iter()
3131
.unzip();
3232

33-
let self_type = semantic_model.infer_call_self_type(&facts.call_expr);
33+
let self_type = semantic_model.resolve_call_self_type(&facts.call_expr);
3434
let colon_range = facts
3535
.call_expr
3636
.get_colon_token()

crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,40 @@ return t
14211421
));
14221422
}
14231423

1424+
#[test]
1425+
fn test_function_parameter_contravariance_assignment() {
1426+
let mut ws = VirtualWorkspace::new();
1427+
ws.def(
1428+
r#"
1429+
---@class A
1430+
---@class B
1431+
---@class C
1432+
---@class D
1433+
1434+
---@param a A | B | C
1435+
---@return boolean
1436+
function condition(a)
1437+
return true
1438+
end
1439+
"#,
1440+
);
1441+
assert!(!ws.has_no_diagnostic(
1442+
DiagnosticCode::AssignTypeMismatch,
1443+
r#"
1444+
---@type fun(a: A | B | C | D): boolean
1445+
local tmp = condition
1446+
"#,
1447+
));
1448+
1449+
assert!(ws.has_no_diagnostic(
1450+
DiagnosticCode::AssignTypeMismatch,
1451+
r#"
1452+
---@type fun(a: A | B): boolean
1453+
local tmp = condition
1454+
"#,
1455+
));
1456+
}
1457+
14241458
#[test]
14251459
fn test_generic_extends_table() {
14261460
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/diagnostic/test/generic_constraint_mismatch_test.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,26 @@ mod test {
531531
));
532532
}
533533

534+
#[test]
535+
fn test_colon_call_constraint_uses_receiver_when_member_is_callable() {
536+
let mut ws = VirtualWorkspace::new();
537+
assert!(ws.has_no_diagnostic(
538+
DiagnosticCode::GenericConstraintMismatch,
539+
r#"
540+
---@class Owner
541+
---@field run CallableMethod
542+
543+
---@class CallableMethod
544+
---@overload fun<T: Owner>(owner: T)
545+
546+
---@type Owner
547+
local owner
548+
549+
owner:run()
550+
"#,
551+
));
552+
}
553+
534554
#[test]
535555
fn test_extend_string() {
536556
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,61 @@ foo({})
260260
));
261261
}
262262

263+
#[test]
264+
fn test_union_enum_array_does_not_report_missing_fields() {
265+
let mut ws = VirtualWorkspace::new();
266+
assert!(ws.has_no_diagnostic(
267+
DiagnosticCode::MissingFields,
268+
r#"
269+
---@enum NiceEnum
270+
local GOODGUYS = {
271+
superman = 1
272+
}
273+
274+
---@alias Evil string | NiceEnum
275+
276+
---@param evils Evil | (Evil[])
277+
local function do_evil(evils) end
278+
279+
do_evil({ "hi", "dead" })
280+
"#
281+
));
282+
}
283+
284+
#[test]
285+
fn test_union_array_named_table_still_reports_missing_fields() {
286+
let mut ws = VirtualWorkspace::new();
287+
assert!(!ws.has_no_diagnostic(
288+
DiagnosticCode::MissingFields,
289+
r#"
290+
---@class Foo
291+
---@field name string
292+
293+
---@param foo Foo | Foo[]
294+
local function use_foo(foo) end
295+
296+
use_foo({ typo = 1 })
297+
"#
298+
));
299+
}
300+
301+
#[test]
302+
fn test_union_array_empty_table_does_not_report_missing_fields() {
303+
let mut ws = VirtualWorkspace::new();
304+
assert!(ws.has_no_diagnostic(
305+
DiagnosticCode::MissingFields,
306+
r#"
307+
---@class Foo
308+
---@field name string
309+
310+
---@param foo Foo | Foo[]
311+
local function use_foo(foo) end
312+
313+
use_foo({})
314+
"#
315+
));
316+
}
317+
263318
#[test]
264319
fn test_multiline_union_nil_field_is_optional() {
265320
let mut ws = VirtualWorkspace::new();

0 commit comments

Comments
 (0)