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