The following section defines several relations on types that are needed to describe how the type checking is done in Nim. 17.1. Type equality Nim uses structural type equivalence for most types. Only for objects, enumerations and distinct types and for generic types name equivalence is used. 17.2. Subtype relation If an object type B inherits from A, B is a subtype of A. For example: type
A = object of RootObj
B = object of A
# B is a subtype of A. This means an object of type B can be passed to routines that expect the type A. This subtype relation is extended to the types var, ref, ptr. If B is a subtype of A and B and A are object types then: • var A is a subtype of var B • ref A is a subtype of ref B • ptr A is a subtype of ptr B.
113
If the subtype relation exists among ref or ptr types they are assignment compatible; an object of a subtype can be assigned to a location that is of the supertype: type
Shape = ref object of RootObj
Circle = ref object of Shape
var a: Shape = Circle() # Circle is a subtype of Shape and can be assigned to an l-value of type Shape.
The subtype relation does not extend from A and B to var ref A
and var ref B. Doing so would open a hole in the type system:
type
A = ref object of RootObj
B = ref object of A
field: string
proc init(a: var A) =
a = A()
var b = B()
b.init()
echo b.field # crash here? b now points to an A, not a B
17.3. Convertible relation A type a is implicitly convertible to type b if and only if the following algorithm returns true: proc isImplicitlyConvertible(a, b: PType): bool =
if isSubtype(a, b):
return true
if isIntLiteral(a):
return b in {int8, int16, int32, int64, int, uint, uint8, uint16,
uint32, uint64, float32, float64}
case a.kind
of int: result = b in {int32, int64}
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}
114
of uint32: result = b in {uint64}
of float32: result = b in {float64}
of float64: result = b in {float32}
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
of proc:
result = typeEquals(a, b) or compatibleParametersAndEffects(a, b)
We used the predicate typeEquals(a, b) for the "type equality" property and the predicate isSubtype(a, b) for the "subtype relation". compatibleParametersAndEffects(a, b) is currently not specified. Implicit conversions are also performed for Nim’s range type constructor. Let a0, b0 of type T. Let A = range[a0..b0] be the argument’s type, F the formal parameter’s type. Then an implicit conversion from A to F exists if a0 >= low(F) and b0 <= high(F) and both T and F are signed integers or if both are unsigned integers. A type a is explicitly convertible to type b if and only if the following algorithm returns true: proc isIntegralType(t: PType): bool =
result = isOrdinal(t) or t.kind in {float, float32, float64}
proc isExplicitlyConvertible(a, b: PType): bool =
isImplicitlyConvertible(a, b) or
typeEquals(a, b) or
a == distinct and typeEquals(a.baseType, b) or
b == distinct and typeEquals(b.baseType, a) or
(isIntegralType(a) and isIntegralType(b)) or
isSubtype(a, b) or
isSubtype(b, a)
The convertible relation can be extended by a user-defined type converter.
115
converter toInt(x: char): int = result = ord(x) var
x: int
chr: char = 'a'
# implicit conversion magic happens here x = chr echo x # => 97 # one 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 if a is of type T or a distinct type of T or if T is a distinct type of typeof(a). 17.4. Assignment compatibility An expression b can be assigned to an expression a if and only if a is an l- value and isImplicitlyConvertible(b.typ, a.typ) holds.