Overload Resolution
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.
- Exact match:
a
andf
are of the same type. - Literal match:
a
is an integer literal of valuev
andf
is a signed or unsigned integer type andv
is inf
\'s range. Or:a
is a floating-point literal of valuev
andf
is a floating-point type andv
is inf
\'s range. - Generic match:
f
is a generic type anda
matches, for instancea
isint
andf
is a generic (constrained) parameter type (like in[T]
or[T: int|char]
). - Subrange or subtype match:
a
is arange[T]
andT
matchesf
exactly. Or:a
is a subtype off
. - Integral conversion match:
a
is convertible tof
andf
anda
is some integer or floating-point type. - Conversion match:
a
is convertible tof
, possibly via a user definedconverter
.
These matching categories have a priority: An exact match is better than
a literal match and that is better than a generic match etc. 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:
for each matching category m in ["exact match", "literal match",
"generic match", "subtype match",
"integral match", "conversion match"]:
if count(p, m) > count(q, m): return true
elif count(p, m) == count(q, m):
discard "continue with next category m"
else:
return false
return "ambiguous"
Some examples:
proc takesInt(x: int) = echo "int"
proc takesInt[T](x: T) = echo "T"
proc takesInt(x: int16) = echo "int16"
takesInt(4) # "int"
var x: int32
takesInt(x) # "T"
var y: int16
takesInt(y) # "int16"
var z: range[0..4] = 0
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:
type
A = object of RootObj
B = object of A
C = object of B
proc p(obj: A) =
echo "A"
proc p(obj: B) =
echo "B"
var c = C()
# not ambiguous, calls 'B', not 'A' since B is a subtype of A
# but not vice versa:
p(c)
proc pp(obj: A, obj2: B) = echo "A B"
proc pp(obj: B, obj2: A) = echo "B A"
# but this is ambiguous:
pp(c, c)
Likewise, for generic matches, the most specialized generic type (that still matches) is preferred:
proc gen[T](x: ref ref T) = echo "ref ref T"
proc gen[T](x: ref T) = echo "ref T"
proc gen[T](x: T) = echo "T"
var ri: ref int
gen(ri) # "ref T"
Overloading based on \'var T\'
If the formal parameter f
is of type var T
in addition to the
ordinary type checking, the argument is checked to be an
l-value
. var T
matches better than
just T
then.
proc sayHi(x: int): string =
# matches a non-var int
result = $x
proc sayHi(x: var int): string =
# matches a var int
result = $(x + 10)
proc sayHello(x: int) =
var m = x # a mutable version of x
echo sayHi(x) # matches the non-var version of sayHi
echo sayHi(m) # matches the var version of sayHi
sayHello(3) # 3
# 13
Lazy type resolution for untyped
Note: An unresolved
expression is an
expression for which no symbol lookups and no type checking have been
performed.
Since templates and macros that are not declared as immediate
participate in overloading resolution, it\'s essential to have a way to
pass unresolved expressions to a template or macro. This is what the
meta-type untyped
accomplishes:
A parameter of type untyped
always matches any argument (as long as
there is any argument passed to it).
But one has to watch out because other overloads might trigger the argument\'s resolution:
template rem(x: untyped) = discard
proc rem[T](x: T) = discard
# undeclared identifier: 'unresolvedExpression'
rem unresolvedExpression(undeclaredIdentifier)
untyped
and varargs[untyped]
are the only metatype that are lazy in
this sense, the other metatypes typed
and typedesc
are not lazy.
Varargs matching
See Varargs.
iterable
A called iterator
yielding type T
can be passed to a template or
macro via a parameter typed as untyped
(for unresolved expressions) or
the type class iterable
or iterable[T]
(after type checking and
overload resolution).
iterator iota(n: int): int =
for i in 0..<n: yield i
template toSeq2[T](a: iterable[T]): seq[T] =
var ret: seq[T]
assert a.typeof is T
for ai in a: ret.add ai
ret
assert iota(3).toSeq2 == @[0, 1, 2]
assert toSeq2(5..7) == @[5, 6, 7]
assert not compiles(toSeq2(@[1,2])) # seq[int] is not an iterable
assert toSeq2(items(@[1,2])) == @[1, 2] # but items(@[1,2]) is