E08056: a guard can skip the stream, but a stream expression has no way to declare the value produced on the skip path; use the statement form with your own accumulator

Category: E08xxx Pre-IR checks (flow analysis) · Reported in phase: PRE_IR_CHECKS · Classification: GUARDED_STREAM_AS_EXPRESSION · All error codes

What went wrong

A stream pipeline whose 'for' source carries a guard is being used as an expression. The guard can skip the whole pipeline, and a stream expression has no way to say what the result should be when that happens.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1381 (Can I put a guard on a stream 'for' pipeline?)
  • ek9 -q 837 (Can I use a guard (including ':=?') inside a switch or ...)
  • ek9 -q 89 (How do I use streams and stream pipelines in EK9?)

Why the other expression forms do not have this

Every other guarded expression form declares its own result, and that declaration's initial value IS the result on the skip path - here "no limit":

rtn: switch limit <- maybeLimit() with limit
  <- result as String: "no limit"
  case 1
    result: "one"
  default
    result: "several"

The same shape works for while, do/while, for-in and try. If you omit the initial value there, you get E08050 instead.

A stream expression ends in '| collect as T' and has no returning param, so there is nowhere to write that initial value. The result is whatever T's default constructor produces - and that differs by type in a way the reading of the code does not show. Collected 'as List of Integer', the skip path gives an EMPTY list, which IS set; collected 'as Integer', it gives an UNSET Integer.

How to fix it

  1. Use the statement form and keep your own accumulator, so you choose the value that survives a skipped guard (the first fix in the [EXAMPLE] section below: a total that starts at 0)
  2. Or hoist the guard into an enclosing 'if', leaving the stream unguarded:
if limit <- maybeLimit()
  rtn: for i in 1 ... limit | collect as List of Integer
  1. Or drop the guard if the pipeline should always run.

Common causes

  • Reaching for a stream expression because it is shorter, when the guard means the result is conditional
  • Assuming 'collect as' always yields an empty-but-set value; it does for collections, not for scalars

[NOTE ON ':='] A plain ':=' pre-flow is NOT a guard - it always assigns and the body always runs - so it does not raise this error. Only the set-checking pre-flows do: '<-' and ':=?'.

See also

  • E08050: return value is not always initialised (the same rule, where a returning param does exist)
  • E08095: assign-if-unset ':=?' can never apply

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E08056: a guard can skip the stream, but a stream expression has no way to declare the value produced on the skip path; use the statement form with your own accumulator

#!ek9
defines module error.e08056.bad

  defines function

    maybeLimit() as pure
      <- rtn as Integer: Integer()

    total()
      <- rtn as Integer?
      rtn: for limit <- maybeLimit() then i in 1 ... limit | collect as Integer

Fixed: Use the statement form with your own accumulator

#!ek9
defines module error.e08056.good

  defines function

    maybeLimit() as pure
      <- rtn as Integer: Integer()

    total()
      <- rtn as Integer: 0
      for limit <- maybeLimit() then i in 1 ... limit
        rtn += i

Fixed: Or hoist the guard into an 'if'; a guarded switch declares its own result

#!ek9
defines module error.e08056.good.forms

  defines function
    maybeLimit() as pure
      <- rtn as Integer: Integer()

    hoisted()
      <- rtn as List of Integer: List()
      if limit <- maybeLimit()
        rtn: for i in 1 ... limit | collect as List of Integer

    described()
      <- rtn as String?
      rtn: switch limit <- maybeLimit() with limit
        <- result as String: "no limit"
        case 1
          result: "one"
        default
          result: "several"

Questions and answers

These Q&A entries show E08056 and its fix: