#
#
#           The Nim Compiler
#        (c) Copyright 2015 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# This module does the instantiation of generic types.

import ast, astalgo, msgs, types, magicsys, semdata, renderer, options,
  lineinfos, modulegraphs

from concepts import makeTypeDesc

when defined(nimPreviewSlimSystem):
  import std/assertions

const tfInstClearedFlags = {tfHasMeta, tfUnresolved}

proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) =
  if t.kind in {tyVar, tyLent} and t[0].kind in {tyVar, tyLent}:
    localError(conf, info, "type 'var var' is not allowed")

proc checkConstructedType*(conf: ConfigRef; info: TLineInfo, typ: PType) =
  var t = typ.skipTypes({tyDistinct})
  if t.kind in tyTypeClasses: discard
  elif t.kind in {tyVar, tyLent} and t[0].kind in {tyVar, tyLent}:
    localError(conf, info, "type 'var var' is not allowed")
  elif computeSize(conf, t) == szIllegalRecursion or isTupleRecursive(t):
    localError(conf, info, "illegal recursion in type '" & typeToString(t) & "'")
  when false:
    if t.kind == tyObject and t[0] != nil:
      if t[0].kind != tyObject or tfFinal in t[0].flags:
        localError(info, errInheritanceOnlyWithNonFinalObjects)

proc searchInstTypes*(g: ModuleGraph; key: PType): PType =
  let genericTyp = key[0]
  if not (genericTyp.kind == tyGenericBody and
      genericTyp.sym != nil): return

  for inst in typeInstCacheItems(g, genericTyp.sym):
    if inst.id == key.id: return inst
    if inst.len < key.len:
      # XXX: This happens for prematurely cached
      # types such as Channel[empty]. Why?
      # See the notes for PActor in handleGenericInvocation
      # if this is return the same type gets cached more than it needs to
      continue
    if not sameFlags(inst, key):
      continue

    block matchType:
      for j in 1..high(key.sons):
        # XXX sameType is not really correct for nested generics?
        if not compareTypes(inst[j], key[j],
                            flags = {ExactGenericParams, PickyCAliases}):
          break matchType

      return inst

proc cacheTypeInst(c: PContext; inst: PType) =
  let gt = inst[0]
  let t = if gt.kind == tyGenericBody: gt.lastSon else: gt
  if t.kind in {tyStatic, tyError, tyGenericParam} + tyTypeClasses:
    return
  addToGenericCache(c, gt.sym, inst)

type
  LayeredIdTable* {.acyclic.} = ref object
    topLayer*: TIdTable
    nextLayer*: LayeredIdTable

  TReplTypeVars* = object
    c*: PContext
    typeMap*: LayeredIdTable  # map PType to PType
    symMap*: TIdTable         # map PSym to PSym
    localCache*: TIdTable     # local cache for remembering already replaced
                              # types during instantiation of meta types
                              # (they are not stored in the global cache)
    info*: TLineInfo
    allowMetaTypes*: bool     # allow types such as seq[Number]
                              # i.e. the result contains unresolved generics
    skipTypedesc*: bool       # whether we should skip typeDescs
    isReturnType*: bool
    owner*: PSym              # where this instantiation comes from
    recursionLimit: int

proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType
proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym
proc replaceTypeVarsN*(cl: var TReplTypeVars, n: PNode; start=0): PNode

proc initLayeredTypeMap*(pt: TIdTable): LayeredIdTable =
  result = LayeredIdTable()
  copyIdTable(result.topLayer, pt)

proc newTypeMapLayer*(cl: var TReplTypeVars): LayeredIdTable =
  result = LayeredIdTable()
  result.nextLayer = cl.typeMap
  initIdTable(result.topLayer)

proc lookup(typeMap: LayeredIdTable, key: PType): PType =
  var tm = typeMap
  while tm != nil:
    result = PType(idTableGet(tm.topLayer, key))
    if result != nil: return
    tm = tm.nextLayer

template put(typeMap: LayeredIdTable, key, value: PType) =
  idTablePut(typeMap.topLayer, key, value)

template checkMetaInvariants(cl: TReplTypeVars, t: PType) = # noop code
  when false:
    if t != nil and tfHasMeta in t.flags and
       cl.allowMetaTypes == false:
      echo "UNEXPECTED META ", t.id, " ", instantiationInfo(-1)
      debug t
      writeStackTrace()

proc replaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType =
  result = replaceTypeVarsTAux(cl, t)
  checkMetaInvariants(cl, result)

proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode =
  let t = replaceTypeVarsT(cl, n.typ)
  if t != nil and t.kind == tyStatic and t.n != nil:
    return if tfUnresolved in t.flags: prepareNode(cl, t.n)
           else: t.n
  result = copyNode(n)
  result.typ = t
  if result.kind == nkSym: result.sym = replaceTypeVarsS(cl, n.sym)
  let isCall = result.kind in nkCallKinds
  for i in 0..<n.safeLen:
    # XXX HACK: ``f(a, b)``, avoid to instantiate `f`
    if isCall and i == 0: result.add(n[i])
    else: result.add(prepareNode(cl, n[i]))

proc isTypeParam(n: PNode): bool =
  # XXX: generic params should use skGenericParam instead of skType
  return n.kind == nkSym and
         (n.sym.kind == skGenericParam or
           (n.sym.kind == skType and sfFromGeneric in n.sym.flags))

when false: # old workaround
  proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
    # This is needed for tuninstantiatedgenericcalls
    # It's possible that a generic param will be used in a proc call to a
    # typedesc accepting proc. After generic param substitution, such procs
    # should be optionally instantiated with the correct type. In order to
    # perform this instantiation, we need to re-run the generateInstance path
    # in the compiler, but it's quite complicated to do so at the moment so we
    # resort to a mild hack; the head symbol of the call is temporary reset and
    # overload resolution is executed again (which may trigger generateInstance).
    if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
      var needsFixing = false
      for i in 1..<n.safeLen:
        if isTypeParam(n[i]): needsFixing = true
      if needsFixing:
        n[0] = newSymNode(n[0].sym.owner)
        return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})

    for i in 0..<n.safeLen:
      n[i] = reResolveCallsWithTypedescParams(cl, n[i])

    return n

proc replaceObjBranches(cl: TReplTypeVars, n: PNode): PNode =
  result = n
  case n.kind
  of nkNone..nkNilLit:
    discard
  of nkRecWhen:
    var branch: PNode = nil              # the branch to take
    for i in 0..<n.len:
      var it = n[i]
      if it == nil: illFormedAst(n, cl.c.config)
      case it.kind
      of nkElifBranch:
        checkSonsLen(it, 2, cl.c.config)
        var cond = it[0]
        var e = cl.c.semConstExpr(cl.c, cond)
        if e.kind != nkIntLit:
          internalError(cl.c.config, e.info, "ReplaceTypeVarsN: when condition not a bool")
        if e.intVal != 0 and branch == nil: branch = it[1]
      of nkElse:
        checkSonsLen(it, 1, cl.c.config)
        if branch == nil: branch = it[0]
      else: illFormedAst(n, cl.c.config)
    if branch != nil:
      result = replaceObjBranches(cl, branch)
    else:
      result = newNodeI(nkRecList, n.info)
  else:
    for i in 0..<n.len:
      n[i] = replaceObjBranches(cl, n[i])

proc hasValuelessStatics(n: PNode): bool =
  # We should only attempt to call an expression that has no tyStatics
  # As those are unresolved generic parameters, which means in the following
  # The compiler attempts to do `T == 300` which errors since the typeclass `MyThing` lacks a parameter
  #[
    type MyThing[T: static int] = object
      when T == 300:
        a
    proc doThing(_: MyThing)
  ]#
  if n.safeLen == 0 and n.kind != nkEmpty: # Some empty nodes can get in here
    n.typ == nil or n.typ.kind == tyStatic
  else:
    for x in n:
      if hasValuelessStatics(x):
        return true
    false

proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode =
  if n == nil: return
  result = copyNode(n)
  if n.typ != nil:
    result.typ = replaceTypeVarsT(cl, n.typ)
    checkMetaInvariants(cl, result.typ)
  case n.kind
  of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
    discard
  of nkOpenSymChoice, nkClosedSymChoice: result = n
  of nkSym:
    result.sym = replaceTypeVarsS(cl, n.sym)
    if result.sym.typ.kind == tyVoid:
      # don't add the 'void' field
      result = newNodeI(nkRecList, n.info)
  of nkRecWhen:
    var branch: PNode = nil              # the branch to take
    for i in 0..<n.len:
      var it = n[i]
      if it == nil: illFormedAst(n, cl.c.config)
      case it.kind
      of nkElifBranch:
        checkSonsLen(it, 2, cl.c.config)
        var cond = prepareNode(cl, it[0])
        if not cond.hasValuelessStatics:
          var e = cl.c.semConstExpr(cl.c, cond)
          if e.kind != nkIntLit:
            internalError(cl.c.config, e.info, "ReplaceTypeVarsN: when condition not a bool")
          if e.intVal != 0 and branch == nil: branch = it[1]
      of nkElse:
        checkSonsLen(it, 1, cl.c.config)
        if branch == nil: branch = it[0]
      else: illFormedAst(n, cl.c.config)
    if branch != nil:
      result = replaceTypeVarsN(cl, branch)
    else:
      result = newNodeI(nkRecList, n.info)
  of nkStaticExpr:
    var n = prepareNode(cl, n)
    when false:
      n = reResolveCallsWithTypedescParams(cl, n)
    result = if cl.allowMetaTypes: n
             else: cl.c.semExpr(cl.c, n)
    if not cl.allowMetaTypes:
      assert result.kind notin nkCallKinds
  else:
    if n.len > 0:
      newSons(result, n.len)
      if start > 0:
        result[0] = n[0]
      for i in start..<n.len:
        result[i] = replaceTypeVarsN(cl, n[i])

proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym =
  if s == nil: return nil
  # symbol is not our business:
  if cl.owner != nil and s.owner != cl.owner:
    return s

  # XXX: Bound symbols in default parameter expressions may reach here.
  # We cannot process them, because `sym.n` may point to a proc body with
  # cyclic references that will lead to an infinite recursion.
  # Perhaps we should not use a black-list here, but a whitelist instead
  # (e.g. skGenericParam and skType).
  # Note: `s.magic` may be `mType` in an example such as:
  # proc foo[T](a: T, b = myDefault(type(a)))
  if s.kind in routineKinds+{skLet, skConst, skVar} or s.magic != mNone:
    return s

  #result = PSym(idTableGet(cl.symMap, s))
  #if result == nil:
  #[

  We cannot naively check for symbol recursions, because otherwise
  object types A, B whould share their fields!

      import tables

      type
        Table[S, T] = object
          x: S
          y: T

        G[T] = object
          inodes: Table[int, T] # A
          rnodes: Table[T, int] # B

      var g: G[string]

  ]#
  result = copySym(s, cl.c.idgen)
  incl(result.flags, sfFromGeneric)
  #idTablePut(cl.symMap, s, result)
  result.owner = s.owner
  result.typ = replaceTypeVarsT(cl, s.typ)
  if result.kind != skType:
    result.ast = replaceTypeVarsN(cl, s.ast)

proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
  result = cl.typeMap.lookup(t)
  if result == nil:
    if cl.allowMetaTypes or tfRetType in t.flags: return
    localError(cl.c.config, t.sym.info, "cannot instantiate: '" & typeToString(t) & "'")
    result = errorType(cl.c)
    # In order to prevent endless recursions, we must remember
    # this bad lookup and replace it with errorType everywhere.
    # These code paths are only active in "nim check"
    cl.typeMap.put(t, result)
  elif result.kind == tyGenericParam and not cl.allowMetaTypes:
    internalError(cl.c.config, cl.info, "substitution with generic parameter")

proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
  # XXX: relying on allowMetaTypes is a kludge
  if cl.allowMetaTypes:
    result = t.exactReplica
  else:
    result = copyType(t, nextTypeId(cl.c.idgen), t.owner)
    copyTypeProps(cl.c.graph, cl.c.idgen.module, result, t)
    #cl.typeMap.topLayer.idTablePut(result, t)

  if cl.allowMetaTypes: return
  result.flags.incl tfFromGeneric
  if not (t.kind in tyMetaTypes or
         (t.kind == tyStatic and t.n == nil)):
    result.flags.excl tfInstClearedFlags
  else:
    result.flags.excl tfHasAsgn
  when false:
    if newDestructors:
      result.assignment = nil
      result.destructor = nil
      result.sink = nil

proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
  # tyGenericInvocation[A, tyGenericInvocation[A, B]]
  # is difficult to handle:
  var body = t[0]
  if body.kind != tyGenericBody:
    internalError(cl.c.config, cl.info, "no generic body")
  var header = t
  # search for some instantiation here:
  if cl.allowMetaTypes:
    result = PType(idTableGet(cl.localCache, t))
  else:
    result = searchInstTypes(cl.c.graph, t)

  if result != nil and sameFlags(result, t):
    when defined(reportCacheHits):
      echo "Generic instantiation cached ", typeToString(result), " for ", typeToString(t)
    return
  for i in 1..<t.len:
    var x = t[i]
    if x.kind in {tyGenericParam}:
      x = lookupTypeVar(cl, x)
      if x != nil:
        if header == t: header = instCopyType(cl, t)
        header[i] = x
        propagateToOwner(header, x)
    else:
      propagateToOwner(header, x)

  if header != t:
    # search again after first pass:
    result = searchInstTypes(cl.c.graph, header)
    if result != nil and sameFlags(result, t):
      when defined(reportCacheHits):
        echo "Generic instantiation cached ", typeToString(result), " for ",
          typeToString(t), " header ", typeToString(header)
      return
  else:
    header = instCopyType(cl, t)

  result = newType(tyGenericInst, nextTypeId(cl.c.idgen), t[0].owner)
  result.flags = header.flags
  # be careful not to propagate unnecessary flags here (don't use rawAddSon)
  result.sons = @[header[0]]
  # ugh need another pass for deeply recursive generic types (e.g. PActor)
  # we need to add the candidate here, before it's fully instantiated for
  # recursive instantions:
  if not cl.allowMetaTypes:
    cacheTypeInst(cl.c, result)
  else:
    idTablePut(cl.localCache, t, result)

  let oldSkipTypedesc = cl.skipTypedesc
  cl.skipTypedesc = true

  cl.typeMap = newTypeMapLayer(cl)

  for i in 1..<t.len:
    var x = replaceTypeVarsT(cl):
      if header[i].kind == tyGenericInst:
        t[i]
      else:
        header[i]
    assert x.kind != tyGenericInvocation
    header[i] = x
    propagateToOwner(header, x)
    cl.typeMap.put(body[i-1], x)

  for i in 1..<t.len:
    # if one of the params is not concrete, we cannot do anything
    # but we already raised an error!
    rawAddSon(result, header[i], propagateHasAsgn = false)

  if body.kind == tyError:
    return

  let bbody = lastSon body
  var newbody = replaceTypeVarsT(cl, bbody)
  cl.skipTypedesc = oldSkipTypedesc
  newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
  result.flags = result.flags + newbody.flags - tfInstClearedFlags

  cl.typeMap = cl.typeMap.nextLayer

  # This is actually wrong: tgeneric_closure fails with this line:
  #newbody.callConv = body.callConv
  # This type may be a generic alias and we want to resolve it here.
  # One step is enough, because the recursive nature of
  # handleGenericInvocation will handle the alias-to-alias-to-alias case
  if newbody.isGenericAlias: newbody = newbody.skipGenericAlias
  rawAddSon(result, newbody)
  checkPartialConstructedType(cl.c.config, cl.info, newbody)
  if not cl.allowMetaTypes:
    let dc = cl.c.graph.getAttachedOp(newbody, attachedDeepCopy)
    if dc != nil and sfFromGeneric notin dc.flags:
      # 'deepCopy' needs to be instantiated for
      # generics *when the type is constructed*:
      cl.c.graph.setAttachedOp(cl.c.module.position, newbody, attachedDeepCopy,
          cl.c.instTypeBoundOp(cl.c, dc, result, cl.info, attachedDeepCopy, 1))
    if newbody.typeInst == nil:
      # doAssert newbody.typeInst == nil
      newbody.typeInst = result
      if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
        # can come here for tyGenericInst too, see tests/metatype/ttypeor.nim
        # need to look into this issue later
        assert newbody.kind in {tyRef, tyPtr}
        if newbody.lastSon.typeInst != nil:
          #internalError(cl.c.config, cl.info, "ref already has a 'typeInst' field")
          discard
        else:
          newbody.lastSon.typeInst = result
    # DESTROY: adding object|opt for opt[topttree.Tree]
    # sigmatch: Formal opt[=destroy.T] real opt[topttree.Tree]
    # adding myseq for myseq[system.int]
    # sigmatch: Formal myseq[=destroy.T] real myseq[system.int]
    #echo "DESTROY: adding ", typeToString(newbody), " for ", typeToString(result, preferDesc)
    let mm = skipTypes(bbody, abstractPtrs)
    if tfFromGeneric notin mm.flags:
      # bug #5479, prevent endless recursions here:
      incl mm.flags, tfFromGeneric
      for col, meth in methodsForGeneric(cl.c.graph, mm):
        # we instantiate the known methods belonging to that type, this causes
        # them to be registered and that's enough, so we 'discard' the result.
        discard cl.c.instTypeBoundOp(cl.c, meth, result, cl.info,
          attachedAsgn, col)
      excl mm.flags, tfFromGeneric

