E08097: assign-if-unset ':=?' always applies because the target is always unset here - it is a plain assignment; use ':=' (or declare the variable with the value)

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

What went wrong

An assign-if-unset ':=?' is applied to a target that is always unset at this point, so it always assigns: it is a plain assignment written as a conditional one. Data flow analysis proves the target unset here - it was given a value made by a no-argument constructor such as 'String()' or 'Integer()', or this code is inside 'if not x?' - so the "only if unset" part never decides anything.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 2254 (Why is 'name <- String()' then 'name :=? "Steve"' reported...)
  • ek9 -q 1263 (If I have a String variable that might be unset, how can I...)
  • ek9 -q 1264 (Walk me through using :=? on an Integer retry counter so th...)
  • ek9 -q 2255 (How does the EK9 compiler decide a variable is definitely...)

Rationale

':=?' exists for a value that MAY already be set: it keeps the value if there is one and supplies a fallback if not. Used on a value that can only be unset, it says something the code does not mean, and a reader looks for the case where it does not assign. It is the mirror of E08095, where the target is always SET and ':=?' can never apply: between them, a ':=?' the compiler can decide is an error either way.

The head of a fallback chain is the usual case. 'x <- String()' then 'x :=? a()' then 'x :=? b()' starts from a value that is known to be unset, so the first ':=?' always applies. Start the chain from its first source instead - 'x <- a()' then 'x :=? b()'.

This is reported only when the compiler is SURE. EK9 has no warnings, so an error must be certain: a value that came from a function call may be set, and the compiler stays silent; inside a loop the value must be unset on every pass. Three places are never reported. A variable, returning variable or property declared with NO value ('x as String?') holds no value at all until it is first written, and ':=?' is the form that fills it - the case it exists for. In a 'pure' scope reassignment is not allowed (E08100), so ':=?' is the only way to give a value there. A guard such as 'if x :=? f()' also gates its block, which ':=' would not.

How to fix it

  1. Use ':=' - it says what the statement does.
  2. Or declare the variable with the value in the first place, instead of declaring it unset

and assigning it on the next line.

  1. For a fallback chain, declare the variable from the first source and keep ':=?' for the

rest.

Common causes

  • 'name <- String()' then 'name :=? "default"' - write 'name <- "default"'
  • A fallback chain that starts from 'String()' or 'Integer()' instead of its first source
  • A returning variable declared '<- rtn as String: String()' and then filled with ':=?'
  • Giving a value with ':=?' inside 'if not x?', the one place it is known to be unset

See also

  • E08095: assign-if-unset ':=?' can never apply (the target is always set)
  • E08096: an operator that keeps an unset value unset
  • E08089: isSet check always false (the same data flow proof, applied to '?')
  • E08100: reassignment not allowed in a pure scope (':=?' is supported)

Examples

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

Fails with E08097: assign-if-unset ':=?' always applies because the target is always unset here - it is a plain assignment; use ':=' (or declare the variable with the value)

#!ek9
defines module error.e08097.bad

  defines function

    //Nothing in the environment: unset
    fromEnvironment()
      <- rtn as String: String()

    resolveSetting()
      <- setting as String: String()
      setting :=? fromEnvironment()
      setting :=? "default"

Fixed: Use ':=?' only where the value may already be set

#!ek9
defines module error.e08097.good

  defines function

    //Nothing saved: unset
    savedName()
      <- rtn as String: String()

    //From a function call the value may be set, so ':=?' decides.
    nameOrDefault()
      <- rtn as String: savedName()
      rtn :=? "anonymous"

    //Declared with no value: ':=?' is the form that fills it.
    greeting()
      <- rtn as String?
      rtn :=? "Hello"

    //Known to be unset inside 'if not total?', so ':=' gives it the value.
    topUp()
      -> amount as Integer
      <- total as Integer: amount
      if not total?
        total := 0

    //A guard also gates its block, which ':=' would not.
    printIfFound()
      found as String?
      if found :=? savedName()
        Stdout().println(found)

Fixed: Start the fallback chain from its first source

#!ek9
defines module error.e08097.good.fix

  defines function

    //Nothing in the environment: unset
    fromEnvironment()
      <- rtn as String: String()

    resolveSetting()
      <- setting as String: fromEnvironment()
      setting :=? "default"

Questions and answers

These Q&A entries show E08097 and its fix: