improved the documentation; overloading resolution finally documented

This commit is contained in:
Araq
2015-03-14 19:49:32 +01:00
parent 2f4472963f
commit 1592067566
2 changed files with 179 additions and 10 deletions

View File

@@ -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`_.

View File

@@ -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: