What is the promote operator?
← Getting Started · Ref: Q25
The promote operator (#^) enables controlled, single-level type widening. It is central to how EK9 resolves method signatures and assignments when types do not match exactly.
HOW IT WORKS
When you assign a value to a variable of a different type, or pass an argument to a function expecting a different type, the compiler checks if the source type has a #^ operator that returns the target type:
intValue <- 42 floatResult as Float: intValue
The compiler sees Integer assigned to Float, finds Integer's #^ operator returns Float, and automatically inserts the promotion call. The same applies to function calls:
acceptsFloat(21)
The Integer literal 21 is automatically promoted to Float because acceptsFloat expects a Float parameter.
BUILT-IN PROMOTIONS
Integer to Float Safe numeric widening Character to String Single character to text Date to DateTime Date to full timestamp Millisecond to Duration Time unit widening
USER-DEFINED PROMOTIONS
You can define #^ on your own types:
Measurement
operator #^ as pure
<- rtn as String: some formatted text
A type can have only ONE #^ operator. This is by design.
SINGLE PROMOTION ONLY (Critical Rule)
The compiler attempts exactly ONE promotion. It does NOT chain promotions. If Integer promotes to Float and Float promotes to something else, the compiler will NOT automatically go Integer to Float to that other type. It tries one step and stops.
This is enforced in the method matching mechanism. After promoting a type, the compiler checks if the promoted type is directly assignable to the target using uncoerced matching only. No further promotion is attempted.
COST-BASED METHOD MATCHING
When resolving which method overload to call, the compiler assigns costs:
Exact type match: 0.0 (perfect, always preferred) Superclass match: 0.05 (inheritance) Trait match: 0.10 (interface implementation) Promotion match: 0.5 (via #^ operator) Any type match: 20.0 (universal fallback) No match: -1.0 (invalid)
The method with the lowest total cost wins. Exact matches are always preferred over promoted matches. If two methods score within 0.001 of each other, the compiler reports an ambiguity error rather than guessing.
WHY OTHER LANGUAGES GET THIS WRONG
Implicit conversion chains are one of the most prolific sources of subtle bugs across programming languages.
C++: User-defined conversions can chain with standard conversions. A converting constructor plus an implicit operator can create paths the developer never intended. The 'explicit' keyword was added specifically to stop this, but developers must remember to use it.
JavaScript: The == operator performs type coercion through multiple steps: [] == false is true, '0' == false is true, but [] == '0' is false. The entire '===' operator exists solely because == coercion is so unreliable.
Scala: Implicit conversions (implicit def) can compose into chains. Scala 2's implicit resolution was so complex that even experienced developers could not predict which conversion path the compiler would choose. Scala 3 replaced this with 'given' and 'using' specifically because chained implicits were too dangerous.
Java: Autoboxing combined with widening produces surprises. Integer == Long compiles but compares object identity, not values. Ternary expressions with mixed numeric types silently widen in unexpected directions.
Python: While mostly explicit, __add__ returning NotImplemented triggers __radd__ on the other operand, which can create surprising dispatch chains with mixed types.
EK9's single-promotion rule eliminates all of these problems. One step, predictable, cost-ranked, compiler-verified. If you need more complex conversion, use an explicit constructor call. The compiler will never silently chain conversions behind your back.
See also Q24 (How do I convert between types?) for the full type conversion story including dispatcher and constructor conversion.
See Q24 for type conversion. See Q39 for integer and float. See Q242 for all conversion and introspection operators. See Q258 for type coercion and promotion in method calls. See Q552 for Date-to-DateTime promotion.
Example
defines module qa.promote.operator defines function acceptsFloat() as pure -> mainValue as Float <- rtn as Float: mainValue * 2.0 defines class Measurement reading <- Float() Measurement() -> reading as Float this.reading :=: reading reading() as pure <- rtn as Float: reading operator #^ as pure <- rtn as String: `${reading}units` default operator ? defines program PromoteDemo() stdout <- Stdout() // Automatic promotion: Integer to Float in assignment intValue <- 42 floatResult as Float: intValue stdout.println(`Promoted int to float: ${floatResult}`) // Automatic promotion: Integer to Float in function call doubled <- acceptsFloat(21) stdout.println(`Promoted in call: ${doubled}`) // Character to String promotion letter <- 'X' textValue as String: letter stdout.println(`Promoted char to string: ${textValue}`) // User-defined promotion sensor <- Measurement(98.6) displayText as String: sensor stdout.println(`Custom promote: ${displayText}`)
Common mistakes
E08180 — Class fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like Float() or a literal value. See ek9 -h E08180 for details.
Incorrect:
reading as Float
Correct:
reading <- Float()
E07520 — The ? operator is inherited from the base type and requires 'default' or 'override'. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details.
Incorrect:
operator ?
Correct:
default operator ?
Other ways to ask this
- How does automatic type promotion work in EK9?
- How does EK9 handle implicit type conversion?
- Why does EK9 only allow one level of promotion?
- How does the #^ operator work?
Coming from another language?
C++: implicit conversions chain (constructor + operator), 'explicit' keyword added to fix. JavaScript: == coercion chains create bizarre equality. Scala 2: implicit def chains unpredictable, replaced in Scala 3. Java: autoboxing + widening surprises (Integer == Long). Python: __add__/NotImplemented/__radd__ chains. EK9: single #^ promotion only, cost-based matching (0.0 exact, 0.05 super, 0.10 trait, 0.5 promotion), no chaining. Predictable, verifiable, safe.
Keywords: beginner, coerce, start, single, implicit, resolution, convert, promote, intro, operator, overload, first, signature, matching, migrate, chain, promotion, widening, coercion, cost