proc eraseVoidParams*(t: PType) =
  # transform '(): void' into '()' because old parts of the compiler really
  # don't deal with '(): void':
  if t[0] != nil and t[0].kind == tyVoid:
    t[0] = nil

  for i in 1..<t.len:
    # don't touch any memory unless necessary
    if t[i].kind == tyVoid:
      var pos = i
      for j in i+1..<t.len:
        if t[j].kind != tyVoid:
          t[pos] = t[j]
          t.n[pos] = t.n[j]
          inc pos
      setLen t.sons, pos
      setLen t.n.sons, pos
      break

proc skipIntLiteralParams*(t: PType; idgen: IdGenerator) =
  for i in 0..<t.len:
    let p = t[i]
    if p == nil: continue
    let skipped = p.skipIntLit(idgen)
    if skipped != p:
      t[i] = skipped
      if i > 0: t.n[i].sym.typ = skipped

  # when the typeof operator is used on a static input
  # param, the results gets infected with static as well:
  if t[0] != nil and t[0].kind == tyStatic:
    t[0] = t[0].base

proc propagateFieldFlags(t: PType, n: PNode) =
  # This is meant for objects and tuples
  # The type must be fully instantiated!
  if n.isNil:
    return
  #internalAssert n.kind != nkRecWhen
  case n.kind
  of nkSym:
    propagateToOwner(t, n.sym.typ)
  of nkRecList, nkRecCase, nkOfBranch, nkElse:
    for son in n:
      propagateFieldFlags(t, son)
  else: discard

proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
  template bailout =
    if (t.sym == nil) or (t.sym != nil and sfGeneratedType in t.sym.flags):
      # In the first case 't.sym' can be 'nil' if the type is a ref/ptr, see
      # issue https://github.com/nim-lang/Nim/issues/20416 for more details.
      # Fortunately for us this works for now because partial ref/ptr types are
      # not allowed in object construction, eg.
      #   type
      #     Container[T] = ...
      #     O = object
      #      val: ref Container
      #
      # In the second case only consider the recursion limit if the symbol is a
      # type with generic parameters that have not been explicitly supplied,
      # typechecking should terminate when generic parameters are explicitly
      # supplied.
      if cl.recursionLimit > 100:
        # bail out, see bug #2509. But note this caching is in general wrong,
        # look at this example where TwoVectors should not share the generic
        # instantiations (bug #3112):
        # type
        #   Vector[N: static[int]] = array[N, float64]
        #   TwoVectors[Na, Nb: static[int]] = (Vector[Na], Vector[Nb])
        result = PType(idTableGet(cl.localCache, t))
        if result != nil: return result
      inc cl.recursionLimit

  result = t
  if t == nil: return

  if t.kind in {tyStatic, tyGenericParam, tyConcept} + tyTypeClasses:
    let lookup = cl.typeMap.lookup(t)
    if lookup != nil: return lookup

  case t.kind
  of tyGenericInvocation:
    result = handleGenericInvocation(cl, t)
    if result.lastSon.kind == tyUserTypeClass:
      result.kind = tyUserTypeClassInst

  of tyGenericBody:
    localError(
      cl.c.config,
      cl.info,
      "cannot instantiate: '" &
      typeToString(t, preferDesc) &
      "'; Maybe generic arguments are missing?")
    result = errorType(cl.c)
    #result = replaceTypeVarsT(cl, lastSon(t))

  of tyFromExpr:
    if cl.allowMetaTypes: return
    # This assert is triggered when a tyFromExpr was created in a cyclic
    # way. You should break the cycle at the point of creation by introducing
    # a call such as: `n.typ = makeTypeFromExpr(c, n.copyTree)`
    # Otherwise, the cycle will be fatal for the prepareNode call below
    assert t.n.typ != t
    var n = prepareNode(cl, t.n)
    if n.kind != nkEmpty:
      n = cl.c.semConstExpr(cl.c, n)
    if n.typ.kind == tyTypeDesc:
      # XXX: sometimes, chained typedescs enter here.
      # It may be worth investigating why this is happening,
      # because it may cause other bugs elsewhere.
      result = n.typ.skipTypes({tyTypeDesc})
      # result = n.typ.base
    else:
      if n.typ.kind != tyStatic and n.kind != nkType:
        # XXX: In the future, semConstExpr should
        # return tyStatic values to let anyone make
        # use of this knowledge. The patching here
        # won't be necessary then.
        result = newTypeS(tyStatic, cl.c)
        result.sons = @[n.typ]
        result.n = n
      else:
        result = n.typ

  of tyInt, tyFloat:
    result = skipIntLit(t, cl.c.idgen)

  of tyTypeDesc:
    let lookup = cl.typeMap.lookup(t)
    if lookup != nil:
      result = lookup
      if result.kind != tyTypeDesc:
        result = makeTypeDesc(cl.c, result)
      elif tfUnresolved in t.flags or cl.skipTypedesc:
        result = result.base
    elif t[0].kind != tyNone:
      result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t[0]))

  of tyUserTypeClass, tyStatic:
    result = t

  of tyGenericInst, tyUserTypeClassInst:
    bailout()
    result = instCopyType(cl, t)
    idTablePut(cl.localCache, t, result)
    for i in 1..<result.len:
      result[i] = replaceTypeVarsT(cl, result[i])
    propagateToOwner(result, result.lastSon)

  else:
    if containsGenericType(t):
      #if not cl.allowMetaTypes:
      bailout()
      result = instCopyType(cl, t)
      result.size = -1 # needs to be recomputed
      #if not cl.allowMetaTypes:
      idTablePut(cl.localCache, t, result)

      for i in 0..<result.len:
        if result[i] != nil:
          if result[i].kind == tyGenericBody:
            localError(cl.c.config, if t.sym != nil: t.sym.info else: cl.info,
              "cannot instantiate '" &
              typeToString(result[i], preferDesc) &
              "' inside of type definition: '" &
              t.owner.name.s & "'; Maybe generic arguments are missing?")
          var r = replaceTypeVarsT(cl, result[i])
          if result.kind == tyObject:
            # carefully coded to not skip the precious tyGenericInst:
            let r2 = r.skipTypes({tyAlias, tySink, tyOwned})
            if r2.kind in {tyPtr, tyRef}:
              r = skipTypes(r2, {tyPtr, tyRef})
          result[i] = r
          if result.kind != tyArray or i != 0:
            propagateToOwner(result, r)
      # bug #4677: Do not instantiate effect lists
      result.n = replaceTypeVarsN(cl, result.n, ord(result.kind==tyProc))
      case result.kind
      of tyArray:
        let idx = result[0]
        internalAssert cl.c.config, idx.kind != tyStatic

      of tyObject, tyTuple:
        propagateFieldFlags(result, result.n)
        if result.kind == tyObject and cl.c.computeRequiresInit(cl.c, result):
          result.flags.incl tfRequiresInit

      of tyProc:
        eraseVoidParams(result)
        skipIntLiteralParams(result, cl.c.idgen)

      of tyRange:
        result[0] = result[0].skipTypes({tyStatic, tyDistinct})

      else: discard
    else:
      # If this type doesn't refer to a generic type we may still want to run it
      # trough replaceObjBranches in order to resolve any pending nkRecWhen nodes
      result = t

      # Slow path, we have some work to do
      if t.kind == tyRef and t.len > 0 and t[0].kind == tyObject and t[0].n != nil:
        discard replaceObjBranches(cl, t[0].n)

      elif result.n != nil and t.kind == tyObject:
        # Invalidate the type size as we may alter its structure
        result.size = -1
        result.n = replaceObjBranches(cl, result.n)

