Why is 'Ratio as Float constrain as >= 1' rejected with E08265?
← Advanced Type System · Ref: Q1391
A constraint's literal must match the parameter type of the operator it is compared with EXACTLY. A constraint is never converted - there is no promotion inside one.
THE RULE
Ratio as Float constrain as >= 1 and <= 100 // E08265: Float's '>=' takes a Float, not an Integer Ratio as Float constrain as >= 1.0 and <= 100.0 // correct
The same holds for every comparison operator and every promotable pair: a Character does not become a String either, so a String constraint writes <> "x", not <> 'x'.
WHY A DECLARATION IS EXACT
In an ordinary expression, ratio >= 1 promotes the Integer to a Float and works. A constraint is different: it is a DECLARATION of the type's admissible values, and the check the compiler generates compares the value with the literal exactly as written. Before E08265 existed, '>= 1' on a Float compiled and then called a Float comparison taking an Integer that does not exist - it failed at the FIRST construction, on the JVM and natively. Rather than hide a conversion inside a declaration, EK9 makes the declaration say exactly what it means.
TWO REMEDIES
1. Built-in base (Float, String, ...): you cannot add operators to a built-in type, so write the literal in the operator's type - 1 becomes 1.0, 'x' becomes "x". The error message gives the respelling.
2. User-defined base: you have a choice. Either write the literal in the type the existing operator takes, or ADD the operator that takes the literal's type. The example below gives Price both 'operator >= -> threshold as Float' and 'operator >= -> threshold as Integer', so its constraint can use whole-number bounds directly.
WHAT IS NOT AFFECTED
Comparing a constrained VALUE with a base value at run time (age >= limit) is an ordinary expression and follows the ordinary rules - see Q717. Only the literal written inside 'constrain as' must match exactly.
See Q257 for constrained types and Q1376 for constraints that no value can satisfy. Use 'ek9 -h E08265' for the full error explanation.
Example
defines module qa.constraint.literal.exact defines record //A user-defined base. It declares '>=' and '<=' for Integer as well as Float, so a constraint //written with whole-number bounds finds an operator taking exactly that type. Price amount as Float? Price() amount: Float() Price() -> initial as Float amount: initial Price() -> other as Price amount: other.amount operator >= as pure -> threshold as Float <- rtn as Boolean: amount >= threshold operator >= as pure -> threshold as Integer <- rtn as Boolean: amount >= #^threshold operator <= as pure -> threshold as Float <- rtn as Boolean: amount <= threshold operator <= as pure -> threshold as Integer <- rtn as Boolean: amount <= #^threshold override operator ? as pure <- rtn as Boolean: amount? operator :=: -> other as Price amount :=: other.amount defines type //Built-in base: the bounds are written as Floats because Float's operators take a Float. Ratio as Float constrain as >= 1.0 and <= 100.0 //Built-in base: a String's '<>' takes a String, so the bound is "x", not the Character 'x'. NotX as String constrain as <> "x" //User-defined base: Integer bounds work because Price declares the Integer operators. Bounded as Price constrain as >= 1 and <= 100 defines program ConstraintLiteralExactType() stdout <- Stdout() stdout.println(`ratio 50.5 accepted: ${Ratio().of(50.5)?}`) stdout.println(`ratio 0.5 accepted: ${Ratio().of(0.5)?}`) stdout.println(`"y" accepted: ${NotX().of("y")?}`) stdout.println(`"x" accepted: ${NotX().of("x")?}`) stdout.println(`price 20.0 accepted: ${Bounded().of(Price(20.0))?}`) stdout.println(`price 250.0 accepted: ${Bounded().of(Price(250.0))?}`)
Common mistakes
E08265 — Float's comparison operators take a Float. A constraint never promotes its literal, so an Integer bound is rejected; write it as a Float.
Incorrect:
Ratio as Float constrain as >= 1 and <= 100
Correct:
Ratio as Float constrain as >= 1.0 and <= 100.0
E08265 — 'x' is a Character and String's '<>' takes a String. A Character is not converted inside a constraint; write the bound as a String literal.
Incorrect:
NotX as String constrain as <> 'x'
Correct:
NotX as String constrain as <> "x"
E08265 — For a user-defined base you may add the operator that takes the literal's type - or write the bound as a Float to use the existing operator.
Incorrect:
//no 'operator >= -> threshold as Integer' - so Bounded's '>= 1' only reaches the Float operator by promotion
Correct:
operator >= as pure -> threshold as Integer <- rtn as Boolean: amount >= #^threshold
Other ways to ask this
- Why must a Float constraint use 1.0 instead of 1?
- Does EK9 promote an Integer literal inside a constraint?
- How do I constrain a user-defined type with whole-number bounds?
- Why can't a String constraint compare with a Character like 'x'?
Coming from another language?
Java Bean Validation annotations such as @Min(1) and @DecimalMin("1") accept a bound in whatever form the annotation takes and convert it silently; a hand-written check widens int to double invisibly. Kotlin and C# likewise widen numeric literals in comparisons. EK9 applies ordinary promotion in expressions, but a constrained type's declaration must state its bounds in the compared type, so the declared constraint and the checked constraint are the same thing.
Keywords: promotion, E08265, coercion, exact, constrain, float, literal, operator, constraint, integer