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 and for generic types name equivalence is used.

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. If A is a subtype of B and A and B 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.

Note: One of the above pointer-indirections is required for assignment from a subtype to its parent type to prevent “object slicing”.

Convertible relation

A type a is implicitly convertible to type b iff the following algorithm returns true:

  1. proc isImplicitlyConvertible(a, b: PType): bool =
  2. if isSubtype(a, b):
  3. return true
  4. if isIntLiteral(a):
  5. return b in {int8, int16, int32, int64, int, uint, uint8, uint16,
  6. uint32, uint64, float32, float64}
  7. case a.kind
  8. of int: result = b in {int32, int64}
  9. of int8: result = b in {int16, int32, int64, int}
  10. of int16: result = b in {int32, int64, int}
  11. of int32: result = b in {int64, int}
  12. of uint: result = b in {uint32, uint64}
  13. of uint8: result = b in {uint16, uint32, uint64}
  14. of uint16: result = b in {uint32, uint64}
  15. of uint32: result = b in {uint64}
  16. of float32: result = b in {float64}
  17. of float64: result = b in {float32}
  18. of seq:
  19. result = b == openArray and typeEquals(a.baseType, b.baseType)
  20. of array:
  21. result = b == openArray and typeEquals(a.baseType, b.baseType)
  22. if a.baseType == char and a.indexType.rangeA == 0:
  23. result = b == cstring
  24. of cstring, ptr:
  25. result = b == pointer
  26. of string:
  27. result = b == cstring
  28. of proc:
  29. 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 iff the following algorithm returns true:

  1. proc isIntegralType(t: PType): bool =
  2. result = isOrdinal(t) or t.kind in {float, float32, float64}
  3. proc isExplicitlyConvertible(a, b: PType): bool =
  4. result = false
  5. if isImplicitlyConvertible(a, b): return true
  6. if typeEquals(a, b): return true
  7. if a == distinct and typeEquals(a.baseType, b): return true
  8. if b == distinct and typeEquals(b.baseType, a): return true
  9. if isIntegralType(a) and isIntegralType(b): return true
  10. if isSubtype(a, b) or isSubtype(b, a): return true

The convertible relation can be relaxed by a user-defined type converter.

  1. converter toInt(x: char): int = result = ord(x)
  2. var
  3. x: int
  4. chr: char = 'a'
  5. # implicit conversion magic happens here
  6. x = chr
  7. echo x # => 97
  8. # one can use the explicit form too
  9. x = chr.toInt
  10. echo x # => 97

The type conversion T(a) is an L-value if a is an L-value and typeEqualsOrDistinct(T, typeof(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.