Class AssignIfUnsetDecidedOrError

All Implemented Interfaces:
Consumer<EK9Parser.AssignmentStatementContext>

final class AssignIfUnsetDecidedOrError extends TypedSymbolAccess implements Consumer<EK9Parser.AssignmentStatementContext>
THE check of an assign-if-unset statement (:=?) whose outcome the value tracker has DECIDED - both outcomes, one class, one reading of the target (E08095 and E08097).

:=? assigns ONLY when the target is unset. It is the same ValueTrackingAnalyzer question already asked by RedundantIsSetCheckOrError, because :=? is an isSet check plus a conditional assign:

  • Target surely SET - E08095: the assignment can never apply; the right-hand side is never evaluated and its value is silently lost. The motivating case is a record property initialised to a set value and then assigned in a pure constructor, where :=? is the only assignment form permitted (E08100):
      Connections
        up as Boolean: false     // SET
        Connections() as pure
          -> isUp as Boolean
          up :=? isUp            // E08095 - never applies, isUp is thrown away
      
  • Target surely UNSET - E08097: the assignment always applies, so it is a plain := written as a conditional one - a misunderstanding of what :=? is for (a fallback when a value MAY be missing):
      greeting as String: String()   // String() makes an UNSET String
      greeting :=? "Hello, World"    // E08097 - always applies; use ':=' or declare it with the value
      
    Not an error where := would be refused: in a pure scope :=? is the ONLY reassignment (E08100), so a surely-unset target is exactly how a pure constructor initialises its fields - the purity test is the one E08100 uses (isPureReassignmentDisallowed). Not an error in a guard (if x :=? f()): there :=? also gates the body, which := would not; a decided gate is E08088/E08089's to report. (A dynamic function's capture is not known at the start of its body at all - the body runs on each CALL - so the tracker holds nothing decided about it there: PreIRListener.seedCapturedVariables.) And never on a target DECLARED WITH NO VALUE (x as T?, <- rtn as T?, a T? property filled in a constructor - UninitialisedAtDeclaration.declaredWithNoValue(ISymbol)): filling that never-written slot is the case :=? exists for (Steve, 2026-10-07 and 2026-10-27; EK9_ABSENCE_UNOBSERVABLE.md). E08097 is about a value MADE unset (T(), or narrowed by if not x?) and then filled as if it might not be.

🔑 Steve's rule (2026-10-26): fire ONLY when SURE. UNKNOWN is always silent - EK9 has no warnings, so an error must be certain. The two outcomes are made sure differently inside a loop, because the body is walked once from the pre-loop state: