Skip to content

Commit d4fcac3

Browse files
committed
fix: respect cast flow in assign type mismatch diagnostics
1 parent 504473f commit d4fcac3

6 files changed

Lines changed: 175 additions & 39 deletions

File tree

crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use emmylua_parser::{
77
use rowan::{NodeOrToken, TextRange};
88

99
use crate::{
10-
DiagnosticCode, LuaBuiltinAttributeKind, LuaDeclExtra, LuaDeclId, LuaMemberKey,
11-
LuaSemanticDeclId, LuaType, SemanticDeclLevel, SemanticModel, TypeCheckFailReason,
12-
TypeCheckResult, VariadicType, infer_index_expr,
10+
DiagnosticCode, LuaBuiltinAttributeKind, LuaDeclId, LuaMemberKey, LuaType, SemanticDeclLevel,
11+
SemanticModel, TypeCheckFailReason, TypeCheckResult, VariadicType,
12+
semantic::infer_expr_without_condition_flow,
1313
};
1414

1515
use super::{Checker, DiagnosticContext, humanize_lint_type};
@@ -74,36 +74,13 @@ fn check_name_expr(
7474
expr: Option<LuaExpr>,
7575
value_type: LuaType,
7676
) -> Option<()> {
77-
let semantic_decl = semantic_model.find_decl(
78-
NodeOrToken::Node(name_expr.syntax().clone()),
79-
SemanticDeclLevel::default(),
80-
)?;
81-
let source_type = match semantic_decl.clone() {
82-
LuaSemanticDeclId::LuaDecl(decl_id) => {
83-
let decl = semantic_model
84-
.get_db()
85-
.get_decl_index()
86-
.get_decl(&decl_id)?;
87-
match decl.extra {
88-
LuaDeclExtra::Param {
89-
idx, signature_id, ..
90-
} => {
91-
let signature = semantic_model
92-
.get_db()
93-
.get_signature_index()
94-
.get(&signature_id)?;
95-
let param_type = signature.get_param_info_by_id(idx)?;
96-
Some(param_type.type_ref.clone())
97-
}
98-
_ => semantic_model
99-
.get_db()
100-
.get_type_index()
101-
.get_type_cache(&decl_id.into())
102-
.map(|cache| cache.as_type().clone()),
103-
}
104-
}
105-
_ => None,
106-
};
77+
let source_type = infer_expr_without_condition_flow(
78+
semantic_model.get_db(),
79+
&mut semantic_model.get_cache().borrow_mut(),
80+
LuaExpr::NameExpr(name_expr.clone()),
81+
)
82+
.ok();
83+
10784
check_assign_type_mismatch(
10885
context,
10986
semantic_model,
@@ -132,11 +109,10 @@ fn check_index_expr(
132109
expr: Option<LuaExpr>,
133110
value_type: LuaType,
134111
) -> Option<()> {
135-
let source_type = infer_index_expr(
112+
let source_type = infer_expr_without_condition_flow(
136113
semantic_model.get_db(),
137114
&mut semantic_model.get_cache().borrow_mut(),
138-
index_expr.clone(),
139-
false,
115+
LuaExpr::IndexExpr(index_expr.clone()),
140116
)
141117
.ok();
142118

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,106 @@ return t
837837
));
838838
}
839839

840+
#[test]
841+
fn test_cast_add_type_flow_assignment() {
842+
let mut ws = VirtualWorkspace::new();
843+
assert!(ws.has_no_diagnostic(
844+
DiagnosticCode::AssignTypeMismatch,
845+
r#"
846+
---@type string | number
847+
local val
848+
849+
---@cast val + boolean
850+
val = true
851+
"#,
852+
));
853+
854+
assert!(ws.has_no_diagnostic(
855+
DiagnosticCode::AssignTypeMismatch,
856+
r#"
857+
---@type string | number
858+
local val
859+
860+
---@cast val boolean
861+
val = true
862+
"#,
863+
));
864+
}
865+
866+
#[test]
867+
fn test_cast_remove_type_flow_assignment() {
868+
let mut ws = VirtualWorkspace::new();
869+
assert!(!ws.has_no_diagnostic(
870+
DiagnosticCode::AssignTypeMismatch,
871+
r#"
872+
---@type string | number | boolean
873+
local val
874+
875+
---@cast val - boolean
876+
val = true
877+
"#,
878+
));
879+
880+
assert!(!ws.has_no_diagnostic(
881+
DiagnosticCode::AssignTypeMismatch,
882+
r#"
883+
---@type string | number
884+
local val
885+
886+
---@cast val boolean
887+
val = 1
888+
"#,
889+
));
890+
}
891+
892+
#[test]
893+
fn test_cast_type_flow_index_assignment() {
894+
let mut ws = VirtualWorkspace::new();
895+
assert!(ws.has_no_diagnostic(
896+
DiagnosticCode::AssignTypeMismatch,
897+
r#"
898+
---@class CastFlowBox
899+
---@field val string | number
900+
901+
---@type CastFlowBox
902+
local box
903+
904+
---@cast box.val + boolean
905+
box.val = true
906+
"#,
907+
));
908+
909+
assert!(!ws.has_no_diagnostic(
910+
DiagnosticCode::AssignTypeMismatch,
911+
r#"
912+
---@class CastFlowBox
913+
---@field val string | number | boolean
914+
915+
---@type CastFlowBox
916+
local box
917+
918+
---@cast box.val - boolean
919+
box.val = true
920+
"#,
921+
));
922+
}
923+
924+
#[test]
925+
fn test_condition_flow_does_not_narrow_assignment_slot() {
926+
let mut ws = VirtualWorkspace::new();
927+
assert!(ws.has_no_diagnostic(
928+
DiagnosticCode::AssignTypeMismatch,
929+
r#"
930+
---@type string | number
931+
local val
932+
933+
if type(val) == "string" then
934+
val = 1
935+
end
936+
"#,
937+
));
938+
}
939+
840940
#[test]
841941
fn test_flow_1() {
842942
let mut ws = VirtualWorkspace::new();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use infer_table::infer_table_expr;
2828
pub use infer_table::{infer_table_field_value_should_be, infer_table_should_be};
2929
use infer_unary::infer_unary_expr;
3030
pub use narrow::VarRefId;
31+
pub(crate) use narrow::infer_expr_without_condition_flow;
3132
pub(in crate::semantic) use narrow::{ConditionFlowAction, InferConditionFlow};
3233

3334
use rowan::TextRange;

crates/emmylua_code_analysis/src/semantic/infer/narrow/get_type_at_flow.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,12 @@ struct FlowTypeEngine<'a> {
421421
}
422422

423423
impl<'a> FlowTypeEngine<'a> {
424-
fn run(&mut self, var_ref_id: &VarRefId, flow_id: FlowId) -> InferResult {
424+
fn run(&mut self, var_ref_id: &VarRefId, flow_id: FlowId, mode: FlowMode) -> InferResult {
425425
let mut stack = Vec::new();
426+
let mut query = FlowQuery::new(self.cache, var_ref_id, flow_id);
427+
query.mode = mode;
426428
let mut step = SchedulerStep::StartQuery {
427-
query: FlowQuery::new(self.cache, var_ref_id, flow_id),
429+
query,
428430
continuation: None,
429431
};
430432

@@ -1641,7 +1643,24 @@ pub(super) fn get_type_at_flow(
16411643
cache,
16421644
root,
16431645
}
1644-
.run(var_ref_id, flow_id)
1646+
.run(var_ref_id, flow_id, FlowMode::WithConditions)
1647+
}
1648+
1649+
pub(super) fn get_type_at_flow_without_conditions(
1650+
db: &DbIndex,
1651+
tree: &FlowTree,
1652+
cache: &mut LuaInferCache,
1653+
root: &LuaChunk,
1654+
var_ref_id: &VarRefId,
1655+
flow_id: FlowId,
1656+
) -> InferResult {
1657+
FlowTypeEngine {
1658+
db,
1659+
tree,
1660+
cache,
1661+
root,
1662+
}
1663+
.run(var_ref_id, flow_id, FlowMode::WithoutConditions)
16451664
}
16461665

16471666
fn get_flow_cache_var_ref_id(cache: &mut LuaInferCache, var_ref_id: &VarRefId) -> u32 {

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,45 @@ pub fn infer_expr_narrow_type(
3737
get_type_at_flow::get_type_at_flow(db, flow_tree, cache, &root, &var_ref_id, flow_id)
3838
}
3939

40+
pub(crate) fn infer_expr_without_condition_flow(
41+
db: &DbIndex,
42+
cache: &mut LuaInferCache,
43+
expr: LuaExpr,
44+
) -> InferResult {
45+
let Some(var_ref_id) = get_var_expr_var_ref_id(db, cache, expr.clone()) else {
46+
return Err(InferFailReason::None);
47+
};
48+
49+
if let LuaExpr::IndexExpr(index_expr) = &expr
50+
&& !cache.index_ref_origin_type_cache.contains_key(&var_ref_id)
51+
&& let Ok(origin_type) =
52+
super::infer_index::infer_index_expr(db, cache, index_expr.clone(), false)
53+
{
54+
cache
55+
.index_ref_origin_type_cache
56+
.insert(var_ref_id.clone(), CacheEntry::Cache(origin_type));
57+
}
58+
59+
let file_id = cache.get_file_id();
60+
let Some(flow_tree) = db.get_flow_index().get_flow_tree(&file_id) else {
61+
return get_var_ref_type(db, cache, &var_ref_id);
62+
};
63+
64+
let Some(flow_id) = flow_tree.get_flow_id(expr.get_syntax_id()) else {
65+
return get_var_ref_type(db, cache, &var_ref_id);
66+
};
67+
68+
let root = LuaChunk::cast(expr.get_root()).ok_or(InferFailReason::None)?;
69+
get_type_at_flow::get_type_at_flow_without_conditions(
70+
db,
71+
flow_tree,
72+
cache,
73+
&root,
74+
&var_ref_id,
75+
flow_id,
76+
)
77+
}
78+
4079
pub(in crate::semantic) fn get_var_ref_type(
4180
db: &DbIndex,
4281
cache: &mut LuaInferCache,

crates/emmylua_code_analysis/src/semantic/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub use generic::*;
5555
pub use guard::{InferGuard, InferGuardRef};
5656
pub use infer::InferFailReason;
5757
pub use infer::infer_call_expr_func;
58+
pub(crate) use infer::infer_expr_without_condition_flow;
5859
pub use infer::infer_param;
5960
pub(crate) use infer::try_infer_expr_for_index;
6061
pub(crate) use infer::{infer_expr, try_infer_expr_no_flow};

0 commit comments

Comments
 (0)