improved the documentation; overloading resolution finally documented
This commit is contained in:
parent
2f4472963f
commit
1592067566
2 changed files with 179 additions and 10 deletions
|
|
@ -18,7 +18,7 @@ algorithm (in pseudo-code) determines type equality:
|
||||||
incl(s, (a,b))
|
incl(s, (a,b))
|
||||||
if a.kind == b.kind:
|
if a.kind == b.kind:
|
||||||
case a.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:
|
bool, nil, void:
|
||||||
# leaf type: kinds identical; nothing more to check
|
# leaf type: kinds identical; nothing more to check
|
||||||
result = true
|
result = true
|
||||||
|
|
@ -61,7 +61,7 @@ with an auxiliary set ``s`` is omitted:
|
||||||
proc typeEqualsOrDistinct(a, b: PType): bool =
|
proc typeEqualsOrDistinct(a, b: PType): bool =
|
||||||
if a.kind == b.kind:
|
if a.kind == b.kind:
|
||||||
case a.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:
|
bool, nil, void:
|
||||||
# leaf type: kinds identical; nothing more to check
|
# leaf type: kinds identical; nothing more to check
|
||||||
result = true
|
result = true
|
||||||
|
|
@ -90,7 +90,7 @@ with an auxiliary set ``s`` is omitted:
|
||||||
result = typeEqualsOrDistinct(a.baseType, b)
|
result = typeEqualsOrDistinct(a.baseType, b)
|
||||||
elif b.kind == distinct:
|
elif b.kind == distinct:
|
||||||
result = typeEqualsOrDistinct(a, b.baseType)
|
result = typeEqualsOrDistinct(a, b.baseType)
|
||||||
|
|
||||||
|
|
||||||
Subtype relation
|
Subtype relation
|
||||||
----------------
|
----------------
|
||||||
|
|
@ -145,7 +145,7 @@ algorithm returns true:
|
||||||
|
|
||||||
A type ``a`` is **explicitly** convertible to type ``b`` iff the following
|
A type ``a`` is **explicitly** convertible to type ``b`` iff the following
|
||||||
algorithm returns true:
|
algorithm returns true:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
proc isIntegralType(t: PType): bool =
|
proc isIntegralType(t: PType): bool =
|
||||||
result = isOrdinal(t) or t.kind in {float, float32, float64}
|
result = isOrdinal(t) or t.kind in {float, float32, float64}
|
||||||
|
|
@ -156,8 +156,8 @@ algorithm returns true:
|
||||||
if typeEqualsOrDistinct(a, b): return true
|
if typeEqualsOrDistinct(a, b): return true
|
||||||
if isIntegralType(a) and isIntegralType(b): return true
|
if isIntegralType(a) and isIntegralType(b): return true
|
||||||
if isSubtype(a, b) or isSubtype(b, a): 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:.
|
`converter`:idx:.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
@ -174,7 +174,7 @@ The convertible relation can be relaxed by a user-defined type
|
||||||
x = chr.toInt
|
x = chr.toInt
|
||||||
echo x # => 97
|
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.
|
``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
|
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
|
In this example ``$`` is applied to any argument that is passed to the
|
||||||
parameter ``a``. (Note that ``$`` applied to strings is a nop.)
|
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
|
Tuples and object types
|
||||||
|
|
@ -695,7 +713,7 @@ via ``{.experimental.}``:
|
||||||
new(n)
|
new(n)
|
||||||
echo n.depth
|
echo n.depth
|
||||||
# no need to write n[].depth either
|
# no need to write n[].depth either
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
In order to simplify structural type checking, recursive tuples are not valid:
|
In order to simplify structural type checking, recursive tuples are not valid:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue