Special Types
=============

static[T]
---------

**Note**: static[T] is still in development.

As their name suggests, static params must be known at compile-time:

.. code-block:: nim

  proc precompiledRegex(pattern: static[string]): TRegEx =
    var res {.global.} = re(pattern)
    return res

  precompiledRegex("/d+") # Replaces the call with a precompiled
                          # regex, stored in a global variable

  precompiledRegex(paramStr(1)) # Error, command-line options
                                # are not known at compile-time


For the purposes of code generation, all static params are treated as
generic params - the proc will be compiled separately for each unique
supplied value (or combination of values). 

Furthermore, the system module defines a `semistatic[T]` type than can be
used to declare procs accepting both static and run-time values, which can
optimize their body according to the supplied param using the `isStatic(p)`
predicate:

.. code-block:: nim

  # The following proc will be compiled once for each unique static
  # value and also once for the case handling all run-time values:

  proc re(pattern: semistatic[string]): TRegEx =
    when isStatic(pattern):
      result = precompiledRegex(pattern)
    else:
      result = compile(pattern)

Static params can also appear in the signatures of generic types:

.. code-block:: nim

  type
    Matrix[M,N: static[int]; T: Number] = array[0..(M*N - 1), T]
      # Note how `Number` is just a type constraint here, while
      # `static[int]` requires us to supply a compile-time int value

    AffineTransform2D[T] = Matrix[3, 3, T]
    AffineTransform3D[T] = Matrix[4, 4, T]

  var m1: AffineTransform3D[float]  # OK
  var m2: AffineTransform2D[string] # Error, `string` is not a `Number`


typedesc
--------

`typedesc` is a special type allowing one to treat types as compile-time values
(i.e. if types are compile-time values and all values have a type, then 
typedesc must be their type).

When used as a regular proc param, typedesc acts as a type class. The proc
will be instantiated for each unique type parameter and one can refer to the
instantiation type using the param name:

.. code-block:: nim

  proc new(T: typedesc): ref T =
    echo "allocating ", T.name
    new(result)

  var n = TNode.new
  var tree = new(TBinaryTree[int])

When multiple typedesc params are present, they act like a distinct type class
(i.e. they will bind freely to different types). To force a bind-once behavior
one can use a named alias or an explicit `typedesc` generic param:

.. code-block:: nim

  # `type1` and `type2` are aliases for typedesc available from system.nim
  proc acceptOnlyTypePairs(A, B: type1; C, D: type2)
  proc acceptOnlyTypePairs[T: typedesc, U: typedesc](A, B: T; C, D: U)

Once bound, typedesc params can appear in the rest of the proc signature:

.. code-block:: nim

  template declareVariableWithType(T: typedesc, value: T) =
    var x: T = value

  declareVariableWithType int, 42

When used with macros and .compileTime. procs on the other hand, the compiler
does not need to instantiate the code multiple times, because types then can be
manipulated using the unified internal symbol representation. In such context
typedesc acts as any other type. One can create variables, store typedesc
values inside containers and so on. For example, here is how one can create 
a type-safe wrapper for the unsafe `printf` function from C:

.. code-block:: nim
  macro safePrintF(formatString: string{lit}, args: varargs[expr]): expr =
    var i = 0
    for c in formatChars(formatString):
      var expectedType = case c
        of 'c': char
        of 'd', 'i', 'x', 'X': int
        of 'f', 'e', 'E', 'g', 'G': float
        of 's': string
        of 'p': pointer
        else: EOutOfRange
      
      var actualType = args[i].getType
      inc i

      if expectedType == EOutOfRange:
        error c & " is not a valid format character"
      elif expectedType != actualType:
        error "type mismatch for argument ", i, ". expected type: ",
              expectedType.name, ", actual type: ", actualType.name
    
    # keep the original callsite, but use cprintf instead
    result = callsite()
    result[0] = newIdentNode(!"cprintf")


Overload resolution can be further influenced by constraining the set of
types that will match the typedesc param:

.. code-block:: nim

  template maxval(T: typedesc[int]): int = high(int)
  template maxval(T: typedesc[float]): float = Inf

  var i = int.maxval
  var f = float.maxval
  var s = string.maxval # error, maxval is not implemented for string

The constraint can be a concrete type or a type class.


