What are tautological conditions and how do I fix them?
← Code Quality · Ref: Q558
A tautological condition is one whose outcome is already determined at compile time. EK9 detects these as dead code indicators.
CONSTANT COMPARISON (E08082)
Comparing two literals produces a known result: '3 > 2' is always true. Use a named constant or computed value instead.
CONSTANT ARITHMETIC (E08083)
Self-arithmetic always produces a constant: 'x - x' is always 0, 'x / x' is always 1, and 'x mod x' and 'x mod 1' are always 0 (likewise 'rem'). Arithmetic between two literals such as '3 + 2' is not flagged, and neither are 'x + x' or 'x * x'.
REDUNDANT BOOLEAN COMPARISON (E08084)
Comparing a Boolean expression with a literal is redundant: 'if flag == true' should be 'if flag'. The comparison adds nothing.
LOGICAL TAUTOLOGY (E08085)
Logical expressions that always produce the same result: 'x or not x' is always true. 'x and not x' is always false.
FLOW-SENSITIVE CONDITIONS (E08086/E08087)
When the compiler tracks variable values through control flow, it can prove some conditions are predetermined. After 'if x > 10', checking 'x > 5' inside that branch is always true (E08086). In the else branch of 'if x == 5', checking 'x == 5' is always false (E08087).
CONSTANT COALESCING (E08094)
Coalescing operators between two literals produce a known result: '5 <? 3' is always 3. The coalescing operators (<?, >?) return a value, not a Boolean, but when both operands are literals the result is still predetermined. Use a variable or named constant instead.
COMMON PATTERNS THAT TRIGGER
Redundant else re-check: 'if x == 5 ... else if x == 5' — the else already implies x is not 5. Post-throw re-check: after 'if x < 0 throw', checking 'x < 0' again is always false. Implied range: 'if x > 100 ... if x > 10' — the inner check is subsumed by the outer.
HOW TO FIX
Use named constants instead of raw literals. Remove redundant inner conditions. Use else-if chains where each branch tests a genuinely different range. Trust post-throw narrowing instead of re-checking.
See Q557 for flow-sensitive dead code detection. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection. See Q637 for safe variable comparison patterns. See Q638 for named constant patterns. See Q639 for Boolean usage without literals. See Q640 for collection isSet check patterns.
Example
#!ek9 defines module qa.codequality.tautology defines constant MINIMUM_AGE <- 18 MAXIMUM_AGE <- 120 SPEED_LIMIT <- 65 PENALTY_THRESHOLD <- 80 DANGER_THRESHOLD <- 100 defines function computeAge() <- rtn as Integer: 25 <?- Correct: each branch tests a genuinely different range. The compiler would reject 'if age >= 18; if age >= 10' because the inner check would be always true when the outer is true. -?> classifyAge() as pure -> age as Integer <- category as String: "minor" if age >= MAXIMUM_AGE category: "centenarian" else if age >= MINIMUM_AGE category: "adult" <?- Correct: using named constants for comparisons avoids E08082. Raw literals like 'if speed > 65' would trigger magic literal detection. -?> classifySpeed() as pure -> speed as Integer <- category as String: "safe" if speed >= DANGER_THRESHOLD category: "dangerous" else if speed >= PENALTY_THRESHOLD category: "ticket-worthy" else if speed > SPEED_LIMIT category: "over-limit" <?- Correct: uses a Boolean directly instead of comparing with true/false. Writing 'isEnabled == true' would trigger E08084. -?> formatFlag() as pure -> isEnabled as Boolean <- label as String: "disabled" if isEnabled label: "enabled" <?- Correct: after throwing on invalid input, the remaining code can trust the variable satisfies the inverse condition without re-checking. -?> validateAndProcess() -> score as Integer <- result as String: "processed" if score < 0 throw Exception("score must not be negative") adjustedScore <- computeAge() if adjustedScore < 0 result: "adjusted-score-negative" defines program TautologicalConditionsDemo() stdout <- Stdout() personAge <- 42 ageCategory <- classifyAge(personAge) stdout.println(`Age ${personAge}: ${ageCategory}`) carSpeed <- 72 speedCategory <- classifySpeed(carSpeed) stdout.println(`Speed ${carSpeed}: ${speedCategory}`) featureOn <- true flagLabel <- formatFlag(featureOn) stdout.println(`Feature: ${flagLabel}`) testScore <- 85 outcome <- validateAndProcess(testScore) stdout.println(`Score ${testScore}: ${outcome}`)
Common mistakes
E08084 — Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details.
Incorrect:
if isEnabled == true
Correct:
if isEnabled
E08082 — Comparing two literal values produces a compile-time constant. Use named constants compared against variables. See ek9 -h E08082 for details.
Incorrect:
if 120 >= 18
Correct:
if age >= MAXIMUM_AGE
Other ways to ask this
- Why does the compiler say my condition is always true?
- What is E08082 constant comparison?
- How do I fix a redundant condition in an if statement?
- Why is my boolean comparison flagged as tautological?
Coming from another language?
Java: no tautological condition detection in javac. SpotBugs has limited checks. SonarQube detects some constant conditions. Rust: clippy has logic_bug and redundant_pattern lints. Go: no tautology detection. Python: pylint detects some constant conditions. C++: -Wtautological-compare warns on a few patterns. EK9: comprehensive detection covering constant comparison, constant coalescing, boolean redundancy, logical tautology, and flow-sensitive narrowing, all as mandatory compiler errors.
Keywords: E08082, E08083, E08084, E08085, E08086, E08087, E08094, always, clean-code, coalescing, comparison, condition, constant, false, metric, migrate, quality, redundant, tautology, true