How does method resolution work with overloading?

← Advanced Type System · Ref: Q255

EK9 uses a cost-based matching algorithm to resolve which overloaded method to call. Each potential match is assigned a numeric cost, and the method with the lowest total cost wins.

FIVE COST LEVELS

The compiler assigns costs based on how well each argument matches each parameter:

  ZERO_COST (0.0) - exact type match, always preferred
  SUPER_COST (0.05) - superclass match, 0.05 per inheritance level
  TRAIT_COST (0.10) - trait match, 0.10 per trait level
  COERCION_COST (0.5) - type promotion via #^ operator
  HIGH_COST (20.0) - Any type match, universal fallback

HOW MATCHING WORKS

For each candidate method, the compiler sums the costs of all parameter matches. The match percentage is calculated as 100.0 minus totalCost. Higher percentage means better match. The method with the highest percentage (lowest cost) wins.

Examples:

  Exact Integer match: cost 0.0, percentage 100.0%
  Superclass one level: cost 0.05, percentage 99.95%
  Trait match: cost 0.10, percentage 99.90%
  Promoted Integer to Float: cost 0.5, percentage 99.5%
  Any fallback: cost 20.0, percentage 80.0%

AMBIGUITY DETECTION

If two methods score within 0.001 of each other, the compiler reports an ambiguity error rather than guessing. This prevents subtle bugs where adding a new overload silently changes which method gets called.

DISPATCHER RESOLUTION ORDER

In a dispatcher, this cost system determines which overload handles each type:

  1. Exact type match scores 100% - always called first
  2. Parent class match scores near 100% - called for subtype
  3. Trait match scores near 100% - called for implementor
  4. Promotion match scores 99.5% - called after type widening
  5. Any fallback scores 80% - called only when nothing else matches

MULTI-PARAMETER COSTS

With multiple parameters, costs are summed. A method with two exact matches (cost 0.0 + 0.0 = 0.0) beats a method with one exact and one promoted match (cost 0.0 + 0.5 = 0.5).

See Q24 for the dispatcher pattern. See Q25 for the promote operator and coercion cost. See Q254 for Any type and HIGH_COST. See Q60 for function dispatching. See Q258 for type coercion details.

Example

defines module qa.advancedtypes.methodresolution

  defines class

    // Three-level hierarchy for demonstrating SUPER_COST
    Shape as open
      default Shape() as pure

      override operator ? as pure
        <- rtn <- true

    Circle extends Shape as open
      default Circle() as pure

    SmallCircle extends Circle
      default SmallCircle() as pure

    // Class with two differently-named methods — no overloading
    Renderer

      // render accepts (Shape, Circle)
      render()
        ->
          first as Shape
          second as Circle
        require first? and second?

      // handleShape accepts (Circle, Shape) — different name, no ambiguity
      handleShape()
        ->
          first as Circle
          second as Shape
        require first? and second?

  defines program

    MethodResolutionDemo()
      stdout <- Stdout()

      shape <- Shape()
      circle <- Circle()
      small <- SmallCircle()

      renderer <- Renderer()

      // Exact match: render(Shape, Circle) cost 0.0 + 0.0 = 0.0
      renderer.render(shape, circle)

      // SmallCircle matches Circle at SUPER_COST: cost 0.0 + 0.05 = 0.05
      renderer.render(shape, small)

      // handleShape is a separate method — no overload ambiguity
      // circle matches Circle(0.0), small matches Shape at 0.10
      renderer.handleShape(circle, small)

      stdout.println("Method resolution completed")

Common mistakes

E06140 — Renaming handleShape to render creates two crossed overloads: render(Shape,Circle) and render(Circle,Shape). Calling render(circle,small) then costs 0.10 for both overloads: circle to Shape(0.05)+small to Circle(0.05) vs circle to Circle(0)+small to Shape(0.10). Equal costs trigger E50210 METHOD_AMBIGUOUS. See ek9 -h E50210 for details.

Incorrect:

render(

Correct:

handleShape(
Other ways to ask this
  • How does EK9 pick which overloaded method to call?
  • What is cost-based method matching in EK9?
  • How does the compiler resolve ambiguous method calls?
  • What are the cost levels in EK9 method resolution?

Coming from another language?

Java: method overloading resolved at COMPILE time based on declared types, not runtime types. Most specific match wins per JLS rules. C++: overload resolution uses implicit conversion sequences ranked by category. Python: no method overloading, uses functools.singledispatch for single-argument dispatch. Rust: no method overloading at all, uses trait dispatch. Go: no method overloading. Kotlin: compile-time overload resolution similar to Java. Julia: multiple dispatch with specificity rules similar to EK9's cost model. EK9: cost-based RUNTIME resolution, five distinct cost levels, ambiguity detection within 0.001 tolerance, works with dispatchers for runtime type-based dispatch.

Keywords: handler, ambiguity, cost, type-system, visitor, overload, promotion, trait, method, percentage, type, zero, resolution, super, sealed, coercion, advanced, matching, dispatch