Skip to content

Commit 9ee4bf7

Browse files
committed
Support __pairs analysis
1 parent caf9994 commit 9ee4bf7

6 files changed

Lines changed: 183 additions & 4 deletions

File tree

crates/emmylua_code_analysis/src/db_index/member/lua_member_owner.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
use std::sync::Arc;
2+
13
use internment::ArcIntern;
24
use rowan::TextRange;
35
use smol_str::SmolStr;
46

5-
use crate::{GlobalId, InFiled, LuaTypeDeclId};
7+
use crate::{GlobalId, InFiled, LuaGenericType, LuaTypeDeclId};
68

79
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
810
pub enum LuaMemberOwner {
911
LocalUnresolve,
1012
Type(LuaTypeDeclId),
1113
Element(InFiled<TextRange>),
1214
GlobalPath(GlobalId),
15+
Generic(Arc<LuaGenericType>),
1316
}
1417

1518
impl From<LuaTypeDeclId> for LuaMemberOwner {
@@ -42,6 +45,12 @@ impl From<GlobalId> for LuaMemberOwner {
4245
}
4346
}
4447

48+
impl From<LuaGenericType> for LuaMemberOwner {
49+
fn from(generic: LuaGenericType) -> Self {
50+
Self::Generic(Arc::new(generic))
51+
}
52+
}
53+
4554
impl LuaMemberOwner {
4655
pub fn get_type_id(&self) -> Option<&LuaTypeDeclId> {
4756
match self {

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,4 +1212,45 @@ mod test {
12121212
"#
12131213
));
12141214
}
1215+
1216+
#[test]
1217+
fn test_meta_pairs() {
1218+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
1219+
ws.def(
1220+
r#"
1221+
---@class RingBufferSpan<T>
1222+
local RingBufferSpan
1223+
1224+
---@return fun(): integer, T
1225+
function RingBufferSpan:__pairs()
1226+
end
1227+
"#,
1228+
);
1229+
assert!(ws.check_code_for(
1230+
DiagnosticCode::ParamTypeNotMatch,
1231+
r#"
1232+
local pairs = pairs
1233+
1234+
---@type RingBufferSpan
1235+
local span
1236+
1237+
for k, v in pairs(span) do
1238+
end
1239+
"#
1240+
));
1241+
1242+
// 测试泛型
1243+
assert!(ws.check_code_for(
1244+
DiagnosticCode::ParamTypeNotMatch,
1245+
r#"
1246+
local pairs = pairs
1247+
1248+
---@type RingBufferSpan<number>
1249+
local span
1250+
1251+
for k, v in pairs(span) do
1252+
end
1253+
"#
1254+
));
1255+
}
12151256
}

crates/emmylua_code_analysis/src/semantic/generic/instantiate_func_generic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub fn instantiate_func_generic(
4343
}
4444

