Overload resolution

In a call p(args) the routine p that matches best is selected. If multiple routines match equally well, the ambiguity is reported during semantic analysis.

Every arg in args needs to match. There are multiple different categories how an argument can match. Let f be the formal parameter’s type and a the type of the argument.

  1. Exact match: a and f are of the same type.
  2. Literal match: a is an integer literal of value v and f is a signed or unsigned integer type and v is in f’s range. Or: a is a floating-point literal of value v and f is a floating-point type and v is in f’s range.
  3. Generic match: f is a generic type and a matches, for instance a is int and f is a generic (constrained) parameter type (like in [T] or [T: int|char]).
  4. Subrange or subtype match: a is a range[T] and T matches f exactly. Or: a is a subtype of f.
  5. Integral conversion match: a is convertible to f and f and a is some integer or floating-point type.
  6. Conversion match: a is convertible to f, possibly via a user defined converter.

There are two major methods of selecting the best matching candidate, namely counting and disambiguation. Counting takes precedence to disambiguation. In counting, each parameter is given a category and the number of parameters in each category is counted. The categories are listed above and are in order of precedence. For example, if a candidate with one exact match is compared to a candidate with multiple generic matches and zero exact matches, the candidate with an exact match will win.

In the following, count(p, m) counts the number of matches of the matching category m for the routine p.

A routine p matches better than a routine q if the following algorithm returns true:

  1. for each matching category m in ["exact match", "literal match",
  2. "generic match", "subtype match",
  3. "integral match", "conversion match"]:
  4. if count(p, m) > count(q, m): return true
  5. elif count(p, m) == count(q, m):
  6. discard "continue with next category m"
  7. else:
  8. return false
  9. return "ambiguous"

When counting is ambiguous, disambiguation begins. Parameters are iterated by position and these parameter pairs are compared for their type relation. The general goal of this comparison is to determine which parameter is more specific. The types considered are not of the inputs from the callsite, but of the competing candidates’ parameters.

Some examples:

  1. proc takesInt(x: int) = echo "int"
  2. proc takesInt[T](x: T) = echo "T"
  3. proc takesInt(x: int16) = echo "int16"
  4. takesInt(4) # "int"
  5. var x: int32
  6. takesInt(x) # "T"
  7. var y: int16
  8. takesInt(y) # "int16"
  9. var z: range[0..4] = 0
  10. takesInt(z) # "T"

If this algorithm returns “ambiguous” further disambiguation is performed: If the argument a matches both the parameter type f of p and g of q via a subtyping relation, the inheritance depth is taken into account:

  1. type
  2. A = object of RootObj
  3. B = object of A
  4. C = object of B
  5. proc p(obj: A) =
  6. echo "A"
  7. proc p(obj: B) =
  8. echo "B"
  9. var c = C()
  10. # not ambiguous, calls 'B', not 'A' since B is a subtype of A
  11. # but not vice versa:
  12. p(c)
  13. proc pp(obj: A, obj2: B) = echo "A B"
  14. proc pp(obj: B, obj2: A) = echo "B A"
  15. # but this is ambiguous:
  16. pp(c, c)

Likewise, for generic matches, the most specialized generic type (that still matches) is preferred:

  1. proc gen[T](x: ref ref T) = echo "ref ref T"
  2. proc gen[T](x: ref T) = echo "ref T"
  3. proc gen[T](x: T) = echo "T"
  4. var ri: ref int
  5. gen(ri) # "ref T"