E08095: assign-if-unset ':=?' can never apply because the target is always set - the right side is never evaluated
What went wrong
This assign-if-unset ':=?' can never apply. ':=?' assigns ONLY when the target is unset, and data flow analysis proves the target is already SET at this point - so the right-hand side is never even evaluated. The code compiles clean and does nothing.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 563 (How do pure constructors work and why use ':=?' for field...)
- ek9 -q 79 (What is the assign-if-unset operator :=? in EK9?)
- ek9 -q 2003 (Does ':=?' initialise a variable declared with no value, eve...)
- ek9 -q 1263 (If I have a String variable that might be unset, how can ...)
- ek9 -q 1038 (How do I try multiple configuration sources and use the f...)
- ek9 -q 311 (What code quality checks does the EK9 compiler perform?)
- ek9 -q 1982 (Which ways of combining the guard operators does the comp...)
- ek9 -q 1981 (What does 'x := f()' do when f() returns an unset value, ...)
Rationale
':=?' is an assign-if-unset: "if the target has no value, give it this one". It is the ONLY assignment permitted inside a 'pure' constructor (see E08100), which makes it the natural thing to reach for when initialising fields - and that is exactly where the mistake happens. If the field was already given a value by its declaration, the guard is closed before the constructor even starts, and every argument passed in is thrown away.
Nothing about this is visible at runtime. The object simply keeps its declared values, so the failure surfaces far away from the cause: a drawing that never appears, a total that never changes, a flag that is never honoured.
The common case
A record property given an initial value, then assigned in a pure constructor. In the [EXAMPLE] section below, 'enabled as Boolean: false' is a VALUE - the field is SET before the constructor runs - so 'enabled :=? isEnabled' never applies and the argument is discarded. Declared 'name as String: String()', the field is UNSET until the constructor gives it one, and ':=?' applies.
Declaring the field unset is not a workaround for the compiler - it is the honest model. Before the constructor runs you do not know that 'up' is false; you do not know it at all. 'Boolean()' (or 'Integer()', or 'MyConstrainedType()') IS that state. If you also want a "known: none" value, provide it deliberately from a separate constructor or function rather than by pre-loading the field.
How to fix it
- Decide what the field should hold BEFORE the constructor runs.
- If the constructor is meant to supply it, declare the field UNSET - 'as Type: Type()' - so the assign-if-unset has something to do.
- If the declared value is genuinely the right default and the argument is meant to override it, this is not an assign-if-unset - use a plain ':' assignment in a non-pure constructor.
- If the assignment really is redundant, delete it and drop the now-unused parameter.
Common causes
- A field initialised to a literal ('false', '0', '""') and then ':=?' in a constructor
- A field initialised by a constructor call WITH arguments ('XCoord(0)', 'Integer(1)'), which produces a SET value - unlike the no-arg form, which is UNSET
- Copying a working record that declares its fields unset, then "tidying" the declarations by giving them starting values
- Reaching for ':=?' because a 'pure' constructor rejected ':' (E08100), without changing the field declarations to match
See also
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E08095: assign-if-unset ':=?' can never apply because the target is always set - the right side is never evaluated
#!ek9 defines module error.e08095.bad defines record //Both fields are given a literal value at declaration, so both are SET before the //constructor body runs - every ':=?' below is dead and discards its argument. LiteralDefault enabled as Boolean: false count as Integer: 0 LiteralDefault() as pure -> isEnabled as Boolean howMany as Integer enabled :=? isEnabled count :=? howMany default operator ? //'this.<field>' is the form a constructor must use when the parameter shadows the field. //It is tracked too, so the dead assignment is still caught. ShadowedParameter count as Integer: 0 ShadowedParameter() as pure -> count as Integer this.count :=? count default operator ? defines function //The same proof on a local variable: assigned from a literal, so it is provably SET. guardedOnSetLocal() <- rtn as String: "unchanged" name <- "Alice" name :=? "Bob" rtn: name
Fixed: Use :=? only on something that may be unset
#!ek9 defines module error.e08095.good defines record //Fields declared UNSET - so ':=?' in the pure constructor genuinely applies. //This is the shape of the book's shared Coord record. UnsetDefault name as String: String() count as Integer: Integer() default UnsetDefault() as pure UnsetDefault() as pure -> aName as String aCount as Integer name :=? String(aName) count :=? Integer(aCount) default operator ? //'this.<field>' against an UNSET field is correct and must NOT be flagged. ShadowedButUnset count as Integer: Integer() ShadowedButUnset() as pure -> count as Integer this.count :=? count default operator ? defines function suppliedName() <- rtn as String: "from-call" //A value from a function call is UNKNOWN to data flow analysis, so neither the //"never applies" nor the "always applies" proof exists - stay silent. guardedOnUnknownLocal() <- rtn as String: "unchanged" candidate <- suppliedName() candidate :=? "fallback" rtn: candidate
Questions and answers
These Q&A entries show E08095 and its fix:
- Q563: How do pure constructors work and why use ':=?' for field assignment?
- Q1263: If I have a String variable that might be unset, how can I conditionally assign a default value to it only when it has not been set yet?
- Q1961: Does ':=?' evaluate its right-hand side when the target is already set?
- Q1981: What does 'x := f()' do when f() returns an unset value, and how do I keep the old value instead?
- Q1982: Which ways of combining the guard operators does the compiler reject, and what should I write instead?
- Q2255: How does the EK9 compiler decide a variable is definitely set or definitely unset?