mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-19 14:00:35 +00:00
improved the documentation; overloading resolution finally documented
This commit is contained in:
@@ -18,7 +18,7 @@ algorithm (in pseudo-code) determines type equality:
|
||||
incl(s, (a,b))
|
||||
if a.kind == b.kind:
|
||||
case a.kind
|
||||
of int, intXX, float, floatXX, char, string, cstring, pointer,
|
||||
of int, intXX, float, floatXX, char, string, cstring, pointer,
|
||||
bool, nil, void:
|
||||
# leaf type: kinds identical; nothing more to check
|
||||
result = true
|
||||
@@ -61,7 +61,7 @@ with an auxiliary set ``s`` is omitted:
|
||||
proc typeEqualsOrDistinct(a, b: PType): bool =
|
||||
if a.kind == b.kind:
|
||||
case a.kind
|
||||
of int, intXX, float, floatXX, char, string, cstring, pointer,
|
||||
of int, intXX, float, floatXX, char, string, cstring, pointer,
|
||||
bool, nil, void:
|
||||
# leaf type: kinds identical; nothing more to check
|
||||
result = true
|
||||
@@ -90,7 +90,7 @@ with an auxiliary set ``s`` is omitted:
|
||||
result = typeEqualsOrDistinct(a.baseType, b)
|
||||
elif b.kind == distinct:
|
||||
result = typeEqualsOrDistinct(a, b.baseType)
|
||||
|
||||
|
||||
|
||||
Subtype relation
|
||||
----------------
|
||||
@@ -145,7 +145,7 @@ algorithm returns true:
|
||||
|
||||
A type ``a`` is **explicitly** convertible to type ``b`` iff the following
|
||||
algorithm returns true:
|
||||
|
||||
|
||||
.. code-block:: nim
|
||||
proc isIntegralType(t: PType): bool =
|
||||
result = isOrdinal(t) or t.kind in {float, float32, float64}
|
||||
@@ -156,8 +156,8 @@ algorithm returns true:
|
||||
if typeEqualsOrDistinct(a, b): return true
|
||||
if isIntegralType(a) and isIntegralType(b): return true
|
||||
if isSubtype(a, b) or isSubtype(b, a): return true
|
||||
|
||||
The convertible relation can be relaxed by a user-defined type
|
||||
|
||||
The convertible relation can be relaxed by a user-defined type
|
||||
`converter`:idx:.
|
||||
|
||||
.. code-block:: nim
|
||||
@@ -174,7 +174,7 @@ The convertible relation can be relaxed by a user-defined type
|
||||
x = chr.toInt
|
||||
echo x # => 97
|
||||
|
||||
The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and
|
||||
The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and
|
||||
``typeEqualsOrDistinct(T, type(a))`` holds.
|
||||
|
||||
|
||||
@@ -186,6 +186,157 @@ An expression ``b`` can be assigned to an expression ``a`` iff ``a`` is an
|
||||
|
||||
|
||||
Overloading resolution
|
||||
----------------------
|
||||
======================
|
||||
|
||||
To be written.
|
||||
In a call ``p(args)`` the routine ``p`` that matches best is selected. If
|
||||
multiple routines match equally well, the ambiguity is reported at compiletime.
|
||||
|
||||
Every arg in args needs to match. There are multiple different category 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``.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
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:
|
||||
|
||||
.. code-block:: nim
|
||||
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`:idx:. ``var T``
|
||||
matches better than just ``T`` then.
|
||||
|
||||
|
||||
Automatic dereferencing
|
||||
-----------------------
|
||||
|
||||
If the `experimental mode <experimental pragma>`_ is active and no other match
|
||||
is found, the first argument ``a`` is dereferenced automatically if it's a
|
||||
pointer type and overloading resolution is tried with ``a[]`` instead.
|
||||
|
||||
|
||||
Lazy type resolution for expr
|
||||
-----------------------------
|
||||
|
||||
**Note**: An `unresolved`:idx: 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 ``expr``
|
||||
accomplishes:
|
||||
|
||||
.. code-block:: nim
|
||||
template rem(x: expr) = discard
|
||||
|
||||
rem unresolvedExpression(undeclaredIdentifier)
|
||||
|
||||
A parameter of type ``expr`` 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:
|
||||
|
||||
.. code-block:: nim
|
||||
template rem(x: expr) = discard
|
||||
proc rem[T](x: T) = discard
|
||||
|
||||
# undeclared identifier: 'unresolvedExpression'
|
||||
rem unresolvedExpression(undeclaredIdentifier)
|
||||
|
||||
``expr`` is the only metatype that is lazy in this sense, the other
|
||||
metatypes ``stmt`` and ``typedesc`` are not lazy.
|
||||
|
||||
|
||||
Varargs matching
|
||||
----------------
|
||||
|
||||
See `Varargs`_.
|
||||
|
||||
@@ -497,6 +497,24 @@ type conversions in this context:
|
||||
In this example ``$`` is applied to any argument that is passed to the
|
||||
parameter ``a``. (Note that ``$`` applied to strings is a nop.)
|
||||
|
||||
Note that an explicit array constructor passed to a ``varargs`` parameter is
|
||||
not wrapped in another implicit array construction:
|
||||
|
||||
.. code-block:: nim
|
||||
proc takeV[T](a: varargs[T]) = discard
|
||||
|
||||
takeV([123, 2, 1]) # takeV's T is "int", not "array of int"
|
||||
|
||||
|
||||
``varargs[expr]`` is treated specially: It matches a variable list of arguments
|
||||
of arbitrary type but *always* constructs an implicit array. This is required
|
||||
so that the builtin ``echo`` proc does what is expected:
|
||||
|
||||
.. code-block:: nim
|
||||
proc echo*(x: varargs[expr, `$`]) {...}
|
||||
|
||||
echo(@[1, 2, 3])
|
||||
# prints "@[1, 2, 3]" and not "123"
|
||||
|
||||
|
||||
Tuples and object types
|
||||
@@ -695,7 +713,7 @@ via ``{.experimental.}``:
|
||||
new(n)
|
||||
echo n.depth
|
||||
# no need to write n[].depth either
|
||||
|
||||
|
||||
|
||||
|
||||
In order to simplify structural type checking, recursive tuples are not valid:
|
||||
|
||||
Reference in New Issue
Block a user