proc initTypeVars*(p: PContext, typeMap: LayeredIdTable, info: TLineInfo;
                   owner: PSym): TReplTypeVars =
  initIdTable(result.symMap)
  initIdTable(result.localCache)
  result.typeMap = typeMap
  result.info = info
  result.c = p
  result.owner = owner

proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode;
                         owner: PSym, allowMetaTypes = false): PNode =
  var typeMap = initLayeredTypeMap(pt)
  var cl = initTypeVars(p, typeMap, n.info, owner)
  cl.allowMetaTypes = allowMetaTypes
  pushInfoContext(p.config, n.info)
  result = replaceTypeVarsN(cl, n)
  popInfoContext(p.config)

when false:
  # deadcode
  proc replaceTypesForLambda*(p: PContext, pt: TIdTable, n: PNode;
                              original, new: PSym): PNode =
    var typeMap = initLayeredTypeMap(pt)
    var cl = initTypeVars(p, typeMap, n.info, original)
    idTablePut(cl.symMap, original, new)
    pushInfoContext(p.config, n.info)
    result = replaceTypeVarsN(cl, n)
    popInfoContext(p.config)

proc recomputeFieldPositions*(t: PType; obj: PNode; currPosition: var int) =
  if t != nil and t.len > 0 and t[0] != nil:
    let b = skipTypes(t[0], skipPtrs)
    recomputeFieldPositions(b, b.n, currPosition)
  case obj.kind
  of nkRecList:
    for i in 0..<obj.len: recomputeFieldPositions(nil, obj[i], currPosition)
  of nkRecCase:
    recomputeFieldPositions(nil, obj[0], currPosition)
    for i in 1..<obj.len:
      recomputeFieldPositions(nil, lastSon(obj[i]), currPosition)
  of nkSym:
    obj.sym.position = currPosition
    inc currPosition
  else: discard "cannot happen"

proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo,
                           t: PType): PType =
  # Given `t` like Foo[T]
  # pt: Table with type mappings: T -> int
  # Desired result: Foo[int]
  # proc (x: T = 0); T -> int ---->  proc (x: int = 0)
  var typeMap = initLayeredTypeMap(pt)
  var cl = initTypeVars(p, typeMap, info, nil)
  pushInfoContext(p.config, info)
  result = replaceTypeVarsT(cl, t)
  popInfoContext(p.config)
  let objType = result.skipTypes(abstractInst)
  if objType.kind == tyObject:
    var position = 0
    recomputeFieldPositions(objType, objType.n, position)

proc prepareMetatypeForSigmatch*(p: PContext, pt: TIdTable, info: TLineInfo,
                                 t: PType): PType =
  var typeMap = initLayeredTypeMap(pt)
  var cl = initTypeVars(p, typeMap, info, nil)
  cl.allowMetaTypes = true
  pushInfoContext(p.config, info)
  result = replaceTypeVarsT(cl, t)
  popInfoContext(p.config)

template generateTypeInstance*(p: PContext, pt: TIdTable, arg: PNode,
                               t: PType): untyped =
  generateTypeInstance(p, pt, arg.info, t)
