Skip to content

Commit f55faa2

Browse files
committed
Support for generic matching table
1 parent 250b34f commit f55faa2

2 files changed

Lines changed: 142 additions & 19 deletions

File tree

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,4 +1134,37 @@ mod test {
11341134
"#
11351135
));
11361136
}
1137+
1138+
#[test]
1139+
fn test_generic_type() {
1140+
let mut ws = VirtualWorkspace::new();
1141+
ws.def(
1142+
r#"
1143+
---@class ObserverParams<T>
1144+
---@field next fun( value: T)
1145+
---@field errorResume? fun(error: any)
1146+
1147+
1148+
---@class Observer<T>
1149+
local Observer = {}
1150+
1151+
---@param observer ObserverParams<T>
1152+
function Observer:subscribe(observer)
1153+
end
1154+
"#,
1155+
);
1156+
assert!(ws.check_code_for(
1157+
DiagnosticCode::ParamTypeNotMatch,
1158+
r#"
1159+
---@type Observer<number>
1160+
local observer
1161+
1162+
observer:subscribe({
1163+
next = function(value)
1164+
print(value)
1165+
end
1166+
})
1167+
"#
1168+
));
1169+
}
11371170
}

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

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use crate::{DbIndex, LuaGenericType, LuaType, TypeSubstitutor};
1+
use std::{collections::HashMap, sync::Arc};
2+
3+
use crate::{
4+
humanize_type, semantic::member::find_members, DbIndex, LuaGenericType, LuaMemberOwner,
5+
LuaType, LuaTypeCache, RenderLevel, TypeSubstitutor,
6+
};
27

38
use super::{
49
check_general_type_compact, type_check_fail_reason::TypeCheckFailReason,
@@ -11,7 +16,7 @@ pub fn check_generic_type_compact(
1116
compact_type: &LuaType,
1217
check_guard: TypeCheckGuard,
1318
) -> TypeCheckResult {
14-
// Do not check generic classes that have not been instantiated yet
19+
// 不检查尚未实例化的泛型类
1520
if source_generic.contain_tpl() {
1621
return Ok(());
1722
}
@@ -22,9 +27,8 @@ pub fn check_generic_type_compact(
2227
.get_type_decl(&source_base_id)
2328
.ok_or(TypeCheckFailReason::TypeNotMatch)?;
2429

25-
let type_params = source_generic.get_params();
26-
2730
if type_decl.is_alias() {
31+
let type_params = source_generic.get_params();
2832
let substitutor = TypeSubstitutor::from_alias(type_params.clone(), source_base_id);
2933
if let Some(origin_type) = type_decl.get_alias_origin(db, Some(&substitutor)) {
3034
return check_general_type_compact(
@@ -37,17 +41,19 @@ pub fn check_generic_type_compact(
3741
}
3842

3943
match compact_type {
40-
LuaType::Generic(compact_generic) => {
41-
return check_generic_type_compact_generic(
42-
db,
43-
source_generic,
44-
compact_generic,
45-
check_guard.next_level()?,
46-
)
47-
}
48-
_ => {
49-
return Err(TypeCheckFailReason::TypeNotMatch);
50-
}
44+
LuaType::Generic(compact_generic) => check_generic_type_compact_generic(
45+
db,
46+
source_generic,
47+
compact_generic,
48+
check_guard.next_level()?,
49+
),
50+
LuaType::TableConst(range) => check_generic_type_compact_table(
51+
db,
52+
source_generic,
53+
LuaMemberOwner::Element(range.clone()),
54+
check_guard.next_level()?,
55+
),
56+
_ => Err(TypeCheckFailReason::TypeNotMatch),
5157
}
5258
}
5359

@@ -69,10 +75,94 @@ fn check_generic_type_compact_generic(
6975
return Err(TypeCheckFailReason::TypeNotMatch);
7076
}
7177

72-
for i in 0..source_params.len() {
73-
let source_param = &source_params[i];
74-
let compact_param = &compact_params[i];
75-
check_general_type_compact(db, source_param, compact_param, check_guard.next_level()?)?;
78+
let next_guard = check_guard.next_level()?;
79+
for (source_param, compact_param) in source_params.iter().zip(compact_params.iter()) {
80+
check_general_type_compact(db, source_param, compact_param, next_guard)?;
81+
}
82+
83+
Ok(())
84+
}
85+
86+
fn check_generic_type_compact_table(
87+
db: &DbIndex,
88+
source_generic: &LuaGenericType,
89+
table_owner: LuaMemberOwner,
90+
check_guard: TypeCheckGuard,
91+
) -> TypeCheckResult {
92+
let member_index = db.get_member_index();
93+
94+
// 构建表成员映射
95+
let table_member_map: HashMap<_, _> = member_index
96+
.get_members(&table_owner)
97+
.map(|members| {
98+
members
99+
.iter()
100+
.map(|m| (m.get_key().clone(), m.get_id().clone()))
101+
.collect()
102+
})
103+
.unwrap_or_default();
104+
105+
// 获取泛型类型的成员,使用 find_members 来获取包括继承的所有成员
106+
let source_type = LuaType::Generic(Arc::new(source_generic.clone()));
107+
let Some(source_type_members) = find_members(db, &source_type) else {
108+
return Ok(()); // 空成员无需检查
109+
};
110+
111+
// 提前计算下一级检查守卫
112+
let next_guard = check_guard.next_level()?;
113+
114+
for source_member in source_type_members {
115+
let source_member_type = source_member.typ;
116+
let key = source_member.key;
117+
118+
match table_member_map.get(&key) {
119+
Some(table_member_id) => {
120+
let table_member = member_index
121+
.get_member(table_member_id)
122+
.ok_or(TypeCheckFailReason::TypeNotMatch)?;
123+
let table_member_type = db
124+
.get_type_index()
125+
.get_type_cache(&table_member.get_id().into())
126+
.unwrap_or(&LuaTypeCache::InferType(LuaType::Any))
127+
.as_type();
128+
129+
if let Err(TypeCheckFailReason::TypeNotMatch) = check_general_type_compact(
130+
db,
131+
&source_member_type,
132+
&table_member_type,
133+
next_guard,
134+
) {
135+
return Err(TypeCheckFailReason::TypeNotMatchWithReason(
136+
t!(
137+
"member %{name} type not match, expect %{expect}, got %{got}",
138+
name = key.to_path(),
139+
expect = humanize_type(db, &source_member_type, RenderLevel::Simple),
140+
got = humanize_type(db, &table_member_type, RenderLevel::Simple)
141+
)
142+
.to_string(),
143+
));
144+
}
145+
}
146+
None if !source_member_type.is_optional() => {
147+
return Err(TypeCheckFailReason::TypeNotMatchWithReason(
148+
t!("missing member %{name}, in table", name = key.to_path()).to_string(),
149+
));
150+
}
151+
_ => {} // 可选成员未找到,继续检查
152+
}
153+
}
154+
155+
// 检查超类型
156+
let source_base_id = source_generic.get_base_type_id();
157+
if let Some(supers) = db.get_type_index().get_super_types(&source_base_id) {
158+
let element_range = table_owner
159+
.get_element_range()
160+
.ok_or(TypeCheckFailReason::TypeNotMatch)?;
161+
let table_type = LuaType::TableConst(element_range.clone());
162+
163+
for super_type in supers {
164+
check_general_type_compact(db, &super_type, &table_type, next_guard)?;
165+
}
76166
}
77167

78168
Ok(())

0 commit comments

Comments
 (0)