Skip to content
Merged
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
77 changes: 52 additions & 25 deletions crates/wright-analyzer/src/canonical/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,33 +261,60 @@ fn duplicate_condition_findings(
rule: &Rule,
value_ids: &HashMap<usize, ValueId>,
) -> Vec<Finding> {
let mut seen: Vec<&Value> = Vec::new();
// A repeated condition is unreachable only as a later branch of the same
// `If`/`Else If` chain, so conditions are compared within the chain that
// owns them. Non-`If` blocks still push an entry so their `End` stays
// aligned; a `disabled` action is unwrapped only to detect a block opener.
enum Block<'a> {
IfChain(Vec<&'a Value>),
Other,
}
let mut blocks: Vec<Block> = Vec::new();
let mut findings = Vec::new();
for (action_id, action) in rule.actions.iter().enumerate() {
let condition = match action {
Action::If { condition }
| Action::ElseIf { condition }
| Action::While { condition } => condition,
_ => continue,
};
if seen
.iter()
.any(|previous| values_equal(previous, condition))
{
let span = program.action_argument_span(rule_id, action_id, 0);
findings.push(Finding {
code: "duplicate-condition".into(),
severity: Severity::Warning,
message: "condition is evaluated more than once in this rule; a later branch can never be taken".into(),
span: span.or_else(|| program.action_span(rule_id, action_id)),
rule: rule_id,
action: Some(action_id),
value: value_ids.get(&(condition as *const Value as usize)).copied(),
evidence: EvidenceClass::Exact,
boundedness: None,
});
} else {
seen.push(condition);
match action {
Action::ElseIf { condition } => {
let Some(Block::IfChain(seen)) = blocks.last_mut() else {
continue;
};
if seen
.iter()
.any(|previous| values_equal(previous, condition))
{
let span = program.action_argument_span(rule_id, action_id, 0);
findings.push(Finding {
code: "duplicate-condition".into(),
severity: Severity::Warning,
message: "an Else If condition repeats an earlier branch's condition in the same If/Else If chain; that branch can never be taken".into(),
span: span.or_else(|| program.action_span(rule_id, action_id)),
rule: rule_id,
action: Some(action_id),
value: value_ids.get(&(condition as *const Value as usize)).copied(),
evidence: EvidenceClass::Exact,
boundedness: None,
});
} else {
seen.push(condition);
}
}
Action::End => {
blocks.pop();
}
_ => {
let mut current = action;
while let Action::Disabled { action } = current {
current = action;
}
match current {
Action::If { condition } => {
blocks.push(Block::IfChain(vec![condition]));
}
Action::While { .. }
| Action::ForGlobalVariable { .. }
| Action::ForPlayerVariable { .. } => blocks.push(Block::Other),
_ => {}
}
}
}
}
findings
Expand Down
18 changes: 12 additions & 6 deletions crates/wright-analyzer/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,16 +304,22 @@ impl Default for LintRegistry {
id: "duplicate-condition",
default_severity: Severity::Warning,
evidence: EvidenceClass::Exact,
summary: "condition is evaluated more than once within one rule",
summary: "an Else If repeats a condition earlier in the same If/Else If chain",
rationale: "Avoid unreachable or redundant conditional branches.",
documentation: concat!(
"The same condition appears in two or more branches of the same rule. ",
"Because Workshop conditions are evaluated sequentially, a later branch ",
"with an identical condition can never be taken.",
"An `Else If` branch repeats the condition of an earlier `If` or ",
"`Else If` branch in the same chain. Workshop evaluates a chain's ",
"conditions in order until one passes, so the repeated branch is ",
"unreachable. Conditions in separate `If` statements, `While` ",
"conditions, and branches of different chains are not compared.",
),
known_limits: concat!(
"Detection is structural (not value-flow) and rule-local: two ",
"structurally identical conditions in different rules are not compared.",
"Detection is structural (not value-flow) and chain-local. ",
"Structurally identical conditions built from nondeterministic ",
"values (such as the `Random *` family) are evaluated separately ",
"per branch and can differ even within one chain; Workshop ",
"semantics expose no nondeterminism fact yet, so such repeats ",
"are still reported.",
),
tags: &["correctness"],
}),
Expand Down
Loading
Loading