Skip to content

Commit 300add7

Browse files
committed
fix(type-check): resolve indexed access through keyof aliases
1 parent 2357628 commit 300add7

4 files changed

Lines changed: 97 additions & 22 deletions

File tree

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

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -540,27 +540,41 @@ impl<'a> TypeHumanizer<'a> {
540540
let operands = inner.get_operands();
541541
let saved = self.level;
542542
self.level = self.child_level();
543-
let result = (|| match inner.get_call_kind() {
543+
let result = match inner.get_call_kind() {
544544
LuaAliasCallKind::KeyOf => {
545-
w.write_str("keyof ")?;
545+
let mut result = w.write_str("keyof ");
546546
for (i, ty) in operands.iter().enumerate() {
547-
if i > 0 {
548-
w.write_char(',')?;
547+
if result.is_ok() && i > 0 {
548+
result = w.write_char(',');
549+
}
550+
if result.is_ok() {
551+
result = self.write_type(ty, w);
549552
}
550-
self.write_type(ty, w)?;
551553
}
552-
Ok(())
554+
result
553555
}
554556
LuaAliasCallKind::Extends if operands.len() == 2 => {
555-
self.write_type(&operands[0], w)?;
556-
w.write_str(" extends ")?;
557-
self.write_type(&operands[1], w)
557+
let mut result = self.write_type(&operands[0], w);
558+
if result.is_ok() {
559+
result = w.write_str(" extends ");
560+
}
561+
if result.is_ok() {
562+
result = self.write_type(&operands[1], w);
563+
}
564+
result
558565
}
559566
LuaAliasCallKind::Index if operands.len() == 2 => {
560-
self.write_type(&operands[0], w)?;
561-
w.write_char('[')?;
562-
self.write_type(&operands[1], w)?;
563-
w.write_char(']')
567+
let mut result = self.write_type(&operands[0], w);
568+
if result.is_ok() {
569+
result = w.write_char('[');
570+
}
571+
if result.is_ok() {
572+
result = self.write_type(&operands[1], w);
573+
}
574+
if result.is_ok() {
575+
result = w.write_char(']');
576+
}
577+
result
564578
}
565579
call_kind => {
566580
let basic = match call_kind {
@@ -574,17 +588,24 @@ impl<'a> TypeHumanizer<'a> {
574588
LuaAliasCallKind::RawGet => "rawget",
575589
LuaAliasCallKind::Merge => "Merge",
576590
};
577-
w.write_str(basic)?;
578-
w.write_char('<')?;
591+
let mut result = w.write_str(basic);
592+
if result.is_ok() {
593+
result = w.write_char('<');
594+
}
579595
for (i, ty) in operands.iter().enumerate() {
580-
if i > 0 {
581-
w.write_char(',')?;
596+
if result.is_ok() && i > 0 {
597+
result = w.write_char(',');
598+
}
599+
if result.is_ok() {
600+
result = self.write_type(ty, w);
582601
}
583-
self.write_type(ty, w)?;
584602
}
585-
w.write_char('>')
603+
if result.is_ok() {
604+
result = w.write_char('>');
605+
}
606+
result
586607
}
587-
})();
608+
};
588609
self.level = saved;
589610
result
590611
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,28 @@ mod test {
15371537
));
15381538
}
15391539

1540+
#[test]
1541+
fn test_index_access_with_keyof_alias() {
1542+
let mut ws = VirtualWorkspace::new();
1543+
assert!(ws.has_no_diagnostic(
1544+
DiagnosticCode::ParamTypeMismatch,
1545+
r#"
1546+
---@class A
1547+
---@field one 1
1548+
1549+
---@alias KeyofA keyof A
1550+
---@type A[KeyofA]
1551+
local tmp
1552+
1553+
---@param v 1
1554+
local function test(v)
1555+
end
1556+
1557+
test(tmp)
1558+
"#,
1559+
));
1560+
}
1561+
15401562
#[test]
15411563
fn test_origin_self() {
15421564
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_special_generic.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ pub(super) fn instantiate_alias_call(
4444
return LuaType::Unknown;
4545
}
4646

47-
let members = get_keyof_members(context.db, &operands[0]).unwrap_or_default();
47+
let owner = instantiate_alias_origin_operand(context, &operands[0])
48+
.unwrap_or_else(|| operands[0].clone());
49+
let members = get_keyof_members(context.db, &owner).unwrap_or_default();
4850
// keyof 表示可取键的联合类型, 不是按位置展开的 tuple.
4951
let member_key_types = members
5052
.iter()
@@ -92,6 +94,7 @@ pub(super) fn instantiate_alias_call(
9294
}
9395

9496
let key = resolve_literal_operand(operand_exprs.get(1), context.substitutor)
97+
.or_else(|| instantiate_alias_origin_operand(context, &operands[1]))
9598
.unwrap_or_else(|| operands[1].clone());
9699

97100
instantiate_rawget_call(context.db, &operands[0], &key)
@@ -108,6 +111,7 @@ pub(super) fn instantiate_alias_call(
108111
}
109112

110113
let key = resolve_literal_operand(operand_exprs.get(1), context.substitutor)
114+
.or_else(|| instantiate_alias_origin_operand(context, &operands[1]))
111115
.unwrap_or_else(|| operands[1].clone());
112116

113117
instantiate_index_call(context.db, &operands[0], &key)
@@ -116,6 +120,22 @@ pub(super) fn instantiate_alias_call(
116120
}
117121
}
118122

123+
fn instantiate_alias_origin_operand(
124+
context: &GenericInstantiateContext,
125+
operand: &LuaType,
126+
) -> Option<LuaType> {
127+
let LuaType::Ref(type_id) = operand else {
128+
return None;
129+
};
130+
let type_decl = context.db.get_type_index().get_type_decl(type_id)?;
131+
if !type_decl.is_alias() {
132+
return None;
133+
}
134+
135+
let origin = type_decl.get_alias_origin(context.db, Some(context.substitutor))?;
136+
Some(instantiate_type_generic_inner(context, &origin))
137+
}
138+
119139
fn instantiate_merge_call(db: &DbIndex, operands: &[LuaType]) -> LuaType {
120140
if operands.len() != 2 {
121141
return LuaType::Unknown;

crates/emmylua_code_analysis/src/semantic/type_check/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ pub use type_check_fail_reason::TypeCheckFailReason;
2121
use type_check_guard::TypeCheckGuard;
2222

2323
use crate::{
24-
LuaGenericType, LuaUnionType, TypeSubstitutor,
24+
LuaAliasCallKind, LuaGenericType, LuaUnionType, TypeSubstitutor,
2525
db_index::{DbIndex, LuaType},
26+
instantiate_type_generic,
2627
semantic::type_check::type_check_context::TypeCheckContext,
2728
};
2829
pub use sub_type::is_sub_type_of;
@@ -266,6 +267,17 @@ fn escape_type(db: &DbIndex, typ: &LuaType) -> Option<LuaType> {
266267
return Some(origin_type.clone());
267268
}
268269
}
270+
LuaType::Call(alias_call)
271+
if matches!(
272+
alias_call.get_call_kind(),
273+
LuaAliasCallKind::Index | LuaAliasCallKind::RawGet
274+
) && !typ.contain_tpl() =>
275+
{
276+
let resolved = instantiate_type_generic(db, typ, &TypeSubstitutor::new());
277+
if resolved != *typ {
278+
return Some(resolved);
279+
}
280+
}
269281
// todo donot escape
270282
LuaType::Instance(inst) => {
271283
let base = inst.get_base();

0 commit comments

Comments
 (0)