Skip to content

Commit de59988

Browse files
committed
Merge remote-tracking branch 'origin/main' into merge
2 parents e4a901b + cca6a90 commit de59988

File tree

11 files changed

+261
-36
lines changed

11 files changed

+261
-36
lines changed

crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/comment.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use emmylua_parser::{LuaAstNode, LuaComment, LuaDocTag};
22

3-
use crate::{compilation::analyzer::flow::binder::FlowBinder, FlowId, FlowNodeKind};
3+
use crate::{
4+
compilation::analyzer::flow::{bind_analyze::exprs::bind_expr, binder::FlowBinder},
5+
FlowId, FlowNodeKind,
6+
};
47

58
pub fn bind_comment(binder: &mut FlowBinder, lua_comment: LuaComment, current: FlowId) -> FlowId {
69
let cast_tags = lua_comment.get_doc_tags().filter_map(|it| match it {
@@ -11,7 +14,9 @@ pub fn bind_comment(binder: &mut FlowBinder, lua_comment: LuaComment, current: F
1114
let mut parent = current;
1215
for cast in cast_tags {
1316
let expr = cast.get_key_expr();
14-
if expr.is_some() {
17+
if let Some(expr) = expr {
18+
bind_expr(binder, expr, current);
19+
1520
let flow_id = binder.create_node(FlowNodeKind::TagCast(cast.to_ptr()));
1621
binder.add_antecedent(flow_id, parent);
1722
parent = flow_id;

crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/stats.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use emmylua_parser::{
2-
LuaAssignStat, LuaAst, LuaAstNode, LuaBlock, LuaBreakStat, LuaCallArgList, LuaCallExprStat,
3-
LuaDoStat, LuaForRangeStat, LuaForStat, LuaFuncStat, LuaGotoStat, LuaIfStat, LuaLabelStat,
4-
LuaLocalStat, LuaRepeatStat, LuaReturnStat, LuaWhileStat,
2+
BinaryOperator, LuaAssignStat, LuaAst, LuaAstNode, LuaBlock, LuaBreakStat, LuaCallArgList,
3+
LuaCallExprStat, LuaDoStat, LuaExpr, LuaForRangeStat, LuaForStat, LuaFuncStat, LuaGotoStat,
4+
LuaIfStat, LuaLabelStat, LuaLocalStat, LuaRepeatStat, LuaReturnStat, LuaWhileStat,
55
};
66

77
use crate::{
@@ -28,8 +28,9 @@ pub fn bind_local_stat(
2828
let name = &local_names[i];
2929
let value = &values[i];
3030
let decl_id = LuaDeclId::new(binder.file_id, name.get_position());
31-
let flow_id = bind_expr(binder, value.clone(), current);
32-
binder.decl_bind_flow_ref.insert(decl_id, flow_id);
31+
if check_local_immutable(binder, decl_id) && check_value_expr_is_check_expr(value.clone()) {
32+
binder.decl_bind_expr_ref.insert(decl_id, value.to_ptr());
33+
}
3334
}
3435

3536
for value in values {
@@ -42,6 +43,41 @@ pub fn bind_local_stat(
4243
local_flow_id
4344
}
4445

46+
fn check_local_immutable(binder: &mut FlowBinder, decl_id: LuaDeclId) -> bool {
47+
let Some(refs) = binder
48+
.db
49+
.get_reference_index()
50+
.get_decl_references(&binder.file_id, &decl_id)
51+
else {
52+
return true;
53+
};
54+
55+
for r in refs {
56+
if r.is_write {
57+
return false;
58+
}
59+
}
60+
61+
true
62+
}
63+
64+
fn check_value_expr_is_check_expr(value_expr: LuaExpr) -> bool {
65+
match value_expr {
66+
LuaExpr::BinaryExpr(binary_expr) => {
67+
let Some(op) = binary_expr.get_op_token() else {
68+
return false;
69+
};
70+
71+
match op.get_op() {
72+
BinaryOperator::OpEq | BinaryOperator::OpNe => true,
73+
_ => false,
74+
}
75+
}
76+
LuaExpr::CallExpr(_) => true,
77+
_ => false, // Other expressions can be checked
78+
}
79+
}
80+
4581
pub fn bind_assign_stat(
4682
binder: &mut FlowBinder,
4783
assign_stat: LuaAssignStat,

crates/emmylua_code_analysis/src/compilation/analyzer/flow/binder.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22

3-
use emmylua_parser::LuaSyntaxId;
3+
use emmylua_parser::{LuaAstPtr, LuaExpr, LuaSyntaxId};
44
use internment::ArcIntern;
55
use rowan::TextSize;
66
use smol_str::SmolStr;
@@ -14,7 +14,7 @@ use crate::{
1414
pub struct FlowBinder<'a> {
1515
pub db: &'a mut DbIndex,
1616
pub file_id: FileId,
17-
pub decl_bind_flow_ref: HashMap<LuaDeclId, FlowId>,
17+
pub decl_bind_expr_ref: HashMap<LuaDeclId, LuaAstPtr<LuaExpr>>,
1818
pub start: FlowId,
1919
pub unreachable: FlowId,
2020
pub loop_label: FlowId,
@@ -35,7 +35,7 @@ impl<'a> FlowBinder<'a> {
3535
file_id,
3636
flow_nodes: Vec::new(),
3737
multiple_antecedents: Vec::new(),
38-
decl_bind_flow_ref: HashMap::new(),
38+
decl_bind_expr_ref: HashMap::new(),
3939
labels: HashMap::new(),
4040
start: FlowId::default(),
4141
unreachable: FlowId::default(),
@@ -171,7 +171,7 @@ impl<'a> FlowBinder<'a> {
171171

172172
pub fn finish(self) -> FlowTree {
173173
FlowTree::new(
174-
self.decl_bind_flow_ref,
174+
self.decl_bind_expr_ref,
175175
self.flow_nodes,
176176
self.multiple_antecedents,
177177
// self.labels,

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,4 +891,58 @@ end
891891
"#,
892892
);
893893
}
894+
895+
#[test]
896+
fn test_feature_inherit_flow_from_const_local() {
897+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
898+
899+
ws.def(
900+
r#"
901+
local ret --- @type string | nil
902+
903+
local h = type(ret) == "string"
904+
if h then
905+
a = ret
906+
end
907+
908+
local e = type(ret)
909+
if e == "string" then
910+
b = ret
911+
end
912+
"#,
913+
);
914+
915+
let a = ws.expr_ty("a");
916+
let a_expected = ws.ty("string");
917+
assert_eq!(a, a_expected);
918+
let b = ws.expr_ty("b");
919+
let b_expected = ws.ty("string");
920+
assert_eq!(b, b_expected);
921+
}
922+
923+
#[test]
924+
fn test_feature_generic_type_guard() {
925+
let mut ws = VirtualWorkspace::new();
926+
927+
ws.def(
928+
r#"
929+
---@generic T
930+
---@param type `T`
931+
---@return TypeGuard<T>
932+
local function instanceOf(inst, type)
933+
return true
934+
end
935+
936+
local ret --- @type string | nil
937+
938+
if instanceOf(ret, "string") then
939+
a = ret
940+
end
941+
"#,
942+
);
943+
944+
let a = ws.expr_ty("a");
945+
let a_expected = ws.ty("string");
946+
assert_eq!(a, a_expected);
947+
}
894948
}

crates/emmylua_code_analysis/src/db_index/flow/flow_tree.rs

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

3-
use emmylua_parser::LuaSyntaxId;
3+
use emmylua_parser::{LuaAstPtr, LuaExpr, LuaSyntaxId};
44

55
use crate::{FlowId, FlowNode, LuaDeclId};
66

77
#[derive(Debug)]
88
pub struct FlowTree {
9-
#[allow(unused)]
10-
decl_bind_flow_ref: HashMap<LuaDeclId, FlowId>,
9+
decl_bind_expr_ref: HashMap<LuaDeclId, LuaAstPtr<LuaExpr>>,
1110
flow_nodes: Vec<FlowNode>,
1211
multiple_antecedents: Vec<Vec<FlowId>>,
1312
// labels: HashMap<LuaClosureId, HashMap<SmolStr, FlowId>>,
@@ -16,17 +15,16 @@ pub struct FlowTree {
1615

1716
impl FlowTree {
1817
pub fn new(
19-
decl_bind_flow_ref: HashMap<LuaDeclId, FlowId>,
18+
decl_bind_expr_ref: HashMap<LuaDeclId, LuaAstPtr<LuaExpr>>,
2019
flow_nodes: Vec<FlowNode>,
2120
multiple_antecedents: Vec<Vec<FlowId>>,
2221
// labels: HashMap<LuaClosureId, HashMap<SmolStr, FlowId>>,
2322
bindings: HashMap<LuaSyntaxId, FlowId>,
2423
) -> Self {
2524
Self {
26-
decl_bind_flow_ref,
25+
decl_bind_expr_ref,
2726
flow_nodes,
2827
multiple_antecedents,
29-
// labels,
3028
bindings,
3129
}
3230
}
@@ -44,4 +42,8 @@ impl FlowTree {
4442
.get(id as usize)
4543
.map(|v| v.as_slice())
4644
}
45+
46+
pub fn get_decl_ref_expr(&self, decl_id: &LuaDeclId) -> Option<LuaAstPtr<LuaExpr>> {
47+
self.decl_bind_expr_ref.get(decl_id).cloned()
48+
}
4749
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ impl LuaType {
291291
LuaType::Nil | LuaType::Boolean | LuaType::Any | LuaType::Unknown => false,
292292
LuaType::BooleanConst(boolean) | LuaType::DocBooleanConst(boolean) => boolean.clone(),
293293
LuaType::Union(u) => u.is_always_truthy(),
294+
LuaType::TypeGuard(_) => false,
294295
_ => true,
295296
}
296297
}
@@ -299,6 +300,7 @@ impl LuaType {
299300
match self {
300301
LuaType::Nil | LuaType::BooleanConst(false) | LuaType::DocBooleanConst(false) => true,
301302
LuaType::Union(u) => u.is_always_falsy(),
303+
LuaType::TypeGuard(_) => false,
302304
_ => false,
303305
}
304306
}
@@ -400,6 +402,7 @@ impl LuaType {
400402
LuaType::StrTplRef(_) => true,
401403
LuaType::SelfInfer => true,
402404
LuaType::MultiLineUnion(inner) => inner.contain_tpl(),
405+
LuaType::TypeGuard(inner) => inner.contain_tpl(),
403406
_ => false,
404407
}
405408
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ pub fn instantiate_type_generic(
4545
LuaType::SelfInfer
4646
}
4747
}
48+
LuaType::TypeGuard(guard) => {
49+
let inner = instantiate_type_generic(db, guard.deref(), substitutor);
50+
LuaType::TypeGuard(inner.into())
51+
}
4852
_ => ty.clone(),
4953
}
5054
}

crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,19 @@ pub fn infer_call_expr_func(
107107
};
108108

109109
let result = if let Ok(func_ty) = result {
110-
unwrapp_return_type(db, cache, func_ty.get_ret().clone(), call_expr).map(|new_ret| {
111-
LuaFunctionType::new(
112-
func_ty.is_async(),
113-
func_ty.is_colon_define(),
114-
func_ty.get_params().to_vec(),
115-
new_ret,
116-
)
117-
.into()
118-
})
110+
let func_ret = func_ty.get_ret();
111+
match func_ret {
112+
LuaType::TypeGuard(_) => Ok(func_ty),
113+
_ => unwrapp_return_type(db, cache, func_ret.clone(), call_expr).map(|new_ret| {
114+
LuaFunctionType::new(
115+
func_ty.is_async(),
116+
func_ty.is_colon_define(),
117+
func_ty.get_params().to_vec(),
118+
new_ret,
119+
)
120+
.into()
121+
}),
122+
}
119123
} else {
120124
result
121125
};

crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use emmylua_parser::{
2-
BinaryOperator, LuaBinaryExpr, LuaCallExpr, LuaChunk, LuaExpr, LuaIndexMemberExpr,
2+
BinaryOperator, LuaAstNode, LuaBinaryExpr, LuaCallExpr, LuaChunk, LuaExpr, LuaIndexMemberExpr,
33
LuaLiteralToken,
44
};
55

@@ -128,6 +128,38 @@ fn maybe_type_guard_binary(
128128
}
129129
}
130130
}
131+
// may ref a type value
132+
} else if let LuaExpr::NameExpr(name_expr) = left_expr {
133+
if let LuaExpr::LiteralExpr(literal_expr) = right_expr {
134+
let Some(decl_id) = db
135+
.get_reference_index()
136+
.get_var_reference_decl(&cache.get_file_id(), name_expr.get_range())
137+
else {
138+
return Ok(ResultTypeOrContinue::Continue);
139+
};
140+
141+
let Some(expr_ptr) = tree.get_decl_ref_expr(&decl_id) else {
142+
return Ok(ResultTypeOrContinue::Continue);
143+
};
144+
145+
let Some(expr) = expr_ptr.to_node(root) else {
146+
return Ok(ResultTypeOrContinue::Continue);
147+
};
148+
149+
if let LuaExpr::CallExpr(call_expr) = expr {
150+
if call_expr.is_type() {
151+
type_guard_expr = Some(call_expr);
152+
match literal_expr.get_literal() {
153+
Some(LuaLiteralToken::String(s)) => {
154+
literal_string = s.get_value();
155+
}
156+
_ => return Ok(ResultTypeOrContinue::Continue),
157+
}
158+
}
159+
} else {
160+
return Ok(ResultTypeOrContinue::Continue);
161+
}
162+
}
131163
}
132164

133165
if type_guard_expr.is_none() || literal_string.is_empty() {

0 commit comments

Comments
 (0)