Skip to content

Commit fba83d6

Browse files
authored
Merge pull request #988 from xuhuanzy/update
update
2 parents 2bdb2b3 + 3d6432d commit fba83d6

17 files changed

Lines changed: 267 additions & 63 deletions

File tree

.gitignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/target
22
.idea
3-
.cursor
43
dhat-heap.json
5-
.claude
64

7-
# MCP, 用于为AI提供lsp上下文
8-
.serena
5+
# AI
6+
.cursor
7+
.claude
8+
.codex
9+
openspec

crates/emmylua_code_analysis/src/compilation/analyzer/doc/file_generic_index.rs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub struct FileGenericIndex {
99
generic_params: Vec<TagGenericParams>,
1010
root_node_ids: Vec<GenericEffectId>,
1111
effect_nodes: Vec<GenericEffectRangeNode>,
12+
pending_type_params: Vec<GenericParam>,
1213
}
1314

1415
impl FileGenericIndex {
@@ -17,6 +18,7 @@ impl FileGenericIndex {
1718
generic_params: Vec::new(),
1819
root_node_ids: Vec::new(),
1920
effect_nodes: Vec::new(),
21+
pending_type_params: Vec::new(),
2022
}
2123
}
2224

@@ -76,6 +78,14 @@ impl FileGenericIndex {
7678
}
7779
}
7880

81+
pub fn append_pending_type_param(&mut self, param: GenericParam) {
82+
self.pending_type_params.push(param);
83+
}
84+
85+
pub fn clear_pending_type_params(&mut self) {
86+
self.pending_type_params.clear();
87+
}
88+
7989
fn get_start(&self, ranges: &[TextRange]) -> Option<usize> {
8090
let params_ids = self.find_generic_params(ranges.first()?.start())?;
8191
let mut start = 0;
@@ -134,22 +144,33 @@ impl FileGenericIndex {
134144
position: TextSize,
135145
name: &str,
136146
) -> Option<(GenericTplId, Option<LuaType>)> {
137-
let params_ids = self.find_generic_params(position)?;
138-
139-
for params_id in params_ids.iter().rev() {
140-
if let Some(params) = self.generic_params.get(*params_id)
141-
&& let Some((id, param)) = params.params.get(name)
142-
{
143-
let tpl_id = if params.is_func {
144-
GenericTplId::Func(*id as u32)
145-
} else {
146-
GenericTplId::Type(*id as u32)
147-
};
148-
return Some((tpl_id, param.type_constraint.clone()));
147+
if let Some(params_ids) = self.find_generic_params(position) {
148+
for params_id in params_ids.iter().rev() {
149+
if let Some(params) = self.generic_params.get(*params_id)
150+
&& let Some((id, param)) = params.params.get(name)
151+
{
152+
let tpl_id = if params.is_func {
153+
GenericTplId::Func(*id as u32)
154+
} else {
155+
GenericTplId::Type(*id as u32)
156+
};
157+
return Some((tpl_id, param.type_constraint.clone()));
158+
}
149159
}
150160
}
151161

152-
None
162+
// 搜索前置类型参数, 例如 ---@alias Pick<T, K extends keyof T>
163+
self.pending_type_params
164+
.iter()
165+
.enumerate()
166+
.rev()
167+
.find(|(_, param)| param.name == name)
168+
.map(|(idx, param)| {
169+
(
170+
GenericTplId::Type(idx as u32),
171+
param.type_constraint.clone(),
172+
)
173+
})
153174
}
154175

155176
fn find_generic_params(&self, position: TextSize) -> Option<Vec<usize>> {

crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_def_tags.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,19 +200,27 @@ fn get_generic_params(
200200
analyzer: &mut DocAnalyzer,
201201
params: LuaDocGenericDeclList,
202202
) -> Vec<GenericParam> {
203+
analyzer.generic_index.clear_pending_type_params();
203204
let mut params_result = Vec::new();
204205
for param in params.get_generic_decl() {
205206
let name = if let Some(param) = param.get_name_token() {
206207
SmolStr::new(param.get_name_text())
207208
} else {
208209
continue;
209210
};
210-
let type_ref = param
211+
212+
let type_constraint = param
211213
.get_type()
212214
.map(|type_ref| infer_type(analyzer, type_ref));
213215

214-
params_result.push(GenericParam::new(name, type_ref, None));
216+
let param = GenericParam::new(name, type_constraint, None);
217+
analyzer
218+
.generic_index
219+
.append_pending_type_param(param.clone());
220+
221+
params_result.push(param);
215222
}
223+
analyzer.generic_index.clear_pending_type_params();
216224

217225
params_result
218226
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,30 @@ mod test {
228228
// so that variadic spreading continues to work as expected
229229
assert_eq!(ws.humanize_type(v_ty), "string");
230230
}
231+
232+
#[test]
233+
fn test_issue_925() {
234+
let mut ws = VirtualWorkspace::new();
235+
ws.def(
236+
r#"
237+
---@class Test<T>
238+
local M = {}
239+
240+
---@generic T
241+
---@param value T
242+
---@return Test<T extends Test<infer U> and U or T>
243+
function M.with_dot(value) end
244+
"#,
245+
);
246+
ws.def(
247+
r#"
248+
---@type Test<integer>
249+
local a
250+
A = a.with_dot(1)
251+
"#,
252+
);
253+
254+
let a_ty = ws.expr_ty("A");
255+
assert_eq!(ws.humanize_type(a_ty), "Test<integer>");
256+
}
231257
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,4 +847,25 @@ mod test {
847847
"#,
848848
));
849849
}
850+
851+
#[test]
852+
fn test_issue_986() {
853+
let mut ws = VirtualWorkspace::new();
854+
ws.def(
855+
r#"
856+
---@class Foo
857+
---@field cost number
858+
859+
---@generic K extends keyof Foo
860+
---@param key K
861+
---@return Foo[K]
862+
function get(key)
863+
end
864+
865+
A = get('cost')
866+
"#,
867+
);
868+
let result_ty = ws.expr_ty("A");
869+
assert_eq!(ws.humanize_type(result_ty), "number");
870+
}
850871
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,16 @@ mod test {
3434
"#,
3535
));
3636
}
37+
38+
#[test]
39+
fn test_issue_925() {
40+
let mut ws = VirtualWorkspace::new();
41+
42+
assert!(ws.check_code_for(
43+
DiagnosticCode::TypeNotFound,
44+
r#"
45+
---@alias Pick<T, K extends keyof T> { [P in K]: T[P]; }
46+
"#,
47+
));
48+
}
3749
}

crates/emmylua_code_analysis/src/db_index/module/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ impl LuaModuleIndex {
397397

398398
pub fn next_library_workspace_id(&self) -> u32 {
399399
let used: HashSet<u32> = self.workspaces.iter().map(|w| w.id.id).collect();
400-
let mut candidate = 2;
400+
let mut candidate = WorkspaceId::LIBRARY_START.id;
401401
while used.contains(&candidate) {
402402
candidate += 1;
403403
}

crates/emmylua_code_analysis/src/db_index/module/workspace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ impl WorkspaceId {
2222
pub const STD: WorkspaceId = WorkspaceId { id: 0 };
2323
pub const MAIN: WorkspaceId = WorkspaceId { id: 1 };
2424
pub const REMOTE: WorkspaceId = WorkspaceId { id: 2 };
25+
pub const LIBRARY_START: WorkspaceId = WorkspaceId { id: 3 };
2526

2627
pub fn is_library(&self) -> bool {
27-
self.id > 2
28+
self.id >= Self::LIBRARY_START.id
2829
}
2930

3031
pub fn is_remote(&self) -> bool {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ pub fn instantiate_alias_call(
6060
return LuaType::Unknown;
6161
}
6262

63+
if operands[0].contain_tpl() || operands[1].contain_tpl() {
64+
return LuaType::Call(
65+
LuaAliasCallType::new(LuaAliasCallKind::Extends, operands).into(),
66+
);
67+
}
68+
6369
let compact = type_check::check_type_compact(db, &operands[0], &operands[1]).is_ok();
6470
LuaType::BooleanConst(compact)
6571
}

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -493,21 +493,24 @@ fn instantiate_conditional(
493493
}
494494
}
495495

496-
// infer 必须位于条件语句中(right), 判断是否包含并收集
497-
if contains_conditional_infer(&right)
498-
&& collect_infer_assignments(db, &left, &right, &mut infer_assignments)
499-
{
500-
condition_result = Some(true);
501-
} else {
502-
condition_result = Some(
503-
check_type_compact_with_level(
504-
db,
505-
&left,
506-
&right,
507-
TypeCheckCheckLevel::GenericConditional,
508-
)
509-
.is_ok(),
510-
);
496+
// 仍有未解析模板时不能提前折叠 conditional, 否则会把 infer 结果固定成悬空占位符.
497+
if !left.contain_tpl() && !right.contain_tpl() {
498+
// infer 必须位于条件语句中(right), 判断是否包含并收集
499+
if contains_conditional_infer(&right)
500+
&& collect_infer_assignments(db, &left, &right, &mut infer_assignments)
501+
{
502+
condition_result = Some(true);
503+
} else {
504+
condition_result = Some(
505+
check_type_compact_with_level(
506+
db,
507+
&left,
508+
&right,
509+
TypeCheckCheckLevel::GenericConditional,
510+
)
511+
.is_ok(),
512+
);
513+
}
511514
}
512515
}
513516

0 commit comments

Comments
 (0)