How does EK9 detect dead code and redundant conditions?
← Code Quality · Ref: Q557
EK9 tracks variable values through control flow and detects conditions whose outcome is already determined. This catches dead code that other languages silently accept.
FLOW-SENSITIVE VALUE TRACKING
The compiler tracks what is known about each variable at every program point. When a variable is assigned a constant or narrowed by a condition, the compiler remembers that information on each branch.
CONDITION ALWAYS TRUE (E08086)
If the compiler can prove a condition will always be satisfied, it flags it. The else branch would be dead code. Examples: checking x > 5 when x is already known to be greater than 10, or checking x <> 5 in the else branch of 'if x == 5' (the else already implies x is not 5).
CONDITION ALWAYS FALSE (E08087)
If the compiler can prove a condition can never be satisfied, it flags it. The if body would be dead code. Examples: checking x == 5 in the else branch of 'if x == 5' (the else already implies x is not 5), or checking x < 0 after throwing on negative values.
WHAT THE COMPILER TRACKS
Integer range constraints from comparisons (x > 10 narrows to Range(11, MAX)). Equality exclusions from else branches (else of x == 5 means x is anything except 5). Constant values from assignments and named constants. Boolean values from if/else narrowing.
POST-THROW NARROWING
When an if branch throws, the code after it only executes when the condition was false. The compiler narrows accordingly: after 'if x < 0 throw', the compiler knows x >= 0.
COMPOUND CONDITIONS
The compiler also detects redundant clauses in AND/OR expressions: 'x > 10 and x > 5' has a redundant clause, and 'x > 5 or x <= 5' covers the entire domain.
See Q558 for specific tautological condition patterns and how to fix them. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection.
Example
defines module qa.codequality.deadcode defines constant ADULT_AGE <- 18 RETIREMENT_AGE <- 65 FREEZING_POINT <- 0 defines function computeAge() <- rtn as Integer: 30 <?- Correct: each branch tests a genuinely different condition. The compiler would reject 'if age > 18; if age > 10' because the inner check is always true when the outer is true. -?> classifyLifeStage() as pure -> age as Integer <- stage as String: "child" if age >= RETIREMENT_AGE stage: "retired" else if age >= ADULT_AGE stage: "adult" <?- Correct: after a throw, the remaining code has narrowed constraints. The compiler accepts the second check because temperature was reassigned from a function call (unknown value). -?> validateAndClassify() -> rawTemperature as Integer <- classification as String: "above-freezing" if rawTemperature < FREEZING_POINT throw Exception("temperature below freezing") adjustedTemp <- computeAge() if adjustedTemp < FREEZING_POINT classification: "adjusted-below-freezing" defines program DeadCodeDetectionDemo() stdout <- Stdout() personAge <- 42 lifeStage <- classifyLifeStage(personAge) stdout.println(`Age ${personAge}: ${lifeStage}`) sensorReading <- 15 tempClass <- validateAndClassify(sensorReading) stdout.println(`Temperature ${sensorReading}: ${tempClass}`)
Common mistakes
E08082 — Comparing two literal values produces a compile-time constant, making the condition pointless dead code. Use variables or named constants. See ek9 -h E08082 for details.
Incorrect:
if 65 >= 18
Correct:
if age >= RETIREMENT_AGE
E08090 — Declaring a variable that is never used is dead code. Every variable must be referenced after declaration. See ek9 -h E08090 for details.
Incorrect:
unused <- classifyLifeStage(personAge)
Correct:
lifeStage <- classifyLifeStage(personAge) stdout.println(`Age ${personAge}: ${lifeStage}`)
Other ways to ask this
- What is E08086 condition always true?
- What is E08087 condition always false?
- How does EK9 find unreachable code in if statements?
- Why does the compiler reject my redundant condition?
Coming from another language?
Java: no flow-sensitive tautology detection in the compiler. SpotBugs has limited constant condition checks. SonarQube detects some always-true conditions but is optional. Rust: compiler warns on some unreachable patterns but not flow-sensitive value tracking. Go: no tautology detection. Python: no dead code detection. C++: some compilers warn on constant conditions with -Wall but not data-flow-based. EK9: mandatory flow-sensitive analysis that tracks values through assignments, branches, and throws.
Keywords: condition, false, always, redundant, dead, unreachable, quality, E08087, E08086, true, tautology, metric, code, flow, clean-code