123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- Type relations
- ==============
- The following section defines several relations on types that are needed to
- describe the type checking done by the compiler.
- Type equality
- -------------
- Nim uses structural type equivalence for most types. Only for objects,
- enumerations and distinct types name equivalence is used. The following
- algorithm, *in pseudo-code*, determines type equality:
- .. code-block:: nim
- proc typeEqualsAux(a, b: PType,
- s: var HashSet[(PType, PType)]): bool =
- if (a,b) in s: return true
- incl(s, (a,b))
- if a.kind == b.kind:
- case a.kind
- of int, intXX, float, floatXX, char, string, cstring, pointer,
- bool, nil, void:
- # leaf type: kinds identical; nothing more to check
- result = true
- of ref, ptr, var, set, seq, openarray:
- result = typeEqualsAux(a.baseType, b.baseType, s)
- of range:
- result = typeEqualsAux(a.baseType, b.baseType, s) and
- (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB)
- of array:
- result = typeEqualsAux(a.baseType, b.baseType, s) and
- typeEqualsAux(a.indexType, b.indexType, s)
- of tuple:
- if a.tupleLen == b.tupleLen:
- for i in 0..a.tupleLen-1:
- if not typeEqualsAux(a[i], b[i], s): return false
- result = true
- of object, enum, distinct:
- result = a == b
- of proc:
- result = typeEqualsAux(a.parameterTuple, b.parameterTuple, s) and
- typeEqualsAux(a.resultType, b.resultType, s) and
- a.callingConvention == b.callingConvention
- proc typeEquals(a, b: PType): bool =
- var s: HashSet[(PType, PType)] = {}
- result = typeEqualsAux(a, b, s)
- Since types are graphs which can have cycles, the above algorithm needs an
- auxiliary set ``s`` to detect this case.
- Type equality modulo type distinction
- -------------------------------------
- The following algorithm (in pseudo-code) determines whether two types
- are equal with no respect to ``distinct`` types. For brevity the cycle check
- with an auxiliary set ``s`` is omitted:
- .. code-block:: nim
- proc typeEqualsOrDistinct(a, b: PType): bool =
- if a.kind == b.kind:
- case a.kind
- of int, intXX, float, floatXX, char, string, cstring, pointer,
- bool, nil, void:
- # leaf type: kinds identical; nothing more to check
- result = true
- of ref, ptr, var, set, seq, openarray:
- result = typeEqualsOrDistinct(a.baseType, b.baseType)
- of range:
- result = typeEqualsOrDistinct(a.baseType, b.baseType) and
- (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB)
- of array:
- result = typeEqualsOrDistinct(a.baseType, b.baseType) and
- typeEqualsOrDistinct(a.indexType, b.indexType)
- of tuple:
- if a.tupleLen == b.tupleLen:
- for i in 0..a.tupleLen-1:
- if not typeEqualsOrDistinct(a[i], b[i]): return false
- result = true
- of distinct:
- result = typeEqualsOrDistinct(a.baseType, b.baseType)
- of object, enum:
- result = a == b
- of proc:
- result = typeEqualsOrDistinct(a.parameterTuple, b.parameterTuple) and
- typeEqualsOrDistinct(a.resultType, b.resultType) and
- a.callingConvention == b.callingConvention
- elif a.kind == distinct:
- result = typeEqualsOrDistinct(a.baseType, b)
- elif b.kind == distinct:
- result = typeEqualsOrDistinct(a, b.baseType)
- Subtype relation
- ----------------
- If object ``a`` inherits from ``b``, ``a`` is a subtype of ``b``. This subtype
- relation is extended to the types ``var``, ``ref``, ``ptr``:
- .. code-block:: nim
- proc isSubtype(a, b: PType): bool =
- if a.kind == b.kind:
- case a.kind
- of object:
- var aa = a.baseType
- while aa != nil and aa != b: aa = aa.baseType
- result = aa == b
- of var, ref, ptr:
- result = isSubtype(a.baseType, b.baseType)
- .. XXX nil is a special value!
- Covariance
- ----------
- Covariance in Nim can be introduced only though pointer-like types such
- as ``ptr`` and ``ref``. Sequence, Array and OpenArray types, instantiated
- with pointer-like types will be considered covariant if and only if they
- are also immutable. The introduction of a ``var`` modifier or additional
- ``ptr`` or ``ref`` indirections would result in invariant treatment of
- these types.
- ``proc`` types are currently always invariant, but future versions of Nim
- may relax this rule.
- User-defined generic types may also be covariant with respect to some of
- their parameters. By default, all generic params are considered invariant,
- but you may choose the apply the prefix modifier ``in`` to a parameter to
- make it contravariant or ``out`` to make it covariant:
- .. code-block:: nim
- type
- AnnotatedPtr[out T] =
- metadata: MyTypeInfo
- p: ref T
- RingBuffer[out T] =
- startPos: int
- data: seq[T]
- Action {.importcpp: "std::function<void ('0)>".} [in T] = object
- When the designated generic parameter is used to instantiate a pointer-like
- type as in the case of `AnnotatedPtr` above, the resulting generic type will
- also have pointer-like covariance:
- .. code-block:: nim
- type
- GuiWidget = object of RootObj
- Button = object of GuiWidget
- ComboBox = object of GuiWidget
- var
- widgetPtr: AnnotatedPtr[GuiWidget]
- buttonPtr: AnnotatedPtr[Button]
- ...
- proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ...
- # you can call procs expecting base types by supplying a derived type
- drawWidget(buttonPtr)
- # and you can convert more-specific pointer types to more general ones
- widgetPtr = buttonPtr
- Just like with regular pointers, covariance will be enabled only for immutable
- values:
- .. code-block:: nim
- proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) =
- x.p = new(ComboBox)
- makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified
- # to point to a ComboBox
- On the other hand, in the `RingBuffer` example above, the designated generic
- param is used to instantiate the non-pointer ``seq`` type, which means that
- the resulting generic type will have covariance that mimics an array or
- sequence (i.e. it will be covariant only when instantiated with ``ptr`` and
- ``ref`` types):
- .. code-block:: nim
- type
- Base = object of RootObj
- Derived = object of Base
- proc consumeBaseValues(b: RingBuffer[Base]) = ...
- var derivedValues: RingBuffer[Derived]
- consumeBaseValues(derivedValues) # Error, Base and Derived values may differ
- # in size
- proc consumeBasePointers(b: RingBuffer[ptr Base]) = ...
- var derivedPointers: RingBuffer[ptr Derived]
- consumeBaseValues(derivedPointers) # This is legal
- Please note that Nim will treat the user-defined pointer-like types as
- proper alternatives to the built-in pointer types. That is, types such
- as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be
- considered covariant and you can create new pointer-like types by instantiating
- other user-defined pointer-like types.
- The contravariant parameters introduced with the ``in`` modifier are currently
- useful only when interfacing with imported types having such semantics.
- Convertible relation
- --------------------
- A type ``a`` is **implicitly** convertible to type ``b`` iff the following
- algorithm returns true:
- .. code-block:: nim
- # XXX range types?
- proc isImplicitlyConvertible(a, b: PType): bool =
- if isSubtype(a, b) or isCovariant(a, b):
- return true
- case a.kind
- of int: result = b in {int8, int16, int32, int64, uint, uint8, uint16,
- uint32, uint64, float, float32, float64}
- of int8: result = b in {int16, int32, int64, int}
- of int16: result = b in {int32, int64, int}
- of int32: result = b in {int64, int}
- of uint: result = b in {uint32, uint64}
- of uint8: result = b in {uint16, uint32, uint64}
- of uint16: result = b in {uint32, uint64}
- of uint32: result = b in {uint64}
- of float: result = b in {float32, float64}
- of float32: result = b in {float64, float}
- of float64: result = b in {float32, float}
- of seq:
- result = b == openArray and typeEquals(a.baseType, b.baseType)
- of array:
- result = b == openArray and typeEquals(a.baseType, b.baseType)
- if a.baseType == char and a.indexType.rangeA == 0:
- result = b = cstring
- of cstring, ptr:
- result = b == pointer
- of string:
- result = b == cstring
- 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}
- proc isExplicitlyConvertible(a, b: PType): bool =
- result = false
- if isImplicitlyConvertible(a, b): return 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
- `converter`:idx:.
- .. code-block:: nim
- converter toInt(x: char): int = result = ord(x)
- var
- x: int
- chr: char = 'a'
- # implicit conversion magic happens here
- x = chr
- echo x # => 97
- # you can use the explicit form too
- x = chr.toInt
- echo x # => 97
- The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and
- ``typeEqualsOrDistinct(T, type(a))`` holds.
- Assignment compatibility
- ------------------------
- An expression ``b`` can be assigned to an expression ``a`` iff ``a`` is an
- `l-value` and ``isImplicitlyConvertible(b.typ, a.typ)`` holds.
- Overloading resolution
- ======================
- 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 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``.
- 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.
- .. code-block:: nim
- 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
- Automatic dereferencing
- -----------------------
- If the `experimental mode <#pragmas-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.
- Automatic self insertions
- -------------------------
- Starting with version 0.14 of the language, Nim supports ``field`` as a
- shortcut for ``self.field`` comparable to the `this`:idx: keyword in Java
- or C++. This feature has to be explicitly enabled via a ``{.this: self.}``
- statement pragma. This pragma is active for the rest of the module:
- .. code-block:: nim
- type
- Parent = object of RootObj
- parentField: int
- Child = object of Parent
- childField: int
- {.this: self.}
- proc sumFields(self: Child): int =
- result = parentField + childField
- # is rewritten to:
- # result = self.parentField + self.childField
- Instead of ``self`` any other identifier can be used too, but
- ``{.this: self.}`` will become the default directive for the whole language
- eventually.
- In addition to fields, routine applications are also rewritten, but only
- if no other interpretation of the call is possible:
- .. code-block:: nim
- proc test(self: Child) =
- echo childField, " ", sumFields()
- # is rewritten to:
- echo self.childField, " ", sumFields(self)
- # but NOT rewritten to:
- echo self, self.childField, " ", sumFields(self)
- Lazy type resolution for untyped
- --------------------------------
- **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 ``untyped``
- accomplishes:
- .. code-block:: nim
- template rem(x: untyped) = discard
- rem unresolvedExpression(undeclaredIdentifier)
- 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:
- .. code-block:: nim
- 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`_.
|