Class RedundantIsSetCheckOrError

All Implemented Interfaces:
Consumer<EK9Parser.ExpressionContext>

final class RedundantIsSetCheckOrError extends TypedSymbolAccess implements Consumer<EK9Parser.ExpressionContext>
Detects isSet checks that are always true or always false based on data flow analysis (E08088/E08089).

The postfix ? operator in EK9 checks if a variable is set. This checker uses the ValueTrackingAnalyzer to determine when such checks are redundant because the variable is provably SET (e.g., assigned from a literal, or inside a guard's true-branch) or provably UNSET (e.g., declared with ? and never assigned).

Examples detected:

  • x <- "hello"; if x? — always true, string literal is always SET (E08088)
  • if name <- getName() { if name? } — always true, guard proved SET (E08088)
  • name as String?; if name? — always false, declared UNSET never assigned (E08089)

Inside require/assert: only the bug-class NEVER_SET case (E08089) fires. The REDUNDANT case (E08088) is suppressed because require x? is the established idiom for "touch this variable" since EK9 enforces capture and has no discard pattern.

Conditions inside loop bodies are suppressed because widening has not yet been applied.

Decision D7 (Steve, 2026-10-07) extends the same errors to the set-checks a reader does not see written as x?, and to the ones hidden inside a larger condition:

  1. A guard's own implicit gate (acceptGate(EK9Parser.PreFlowStatementContext, ConditionResult, boolean)) - if seven <- 7 always enters (E08088), if v <- Integer() never does (E08089), and if late :=? 5 on a provably unset late always does. A Boolean truth guard's gate is "set AND true" - a condition, not a set-check - so a provably decided one is E08086 / E08087. A :=? whose target is provably set is left to E08095.
  2. Re-testing the guard's own target in its then/with condition - if v <- f() then v? and if x :=? f() then x? - is ALWAYS redundant, with no data flow needed: the condition is evaluated only once the gate has passed (GuardedConditionEvaluator). So is then b after a Boolean truth guard on b (D9.2), which is E08086. Only if / else if: a switch's then expression is the value switched on, and a while re-evaluates its condition every iteration while its gate runs once at entry, so a re-test there can matter.
  3. A conjunct - an x? that is an operand of a top-level and chain of a condition (if x := 7 then x? and ready) - is judged by data flow exactly as a whole condition is. A blind if x := f() then x? stays clean: nothing proves f() set or unset.
  • Constructor Details

  • Method Details

    • accept

      public void accept(EK9Parser.ExpressionContext ctx)
      Specified by:
      accept in interface Consumer<EK9Parser.ExpressionContext>
    • acceptIdentifierCondition

      void acceptIdentifierCondition(EK9Parser.ExpressionContext expr)
      A bare identifier that is the condition (or a conjunct of the condition) of an if / else if headed by a Boolean truth guard on that very identifier: if ok <- check() then ok. The gate has already required ok to be set and true, so the condition is always true (E08086, D7.2 with D9.2).
      Parameters:
      expr - any expression; only a bare identifier reference is examined
    • acceptGate

      void acceptGate(EK9Parser.PreFlowStatementContext preFlow, ConditionResult targetBefore, boolean storesUnset)
      The implicit gate of a guard head (<- or :=?), judged once the pre-flow's store has been tracked (D7.1). Inside a loop nothing is judged, exactly as for a written x?.

      A literal type's no-argument constructor (Integer(), String(), ...) makes a present but UNSET value, so a guard storing one never enters. That fact is applied to the gate only: the value tracker in general does not yet record it (see the ConstrainedSpecNegative "known limit"), because doing so moves E08095/E08089 across teaching examples that rely on the gap - a separate decision.

      Parameters:
      preFlow - the pre-flow, after its store has been tracked
      targetBefore - a :=? target's set-state BEFORE the store (UNKNOWN for a declaration). A provably set target is E08095's case (the assignment never applies) and is not reported again here.
      storesUnset - whether the pre-flow's right-hand side is provably unset (a literal type's no-argument constructor)