Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions crates/emmylua_code_analysis/src/compilation/test/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,134 @@ mod test {
assert_eq!(ws.expr_ty("after_guard"), ws.ty("string"));
}

#[test]
fn test_plain_call_condition_keeps_inner_call_prefix_type() {
let mut ws = VirtualWorkspace::new();
let code = r#"
local function a() end
local function b() end

b()
if a() then
b()
inner = b
end
"#;
ws.def(code);

let ty = ws.expr_ty("inner");
assert!(ty.is_function());

let mut diag_ws = VirtualWorkspace::new();
assert!(diag_ws.has_no_diagnostic(DiagnosticCode::CallNonCallable, code));
}

#[test]
fn test_false_call_condition_keeps_inner_unrelated_type() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@return false
local function always_false()
return false
end

---@type string
local value = "ok"
if always_false() then
inner = value
end
"#,
);

assert_eq!(ws.expr_ty("inner"), ws.ty("string"));
}

#[test]
fn test_true_call_condition_keeps_else_call_prefix_type() {
let mut ws = VirtualWorkspace::new();
let code = r#"
---@return true
local function always_true()
return true
end

local function b() end
if always_true() then
else
b()
end
"#;

assert!(ws.has_no_diagnostic(DiagnosticCode::CallNonCallable, code));
}

#[test]
fn test_false_call_condition_assignment_does_not_contribute_to_merge() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@return false
local function always_false()
return false
end

local value = "before"
if always_false() then
value = 1
end
after = value
"#,
);

let after = ws.expr_ty("after");
assert_eq!(ws.humanize_type(after), "string");
}

#[test]
fn test_false_call_condition_tag_cast_does_not_contribute_to_merge() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@return false
local function always_false()
return false
end

local value = "before"
if always_false() then
---@cast value integer
end
after = value
"#,
);

let after = ws.expr_ty("after");
assert_eq!(ws.humanize_type(after), r#""before""#);
}

#[test]
fn test_false_call_condition_doc_assignment_does_not_contribute_to_merge() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@return false
local function always_false()
return false
end

local value = "before"
if always_false() then
---@type integer
value = 1
end
after = value
"#,
);

assert_eq!(ws.expr_ty("after"), ws.ty("string"));
}

#[test]
fn test_branch_join_keeps_union_when_only_one_side_narrows() {
let mut ws = VirtualWorkspace::new();
Expand Down
25 changes: 19 additions & 6 deletions crates/emmylua_code_analysis/src/semantic/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,32 @@ pub(in crate::semantic) struct FlowAssignmentInfo {

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(in crate::semantic) enum FlowMode {
WithConditions,
WithoutConditions,
Normal,
// Query one predecessor of a merge label; if that walk reaches an
// unreachable condition edge, it contributes `never` to the merged type.
MergeBranch,
// Re-query assignment antecedents without applying condition narrows.
IgnoreConditions,
}

impl FlowMode {
pub fn uses_conditions(self) -> bool {
matches!(self, Self::WithConditions)
#[derive(Debug, Clone)]
pub(in crate::semantic) enum FlowQueryResult {
Type(LuaType),
Unreachable,
}

impl FlowQueryResult {
pub(in crate::semantic) fn into_type(self) -> LuaType {
match self {
Self::Type(typ) => typ,
Self::Unreachable => LuaType::Never,
}
}
}

#[derive(Debug, Default)]
pub(in crate::semantic) struct FlowVarCache {
pub type_cache: HashMap<(FlowId, FlowMode), CacheEntry<LuaType>>,
pub type_cache: HashMap<(FlowId, FlowMode), CacheEntry<FlowQueryResult>>,
pub condition_cache: HashMap<(FlowId, InferConditionFlow), CacheEntry<ConditionFlowAction>>,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub(in crate::semantic) enum CorrelatedDiscriminantNarrow {
#[derive(Debug, Clone)]
pub(in crate::semantic) enum ConditionFlowAction {
Continue,
Unreachable,
Result(LuaType),
Pending(PendingConditionNarrow),
NeedExprType {
Expand Down Expand Up @@ -712,12 +713,12 @@ pub(in crate::semantic::infer::narrow) fn resolve_expr_type_continuation(
condition_flow,
),
ExprTypeContinuation::Truthiness { condition_flow } => Ok(match condition_flow {
_ if expr_type.is_never() => ConditionFlowAction::Result(LuaType::Never),
_ if expr_type.is_never() => ConditionFlowAction::Unreachable,
InferConditionFlow::TrueCondition if expr_type.is_always_falsy() => {
ConditionFlowAction::Result(LuaType::Never)
ConditionFlowAction::Unreachable
}
InferConditionFlow::FalseCondition if expr_type.is_always_truthy() => {
ConditionFlowAction::Result(LuaType::Never)
ConditionFlowAction::Unreachable
}
_ => ConditionFlowAction::Continue,
}),
Expand Down
Loading
Loading