4545
let mut substitutor = TypeSubstitutor::new();
46+
substitutor.set_call_expr(call_expr.clone());
4647
tpl_pattern_match_args(
4748
db,
4849
cache,

crates/emmylua_code_analysis/src/semantic/generic/tpl_pattern.rs

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
use std::ops::Deref;
22

3-
use emmylua_parser::LuaSyntaxNode;
3+
use emmylua_parser::{LuaAstNode, LuaSyntaxNode};
44
use itertools::Itertools;
5+
use rowan::NodeOrToken;
56
use smol_str::SmolStr;
67

78
use crate::{
89
check_type_compact,
910
db_index::{DbIndex, LuaGenericType, LuaType},
11+
infer_node_semantic_decl,
1012
semantic::{
1113
member::{find_index_operations, get_member_map},
1214
LuaInferCache,
1315
},
14-
InferFailReason, LuaFunctionType, LuaMemberKey, LuaMemberOwner, LuaObjectType,
15-
LuaSemanticDeclId, LuaTupleType, LuaUnionType, VariadicType,
16+
InferFailReason, LuaFunctionType, LuaMemberInfo, LuaMemberKey, LuaMemberOwner, LuaObjectType,
17+
LuaSemanticDeclId, LuaTupleType, LuaUnionType, SemanticDeclLevel, VariadicType,
1618
};
1719

1820
use super::type_substitutor::TypeSubstitutor;
21+
use std::collections::HashMap;
1922

2023
type TplPatternMatchResult = Result<(), InferFailReason>;
2124

@@ -433,6 +436,17 @@ fn table_generic_tpl_pattern_match(
433436
substitutor,
434437
)?;
435438
}
439+
LuaType::Generic(generic) => {
440+
let owner = LuaMemberOwner::Generic(generic.clone());
441+
table_generic_tpl_pattern_member_owner_match(
442+
db,
443+
cache,
444+
root,
445+
table_generic_params,
446+
owner,
447+
substitutor,
448+
)?;
449+
}
436450
LuaType::Object(obj) => {
437451
let mut keys = Vec::new();
438452
let mut values = Vec::new();
@@ -498,6 +512,7 @@ fn table_generic_tpl_pattern_match(
498512
Ok(())
499513
}
500514

515+
// KV 表匹配 ref/def/tableconst
501516
fn table_generic_tpl_pattern_member_owner_match(
502517
db: &DbIndex,
503518
cache: &mut LuaInferCache,
@@ -513,12 +528,22 @@ fn table_generic_tpl_pattern_member_owner_match(
513528
let owner_type = match &owner {
514529
LuaMemberOwner::Element(inst) => LuaType::TableConst(inst.clone()),
515530
LuaMemberOwner::Type(type_id) => LuaType::Ref(type_id.clone()),
531+
LuaMemberOwner::Generic(generic) => LuaType::Generic(generic.clone()),
516532
_ => {
517533
return Err(InferFailReason::None);
518534
}
519535
};
520536

521537
let members = get_member_map(db, &owner_type).ok_or(InferFailReason::None)?;
538+
// 如果是 pairs 调用, 我们需要尝试寻找元方法, 但目前`__pairs` 被放进成员表中
539+
if is_pairs_call(db, cache, substitutor).unwrap_or(false) {
540+
if try_handle_pairs_metamethod(db, cache, root, table_generic_params, &members, substitutor)
541+
.is_ok()
542+
{
543+
return Ok(());
544+
}
545+
}
546+
522547
let target_key_type = table_generic_params[0].clone();
523548
let mut keys = Vec::new();
524549
let mut values = Vec::new();
@@ -1000,3 +1025,89 @@ fn escape_alias(db: &DbIndex, may_alias: &LuaType) -> LuaType {
10001025

10011026
may_alias.clone()
10021027
}
1028+
1029+
fn is_pairs_call(
1030+
db: &DbIndex,
1031+
cache: &mut LuaInferCache,
1032+
substitutor: &mut TypeSubstitutor,
1033+
) -> Option<bool> {
1034+
let call_expr = substitutor.get_call_expr()?;
1035+
let prefix_expr = call_expr.get_prefix_expr()?;
1036+
let semantic_decl = match prefix_expr.syntax().clone().into() {
1037+
NodeOrToken::Node(node) => {
1038+
infer_node_semantic_decl(db, cache, node, SemanticDeclLevel::default())
1039+
}
1040+
_ => None,
1041+
}?;
1042+
1043+
let LuaSemanticDeclId::LuaDecl(decl_id) = semantic_decl else {
1044+
return None;
1045+
};
1046+
let decl = db.get_decl_index().get_decl(&decl_id)?;
1047+
if !db.get_module_index().is_std(&decl.get_file_id()) {
1048+
return None;
1049+
}
1050+
let name = decl.get_name();
1051+
if name != "pairs" {
1052+
return None;
1053+
}
1054+
Some(true)
1055+
}
1056+
1057+
fn try_handle_pairs_metamethod(
1058+
db: &DbIndex,
1059+
cache: &mut LuaInferCache,
1060+
root: &LuaSyntaxNode,
1061+
table_generic_params: &[LuaType],
1062+
members: &HashMap<LuaMemberKey, Vec<LuaMemberInfo>>,
1063+
substitutor: &mut TypeSubstitutor,
1064+
) -> TplPatternMatchResult {
1065+
let pairs_member = members
1066+
.get(&LuaMemberKey::Name("__pairs".into()))
1067+
.ok_or(InferFailReason::None)?
1068+
.get(0)
1069+
.ok_or(InferFailReason::None)?;
1070+
// 获取迭代函数返回类型
1071+
let meta_return = match &pairs_member.typ {
1072+
LuaType::Signature(signature_id) => db
1073+
.get_signature_index()
1074+
.get(signature_id)
1075+
.map(|s| s.get_return_type()),
1076+
LuaType::DocFunction(doc_func) => Some(doc_func.get_ret().clone()),
1077+
_ => None,
1078+
}
1079+
.ok_or(InferFailReason::None)?;
1080+
1081+
// 解析出迭代函数返回类型
1082+
let final_return_type = match meta_return {
1083+
LuaType::DocFunction(doc_func) => Some(doc_func.get_ret().clone()),
1084+
LuaType::Signature(signature_id) => db
1085+
.get_signature_index()
1086+
.get(&signature_id)
1087+
.map(|s| s.get_return_type()),
1088+
_ => None,
1089+
};
1090+
1091+
if let Some(LuaType::Variadic(variadic)) = &final_return_type {
1092+
let key_type = variadic.get_type(0).ok_or(InferFailReason::None)?;
1093+
let value_type = variadic.get_type(1).ok_or(InferFailReason::None)?;
1094+
tpl_pattern_match(
1095+
db,
1096+
cache,
1097+
root,
1098+
&table_generic_params[0],
1099+
&key_type,
1100+
substitutor,
1101+
)?;
1102+
tpl_pattern_match(
1103+
db,
1104+
cache,
1105+
root,
1106+
&table_generic_params[1],
1107+
&value_type,
1108+
substitutor,
1109+
)?;
1110+
return Ok(());
1111+
}
1112+
Err(InferFailReason::None)
1113+
}

crates/emmylua_code_analysis/src/semantic/generic/type_substitutor.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::collections::HashMap;
22

3+
use emmylua_parser::LuaCallExpr;
4+
35
use crate::{GenericTplId, LuaType, LuaTypeDeclId};
46

57
#[derive(Debug, Clone)]
68
pub struct TypeSubstitutor {
79
tpl_replace_map: HashMap<GenericTplId, SubstitutorValue>,
810
alias_type_id: Option<LuaTypeDeclId>,
911
self_type: Option<LuaType>,
12+
call_expr: Option<LuaCallExpr>,
1013
}
1114

1215
impl TypeSubstitutor {
@@ -15,6 +18,7 @@ impl TypeSubstitutor {
1518
tpl_replace_map: HashMap::new(),
1619
alias_type_id: None,
1720
self_type: None,
21+
call_expr: None,
1822
}
1923
}
2024

@@ -27,6 +31,7 @@ impl TypeSubstitutor {
2731
tpl_replace_map,
2832
alias_type_id: None,
2933
self_type: None,
34+
call_expr: None,
3035
}
3136
}
3237

@@ -39,6 +44,7 @@ impl TypeSubstitutor {
3944
tpl_replace_map,
4045
alias_type_id: Some(alias_type_id),
4146
self_type: None,
47+
call_expr: None,
4248
}
4349
}
4450

@@ -129,6 +135,14 @@ impl TypeSubstitutor {
129135
}
130136
}
131137
}
138+
139+
pub fn set_call_expr(&mut self, call_expr: LuaCallExpr) {
140+
self.call_expr = Some(call_expr);
141+
}
142+
143+
pub fn get_call_expr(&self) -> Option<&LuaCallExpr> {
144+
self.call_expr.as_ref()
145+
}
132146
}
133147

134148
fn convert_type_def_to_ref(ty: &LuaType) -> LuaType {

crates/emmylua_code_analysis/src/semantic/type_check/complex_type/table_generic_check.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ pub fn check_table_generic_type_compact(
8080
check_guard.next_level()?,
8181
);
8282
}
83+
LuaType::Generic(_) => {
84+
return Ok(());
85+
}
8386
LuaType::Union(union) => {
8487
for union_type in union.into_vec() {
8588
check_table_generic_type_compact(

0 commit comments

Comments
 (0)