summary refs log tree commit diff stats
path: root/compiler/sigmatch.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/sigmatch.nim')
-rw-r--r--compiler/sigmatch.nim3215
1 files changed, 3215 insertions, 0 deletions
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
new file mode 100644
index 000000000..6ea2c7bb5
--- /dev/null
+++ b/compiler/sigmatch.nim
@@ -0,0 +1,3215 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2013 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the signature matching for resolving
+## the call to overloaded procs, generic procs and operators.
+
+import
+  ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst,
+  magicsys, idents, lexer, options, parampatterns, trees,
+  linter, lineinfos, lowerings, modulegraphs, concepts
+
+import std/[intsets, strutils, tables]
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
+type
+  MismatchKind* = enum
+    kUnknown, kAlreadyGiven, kUnknownNamedParam, kTypeMismatch, kVarNeeded,
+    kMissingParam, kExtraArg, kPositionalAlreadyGiven,
+    kGenericParamTypeMismatch, kMissingGenericParam, kExtraGenericParam
+
+  MismatchInfo* = object
+    kind*: MismatchKind # reason for mismatch
+    arg*: int           # position of provided arguments that mismatches
+    formal*: PSym       # parameter that mismatches against provided argument
+                        # its position can differ from `arg` because of varargs
+
+  TCandidateState* = enum
+    csEmpty, csMatch, csNoMatch
+
+  CandidateError* = object
+    sym*: PSym
+    firstMismatch*: MismatchInfo
+    diagnostics*: seq[string]
+    enabled*: bool
+
+  CandidateErrors* = seq[CandidateError]
+
+  TCandidate* = object
+    c*: PContext
+    exactMatches*: int       # also misused to prefer iters over procs
+    genericMatches: int      # also misused to prefer constraints
+    subtypeMatches: int
+    intConvMatches: int      # conversions to int are not as expensive
+    convMatches: int
+    state*: TCandidateState
+    callee*: PType           # may not be nil!
+    calleeSym*: PSym         # may be nil
+    calleeScope*: int        # scope depth:
+                             # is this a top-level symbol or a nested proc?
+    call*: PNode             # modified call
+    bindings*: TypeMapping   # maps types to types
+    magic*: TMagic           # magic of operation
+    baseTypeMatch: bool      # needed for conversions from T to openarray[T]
+                             # for example
+    matchedErrorType*: bool  # match is considered successful after matching
+                             # error type to avoid cascading errors
+                             # this is used to prevent instantiations.
+    genericConverter*: bool  # true if a generic converter needs to
+                             # be instantiated
+    coerceDistincts*: bool   # this is an explicit coercion that can strip away
+                             # a distrinct type
+    typedescMatched*: bool
+    isNoCall*: bool          # misused for generic type instantiations C[T]
+    inferredTypes: seq[PType] # inferred types during the current signature
+                              # matching. they will be reset if the matching
+                              # is not successful. may replace the bindings
+                              # table in the future.
+    diagnostics*: seq[string] # \
+                              # when diagnosticsEnabled, the matching process
+                              # will collect extra diagnostics that will be
+                              # displayed to the user.
+                              # triggered when overload resolution fails
+                              # or when the explain pragma is used. may be
+                              # triggered with an idetools command in the
+                              # future.
+                              # to prefer closest father object type
+    inheritancePenalty: int
+    firstMismatch*: MismatchInfo # mismatch info for better error messages
+    diagnosticsEnabled*: bool
+
+  TTypeRelFlag* = enum
+    trDontBind
+    trNoCovariance
+    trBindGenericParam  # bind tyGenericParam even with trDontBind
+    trIsOutParam
+
+  TTypeRelFlags* = set[TTypeRelFlag]
+
+
+const
+  isNilConversion = isConvertible # maybe 'isIntConv' fits better?
+  maxInheritancePenalty = high(int) div 2
+
+proc markUsed*(c: PContext; info: TLineInfo, s: PSym; checkStyle = true)
+proc markOwnerModuleAsUsed*(c: PContext; s: PSym)
+
+proc initCandidateAux(ctx: PContext,
+                      callee: PType): TCandidate {.inline.} =
+  result = TCandidate(c: ctx, exactMatches: 0, subtypeMatches: 0,
+                      convMatches: 0, intConvMatches: 0, genericMatches: 0,
+                      state: csEmpty, firstMismatch: MismatchInfo(),
+                      callee: callee, call: nil, baseTypeMatch: false,
+                      genericConverter: false, inheritancePenalty: -1
+  )
+
+proc initCandidate*(ctx: PContext, callee: PType): TCandidate =
+  result = initCandidateAux(ctx, callee)
+  result.calleeSym = nil
+  result.bindings = initTypeMapping()
+
+proc put(c: var TCandidate, key, val: PType) {.inline.} =
+  ## Given: proc foo[T](x: T); foo(4)
+  ## key: 'T'
+  ## val: 'int' (typeof(4))
+  when false:
+    let old = idTableGet(c.bindings, key)
+    if old != nil:
+      echo "Putting ", typeToString(key), " ", typeToString(val), " and old is ", typeToString(old)
+      if typeToString(old) == "float32":
+        writeStackTrace()
+    if c.c.module.name.s == "temp3":
+      echo "binding ", key, " -> ", val
+  idTablePut(c.bindings, key, val.skipIntLit(c.c.idgen))
+
+proc typeRel*(c: var TCandidate, f, aOrig: PType,
+              flags: TTypeRelFlags = {}): TTypeRelation
+
+proc matchGenericParam(m: var TCandidate, formal: PType, n: PNode) =
+  var arg = n.typ
+  if m.c.inGenericContext > 0:
+    # don't match yet-unresolved generic instantiations
+    while arg != nil and arg.kind == tyGenericParam:
+      arg = idTableGet(m.bindings, arg)
+    if arg == nil or arg.containsUnresolvedType:
+      m.state = csNoMatch
+      return
+  # fix up the type to get ready to match formal:
+  var formalBase = formal
+  while formalBase.kind == tyGenericParam and
+      formalBase.genericParamHasConstraints:
+    formalBase = formalBase.genericConstraint
+  if formalBase.kind == tyStatic and arg.kind != tyStatic:
+    # maybe call `paramTypesMatch` here, for now be conservative
+    if n.kind in nkSymChoices: n.flags.excl nfSem
+    let evaluated = m.c.semTryConstExpr(m.c, n, formalBase.skipTypes({tyStatic}))
+    if evaluated != nil:
+      arg = newTypeS(tyStatic, m.c, son = evaluated.typ)
+      arg.n = evaluated
+  elif formalBase.kind == tyTypeDesc:
+    if arg.kind != tyTypeDesc:
+      arg = makeTypeDesc(m.c, arg)
+  else:
+    arg = arg.skipTypes({tyTypeDesc})
+  let tm = typeRel(m, formal, arg)
+  if tm in {isNone, isConvertible}:
+    m.state = csNoMatch
+    m.firstMismatch.kind = kGenericParamTypeMismatch
+    return
+
+proc matchGenericParams*(m: var TCandidate, binding: PNode, callee: PSym) =
+  ## matches explicit generic instantiation `binding` against generic params of
+  ## proc symbol `callee`
+  ## state is set to `csMatch` if all generic params match, `csEmpty` if
+  ## implicit generic parameters are missing (matches but cannot instantiate),
+  ## `csNoMatch` if a constraint fails or param count doesn't match
+  let c = m.c
+  let typeParams = callee.ast[genericParamsPos]
+  let paramCount = typeParams.len
+  let bindingCount = binding.len-1
+  if bindingCount > paramCount:
+    m.state = csNoMatch
+    m.firstMismatch.kind = kExtraGenericParam
+    m.firstMismatch.arg = paramCount + 1
+    return
+  for i in 1..bindingCount:
+    matchGenericParam(m, typeParams[i-1].typ, binding[i])
+    if m.state == csNoMatch:
+      m.firstMismatch.arg = i
+      m.firstMismatch.formal = typeParams[i-1].sym
+      return
+  # not enough generic params given, check if remaining have defaults:
+  for i in bindingCount ..< paramCount:
+    let param = typeParams[i]
+    assert param.kind == nkSym
+    let paramSym = param.sym
+    if paramSym.ast != nil:
+      matchGenericParam(m, param.typ, paramSym.ast)
+      if m.state == csNoMatch:
+        m.firstMismatch.arg = i + 1
+        m.firstMismatch.formal = paramSym
+        return
+    elif tfImplicitTypeParam in paramSym.typ.flags:
+      # not a mismatch, but can't create sym
+      m.state = csEmpty
+      return
+    else:
+      m.state = csNoMatch
+      m.firstMismatch.kind = kMissingGenericParam
+      m.firstMismatch.arg = i + 1
+      m.firstMismatch.formal = paramSym
+      return
+  m.state = csMatch
+
+proc copyingEraseVoidParams(m: TCandidate, t: var PType) =
+  ## if `t` is a proc type with void parameters, copies it and erases them
+  assert t.kind == tyProc
+  let original = t
+  var copied = false
+  for i in 1 ..< original.len:
+    var f = original[i]
+    var isVoidParam = f.kind == tyVoid
+    if not isVoidParam:
+      let prev = idTableGet(m.bindings, f)
+      if prev != nil: f = prev
+      isVoidParam = f.kind == tyVoid
+    if isVoidParam:
+      if not copied:
+        # keep first i children
+        t = copyType(original, m.c.idgen, t.owner)
+        t.setSonsLen(i)
+        t.n = copyNode(original.n)
+        t.n.sons = original.n.sons
+        t.n.sons.setLen(i)
+        copied = true
+    elif copied:
+      t.add(f)
+      t.n.add(original.n[i])
+
+proc initCandidate*(ctx: PContext, callee: PSym,
+                    binding: PNode, calleeScope = -1,
+                    diagnosticsEnabled = false): TCandidate =
+  result = initCandidateAux(ctx, callee.typ)
+  result.calleeSym = callee
+  if callee.kind in skProcKinds and calleeScope == -1:
+    result.calleeScope = cmpScopes(ctx, callee)
+  else:
+    result.calleeScope = calleeScope
+  result.diagnostics = @[] # if diagnosticsEnabled: @[] else: nil
+  result.diagnosticsEnabled = diagnosticsEnabled
+  result.magic = result.calleeSym.magic
+  result.bindings = initTypeMapping()
+  if binding != nil and callee.kind in routineKinds:
+    matchGenericParams(result, binding, callee)
+    let genericMatch = result.state
+    if genericMatch != csNoMatch:
+      result.state = csEmpty
+      if genericMatch == csMatch: # csEmpty if not fully instantiated
+        # instantiate the type, emulates old compiler behavior
+        # wouldn't be needed if sigmatch could handle complex cases,
+        # examples are in texplicitgenerics
+        # might be buggy, see rest of generateInstance if problems occur
+        let typ = ctx.instantiateOnlyProcType(ctx, result.bindings, callee, binding.info)
+        result.callee = typ
+      else:
+        # createThread[void] requires this if the above branch is removed:
+        copyingEraseVoidParams(result, result.callee)
+
+proc newCandidate*(ctx: PContext, callee: PSym,
+                   binding: PNode, calleeScope = -1): TCandidate =
+  result = initCandidate(ctx, callee, binding, calleeScope)
+
+proc newCandidate*(ctx: PContext, callee: PType): TCandidate =
+  result = initCandidate(ctx, callee)
+
+proc copyCandidate(dest: var TCandidate, src: TCandidate) =
+  dest.c = src.c
+  dest.exactMatches = src.exactMatches
+  dest.subtypeMatches = src.subtypeMatches
+  dest.convMatches = src.convMatches
+  dest.intConvMatches = src.intConvMatches
+  dest.genericMatches = src.genericMatches
+  dest.state = src.state
+  dest.callee = src.callee
+  dest.calleeSym = src.calleeSym
+  dest.call = copyTree(src.call)
+  dest.baseTypeMatch = src.baseTypeMatch
+  dest.bindings = src.bindings
+
+proc checkGeneric(a, b: TCandidate): int =
+  let c = a.c
+  let aa = a.callee
+  let bb = b.callee
+  var winner = 0
+  for aai, bbi in underspecifiedPairs(aa, bb, 1):
+    var ma = newCandidate(c, bbi)
+    let tra = typeRel(ma, bbi, aai, {trDontBind})
+    var mb = newCandidate(c, aai)
+    let trb = typeRel(mb, aai, bbi, {trDontBind})
+    if tra == isGeneric and trb in {isNone, isInferred, isInferredConvertible}:
+      if winner == -1: return 0
+      winner = 1
+    if trb == isGeneric and tra in {isNone, isInferred, isInferredConvertible}:
+      if winner == 1: return 0
+      winner = -1
+  result = winner
+
+proc sumGeneric(t: PType): int =
+  # count the "genericness" so that Foo[Foo[T]] has the value 3
+  # and Foo[T] has the value 2 so that we know Foo[Foo[T]] is more
+  # specific than Foo[T].
+  result = 0
+  var t = t
+  while true:
+    case t.kind
+    of tyAlias, tySink, tyNot: t = t.skipModifier
+    of tyArray, tyRef, tyPtr, tyDistinct, tyUncheckedArray,
+        tyOpenArray, tyVarargs, tySet, tyRange, tySequence,
+        tyLent, tyOwned, tyVar:
+      t = t.elementType
+      inc result
+    of tyBool, tyChar, tyEnum, tyObject, tyPointer, tyVoid,
+        tyString, tyCstring, tyInt..tyInt64, tyFloat..tyFloat128,
+        tyUInt..tyUInt64, tyCompositeTypeClass, tyBuiltInTypeClass:
+      inc result
+      break
+    of tyGenericBody:
+      t = t.typeBodyImpl
+    of tyGenericInst, tyStatic:
+      t = t.skipModifier
+      inc result
+    of tyOr:
+      var maxBranch = 0
+      for branch in t.kids:
+        let branchSum = sumGeneric(branch)
+        if branchSum > maxBranch: maxBranch = branchSum
+      inc result, maxBranch
+      break
+    of tyTypeDesc:
+      t = t.elementType
+      if t.kind == tyEmpty: break
+      inc result
+    of tyGenericParam:
+      if t.len > 0:
+        t = t.skipModifier
+      else:
+        inc result
+        break
+    of tyUntyped, tyTyped: break
+    of tyGenericInvocation, tyTuple, tyAnd:
+      result += ord(t.kind == tyAnd)
+      for a in t.kids:
+        if a != nil:
+          result += sumGeneric(a)
+      break
+    of tyProc:
+      if t.returnType != nil:
+        result += sumGeneric(t.returnType)
+      for _, a in t.paramTypes:
+        result += sumGeneric(a)
+      break
+    else:
+      break
+
+proc complexDisambiguation(a, b: PType): int =
+  # 'a' matches better if *every* argument matches better or equal than 'b'.
+  var winner = 0
+  for ai, bi in underspecifiedPairs(a, b, 1):
+    let x = ai.sumGeneric
+    let y = bi.sumGeneric
+    if x != y:
+      if winner == 0:
+        if x > y: winner = 1
+        else: winner = -1
+      elif x > y:
+        if winner != 1:
+          # contradiction
+          return 0
+      else:
+        if winner != -1:
+          return 0
+  result = winner
+  when false:
+    var x, y: int
+    for i in 1..<a.len: x += ai.sumGeneric
+    for i in 1..<b.len: y += bi.sumGeneric
+    result = x - y
+
+proc writeMatches*(c: TCandidate) =
+  echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info
+  echo "  exact matches: ", c.exactMatches
+  echo "  generic matches: ", c.genericMatches
+  echo "  subtype matches: ", c.subtypeMatches
+  echo "  intconv matches: ", c.intConvMatches
+  echo "  conv matches: ", c.convMatches
+  echo "  inheritance: ", c.inheritancePenalty
+
+proc cmpInheritancePenalty(a, b: int): int =
+  var eb = b
+  var ea = a
+  if b < 0:
+    eb = maxInheritancePenalty  # defacto max penalty
+  if a < 0:
+    ea = maxInheritancePenalty
+  eb - ea
+
+proc cmpCandidates*(a, b: TCandidate, isFormal=true): int =
+  result = a.exactMatches - b.exactMatches
+  if result != 0: return
+  result = a.genericMatches - b.genericMatches
+  if result != 0: return
+  result = a.subtypeMatches - b.subtypeMatches
+  if result != 0: return
+  result = a.intConvMatches - b.intConvMatches
+  if result != 0: return
+  result = a.convMatches - b.convMatches
+  if result != 0: return
+  result = cmpInheritancePenalty(a.inheritancePenalty, b.inheritancePenalty)
+  if result != 0: return
+  if isFormal:
+    # check for generic subclass relation
+    result = checkGeneric(a, b)
+    if result != 0: return
+    # prefer more specialized generic over more general generic:
+    result = complexDisambiguation(a.callee, b.callee)
+  if result != 0: return
+  # only as a last resort, consider scoping:
+  result = a.calleeScope - b.calleeScope
+
+proc argTypeToString(arg: PNode; prefer: TPreferedDesc): string =
+  if arg.kind in nkSymChoices:
+    result = typeToString(arg[0].typ, prefer)
+    for i in 1..<arg.len:
+      result.add(" | ")
+      result.add typeToString(arg[i].typ, prefer)
+  elif arg.typ == nil:
+    result = "void"
+  else:
+    result = arg.typ.typeToString(prefer)
+
+template describeArgImpl(c: PContext, n: PNode, i: int, startIdx = 1; prefer = preferName) =
+  var arg = n[i]
+  if n[i].kind == nkExprEqExpr:
+    result.add renderTree(n[i][0])
+    result.add ": "
+    if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo}:
+      arg = c.semTryExpr(c, n[i][1])
+      if arg == nil:
+        arg = n[i][1]
+        arg.typ = newTypeS(tyUntyped, c)
+      else:
+        if arg.typ == nil:
+          arg.typ = newTypeS(tyVoid, c)
+        n[i].typ = arg.typ
+        n[i][1] = arg
+  else:
+    if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo, nkElse,
+                                          nkOfBranch, nkElifBranch,
+                                          nkExceptBranch}:
+      arg = c.semTryExpr(c, n[i])
+      if arg == nil:
+        arg = n[i]
+        arg.typ = newTypeS(tyUntyped, c)
+      else:
+        if arg.typ == nil:
+          arg.typ = newTypeS(tyVoid, c)
+        n[i] = arg
+  if arg.typ != nil and arg.typ.kind == tyError: return
+  result.add argTypeToString(arg, prefer)
+
+proc describeArg*(c: PContext, n: PNode, i: int, startIdx = 1; prefer = preferName): string =
+  result = ""
+  describeArgImpl(c, n, i, startIdx, prefer)
+
+proc describeArgs*(c: PContext, n: PNode, startIdx = 1; prefer = preferName): string =
+  result = ""
+  for i in startIdx..<n.len:
+    describeArgImpl(c, n, i, startIdx, prefer)
+    if i != n.len - 1: result.add ", "
+
+proc concreteType(c: TCandidate, t: PType; f: PType = nil): PType =
+  case t.kind
+  of tyTypeDesc:
+    if c.isNoCall: result = t
+    else: result = nil
+  of tySequence, tySet:
+    if t.elementType.kind == tyEmpty: result = nil
+    else: result = t
+  of tyGenericParam, tyAnything, tyConcept:
+    result = t
+    if c.isNoCall: return
+    while true:
+      result = idTableGet(c.bindings, t)
+      if result == nil:
+        break # it's ok, no match
+        # example code that triggers it:
+        # proc sort[T](cmp: proc(a, b: T): int = cmp)
+      if result.kind != tyGenericParam: break
+  of tyGenericInvocation:
+    result = nil
+  of tyOwned:
+    # bug #11257: the comparison system.`==`[T: proc](x, y: T) works
+    # better without the 'owned' type:
+    if f != nil and f.hasElementType and f.elementType.skipTypes({tyBuiltInTypeClass, tyOr}).kind == tyProc:
+      result = t.skipModifier
+    else:
+      result = t
+  else:
+    result = t                # Note: empty is valid here
+
+proc handleRange(c: PContext, f, a: PType, min, max: TTypeKind): TTypeRelation =
+  if a.kind == f.kind:
+    result = isEqual
+  else:
+    let ab = skipTypes(a, {tyRange})
+    let k = ab.kind
+    let nf = c.config.normalizeKind(f.kind)
+    let na = c.config.normalizeKind(k)
+    if k == f.kind:
+      # `a` is a range type matching its base type
+      # see very bottom for range types matching different types
+      if isIntLit(ab):
+        # range type can only give isFromIntLit for base type
+        result = isFromIntLit
+      else:
+        result = isSubrange
+    elif a.kind == tyInt and f.kind in {tyRange, tyInt..tyInt64,
+                                        tyUInt..tyUInt64} and
+        isIntLit(ab) and getInt(ab.n) >= firstOrd(nil, f) and
+                         getInt(ab.n) <= lastOrd(nil, f):
+      # passing 'nil' to firstOrd/lastOrd here as type checking rules should
+      # not depend on the target integer size configurations!
+      # integer literal in the proper range; we want ``i16 + 4`` to stay an
+      # ``int16`` operation so we declare the ``4`` pseudo-equal to int16
+      result = isFromIntLit
+    elif a.kind == tyInt and nf == c.config.targetSizeSignedToKind:
+      result = isIntConv
+    elif a.kind == tyUInt and nf == c.config.targetSizeUnsignedToKind:
+      result = isIntConv
+    elif f.kind == tyInt and na in {tyInt8 .. pred(c.config.targetSizeSignedToKind)}:
+      result = isIntConv
+    elif f.kind == tyUInt and na in {tyUInt8 .. pred(c.config.targetSizeUnsignedToKind)}:
+      result = isIntConv
+    elif k >= min and k <= max:
+      result = isConvertible
+    elif a.kind == tyRange and
+      # Make sure the conversion happens between types w/ same signedness
+      (f.kind in {tyInt..tyInt64} and a[0].kind in {tyInt..tyInt64} or
+       f.kind in {tyUInt8..tyUInt32} and a[0].kind in {tyUInt8..tyUInt32}) and
+      a.n[0].intVal >= firstOrd(nil, f) and a.n[1].intVal <= lastOrd(nil, f):
+      # passing 'nil' to firstOrd/lastOrd here as type checking rules should
+      # not depend on the target integer size configurations!
+      result = isConvertible
+    else: result = isNone
+
+proc isConvertibleToRange(c: PContext, f, a: PType): bool =
+  if f.kind in {tyInt..tyInt64, tyUInt..tyUInt64} and
+     a.kind in {tyInt..tyInt64, tyUInt..tyUInt64}:
+    case f.kind
+    of tyInt8: result = isIntLit(a) or a.kind in {tyInt8}
+    of tyInt16: result = isIntLit(a) or a.kind in {tyInt8, tyInt16}
+    of tyInt32: result = isIntLit(a) or a.kind in {tyInt8, tyInt16, tyInt32}
+    # This is wrong, but seems like there's a lot of code that relies on it :(
+    of tyInt, tyUInt: result = true
+    # of tyInt: result = isIntLit(a) or a.kind in {tyInt8 .. c.config.targetSizeSignedToKind}
+    of tyInt64: result = isIntLit(a) or a.kind in {tyInt8, tyInt16, tyInt32, tyInt, tyInt64}
+    of tyUInt8: result = isIntLit(a) or a.kind in {tyUInt8}
+    of tyUInt16: result = isIntLit(a) or a.kind in {tyUInt8, tyUInt16}
+    of tyUInt32: result = isIntLit(a) or a.kind in {tyUInt8, tyUInt16, tyUInt32}
+    # of tyUInt: result = isIntLit(a) or a.kind in {tyUInt8 .. c.config.targetSizeUnsignedToKind}
+    of tyUInt64: result = isIntLit(a) or a.kind in {tyUInt8, tyUInt16, tyUInt32, tyUInt64}
+    else: result = false
+  elif f.kind in {tyFloat..tyFloat128}:
+    # `isIntLit` is correct and should be used above as well, see PR:
+    # https://github.com/nim-lang/Nim/pull/11197
+    result = isIntLit(a) or a.kind in {tyFloat..tyFloat128}
+  else:
+    result = false
+
+proc handleFloatRange(f, a: PType): TTypeRelation =
+  if a.kind == f.kind:
+    result = isEqual
+  else:
+    let ab = skipTypes(a, {tyRange})
+    var k = ab.kind
+    if k == f.kind: result = isSubrange
+    elif isFloatLit(ab): result = isFromIntLit
+    elif isIntLit(ab): result = isConvertible
+    elif k >= tyFloat and k <= tyFloat128:
+      # conversion to "float32" is not as good:
+      if f.kind == tyFloat32: result = isConvertible
+      else: result = isIntConv
+    else: result = isNone
+
+proc reduceToBase(f: PType): PType =
+  #[
+    Returns the lowest order (most general) type that that is compatible with the input.
+    E.g.
+    A[T] = ptr object ... A -> ptr object
+    A[N: static[int]] = array[N, int] ... A -> array
+  ]#
+  case f.kind:
+  of tyGenericParam:
+    if f.len <= 0 or f.skipModifier == nil:
+      result = f
+    else:
+      result = reduceToBase(f.skipModifier)
+  of tyGenericInvocation:
+    result = reduceToBase(f.baseClass)
+  of tyCompositeTypeClass, tyAlias:
+    if not f.hasElementType or f.elementType == nil:
+      result = f
+    else:
+      result = reduceToBase(f.elementType)
+  of tyGenericInst:
+    result = reduceToBase(f.skipModifier)
+  of tyGenericBody:
+    result = reduceToBase(f.typeBodyImpl)
+  of tyUserTypeClass:
+    if f.isResolvedUserTypeClass:
+      result = f.base  # ?? idk if this is right
+    else:
+      result = f.skipModifier
+  of tyStatic, tyOwned, tyVar, tyLent, tySink:
+    result = reduceToBase(f.base)
+  of tyInferred:
+    # This is not true "After a candidate type is selected"
+    result = reduceToBase(f.base)
+  of tyRange:
+    result = f.elementType
+  else:
+    result = f
+
+proc genericParamPut(c: var TCandidate; last, fGenericOrigin: PType) =
+  if fGenericOrigin != nil and last.kind == tyGenericInst and
+     last.kidsLen-1 == fGenericOrigin.kidsLen:
+    for i in FirstGenericParamAt..<fGenericOrigin.kidsLen:
+      let x = idTableGet(c.bindings, fGenericOrigin[i])
+      if x == nil:
+        put(c, fGenericOrigin[i], last[i])
+
+proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
+  var t = a
+  assert t.kind == tyObject
+  var depth = 0
+  var last = a
+  while t != nil and not sameObjectTypes(f, t):
+    if t.kind != tyObject:  # avoid entering generic params etc
+      return -1
+    t = t.baseClass
+    if t == nil: break
+    last = t
+    t = skipTypes(t, skipPtrs)
+    inc depth
+  if t != nil:
+    genericParamPut(c, last, fGenericOrigin)
+    result = depth
+  else:
+    result = -1
+
+type
+  SkippedPtr = enum skippedNone, skippedRef, skippedPtr
+
+proc skipToObject(t: PType; skipped: var SkippedPtr): PType =
+  var r = t
+  # we're allowed to skip one level of ptr/ref:
+  var ptrs = 0
+  while r != nil:
+    case r.kind
+    of tyGenericInvocation:
+      r = r.genericHead
+    of tyRef:
+      inc ptrs
+      skipped = skippedRef
+      r = r.elementType
+    of tyPtr:
+      inc ptrs
+      skipped = skippedPtr
+      r = r.elementType
+    of tyGenericInst, tyAlias, tySink, tyOwned:
+      r = r.elementType
+    of tyGenericBody:
+      r = r.typeBodyImpl
+    else:
+      break
+  if r.kind == tyObject and ptrs <= 1: result = r
+  else: result = nil
+
+proc isGenericSubtype(c: var TCandidate; a, f: PType, d: var int, fGenericOrigin: PType): bool =
+  assert f.kind in {tyGenericInst, tyGenericInvocation, tyGenericBody}
+  var askip = skippedNone
+  var fskip = skippedNone
+  var t = a.skipToObject(askip)
+  let r = f.skipToObject(fskip)
+  if r == nil: return false
+  var depth = 0
+  var last = a
+  # XXX sameObjectType can return false here. Need to investigate
+  # why that is but sameObjectType does way too much work here anyway.
+  while t != nil and r.sym != t.sym and askip == fskip:
+    t = t.baseClass
+    if t == nil: break
+    last = t
+    t = t.skipToObject(askip)
+    inc depth
+  if t != nil and askip == fskip:
+    genericParamPut(c, last, fGenericOrigin)
+    d = depth
+    result = true
+  else:
+    result = false
+
+proc minRel(a, b: TTypeRelation): TTypeRelation =
+  if a <= b: result = a
+  else: result = b
+
+proc recordRel(c: var TCandidate, f, a: PType, flags: TTypeRelFlags): TTypeRelation =
+  result = isNone
+  if sameType(f, a):
+    result = isEqual
+  elif sameTupleLengths(a, f):
+    result = isEqual
+    let firstField = if f.kind == tyTuple: 0
+                     else: 1
+    for _, ff, aa in tupleTypePairs(f, a):
+      var m = typeRel(c, ff, aa, flags)
+      if m < isSubtype: return isNone
+      if m == isSubtype and aa.kind != tyNil and c.inheritancePenalty > -1:
+        # we can't process individual element type conversions from a
+        # type conversion for the whole tuple
+        # subtype relations need type conversions when inheritance is used
+        return isNone
+      result = minRel(result, m)
+    if f.n != nil and a.n != nil:
+      for i in 0..<f.n.len:
+        # check field names:
+        if f.n[i].kind != nkSym: return isNone
+        elif a.n[i].kind != nkSym: return isNone
+        else:
+          var x = f.n[i].sym
+          var y = a.n[i].sym
+          if f.kind == tyObject and typeRel(c, x.typ, y.typ, flags) < isSubtype:
+            return isNone
+          if x.name.id != y.name.id: return isNone
+
+proc allowsNil(f: PType): TTypeRelation {.inline.} =
+  result = if tfNotNil notin f.flags: isSubtype else: isNone
+
+proc inconsistentVarTypes(f, a: PType): bool {.inline.} =
+  result = (f.kind != a.kind and
+    (f.kind in {tyVar, tyLent, tySink} or a.kind in {tyVar, tyLent, tySink})) or
+    isOutParam(f) != isOutParam(a)
+
+proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
+  ## For example we have:
+  ##   ```nim
+  ##   proc myMap[T,S](sIn: seq[T], f: proc(x: T): S): seq[S] = ...
+  ##   proc innerProc[Q,W](q: Q): W = ...
+  ##   ```
+  ## And we want to match: myMap(@[1,2,3], innerProc)
+  ## This proc (procParamTypeRel) will do the following steps in
+  ## three different calls:
+  ## - matches f=T to a=Q. Since f is metatype, we resolve it
+  ##    to int (which is already known at this point). So in this case
+  ##    Q=int mapping will be saved to c.bindings.
+  ## - matches f=S to a=W. Both of these metatypes are unknown, so we
+  ##    return with isBothMetaConvertible to ask for rerun.
+  ## - matches f=S to a=W. At this point the return type of innerProc
+  ##    is known (we get it from c.bindings). We can use that value
+  ##    to match with f, and save back to c.bindings.
+  var
+    f = f
+    a = a
+
+  if a.isMetaType:
+    let aResolved = idTableGet(c.bindings, a)
+    if aResolved != nil:
+      a = aResolved
+  if a.isMetaType:
+    if f.isMetaType:
+      # We are matching a generic proc (as proc param)
+      # to another generic type appearing in the proc
+      # signature. There is a chance that the target
+      # type is already fully-determined, so we are
+      # going to try resolve it
+      if c.call != nil:
+        f = generateTypeInstance(c.c, c.bindings, c.call.info, f)
+      else:
+        f = nil
+      if f == nil or f.isMetaType:
+        # no luck resolving the type, so the inference fails
+        return isBothMetaConvertible
+    # Note that this typeRel call will save a's resolved type into c.bindings
+    let reverseRel = typeRel(c, a, f)
+    if reverseRel >= isGeneric:
+      result = isInferred
+      #inc c.genericMatches
+    else:
+      result = isNone
+  else:
+    # Note that this typeRel call will save f's resolved type into c.bindings
+    # if f is metatype.
+    result = typeRel(c, f, a)
+
+  if result <= isSubrange or inconsistentVarTypes(f, a):
+    result = isNone
+
+  #if result == isEqual:
+  #  inc c.exactMatches
+
+proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
+  case a.kind
+  of tyProc:
+    var f = f
+    copyingEraseVoidParams(c, f)
+    if f.signatureLen != a.signatureLen: return
+    result = isEqual      # start with maximum; also correct for no
+                          # params at all
+
+    if f.flags * {tfIterator} != a.flags * {tfIterator}:
+      return isNone
+
+    template checkParam(f, a) =
+      result = minRel(result, procParamTypeRel(c, f, a))
+      if result == isNone: return
+
+    # Note: We have to do unification for the parameters before the
+    # return type!
+    for i in 1..<f.len:
+      checkParam(f[i], a[i])
+
+    if f[0] != nil:
+      if a[0] != nil:
+        checkParam(f[0], a[0])
+      else:
+        return isNone
+    elif a[0] != nil:
+      return isNone
+
+    result = getProcConvMismatch(c.c.config, f, a, result)[1]
+
+    when useEffectSystem:
+      if compatibleEffects(f, a) != efCompat: return isNone
+    when defined(drnim):
+      if not c.c.graph.compatibleProps(c.c.graph, f, a): return isNone
+
+  of tyNil:
+    result = f.allowsNil
+  else: result = isNone
+
+proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
+  template checkRange[T](a0, a1, f0, f1: T): TTypeRelation =
+    if a0 == f0 and a1 == f1:
+      isEqual
+    elif a0 >= f0 and a1 <= f1:
+      isConvertible
+    elif a0 <= f1 and f0 <= a1:
+      # X..Y and C..D overlap iff (X <= D and C <= Y)
+      isConvertible
+    else:
+      isNone
+
+  if f.isOrdinalType:
+    checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f))
+  else:
+    checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f))
+
+
+proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
+  var
+    c = m.c
+    typeClass = ff.skipTypes({tyUserTypeClassInst})
+    body = typeClass.n[3]
+    matchedConceptContext = TMatchedConcept()
+    prevMatchedConcept = c.matchedConcept
+    prevCandidateType = typeClass[0][0]
+
+  if prevMatchedConcept != nil:
+    matchedConceptContext.prev = prevMatchedConcept
+    matchedConceptContext.depth = prevMatchedConcept.depth + 1
+    if prevMatchedConcept.depth > 4:
+      localError(m.c.graph.config, body.info, $body & " too nested for type matching")
+      return nil
+
+  openScope(c)
+  matchedConceptContext.candidateType = a
+  typeClass[0][0] = a
+  c.matchedConcept = addr(matchedConceptContext)
+  defer:
+    c.matchedConcept = prevMatchedConcept
+    typeClass[0][0] = prevCandidateType
+    closeScope(c)
+
+  var typeParams: seq[(PSym, PType)] = @[]
+
+  if ff.kind == tyUserTypeClassInst:
+    for i in 1..<(ff.len - 1):
+      var
+        typeParamName = ff.base[i-1].sym.name
+        typ = ff[i]
+        param: PSym = nil
+        alreadyBound = idTableGet(m.bindings, typ)
+
+      if alreadyBound != nil: typ = alreadyBound
+
+      template paramSym(kind): untyped =
+        newSym(kind, typeParamName, c.idgen, typeClass.sym, typeClass.sym.info, {})
+
+      block addTypeParam:
+        for prev in typeParams:
+          if prev[1].id == typ.id:
+            param = paramSym prev[0].kind
+            param.typ = prev[0].typ
+            break addTypeParam
+
+        case typ.kind
+        of tyStatic:
+          param = paramSym skConst
+          param.typ = typ.exactReplica
+          #copyType(typ, c.idgen, typ.owner)
+          if typ.n == nil:
+            param.typ.flags.incl tfInferrableStatic
+          else:
+            param.ast = typ.n
+        of tyFromExpr:
+          param = paramSym skVar
+          param.typ = typ.exactReplica
+          #copyType(typ, c.idgen, typ.owner)
+        else:
+          param = paramSym skType
+          param.typ = if typ.isMetaType:
+                        newTypeS(tyInferred, c, typ)
+                      else:
+                        makeTypeDesc(c, typ)
+
+        typeParams.add((param, typ))
+
+      addDecl(c, param)
+
+  var
+    oldWriteHook = default typeof(m.c.config.writelnHook)
+    diagnostics: seq[string] = @[]
+    errorPrefix: string
+    flags: TExprFlags = {}
+    collectDiagnostics = m.diagnosticsEnabled or
+                         sfExplain in typeClass.sym.flags
+
+  if collectDiagnostics:
+    oldWriteHook = m.c.config.writelnHook
+    # XXX: we can't write to m.diagnostics directly, because
+    # Nim doesn't support capturing var params in closures
+    diagnostics = @[]
+    flags = {efExplain}
+    m.c.config.writelnHook = proc (s: string) =
+      if errorPrefix.len == 0: errorPrefix = typeClass.sym.name.s & ":"
+      let msg = s.replace("Error:", errorPrefix)
+      if oldWriteHook != nil: oldWriteHook msg
+      diagnostics.add msg
+
+  var checkedBody = c.semTryExpr(c, body.copyTree, flags)
+
+  if collectDiagnostics:
+    m.c.config.writelnHook = oldWriteHook
+    for msg in diagnostics:
+      m.diagnostics.add msg
+      m.diagnosticsEnabled = true
+
+  if checkedBody == nil: return nil
+
+  # The inferrable type params have been identified during the semTryExpr above.
+  # We need to put them in the current sigmatch's binding table in order for them
+  # to be resolvable while matching the rest of the parameters
+  for p in typeParams:
+    put(m, p[1], p[0].typ)
+
+  if ff.kind == tyUserTypeClassInst:
+    result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
+  else:
+    result = ff.exactReplica
+    #copyType(ff, c.idgen, ff.owner)
+
+  result.n = checkedBody
+
+proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool =
+  # XXX This is bad as 'considerQuotedIdent' can produce an error!
+  if rules.kind == nkWith:
+    for r in rules:
+      if considerQuotedIdent(m.c, r) == callIdent: return true
+    return false
+  else:
+    for r in rules:
+      if considerQuotedIdent(m.c, r) == callIdent: return false
+    return true
+
+proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
+  if t != nil and t.kind == tyDistinct and t.n != nil and
+     shouldSkipDistinct(m, t.n, callee.name):
+    result = t.base
+  else:
+    result = t
+
+proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
+                            allowUnresolved = false,
+                            allowCalls = false,
+                            expectedType: PType = nil): PNode =
+  # Consider this example:
+  #   type Value[N: static[int]] = object
+  #   proc foo[N](a: Value[N], r: range[0..(N-1)])
+  # Here, N-1 will be initially nkStaticExpr that can be evaluated only after
+  # N is bound to a concrete value during the matching of the first param.
+  # This proc is used to evaluate such static expressions.
+  let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil,
+                                        allowMetaTypes = allowUnresolved)
+  if not allowCalls and instantiated.kind in nkCallKinds:
+    return nil
+  result = c.c.semExpr(c.c, instantiated)
+
+proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool =
+  # This is a simple integer arithimetic equation solver,
+  # capable of deriving the value of a static parameter in
+  # expressions such as (N + 5) / 2 = rhs
+  #
+  # Preconditions:
+  #
+  #   * The input of this proc must be semantized
+  #     - all templates should be expanded
+  #     - aby constant folding possible should already be performed
+  #
+  #   * There must be exactly one unresolved static parameter
+  #
+  # Result:
+  #
+  #   The proc will return true if the static types was successfully
+  #   inferred. The result will be bound to the original static type
+  #   in the TCandidate.
+  #
+  if lhs.kind in nkCallKinds and lhs[0].kind == nkSym:
+    case lhs[0].sym.magic
+    of mAddI, mAddU, mInc, mSucc:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(c, lhs[2], rhs - lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs - lhs[2].intVal)
+
+    of mDec, mSubI, mSubU, mPred:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(c, lhs[2], lhs[1].intVal - rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs + lhs[2].intVal)
+
+    of mMulI, mMulU:
+      if lhs[1].kind == nkIntLit:
+        if rhs mod lhs[1].intVal == 0:
+          return inferStaticParam(c, lhs[2], rhs div lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        if rhs mod lhs[2].intVal == 0:
+          return inferStaticParam(c, lhs[1], rhs div lhs[2].intVal)
+
+    of mDivI, mDivU:
+      if lhs[1].kind == nkIntLit:
+        if lhs[1].intVal mod rhs == 0:
+          return inferStaticParam(c, lhs[2], lhs[1].intVal div rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], lhs[2].intVal * rhs)
+
+    of mShlI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs shr lhs[2].intVal)
+
+    of mShrI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs shl lhs[2].intVal)
+
+    of mAshrI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], ashr(rhs, lhs[2].intVal))
+
+    of mUnaryMinusI:
+      return inferStaticParam(c, lhs[1], -rhs)
+
+    of mUnaryPlusI:
+      return inferStaticParam(c, lhs[1], rhs)
+
+    else: discard
+
+  elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and
+      (lhs.typ.n == nil or idTableGet(c.bindings, lhs.typ) == nil):
+    var inferred = newTypeS(tyStatic, c.c, lhs.typ.elementType)
+    inferred.n = newIntNode(nkIntLit, rhs)
+    put(c, lhs.typ, inferred)
+    if c.c.matchedConcept != nil:
+      # inside concepts, binding is currently done with
+      # direct mutation of the involved types:
+      lhs.typ.n = inferred.n
+    return true
+
+  return false
+
+proc failureToInferStaticParam(conf: ConfigRef; n: PNode) =
+  let staticParam = n.findUnresolvedStatic
+  let name = if staticParam != nil: staticParam.sym.name.s
+             else: "unknown"
+  localError(conf, n.info, "cannot infer the value of the static param '" & name & "'")
+
+proc inferStaticsInRange(c: var TCandidate,
+                         inferred, concrete: PType): TTypeRelation =
+  let lowerBound = tryResolvingStaticExpr(c, inferred.n[0],
+                                          allowUnresolved = true)
+  let upperBound = tryResolvingStaticExpr(c, inferred.n[1],
+                                          allowUnresolved = true)
+  template doInferStatic(e: PNode, r: Int128) =
+    var exp = e
+    var rhs = r
+    if inferStaticParam(c, exp, toInt64(rhs)):
+      return isGeneric
+    else:
+      failureToInferStaticParam(c.c.config, exp)
+
+  result = isNone
+  if lowerBound.kind == nkIntLit:
+    if upperBound.kind == nkIntLit:
+      if lengthOrd(c.c.config, concrete) == upperBound.intVal - lowerBound.intVal + 1:
+        return isGeneric
+      else:
+        return isNone
+    doInferStatic(upperBound, lengthOrd(c.c.config, concrete) + lowerBound.intVal - 1)
+  elif upperBound.kind == nkIntLit:
+    doInferStatic(lowerBound, getInt(upperBound) + 1 - lengthOrd(c.c.config, concrete))
+
+template subtypeCheck() =
+  case result
+  of isIntConv:
+    result = isNone
+  of isSubrange:
+    discard # XXX should be isNone with preview define, warnings
+  of isConvertible:
+    if f.last.skipTypes(abstractInst).kind != tyOpenArray:
+      # exclude var openarray which compiler supports
+      result = isNone
+  of isSubtype:
+    if f.last.skipTypes(abstractInst).kind in {
+        tyRef, tyPtr, tyVar, tyLent, tyOwned}:
+      # compiler can't handle subtype conversions with pointer indirection
+      result = isNone
+  else: discard
+
+proc isCovariantPtr(c: var TCandidate, f, a: PType): bool =
+  # this proc is always called for a pair of matching types
+  assert f.kind == a.kind
+
+  template baseTypesCheck(lhs, rhs: PType): bool =
+    lhs.kind notin {tyPtr, tyRef, tyVar, tyLent, tyOwned} and
+      typeRel(c, lhs, rhs, {trNoCovariance}) == isSubtype
+
+  case f.kind
+  of tyRef, tyPtr, tyOwned:
+    return baseTypesCheck(f.base, a.base)
+  of tyGenericInst:
+    let body = f.base
+    return body == a.base and
+           a.len == 3 and
+           tfWeakCovariant notin body[0].flags and
+           baseTypesCheck(f[1], a[1])
+  else:
+    return false
+
+when false:
+  proc maxNumericType(prev, candidate: PType): PType =
+    let c = candidate.skipTypes({tyRange})
+    template greater(s) =
+      if c.kind in s: result = c
+    case prev.kind
+    of tyInt: greater({tyInt64})
+    of tyInt8: greater({tyInt, tyInt16, tyInt32, tyInt64})
+    of tyInt16: greater({tyInt, tyInt32, tyInt64})
+    of tyInt32: greater({tyInt64})
+
+    of tyUInt: greater({tyUInt64})
+    of tyUInt8: greater({tyUInt, tyUInt16, tyUInt32, tyUInt64})
+    of tyUInt16: greater({tyUInt, tyUInt32, tyUInt64})
+    of tyUInt32: greater({tyUInt64})
+
+    of tyFloat32: greater({tyFloat64, tyFloat128})
+    of tyFloat64: greater({tyFloat128})
+    else: discard
+
+template skipOwned(a) =
+  if a.kind == tyOwned: a = a.skipTypes({tyOwned, tyGenericInst})
+
+proc typeRel(c: var TCandidate, f, aOrig: PType,
+             flags: TTypeRelFlags = {}): TTypeRelation =
+  # typeRel can be used to establish various relationships between types:
+  #
+  # 1) When used with concrete types, it will check for type equivalence
+  # or a subtype relationship.
+  #
+  # 2) When used with a concrete type against a type class (such as generic
+  # signature of a proc), it will check whether the concrete type is a member
+  # of the designated type class.
+  #
+  # 3) When used with two type classes, it will check whether the types
+  # matching the first type class (aOrig) are a strict subset of the types matching
+  # the other (f). This allows us to compare the signatures of generic procs in
+  # order to give preferrence to the most specific one:
+  #
+  # seq[seq[any]] is a strict subset of seq[any] and hence more specific.
+
+  result = isNone
+  assert(f != nil)
+
+  when declared(deallocatedRefId):
+    let corrupt = deallocatedRefId(cast[pointer](f))
+    if corrupt != 0:
+      c.c.config.quitOrRaise "it's corrupt " & $corrupt
+
+  if f.kind == tyUntyped:
+    if aOrig != nil: put(c, f, aOrig)
+    return isGeneric
+
+  assert(aOrig != nil)
+
+  var
+    useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
+                                     not c.isNoCall and
+                                     f.kind != tyTypeDesc and
+                                     tfExplicit notin aOrig.flags and
+                                     tfConceptMatchedTypeSym notin aOrig.flags
+
+    aOrig = if useTypeLoweringRuleInTypeClass:
+          aOrig.skipTypes({tyTypeDesc})
+        else:
+          aOrig
+
+  if aOrig.kind == tyInferred:
+    let prev = aOrig.previouslyInferred
+    if prev != nil:
+      return typeRel(c, f, prev, flags)
+    else:
+      var candidate = f
+
+      case f.kind
+      of tyGenericParam:
+        var prev = idTableGet(c.bindings, f)
+        if prev != nil: candidate = prev
+      of tyFromExpr:
+        let computedType = tryResolvingStaticExpr(c, f.n).typ
+        case computedType.kind
+        of tyTypeDesc:
+          candidate = computedType.base
+        of tyStatic:
+          candidate = computedType
+        else:
+          # XXX What is this non-sense? Error reporting in signature matching?
+          discard "localError(f.n.info, errTypeExpected)"
+      else:
+        discard
+
+      result = typeRel(c, aOrig.base, candidate, flags)
+      if result != isNone:
+        c.inferredTypes.add aOrig
+        aOrig.add candidate
+        result = isEqual
+      return
+
+  template doBind: bool = trDontBind notin flags
+
+  # var, sink and static arguments match regular modifier-free types
+  var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym)
+  # XXX: Theoretically, maybeSkipDistinct could be called before we even
+  # start the param matching process. This could be done in `prepareOperand`
+  # for example, but unfortunately `prepareOperand` is not called in certain
+  # situation when nkDotExpr are rotated to nkDotCalls
+
+  if aOrig.kind in {tyAlias, tySink}:
+    return typeRel(c, f, skipModifier(aOrig), flags)
+
+  if a.kind == tyGenericInst and
+      skipTypes(f, {tyStatic, tyVar, tyLent, tySink}).kind notin {
+        tyGenericBody, tyGenericInvocation,
+        tyGenericInst, tyGenericParam} + tyTypeClasses:
+    return typeRel(c, f, skipModifier(a), flags)
+
+  if a.isResolvedUserTypeClass:
+    return typeRel(c, f, a.skipModifier, flags)
+
+  template bindingRet(res) =
+    if doBind:
+      let bound = aOrig.skipTypes({tyRange}).skipIntLit(c.c.idgen)
+      put(c, f, bound)
+    return res
+
+  template considerPreviousT(body: untyped) =
+    var prev = idTableGet(c.bindings, f)
+    if prev == nil: body
+    else: return typeRel(c, prev, a, flags)
+
+  if c.c.inGenericContext > 0 and not c.isNoCall and
+      (tfUnresolved in a.flags or a.kind in tyTypeClasses):
+    # cheap check for unresolved arg, not nested
+    return isNone
+
+  case a.kind
+  of tyOr:
+    # XXX: deal with the current dual meaning of tyGenericParam
+    c.typedescMatched = true
+    # seq[int|string] vs seq[number]
+    # both int and string must match against number
+    # but ensure that '[T: A|A]' matches as good as '[T: A]' (bug #2219):
+    result = isGeneric
+    for branch in a.kids:
+      let x = typeRel(c, f, branch, flags + {trDontBind})
+      if x == isNone: return isNone
+      if x < result: result = x
+    return result
+  of tyAnd:
+    # XXX: deal with the current dual meaning of tyGenericParam
+    c.typedescMatched = true
+    # seq[Sortable and Iterable] vs seq[Sortable]
+    # only one match is enough
+    for branch in a.kids:
+      let x = typeRel(c, f, branch, flags + {trDontBind})
+      if x != isNone:
+        return if x >= isGeneric: isGeneric else: x
+    return isNone
+  of tyIterable:
+    if f.kind != tyIterable: return isNone
+  of tyNot:
+    case f.kind
+    of tyNot:
+      # seq[!int] vs seq[!number]
+      # seq[float] matches the first, but not the second
+      # we must turn the problem around:
+      # is number a subset of int?
+      return typeRel(c, a.elementType, f.elementType, flags)
+
+    else:
+      # negative type classes are essentially infinite,
+      # so only the `any` type class is their superset
+      return if f.kind == tyAnything: isGeneric
+             else: isNone
+  of tyAnything:
+    if f.kind == tyAnything: return isGeneric
+    else: return isNone
+  of tyUserTypeClass, tyUserTypeClassInst:
+    if c.c.matchedConcept != nil and c.c.matchedConcept.depth <= 4:
+      # consider this: 'var g: Node' *within* a concept where 'Node'
+      # is a concept too (tgraph)
+      inc c.c.matchedConcept.depth
+      let x = typeRel(c, a, f, flags + {trDontBind})
+      if x >= isGeneric:
+        return isGeneric
+  of tyFromExpr:
+    if c.c.inGenericContext > 0:
+      if not c.isNoCall:
+        # generic type bodies can sometimes compile call expressions
+        # prevent expressions with unresolved types from
+        # being passed as parameters
+        return isNone
+      else:
+        # Foo[templateCall(T)] shouldn't fail early if Foo has a constraint
+        # and we can't evaluate `templateCall(T)` yet
+        return isGeneric
+  else: discard
+
+  case f.kind
+  of tyEnum:
+    if a.kind == f.kind and sameEnumTypes(f, a): result = isEqual
+    elif sameEnumTypes(f, skipTypes(a, {tyRange})): result = isSubtype
+  of tyBool, tyChar:
+    if a.kind == f.kind: result = isEqual
+    elif skipTypes(a, {tyRange}).kind == f.kind: result = isSubtype
+  of tyRange:
+    if a.kind == f.kind:
+      if f.base.kind == tyNone: return isGeneric
+      result = typeRel(c, base(f), base(a), flags)
+      # bugfix: accept integer conversions here
+      #if result < isGeneric: result = isNone
+      if result notin {isNone, isGeneric}:
+        # resolve any late-bound static expressions
+        # that may appear in the range:
+        let expectedType = base(f)
+        for i in 0..1:
+          if f.n[i].kind == nkStaticExpr:
+            let r = tryResolvingStaticExpr(c, f.n[i], expectedType = expectedType)
+            if r != nil:
+              f.n[i] = r
+        result = typeRangeRel(f, a)
+    else:
+      let f = skipTypes(f, {tyRange})
+      if f.kind == a.kind and (f.kind != tyEnum or sameEnumTypes(f, a)):
+        result = isIntConv
+      elif isConvertibleToRange(c.c, f, a):
+        result = isConvertible  # a convertible to f
+  of tyInt:      result = handleRange(c.c, f, a, tyInt8, c.c.config.targetSizeSignedToKind)
+  of tyInt8:     result = handleRange(c.c, f, a, tyInt8, tyInt8)
+  of tyInt16:    result = handleRange(c.c, f, a, tyInt8, tyInt16)
+  of tyInt32:    result = handleRange(c.c, f, a, tyInt8, tyInt32)
+  of tyInt64:    result = handleRange(c.c, f, a, tyInt, tyInt64)
+  of tyUInt:     result = handleRange(c.c, f, a, tyUInt8, c.c.config.targetSizeUnsignedToKind)
+  of tyUInt8:    result = handleRange(c.c, f, a, tyUInt8, tyUInt8)
+  of tyUInt16:   result = handleRange(c.c, f, a, tyUInt8, tyUInt16)
+  of tyUInt32:   result = handleRange(c.c, f, a, tyUInt8, tyUInt32)
+  of tyUInt64:   result = handleRange(c.c, f, a, tyUInt, tyUInt64)
+  of tyFloat:    result = handleFloatRange(f, a)
+  of tyFloat32:  result = handleFloatRange(f, a)
+  of tyFloat64:  result = handleFloatRange(f, a)
+  of tyFloat128: result = handleFloatRange(f, a)
+  of tyVar:
+    let flags = if isOutParam(f): flags + {trIsOutParam} else: flags
+    if aOrig.kind == f.kind and (isOutParam(aOrig) == isOutParam(f)):
+      result = typeRel(c, f.base, aOrig.base, flags)
+    else:
+      result = typeRel(c, f.base, aOrig, flags + {trNoCovariance})
+    subtypeCheck()
+  of tyLent:
+    if aOrig.kind == f.kind:
+      result = typeRel(c, f.base, aOrig.base, flags)
+    else:
+      result = typeRel(c, f.base, aOrig, flags + {trNoCovariance})
+    subtypeCheck()
+  of tyArray:
+    a = reduceToBase(a)
+    if a.kind == tyArray:
+      var fRange = f.indexType
+      var aRange = a.indexType
+      if fRange.kind in {tyGenericParam, tyAnything}:
+        var prev = idTableGet(c.bindings, fRange)
+        if prev == nil:
+          if typeRel(c, fRange, aRange) == isNone:
+            return isNone
+          put(c, fRange, a.indexType)
+          fRange = a
+        else:
+          fRange = prev
+      let ff = f[1].skipTypes({tyTypeDesc})
+      # This typeDesc rule is wrong, see bug #7331
+      let aa = a[1] #.skipTypes({tyTypeDesc})
+
+      if f.indexType.kind != tyGenericParam and aa.kind == tyEmpty:
+        result = isGeneric
+      else:
+        result = typeRel(c, ff, aa, flags)
+      if result < isGeneric:
+        if nimEnableCovariance and
+           trNoCovariance notin flags and
+           ff.kind == aa.kind and
+           isCovariantPtr(c, ff, aa):
+          result = isSubtype
+        else:
+          return isNone
+
+      if fRange.rangeHasUnresolvedStatic:
+        if aRange.kind in {tyGenericParam} and aRange.reduceToBase() == aRange:
+          return
+        return inferStaticsInRange(c, fRange, a)
+      elif c.c.matchedConcept != nil and aRange.rangeHasUnresolvedStatic:
+        return inferStaticsInRange(c, aRange, f)
+      elif result == isGeneric and concreteType(c, aa, ff) == nil:
+        return isNone
+      else:
+        if lengthOrd(c.c.config, fRange) != lengthOrd(c.c.config, aRange):
+          result = isNone
+  of tyOpenArray, tyVarargs:
+    # varargs[untyped] is special too but handled earlier. So we only need to
+    # handle varargs[typed]:
+    if f.kind == tyVarargs:
+      if tfVarargs in a.flags:
+        return typeRel(c, f.base, a.elementType, flags)
+      if f[0].kind == tyTyped: return
+
+    template matchArrayOrSeq(aBase: PType) =
+      let ff = f.base
+      let aa = aBase
+      let baseRel = typeRel(c, ff, aa, flags)
+      if baseRel >= isGeneric:
+        result = isConvertible
+      elif nimEnableCovariance and
+           trNoCovariance notin flags and
+           ff.kind == aa.kind and
+           isCovariantPtr(c, ff, aa):
+        result = isConvertible
+
+    case a.kind
+    of tyOpenArray, tyVarargs:
+      result = typeRel(c, base(f), base(a), flags)
+      if result < isGeneric: result = isNone
+    of tyArray:
+      if (f[0].kind != tyGenericParam) and (a.elementType.kind == tyEmpty):
+        return isSubtype
+      matchArrayOrSeq(a.elementType)
+    of tySequence:
+      if (f[0].kind != tyGenericParam) and (a.elementType.kind == tyEmpty):
+        return isConvertible
+      matchArrayOrSeq(a.elementType)
+    of tyString:
+      if f.kind == tyOpenArray:
+        if f[0].kind == tyChar:
+          result = isConvertible
+        elif f[0].kind == tyGenericParam and a.len > 0 and
+            typeRel(c, base(f), base(a), flags) >= isGeneric:
+          result = isConvertible
+    else: discard
+  of tySequence, tyUncheckedArray:
+    if a.kind == f.kind:
+      if (f[0].kind != tyGenericParam) and (a.elementType.kind == tyEmpty):
+        result = isSubtype
+      else:
+        let ff = f[0]
+        let aa = a.elementType
+        result = typeRel(c, ff, aa, flags)
+        if result < isGeneric:
+          if nimEnableCovariance and
+             trNoCovariance notin flags and
+             ff.kind == aa.kind and
+             isCovariantPtr(c, ff, aa):
+            result = isSubtype
+          else:
+            result = isNone
+    elif a.kind == tyNil:
+      result = isNone
+  of tyOrdinal:
+    if isOrdinalType(a):
+      var x = if a.kind == tyOrdinal: a.elementType else: a
+      if f[0].kind == tyNone:
+        result = isGeneric
+      else:
+        result = typeRel(c, f[0], x, flags)
+        if result < isGeneric: result = isNone
+    elif a.kind == tyGenericParam:
+      result = isGeneric
+  of tyForward:
+    #internalError("forward type in typeRel()")
+    result = isNone
+  of tyNil:
+    skipOwned(a)
+    if a.kind == f.kind: result = isEqual
+  of tyTuple:
+    if a.kind == tyTuple: result = recordRel(c, f, a, flags)
+  of tyObject:
+    let effectiveArgType = if useTypeLoweringRuleInTypeClass:
+        a
+      else:
+        reduceToBase(a)
+    if effectiveArgType.kind == tyObject:
+      if sameObjectTypes(f, effectiveArgType):
+        c.inheritancePenalty = if tfFinal in f.flags: -1 else: 0
+        result = isEqual
+        # elif tfHasMeta in f.flags: result = recordRel(c, f, a)
+      elif trIsOutParam notin flags:
+        c.inheritancePenalty = isObjectSubtype(c, effectiveArgType, f, nil)
+        if c.inheritancePenalty > 0:
+          result = isSubtype
+  of tyDistinct:
+    a = a.skipTypes({tyOwned, tyGenericInst, tyRange})
+    if a.kind == tyDistinct:
+      if sameDistinctTypes(f, a): result = isEqual
+      #elif f.base.kind == tyAnything: result = isGeneric  # issue 4435
+      elif c.coerceDistincts: result = typeRel(c, f.base, a, flags)
+    elif c.coerceDistincts: result = typeRel(c, f.base, a, flags)
+  of tySet:
+    if a.kind == tySet:
+      if f[0].kind != tyGenericParam and a[0].kind == tyEmpty:
+        result = isSubtype
+      else:
+        result = typeRel(c, f[0], a[0], flags)
+        if result < isGeneric:
+          if tfIsConstructor notin a.flags:
+            # set['a'..'z'] and set[char] have different representations
+            result = isNone
+          else:
+            # but we can convert individual elements of the constructor
+            result = isConvertible
+  of tyPtr, tyRef:
+    a = reduceToBase(a)
+    if a.kind == f.kind:
+      # ptr[R, T] can be passed to ptr[T], but not the other way round:
+      if a.len < f.len: return isNone
+      for i in 0..<f.len-1:
+        if typeRel(c, f[i], a[i], flags) == isNone: return isNone
+      result = typeRel(c, f.elementType, a.elementType, flags + {trNoCovariance})
+      subtypeCheck()
+      if result <= isIntConv: result = isNone
+      elif tfNotNil in f.flags and tfNotNil notin a.flags:
+        result = isNilConversion
+    elif a.kind == tyNil: result = f.allowsNil
+    else: discard
+  of tyProc:
+    skipOwned(a)
+    result = procTypeRel(c, f, a)
+    if result != isNone and tfNotNil in f.flags and tfNotNil notin a.flags:
+      result = isNilConversion
+  of tyOwned:
+    case a.kind
+    of tyOwned:
+      result = typeRel(c, skipModifier(f), skipModifier(a), flags)
+    of tyNil: result = f.allowsNil
+    else: discard
+  of tyPointer:
+    skipOwned(a)
+    case a.kind
+    of tyPointer:
+      if tfNotNil in f.flags and tfNotNil notin a.flags:
+        result = isNilConversion
+      else:
+        result = isEqual
+    of tyNil: result = f.allowsNil
+    of tyProc:
+      if isDefined(c.c.config, "nimPreviewProcConversion"):
+        result = isNone
+      else:
+        if a.callConv != ccClosure: result = isConvertible
+    of tyPtr:
+      # 'pointer' is NOT compatible to regionized pointers
+      # so 'dealloc(regionPtr)' fails:
+      if a.len == 1: result = isConvertible
+    of tyCstring: result = isConvertible
+    else: discard
+  of tyString:
+    case a.kind
+    of tyString: result = isEqual
+    of tyNil: result = isNone
+    else: discard
+  of tyCstring:
+    # conversion from string to cstring is automatic:
+    case a.kind
+    of tyCstring:
+      if tfNotNil in f.flags and tfNotNil notin a.flags:
+        result = isNilConversion
+      else:
+        result = isEqual
+    of tyNil: result = f.allowsNil
+    of tyString: result = isConvertible
+    of tyPtr:
+      if isDefined(c.c.config, "nimPreviewCstringConversion"):
+        result = isNone
+      else:
+        if a.len == 1:
+          let pointsTo = a[0].skipTypes(abstractInst)
+          if pointsTo.kind == tyChar: result = isConvertible
+          elif pointsTo.kind == tyUncheckedArray and pointsTo[0].kind == tyChar:
+            result = isConvertible
+          elif pointsTo.kind == tyArray and firstOrd(nil, pointsTo[0]) == 0 and
+              skipTypes(pointsTo[0], {tyRange}).kind in {tyInt..tyInt64} and
+              pointsTo[1].kind == tyChar:
+            result = isConvertible
+    else: discard
+  of tyEmpty, tyVoid:
+    if a.kind == f.kind: result = isEqual
+  of tyAlias, tySink:
+    result = typeRel(c, skipModifier(f), a, flags)
+  of tyIterable:
+    if a.kind == tyIterable:
+      if f.len == 1:
+        result = typeRel(c, skipModifier(f), skipModifier(a), flags)
+      else:
+        # f.len = 3, for some reason
+        result = isGeneric
+    else:
+      result = isNone
+  of tyGenericInst:
+    var prev = idTableGet(c.bindings, f)
+    let origF = f
+    var f = if prev == nil: f else: prev
+
+    let deptha = a.genericAliasDepth()
+    let depthf = f.genericAliasDepth()
+    let skipBoth = deptha == depthf and (a.len > 0 and f.len > 0 and a.base != f.base)
+
+    let roota = if skipBoth or deptha > depthf: a.skipGenericAlias else: a
+    let rootf = if skipBoth or depthf > deptha: f.skipGenericAlias else: f
+
+    if a.kind == tyGenericInst:
+      if roota.base == rootf.base:
+        let nextFlags = flags + {trNoCovariance}
+        var hasCovariance = false
+        # YYYY
+        result = isEqual
+
+        for i in 1..<rootf.len-1:
+          let ff = rootf[i]
+          let aa = roota[i]
+          let res = typeRel(c, ff, aa, nextFlags)
+          if res != isNone and res != isEqual: result = isGeneric
+          if res notin {isEqual, isGeneric}:
+            if trNoCovariance notin flags and ff.kind == aa.kind:
+              let paramFlags = rootf.base[i-1].flags
+              hasCovariance =
+                if tfCovariant in paramFlags:
+                  if tfWeakCovariant in paramFlags:
+                    isCovariantPtr(c, ff, aa)
+                  else:
+                    ff.kind notin {tyRef, tyPtr} and res == isSubtype
+                else:
+                  tfContravariant in paramFlags and
+                    typeRel(c, aa, ff, flags) == isSubtype
+              if hasCovariance:
+                continue
+
+            return isNone
+        if prev == nil: put(c, f, a)
+      else:
+        let fKind = rootf.last.kind
+        if fKind in {tyAnd, tyOr}:
+          result = typeRel(c, last(f), a, flags)
+          if result != isNone: put(c, f, a)
+          return
+
+        var aAsObject = roota.last
+
+        if fKind in {tyRef, tyPtr}:
+          if aAsObject.kind == tyObject:
+            # bug #7600, tyObject cannot be passed
+            # as argument to tyRef/tyPtr
+            return isNone
+          elif aAsObject.kind == fKind:
+            aAsObject = aAsObject.base
+
+        if aAsObject.kind == tyObject and trIsOutParam notin flags:
+          let baseType = aAsObject.base
+          if baseType != nil:
+            inc c.inheritancePenalty, 1 + int(c.inheritancePenalty < 0)
+            let ret = typeRel(c, f, baseType, flags)
+            return if ret in {isEqual,isGeneric}: isSubtype else: ret
+
+        result = isNone
+    else:
+      assert last(origF) != nil
+      result = typeRel(c, last(origF), a, flags)
+      if result != isNone and a.kind != tyNil:
+        put(c, f, a)
+  of tyGenericBody:
+    considerPreviousT:
+      if a == f or a.kind == tyGenericInst and a.skipGenericAlias[0] == f:
+        bindingRet isGeneric
+      let ff = last(f)
+      if ff != nil:
+        result = typeRel(c, ff, a, flags)
+  of tyGenericInvocation:
+    var x = a.skipGenericAlias
+    if x.kind == tyGenericParam and x.len > 0:
+      x = x.last
+    let concpt = f[0].skipTypes({tyGenericBody})
+    var preventHack = concpt.kind == tyConcept
+    if x.kind == tyOwned and f[0].kind != tyOwned:
+      preventHack = true
+      x = x.last
+    # XXX: This is very hacky. It should be moved back into liftTypeParam
+    if x.kind in {tyGenericInst, tyArray} and
+      c.calleeSym != nil and
+      c.calleeSym.kind in {skProc, skFunc} and c.call != nil and not preventHack:
+      let inst = prepareMetatypeForSigmatch(c.c, c.bindings, c.call.info, f)
+      return typeRel(c, inst, a, flags)
+
+    if x.kind == tyGenericInvocation:
+      if f[0] == x[0]:
+        for i in 1..<f.len:
+          # Handle when checking against a generic that isn't fully instantiated
+          if i >= x.len: return
+          let tr = typeRel(c, f[i], x[i], flags)
+          if tr <= isSubtype: return
+        result = isGeneric
+    elif x.kind == tyGenericInst and f[0] == x[0] and
+          x.len - 1 == f.len:
+      for i in 1..<f.len:
+        if x[i].kind == tyGenericParam:
+          internalError(c.c.graph.config, "wrong instantiated type!")
+        elif typeRel(c, f[i], x[i], flags) <= isSubtype:
+          # Workaround for regression #4589
+          if f[i].kind != tyTypeDesc: return
+      result = isGeneric
+    elif x.kind == tyGenericInst and concpt.kind == tyConcept:
+      result = if concepts.conceptMatch(c.c, concpt, x, c.bindings, f): isGeneric
+               else: isNone
+    else:
+      let genericBody = f[0]
+      var askip = skippedNone
+      var fskip = skippedNone
+      let aobj = x.skipToObject(askip)
+      let fobj = genericBody.last.skipToObject(fskip)
+      result = typeRel(c, genericBody, x, flags)
+      if result != isNone:
+        # see tests/generics/tgeneric3.nim for an example that triggers this
+        # piece of code:
+        #
+        # proc internalFind[T,D](n: PNode[T,D], key: T): ref TItem[T,D]
+        # proc internalPut[T,D](ANode: ref TNode[T,D], Akey: T, Avalue: D,
+        #                       Oldvalue: var D): ref TNode[T,D]
+        # var root = internalPut[int, int](nil, 312, 312, oldvalue)
+        # var it1 = internalFind(root, 312) # cannot instantiate: 'D'
+        #
+        # we steal the generic parameters from the tyGenericBody:
+        for i in 1..<f.len:
+          let x = idTableGet(c.bindings, genericBody[i-1])
+          if x == nil:
+            discard "maybe fine (for e.g. a==tyNil)"
+          elif x.kind in {tyGenericInvocation, tyGenericParam}:
+            internalError(c.c.graph.config, "wrong instantiated type!")
+          else:
+            let key = f[i]
+            let old = idTableGet(c.bindings, key)
+            if old == nil:
+              put(c, key, x)
+            elif typeRel(c, old, x, flags + {trDontBind}) == isNone:
+              return isNone
+      var depth = -1
+      if fobj != nil and aobj != nil and askip == fskip:
+        depth = isObjectSubtype(c, aobj, fobj, f)
+
+      if result == isNone:
+        # Here object inheriting from generic/specialized generic object
+        # crossing path with metatypes/aliases, so we need to separate them
+        # by checking sym.id
+        let genericSubtype = isGenericSubtype(c, x, f, depth, f)
+        if not (genericSubtype and aobj.sym.id != fobj.sym.id) and aOrig.kind != tyGenericBody:
+          depth = -1
+
+      if depth >= 0:
+        inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0)
+        # bug #4863: We still need to bind generic alias crap, so
+        # we cannot return immediately:
+        result = if depth == 0: isGeneric else: isSubtype
+  of tyAnd:
+    considerPreviousT:
+      result = isEqual
+      for branch in f.kids:
+        let x = typeRel(c, branch, aOrig, flags)
+        if x < isSubtype: return isNone
+        # 'and' implies minimum matching result:
+        if x < result: result = x
+      if result > isGeneric: result = isGeneric
+      bindingRet result
+  of tyOr:
+    considerPreviousT:
+      result = isNone
+      let oldInheritancePenalty = c.inheritancePenalty
+      var minInheritance = maxInheritancePenalty
+      for branch in f.kids:
+        c.inheritancePenalty = -1
+        let x = typeRel(c, branch, aOrig, flags)
+        if x >= result:
+          if  c.inheritancePenalty > -1:
+            minInheritance = min(minInheritance, c.inheritancePenalty)
+          result = x
+      if result >= isIntConv:
+        if minInheritance < maxInheritancePenalty:
+          c.inheritancePenalty = oldInheritancePenalty + minInheritance
+        if result > isGeneric: result = isGeneric
+        bindingRet result
+      else:
+        result = isNone
+  of tyNot:
+    considerPreviousT:
+      if typeRel(c, f.elementType, aOrig, flags) != isNone:
+        return isNone
+
+      bindingRet isGeneric
+  of tyAnything:
+    considerPreviousT:
+      var concrete = concreteType(c, a)
+      if concrete != nil and doBind:
+        put(c, f, concrete)
+      return isGeneric
+  of tyBuiltInTypeClass:
+    considerPreviousT:
+      let target = f.genericHead
+      let targetKind = target.kind
+      var effectiveArgType = reduceToBase(a)
+      effectiveArgType = effectiveArgType.skipTypes({tyBuiltInTypeClass})
+      if targetKind == effectiveArgType.kind:
+        if effectiveArgType.isEmptyContainer:
+          return isNone
+        if targetKind == tyProc:
+          if target.flags * {tfIterator} != effectiveArgType.flags * {tfIterator}:
+            return isNone
+          if tfExplicitCallConv in target.flags and
+              target.callConv != effectiveArgType.callConv:
+            return isNone
+        if doBind: put(c, f, a)
+        return isGeneric
+      else:
+        return isNone
+  of tyUserTypeClassInst, tyUserTypeClass:
+    if f.isResolvedUserTypeClass:
+      result = typeRel(c, f.last, a, flags)
+    else:
+      considerPreviousT:
+        if aOrig == f: return isEqual
+        var matched = matchUserTypeClass(c, f, aOrig)
+        if matched != nil:
+          bindConcreteTypeToUserTypeClass(matched, a)
+          if doBind: put(c, f, matched)
+          result = isGeneric
+        elif a.len > 0 and a.last == f:
+          # Needed for checking `Y` == `Addable` in the following
+          #[
+            type
+              Addable = concept a, type A
+                a + a is A
+              MyType[T: Addable; Y: static T] = object
+          ]#
+          result = isGeneric
+        else:
+          result = isNone
+  of tyConcept:
+    result = if concepts.conceptMatch(c.c, f, a, c.bindings, nil): isGeneric
+             else: isNone
+  of tyCompositeTypeClass:
+    considerPreviousT:
+      let roota = a.skipGenericAlias
+      let rootf = f.last.skipGenericAlias
+      if a.kind == tyGenericInst and roota.base == rootf.base:
+        for i in 1..<rootf.len-1:
+          let ff = rootf[i]
+          let aa = roota[i]
+          result = typeRel(c, ff, aa, flags)
+          if result == isNone: return
+          if ff.kind == tyRange and result != isEqual: return isNone
+      else:
+        result = typeRel(c, rootf.last, a, flags)
+      if result != isNone:
+        put(c, f, a)
+        result = isGeneric
+  of tyGenericParam:
+    let doBindGP = doBind or trBindGenericParam in flags
+    var x = idTableGet(c.bindings, f)
+    if x == nil:
+      if c.callee.kind == tyGenericBody and not c.typedescMatched:
+        # XXX: The fact that generic types currently use tyGenericParam for
+        # their parameters is really a misnomer. tyGenericParam means "match
+        # any value" and what we need is "match any type", which can be encoded
+        # by a tyTypeDesc params. Unfortunately, this requires more substantial
+        # changes in semtypinst and elsewhere.
+        if tfWildcard in a.flags:
+          result = isGeneric
+        elif a.kind == tyTypeDesc:
+          if f.len == 0:
+            result = isGeneric
+          else:
+            internalAssert c.c.graph.config, a.len > 0
+            c.typedescMatched = true
+            var aa = a
+            while aa.kind in {tyTypeDesc, tyGenericParam} and aa.len > 0:
+              aa = last(aa)
+            if aa.kind in {tyGenericParam} + tyTypeClasses:
+              # If the constraint is a genericParam or typeClass this isGeneric
+              return isGeneric
+            result = typeRel(c, f.base, aa, flags)
+            if result > isGeneric: result = isGeneric
+        elif c.isNoCall:
+          if doBindGP:
+            let concrete = concreteType(c, a, f)
+            if concrete == nil: return isNone
+            put(c, f, concrete)
+          result = isGeneric
+        else:
+          result = isNone
+      else:
+        # check if 'T' has a constraint as in 'proc p[T: Constraint](x: T)'
+        if f.len > 0 and f[0].kind != tyNone:
+          result = typeRel(c, f[0], a, flags + {trDontBind, trBindGenericParam})
+          if doBindGP and result notin {isNone, isGeneric}:
+            let concrete = concreteType(c, a, f)
+            if concrete == nil: return isNone
+            put(c, f, concrete)
+          if result in {isEqual, isSubtype}:
+            result = isGeneric
+        elif a.kind == tyTypeDesc:
+          # somewhat special typing rule, the following is illegal:
+          # proc p[T](x: T)
+          # p(int)
+          result = isNone
+        else:
+          result = isGeneric
+
+      if result == isGeneric:
+        var concrete = a
+        if tfWildcard in a.flags:
+          a.sym.transitionGenericParamToType()
+          a.flags.excl tfWildcard
+        elif doBind:
+          # careful: `trDontDont` (set by `checkGeneric`) is not always respected in this call graph.
+          # typRel having two different modes (binding and non-binding) can make things harder to
+          # reason about and maintain. Refactoring typeRel to not be responsible for setting, or
+          # at least validating, bindings can have multiple benefits. This is debatable. I'm not 100% sure.
+          # A design that allows a proper complexity analysis of types like `tyOr` would be ideal.
+          concrete = concreteType(c, a, f)
+          if concrete == nil:
+            return isNone
+        if doBindGP:
+          put(c, f, concrete)
+      elif result > isGeneric:
+        result = isGeneric
+    elif a.kind == tyEmpty:
+      result = isGeneric
+    elif x.kind == tyGenericParam:
+      result = isGeneric
+    else:
+      # This is the bound type - can't benifit from these tallies
+      let
+        inheritancePenaltyOld = c.inheritancePenalty
+      result = typeRel(c, x, a, flags) # check if it fits
+      c.inheritancePenalty = inheritancePenaltyOld
+      if result > isGeneric: result = isGeneric
+  of tyStatic:
+    let prev = idTableGet(c.bindings, f)
+    if prev == nil:
+      if aOrig.kind == tyStatic:
+        if c.c.inGenericContext > 0 and aOrig.n == nil and not c.isNoCall:
+          # don't match unresolved static value to static param to avoid
+          # faulty instantiations in calls in generic bodies
+          # but not for generic invocations as they only check constraints
+          result = isNone
+        elif f.base.kind notin {tyNone, tyGenericParam}:
+          result = typeRel(c, f.base, a, flags)
+          if result != isNone and f.n != nil:
+            var r = tryResolvingStaticExpr(c, f.n)
+            if r == nil: r = f.n
+            if not exprStructuralEquivalent(r, aOrig.n) and
+                not (aOrig.n != nil and aOrig.n.kind == nkIntLit and
+                  inferStaticParam(c, r, aOrig.n.intVal)):
+              result = isNone
+        elif f.base.kind == tyGenericParam:
+          # Handling things like `type A[T; Y: static T] = object`
+          if f.base.len > 0: # There is a constraint, handle it
+            result = typeRel(c, f.base.last, a, flags)
+          else:
+            # No constraint
+            if tfGenericTypeParam in f.flags:
+              result = isGeneric
+            else:
+              # for things like `proc fun[T](a: static[T])`
+              result = typeRel(c, f.base, a, flags)
+        else:
+          result = isGeneric
+        if result != isNone: put(c, f, aOrig)
+      elif aOrig.n != nil and aOrig.n.typ != nil:
+        result = if f.base.kind != tyNone:
+                   typeRel(c, f.last, aOrig.n.typ, flags)
+                 else: isGeneric
+        if result != isNone:
+          var boundType = newTypeS(tyStatic, c.c, aOrig.n.typ)
+          boundType.n = aOrig.n
+          put(c, f, boundType)
+      else:
+        result = isNone
+    elif prev.kind == tyStatic:
+      if aOrig.kind == tyStatic:
+        result = typeRel(c, prev.last, a, flags)
+        if result != isNone and prev.n != nil:
+          if not exprStructuralEquivalent(prev.n, aOrig.n):
+            result = isNone
+      else: result = isNone
+    else:
+      # XXX endless recursion?
+      #result = typeRel(c, prev, aOrig, flags)
+      result = isNone
+  of tyInferred:
+    let prev = f.previouslyInferred
+    if prev != nil:
+      result = typeRel(c, prev, a, flags)
+    else:
+      result = typeRel(c, f.base, a, flags)
+      if result != isNone:
+        c.inferredTypes.add f
+        f.add a
+  of tyTypeDesc:
+    var prev = idTableGet(c.bindings, f)
+    if prev == nil:
+      # proc foo(T: typedesc, x: T)
+      # when `f` is an unresolved typedesc, `a` could be any
+      # type, so we should not perform this check earlier
+      if c.c.inGenericContext > 0 and a.containsUnresolvedType:
+        # generic type bodies can sometimes compile call expressions
+        # prevent unresolved generic parameters from being passed to procs as
+        # typedesc parameters
+        result = isNone
+      elif a.kind != tyTypeDesc:
+        if a.kind == tyGenericParam and tfWildcard in a.flags:
+          # TODO: prevent `a` from matching as a wildcard again
+          result = isGeneric
+        else:
+          result = isNone
+      elif f.base.kind == tyNone:
+        result = isGeneric
+      else:
+        result = typeRel(c, f.base, a.base, flags)
+
+      if result != isNone:
+        put(c, f, a)
+    else:
+      if tfUnresolved in f.flags:
+        result = typeRel(c, prev.base, a, flags)
+      elif a.kind == tyTypeDesc:
+        result = typeRel(c, prev.base, a.base, flags)
+      else:
+        result = isNone
+  of tyTyped:
+    if aOrig != nil:
+      put(c, f, aOrig)
+    result = isGeneric
+  of tyError:
+    result = isEqual
+  of tyFromExpr:
+    # fix the expression, so it contains the already instantiated types
+    if f.n == nil or f.n.kind == nkEmpty: return isGeneric
+    if c.c.inGenericContext > 0:
+      # need to delay until instantiation
+      # also prevent infinite recursion below
+      return isNone
+    inc c.c.inGenericContext # to generate tyFromExpr again if unresolved
+    # use prepareNode for consistency with other tyFromExpr in semtypinst:
+    let instantiated = prepareTypesInBody(c.c, c.bindings, f.n)
+    let reevaluated = c.c.semExpr(c.c, instantiated).typ
+    dec c.c.inGenericContext
+    case reevaluated.kind
+    of tyFromExpr:
+      # not resolved
+      result = isNone
+    of tyTypeDesc:
+      result = typeRel(c, reevaluated.base, a, flags)
+    of tyStatic:
+      result = typeRel(c, reevaluated.base, a, flags)
+      if result != isNone and reevaluated.n != nil:
+        if not exprStructuralEquivalent(aOrig.n, reevaluated.n):
+          result = isNone
+    else:
+      # bug #14136: other types are just like 'tyStatic' here:
+      result = typeRel(c, reevaluated, a, flags)
+      if result != isNone and reevaluated.n != nil:
+        if not exprStructuralEquivalent(aOrig.n, reevaluated.n):
+          result = isNone
+  of tyNone:
+    if a.kind == tyNone: result = isEqual
+  else:
+    internalError c.c.graph.config, " unknown type kind " & $f.kind
+
+when false:
+  var nowDebug = false
+  var dbgCount = 0
+
+  proc typeRel(c: var TCandidate, f, aOrig: PType,
+              flags: TTypeRelFlags = {}): TTypeRelation =
+    if nowDebug:
+      echo f, " <- ", aOrig
+      inc dbgCount
+      if dbgCount == 2:
+        writeStackTrace()
+    result = typeRelImpl(c, f, aOrig, flags)
+    if nowDebug:
+      echo f, " <- ", aOrig, " res ", result
+
+proc cmpTypes*(c: PContext, f, a: PType): TTypeRelation =
+  var m = newCandidate(c, f)
+  result = typeRel(m, f, a)
+
+proc getInstantiatedType(c: PContext, arg: PNode, m: TCandidate,
+                         f: PType): PType =
+  result = idTableGet(m.bindings, f)
+  if result == nil:
+    result = generateTypeInstance(c, m.bindings, arg, f)
+  if result == nil:
+    internalError(c.graph.config, arg.info, "getInstantiatedType")
+    result = errorType(c)
+
+proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
+                  c: PContext): PNode =
+  result = newNodeI(kind, arg.info)
+  if containsGenericType(f):
+    if not m.matchedErrorType:
+      result.typ = getInstantiatedType(c, arg, m, f).skipTypes({tySink})
+    else:
+      result.typ = errorType(c)
+  else:
+    result.typ = f.skipTypes({tySink})
+  # keep varness
+  if arg.typ != nil and arg.typ.kind == tyVar:
+    result.typ = toVar(result.typ, tyVar, c.idgen)
+  else:
+    result.typ = result.typ.skipTypes({tyVar})
+
+  if result.typ == nil: internalError(c.graph.config, arg.info, "implicitConv")
+  result.add c.graph.emptyNode
+  if arg.typ != nil and arg.typ.kind == tyLent:
+    let a = newNodeIT(nkHiddenDeref, arg.info, arg.typ.elementType)
+    a.add arg
+    result.add a
+  else:
+    result.add arg
+
+proc convertLiteral(kind: TNodeKind, c: PContext, m: TCandidate; n: PNode, newType: PType): PNode =
+  # based off changeType but generates implicit conversions instead
+  template addConsiderNil(s, node) =
+    let val = node
+    if val.isNil: return nil
+    s.add(val)
+  case n.kind
+  of nkCurly:
+    result = copyNode(n)
+    for i in 0..<n.len:
+      if n[i].kind == nkRange:
+        var x = copyNode(n[i])
+        x.addConsiderNil convertLiteral(kind, c, m, n[i][0], elemType(newType))
+        x.addConsiderNil convertLiteral(kind, c, m, n[i][1], elemType(newType))
+        result.add x
+      else:
+        result.addConsiderNil convertLiteral(kind, c, m, n[i], elemType(newType))
+    result.typ = newType
+    return
+  of nkBracket:
+    result = copyNode(n)
+    for i in 0..<n.len:
+      result.addConsiderNil convertLiteral(kind, c, m, n[i], elemType(newType))
+    result.typ = newType
+    return
+  of nkPar, nkTupleConstr:
+    let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
+    if tup.kind == tyTuple:
+      result = copyNode(n)
+      if n.len > 0 and n[0].kind == nkExprColonExpr:
+        # named tuple?
+        for i in 0..<n.len:
+          var name = n[i][0]
+          if name.kind != nkSym:
+            #globalError(c.config, name.info, "invalid tuple constructor")
+            return nil
+          if tup.n != nil:
+            var f = getSymFromList(tup.n, name.sym.name)
+            if f == nil:
+              #globalError(c.config, name.info, "unknown identifier: " & name.sym.name.s)
+              return nil
+            result.addConsiderNil convertLiteral(kind, c, m, n[i][1], f.typ)
+          else:
+            result.addConsiderNil convertLiteral(kind, c, m, n[i][1], tup[i])
+      else:
+        for i in 0..<n.len:
+          result.addConsiderNil convertLiteral(kind, c, m, n[i], tup[i])
+      result.typ = newType
+      return
+  of nkCharLit..nkUInt64Lit:
+    if n.kind != nkUInt64Lit and not sameTypeOrNil(n.typ, newType) and isOrdinalType(newType):
+      let value = n.intVal
+      if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType):
+        return nil
+      result = copyNode(n)
+      result.typ = newType
+      return
+  of nkFloatLit..nkFloat64Lit:
+    if newType.skipTypes(abstractVarRange-{tyTypeDesc}).kind == tyFloat:
+      if not floatRangeCheck(n.floatVal, newType):
+        return nil
+      result = copyNode(n)
+      result.typ = newType
+      return
+  of nkSym:
+    if n.sym.kind == skEnumField and not sameTypeOrNil(n.sym.typ, newType) and isOrdinalType(newType):
+      let value = n.sym.position
+      if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType):
+        return nil
+      result = copyNode(n)
+      result.typ = newType
+      return
+  else: discard
+  return implicitConv(kind, newType, n, m, c)
+
+proc isLValue(c: PContext; n: PNode, isOutParam = false): bool {.inline.} =
+  let aa = isAssignable(nil, n)
+  case aa
+  of arLValue, arLocalLValue, arStrange:
+    result = true
+  of arDiscriminant:
+    result = c.inUncheckedAssignSection > 0
+  of arAddressableConst:
+    let sym = getRoot(n)
+    result = strictDefs in c.features and sym != nil and sym.kind == skLet and isOutParam
+  else:
+    result = false
+
+proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
+                   arg: PNode): PNode =
+  result = nil
+  for i in 0..<c.converters.len:
+    var src = c.converters[i].typ.firstParamType
+    var dest = c.converters[i].typ.returnType
+    # for generic type converters we need to check 'src <- a' before
+    # 'f <- dest' in order to not break the unification:
+    # see tests/tgenericconverter:
+    let srca = typeRel(m, src, a)
+    if srca notin {isEqual, isGeneric, isSubtype}: continue
+
+    # What's done below matches the logic in ``matchesAux``
+    let constraint = c.converters[i].typ.n[1].sym.constraint
+    if not constraint.isNil and not matchNodeKinds(constraint, arg):
+      continue
+    if src.kind in {tyVar, tyLent} and not isLValue(c, arg):
+      continue
+
+    let destIsGeneric = containsGenericType(dest)
+    if destIsGeneric:
+      dest = generateTypeInstance(c, m.bindings, arg, dest)
+    let fdest = typeRel(m, f, dest)
+    if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind in {tyVar}):
+      markUsed(c, arg.info, c.converters[i])
+      var s = newSymNode(c.converters[i])
+      s.typ = c.converters[i].typ
+      s.info = arg.info
+      result = newNodeIT(nkHiddenCallConv, arg.info, dest)
+      result.add s
+      # We build the call expression by ourselves in order to avoid passing this
+      # expression trough the semantic check phase once again so let's make sure
+      # it is correct
+      var param: PNode = nil
+      if srca == isSubtype:
+        param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c)
+      elif src.kind in {tyVar}:
+        # Analyse the converter return type.
+        param = newNodeIT(nkHiddenAddr, arg.info, s.typ.firstParamType)
+        param.add copyTree(arg)
+      else:
+        param = copyTree(arg)
+      result.add param
+
+      if dest.kind in {tyVar, tyLent}:
+        dest.flags.incl tfVarIsPtr
+        result = newDeref(result)
+
+      inc(m.convMatches)
+      if not m.genericConverter:
+        m.genericConverter = srca == isGeneric or destIsGeneric
+      return result
+
+proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
+                    arg: PNode): PNode =
+  # arg.typ can be nil in 'suggest':
+  if isNil(arg.typ): return nil
+
+  # sem'checking for 'echo' needs to be re-entrant:
+  # XXX we will revisit this issue after 0.10.2 is released
+  if f == arg.typ and arg.kind == nkHiddenStdConv: return arg
+
+  var call = newNodeI(nkCall, arg.info)
+  call.add(f.n.copyTree)
+  call.add(arg.copyTree)
+  # XXX: This would be much nicer if we don't use `semTryExpr` and
+  # instead we directly search for overloads with `resolveOverloads`:
+  result = c.semTryExpr(c, call, {efNoSem2Check})
+
+  if result != nil:
+    if result.typ == nil: return nil
+    # bug #13378, ensure we produce a real generic instantiation:
+    result = c.semExpr(c, call, {efNoSem2Check})
+    # resulting type must be consistent with the other arguments:
+    var r = typeRel(m, f[0], result.typ)
+    if r < isGeneric: return nil
+    if result.kind == nkCall: result.transitionSonsKind(nkHiddenCallConv)
+    inc(m.convMatches)
+    if r == isGeneric:
+      result.typ = getInstantiatedType(c, arg, m, base(f))
+    m.baseTypeMatch = true
+
+proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) =
+  case r
+  of isConvertible, isIntConv: inc(m.convMatches, convMatch)
+  of isSubtype, isSubrange: inc(m.subtypeMatches)
+  of isGeneric, isInferred, isBothMetaConvertible: inc(m.genericMatches)
+  of isFromIntLit: inc(m.intConvMatches, 256)
+  of isInferredConvertible:
+    inc(m.convMatches)
+  of isEqual: inc(m.exactMatches)
+  of isNone: discard
+
+template matchesVoidProc(t: PType): bool =
+  (t.kind == tyProc and t.len == 1 and t.returnType == nil) or
+    (t.kind == tyBuiltInTypeClass and t.elementType.kind == tyProc)
+
+proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
+                        argSemantized, argOrig: PNode): PNode =
+  result = nil
+  var
+    fMaybeStatic = f.skipTypes({tyDistinct})
+    arg = argSemantized
+    a = a
+    c = m.c
+  if tfHasStatic in fMaybeStatic.flags:
+    # XXX: When implicit statics are the default
+    # this will be done earlier - we just have to
+    # make sure that static types enter here
+
+    # Zahary: weaken tyGenericParam and call it tyGenericPlaceholder
+    # and finally start using tyTypedesc for generic types properly.
+    # Araq: This would only shift the problems around, in 'proc p[T](x: T)'
+    # the T is NOT a typedesc.
+    if a.kind == tyGenericParam and tfWildcard in a.flags:
+      a.assignType(f)
+      # put(m.bindings, f, a)
+      return argSemantized
+
+    if a.kind == tyStatic:
+      if m.callee.kind == tyGenericBody and
+         a.n == nil and
+         tfGenericTypeParam notin a.flags:
+        return newNodeIT(nkType, argOrig.info, makeTypeFromExpr(c, arg))
+    elif a.kind == tyFromExpr and c.inGenericContext > 0:
+      # don't try to evaluate
+      discard
+    elif arg.kind != nkEmpty:
+      var evaluated = c.semTryConstExpr(c, arg)
+      if evaluated != nil:
+        # Don't build the type in-place because `evaluated` and `arg` may point
+        # to the same object and we'd end up creating recursive types (#9255)
+        let typ = newTypeS(tyStatic, c, son = evaluated.typ)
+        typ.n = evaluated
+        arg = copyTree(arg) # fix #12864
+        arg.typ = typ
+        a = typ
+      else:
+        if m.callee.kind == tyGenericBody:
+          if f.kind == tyStatic and typeRel(m, f.base, a) != isNone:
+            result = makeStaticExpr(m.c, arg)
+            result.typ.flags.incl tfUnresolved
+            result.typ.n = arg
+            return
+
+  let oldInheritancePenalty = m.inheritancePenalty
+  var r = typeRel(m, f, a)
+
+  # This special typing rule for macros and templates is not documented
+  # anywhere and breaks symmetry. It's hard to get rid of though, my
+  # custom seqs example fails to compile without this:
+  if r != isNone and m.calleeSym != nil and
+    m.calleeSym.kind in {skMacro, skTemplate}:
+    # XXX: duplicating this is ugly, but we cannot (!) move this
+    # directly into typeRel using return-like templates
+    incMatches(m, r)
+    if f.kind == tyTyped:
+      return arg
+    elif f.kind == tyTypeDesc:
+      return arg
+    elif f.kind == tyStatic and arg.typ.n != nil:
+      return arg.typ.n
+    else:
+      return argSemantized # argOrig
+
+  block instantiateGenericRoutine:
+    # In the case where the matched value is a generic proc, we need to
+    # fully instantiate it and then rerun typeRel to make sure it matches.
+    # instantiationCounter is for safety to avoid any infinite loop,
+    #  I don't have any example when it is needed.
+    # lastBindingCount is used to check whether m.bindings remains the same,
+    #  because in that case there is no point in continuing.
+    var instantiationCounter = 0
+    var lastBindingCount = -1
+    while r in {isBothMetaConvertible, isInferred, isInferredConvertible} and
+        lastBindingCount != m.bindings.len and
+        instantiationCounter < 100:
+      lastBindingCount = m.bindings.len
+      inc(instantiationCounter)
+      if arg.kind in {nkProcDef, nkFuncDef, nkIteratorDef} + nkLambdaKinds:
+        result = c.semInferredLambda(c, m.bindings, arg)
+      elif arg.kind != nkSym:
+        return nil
+      elif arg.sym.kind in {skMacro, skTemplate}:
+        return nil
+      else:
+        if arg.sym.ast == nil:
+          return nil
+        let inferred = c.semGenerateInstance(c, arg.sym, m.bindings, arg.info)
+        result = newSymNode(inferred, arg.info)
+      arg = result
+      r = typeRel(m, f, arg.typ)
+
+  case r
+  of isConvertible:
+    if f.skipTypes({tyRange}).kind in {tyInt, tyUInt}:
+      inc(m.convMatches)
+    inc(m.convMatches)
+    if skipTypes(f, abstractVar-{tyTypeDesc}).kind == tySet:
+      if tfIsConstructor in a.flags and arg.kind == nkCurly:
+        # we marked the set as convertible only because the arg is a literal
+        # in which case we individually convert each element
+        let t =
+          if containsGenericType(f):
+            getInstantiatedType(c, arg, m, f).skipTypes({tySink})
+          else:
+            f.skipTypes({tySink})
+        result = convertLiteral(nkHiddenStdConv, c, m, arg, t)
+      else:
+        result = nil
+    else:
+      result = implicitConv(nkHiddenStdConv, f, arg, m, c)
+  of isIntConv:
+    # I'm too lazy to introduce another ``*matches`` field, so we conflate
+    # ``isIntConv`` and ``isIntLit`` here:
+    if f.skipTypes({tyRange}).kind notin {tyInt, tyUInt}:
+      inc(m.intConvMatches)
+    inc(m.intConvMatches)
+    result = implicitConv(nkHiddenStdConv, f, arg, m, c)
+  of isSubtype:
+    inc(m.subtypeMatches)
+    if f.kind == tyTypeDesc:
+      result = arg
+    else:
+      result = implicitConv(nkHiddenSubConv, f, arg, m, c)
+  of isSubrange:
+    inc(m.subtypeMatches)
+    if f.kind in {tyVar}:
+      result = arg
+    else:
+      result = implicitConv(nkHiddenStdConv, f, arg, m, c)
+  of isInferred:
+    # result should be set in above while loop:
+    assert result != nil
+    inc(m.genericMatches)
+  of isInferredConvertible:
+    # result should be set in above while loop:
+    assert result != nil
+    inc(m.convMatches)
+    result = implicitConv(nkHiddenStdConv, f, result, m, c)
+  of isGeneric:
+    inc(m.genericMatches)
+    if arg.typ == nil:
+      result = arg
+    elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple or cmpInheritancePenalty(oldInheritancePenalty, m.inheritancePenalty) > 0:
+      result = implicitConv(nkHiddenSubConv, f, arg, m, c)
+    elif arg.typ.isEmptyContainer:
+      result = arg.copyTree
+      result.typ = getInstantiatedType(c, arg, m, f)
+    else:
+      result = arg
+  of isBothMetaConvertible:
+    # result should be set in above while loop:
+    assert result != nil
+    inc(m.convMatches)
+    result = arg
+  of isFromIntLit:
+    # too lazy to introduce another ``*matches`` field, so we conflate
+    # ``isIntConv`` and ``isIntLit`` here:
+    inc(m.intConvMatches, 256)
+    result = implicitConv(nkHiddenStdConv, f, arg, m, c)
+  of isEqual:
+    inc(m.exactMatches)
+    result = arg
+    let ff = skipTypes(f, abstractVar-{tyTypeDesc})
+    if ff.kind == tyTuple or
+      (arg.typ != nil and skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple):
+      result = implicitConv(nkHiddenSubConv, f, arg, m, c)
+  of isNone:
+    # do not do this in ``typeRel`` as it then can't infer T in ``ref T``:
+    if a.kind == tyFromExpr: return nil
+    elif a.kind == tyError:
+      inc(m.genericMatches)
+      m.matchedErrorType = true
+      return arg
+    elif a.kind == tyVoid and f.matchesVoidProc and argOrig.kind == nkStmtList:
+      # lift do blocks without params to lambdas
+      # now deprecated
+      message(c.config, argOrig.info, warnStmtListLambda)
+      let p = c.graph
+      let lifted = c.semExpr(c, newProcNode(nkDo, argOrig.info, body = argOrig,
+          params = nkFormalParams.newTree(p.emptyNode), name = p.emptyNode, pattern = p.emptyNode,
+          genericParams = p.emptyNode, pragmas = p.emptyNode, exceptions = p.emptyNode), {})
+      if f.kind == tyBuiltInTypeClass:
+        inc m.genericMatches
+        put(m, f, lifted.typ)
+      inc m.convMatches
+      return implicitConv(nkHiddenStdConv, f, lifted, m, c)
+    result = userConvMatch(c, m, f, a, arg)
+    # check for a base type match, which supports varargs[T] without []
+    # constructor in a call:
+    if result == nil and f.kind == tyVarargs:
+      if f.n != nil:
+        # Forward to the varargs converter
+        result = localConvMatch(c, m, f, a, arg)
+      elif f[0].kind == tyTyped:
+        inc m.genericMatches
+        result = arg
+      else:
+        r = typeRel(m, base(f), a)
+        case r
+        of isGeneric:
+          inc(m.convMatches)
+          result = copyTree(arg)
+          result.typ = getInstantiatedType(c, arg, m, base(f))
+          m.baseTypeMatch = true
+        of isFromIntLit:
+          inc(m.intConvMatches, 256)
+          result = implicitConv(nkHiddenStdConv, f[0], arg, m, c)
+          m.baseTypeMatch = true
+        of isEqual:
+          inc(m.convMatches)
+          result = copyTree(arg)
+          m.baseTypeMatch = true
+        of isSubtype: # bug #4799, varargs accepting subtype relation object
+          inc(m.subtypeMatches)
+          if base(f).kind == tyTypeDesc:
+            result = arg
+          else:
+            result = implicitConv(nkHiddenSubConv, base(f), arg, m, c)
+          m.baseTypeMatch = true
+        else:
+          result = userConvMatch(c, m, base(f), a, arg)
+          if result != nil: m.baseTypeMatch = true
+
+proc staticAwareTypeRel(m: var TCandidate, f: PType, arg: var PNode): TTypeRelation =
+  if f.kind == tyStatic and f.base.kind == tyProc:
+    # The ast of the type does not point to the symbol.
+    # Without this we will never resolve a `static proc` with overloads
+    let copiedNode = copyNode(arg)
+    copiedNode.typ = exactReplica(copiedNode.typ)
+    copiedNode.typ.n = arg
+    arg = copiedNode
+  typeRel(m, f, arg.typ)
+
+
+proc paramTypesMatch*(m: var TCandidate, f, a: PType,
+                      arg, argOrig: PNode): PNode =
+  if arg == nil or arg.kind notin nkSymChoices:
+    result = paramTypesMatchAux(m, f, a, arg, argOrig)
+  else:
+    # symbol kinds that don't participate in symchoice type disambiguation:
+    let matchSet = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage}
+
+    var best = -1
+    result = arg
+
+    var actingF = f
+    if f.kind == tyVarargs:
+      if m.calleeSym.kind in {skTemplate, skMacro}:
+        actingF = f[0]
+    if actingF.kind in {tyTyped, tyUntyped}:
+      var
+        bestScope = -1
+        counts = 0
+      for i in 0..<arg.len:
+        if arg[i].sym.kind in matchSet:
+          let thisScope = cmpScopes(m.c, arg[i].sym)
+          if thisScope > bestScope:
+            best = i
+            bestScope = thisScope
+            counts = 0
+          elif thisScope == bestScope:
+            inc counts
+      if best == -1:
+        result = nil
+      elif counts > 0:
+        m.genericMatches = 1
+        best = -1
+    else:
+      # CAUTION: The order depends on the used hashing scheme. Thus it is
+      # incorrect to simply use the first fitting match. However, to implement
+      # this correctly is inefficient. We have to copy `m` here to be able to
+      # roll back the side effects of the unification algorithm.
+      let c = m.c
+      var
+        x = newCandidate(c, m.callee)  # potential "best"
+        y = newCandidate(c, m.callee)  # potential competitor with x
+        z = newCandidate(c, m.callee)  # buffer for copies of m
+      x.calleeSym = m.calleeSym
+      y.calleeSym = m.calleeSym
+      z.calleeSym = m.calleeSym
+
+      for i in 0..<arg.len:
+        if arg[i].sym.kind in matchSet:
+          copyCandidate(z, m)
+          z.callee = arg[i].typ
+          if arg[i].sym.kind == skType and z.callee.kind != tyTypeDesc:
+            # creating the symchoice with the type sym having typedesc type
+            # breaks a lot of stuff, so we make the typedesc type here
+            # mirrored from `newSymNodeTypeDesc`
+            z.callee = newType(tyTypeDesc, c.idgen, arg[i].sym.owner)
+            z.callee.addSonSkipIntLit(arg[i].sym.typ, c.idgen)
+          if tfUnresolved in z.callee.flags: continue
+          z.calleeSym = arg[i].sym
+          z.calleeScope = cmpScopes(m.c, arg[i].sym)
+          # XXX this is still all wrong: (T, T) should be 2 generic matches
+          # and  (int, int) 2 exact matches, etc. Essentially you cannot call
+          # typeRel here and expect things to work!
+          let r = staticAwareTypeRel(z, f, arg[i])
+          incMatches(z, r, 2)
+          if r != isNone:
+            z.state = csMatch
+            case x.state
+            of csEmpty, csNoMatch:
+              x = z
+              best = i
+            of csMatch:
+              let cmp = cmpCandidates(x, z, isFormal=false)
+              if cmp < 0:
+                best = i
+                x = z
+              elif cmp == 0:
+                y = z           # z is as good as x
+
+      if x.state == csEmpty:
+        result = nil
+      elif y.state == csMatch and cmpCandidates(x, y, isFormal=false) == 0:
+        if x.state != csMatch:
+          internalError(m.c.graph.config, arg.info, "x.state is not csMatch")
+        result = nil
+    if best > -1 and result != nil:
+      # only one valid interpretation found:
+      markUsed(m.c, arg.info, arg[best].sym)
+      onUse(arg.info, arg[best].sym)
+      result = paramTypesMatchAux(m, f, arg[best].typ, arg[best], argOrig)
+  when false:
+    if m.calleeSym != nil and m.calleeSym.name.s == "[]":
+      echo m.c.config $ arg.info, " for ", m.calleeSym.name.s, " ", m.c.config $ m.calleeSym.info
+      writeMatches(m)
+
+proc setSon(father: PNode, at: int, son: PNode) =
+  let oldLen = father.len
+  if oldLen <= at:
+    setLen(father.sons, at + 1)
+  father[at] = son
+  # insert potential 'void' parameters:
+  #for i in oldLen..<at:
+  #  father[i] = newNodeIT(nkEmpty, son.info, getSysType(tyVoid))
+
+# we are allowed to modify the calling node in the 'prepare*' procs:
+proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode =
+  if formal.kind == tyUntyped and formal.len != 1:
+    # {tyTypeDesc, tyUntyped, tyTyped, tyError}:
+    # a.typ == nil is valid
+    result = a
+  elif a.typ.isNil:
+    if formal.kind == tyIterable:
+      let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable}
+      result = c.semOperand(c, a, flags)
+    else:
+      # XXX This is unsound! 'formal' can differ from overloaded routine to
+      # overloaded routine!
+      let flags = {efDetermineType, efAllowStmt}
+                  #if formal.kind == tyIterable: {efDetermineType, efWantIterator}
+                  #else: {efDetermineType, efAllowStmt}
+                  #elif formal.kind == tyTyped: {efDetermineType, efWantStmt}
+                  #else: {efDetermineType}
+      result = c.semOperand(c, a, flags)
+  else:
+    result = a
+    considerGenSyms(c, result)
+    if result.kind != nkHiddenDeref and result.typ.kind in {tyVar, tyLent} and c.matchedConcept == nil:
+      result = newDeref(result)
+
+proc prepareOperand(c: PContext; a: PNode): PNode =
+  if a.typ.isNil:
+    result = c.semOperand(c, a, {efDetermineType})
+  else:
+    result = a
+    considerGenSyms(c, result)
+
+proc prepareNamedParam(a: PNode; c: PContext) =
+  if a[0].kind != nkIdent:
+    var info = a[0].info
+    a[0] = newIdentNode(considerQuotedIdent(c, a[0]), info)
+
+proc arrayConstr(c: PContext, n: PNode): PType =
+  result = newTypeS(tyArray, c)
+  rawAddSon(result, makeRangeType(c, 0, 0, n.info))
+  addSonSkipIntLit(result, skipTypes(n.typ,
+      {tyVar, tyLent, tyOrdinal}), c.idgen)
+
+proc arrayConstr(c: PContext, info: TLineInfo): PType =
+  result = newTypeS(tyArray, c)
+  rawAddSon(result, makeRangeType(c, 0, -1, info))
+  rawAddSon(result, newTypeS(tyEmpty, c)) # needs an empty basetype!
+
+proc incrIndexType(t: PType) =
+  assert t.kind == tyArray
+  inc t.indexType.n[1].intVal
+
+template isVarargsUntyped(x): untyped =
+  x.kind == tyVarargs and x[0].kind == tyUntyped
+
+template isVarargsTyped(x): untyped =
+  x.kind == tyVarargs and x[0].kind == tyTyped
+
+proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
+  # see https://github.com/nim-lang/RFCs/issues/405
+  result = int.high
+  for a2 in countdown(n.len-1, 0):
+    # checking `nfBlockArg in n[a2].flags` wouldn't work inside templates
+    if n[a2].kind != nkStmtList: break
+    let formalLast = m.callee.n[m.callee.n.len - (n.len - a2)]
+    # parameter has to occupy space (no default value, not void or varargs)
+    if formalLast.kind == nkSym and formalLast.sym.ast == nil and
+        formalLast.sym.typ.kind notin {tyVoid, tyVarargs}:
+      result = a2
+    else: break
+
+proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =
+
+  template noMatch() =
+    c.mergeShadowScope #merge so that we don't have to resem for later overloads
+    m.state = csNoMatch
+    m.firstMismatch.arg = a
+    m.firstMismatch.formal = formal
+    return
+
+  template checkConstraint(n: untyped) {.dirty.} =
+    if not formal.constraint.isNil and sfCodegenDecl notin formal.flags:
+      if matchNodeKinds(formal.constraint, n):
+        # better match over other routines with no such restriction:
+        inc(m.genericMatches, 100)
+      else:
+        noMatch()
+
+    if formal.typ.kind in {tyVar}:
+      let argConverter = if arg.kind == nkHiddenDeref: arg[0] else: arg
+      if argConverter.kind == nkHiddenCallConv:
+        if argConverter.typ.kind notin {tyVar}:
+          m.firstMismatch.kind = kVarNeeded
+          noMatch()
+      elif not (isLValue(c, n, isOutParam(formal.typ))):
+        m.firstMismatch.kind = kVarNeeded
+        noMatch()
+
+  m.state = csMatch # until proven otherwise
+  m.firstMismatch = MismatchInfo()
+  m.call = newNodeIT(n.kind, n.info, m.callee.base)
+  m.call.add n[0]
+
+  var
+    a = 1 # iterates over the actual given arguments
+    f = if m.callee.kind != tyGenericBody: 1
+        else: 0 # iterates over formal parameters
+    arg: PNode = nil # current prepared argument
+    formalLen = m.callee.n.len
+    formal = if formalLen > 1: m.callee.n[1].sym else: nil # current routine parameter
+    container: PNode = nil # constructed container
+  let firstArgBlock = findFirstArgBlock(m, n)
+  while a < n.len:
+    c.openShadowScope
+
+    if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped:
+      formal = m.callee.n[f].sym
+      incl(marker, formal.position)
+
+      if n[a].kind == nkHiddenStdConv:
+        doAssert n[a][0].kind == nkEmpty and
+                 n[a][1].kind in {nkBracket, nkArgList}
+        # Steal the container and pass it along
+        setSon(m.call, formal.position + 1, n[a][1])
+      else:
+        if container.isNil:
+          container = newNodeIT(nkArgList, n[a].info, arrayConstr(c, n.info))
+          setSon(m.call, formal.position + 1, container)
+        else:
+          incrIndexType(container.typ)
+        container.add n[a]
+    elif n[a].kind == nkExprEqExpr:
+      # named param
+      m.firstMismatch.kind = kUnknownNamedParam
+      # check if m.callee has such a param:
+      prepareNamedParam(n[a], c)
+      if n[a][0].kind != nkIdent:
+        localError(c.config, n[a].info, "named parameter has to be an identifier")
+        noMatch()
+      formal = getNamedParamFromList(m.callee.n, n[a][0].ident)
+      if formal == nil:
+        # no error message!
+        noMatch()
+      if containsOrIncl(marker, formal.position):
+        m.firstMismatch.kind = kAlreadyGiven
+        # already in namedParams, so no match
+        # we used to produce 'errCannotBindXTwice' here but see
+        # bug #3836 of why that is not sound (other overload with
+        # different parameter names could match later on):
+        when false: localError(n[a].info, errCannotBindXTwice, formal.name.s)
+        noMatch()
+      m.baseTypeMatch = false
+      m.typedescMatched = false
+      n[a][1] = prepareOperand(c, formal.typ, n[a][1])
+      n[a].typ = n[a][1].typ
+      arg = paramTypesMatch(m, formal.typ, n[a].typ,
+                                n[a][1], n[a][1])
+      m.firstMismatch.kind = kTypeMismatch
+      if arg == nil:
+        noMatch()
+      checkConstraint(n[a][1])
+      if m.baseTypeMatch:
+        #assert(container == nil)
+        container = newNodeIT(nkBracket, n[a].info, arrayConstr(c, arg))
+        container.add arg
+        setSon(m.call, formal.position + 1, container)
+        if f != formalLen - 1: container = nil
+      else:
+        setSon(m.call, formal.position + 1, arg)
+      inc f
+    else:
+      # unnamed param
+      if f >= formalLen:
+        # too many arguments?
+        if tfVarargs in m.callee.flags:
+          # is ok... but don't increment any counters...
+          # we have no formal here to snoop at:
+          n[a] = prepareOperand(c, n[a])
+          if skipTypes(n[a].typ, abstractVar-{tyTypeDesc}).kind==tyString:
+            m.call.add implicitConv(nkHiddenStdConv,
+                  getSysType(c.graph, n[a].info, tyCstring),
+                  copyTree(n[a]), m, c)
+          else:
+            m.call.add copyTree(n[a])
+        elif formal != nil and formal.typ.kind == tyVarargs:
+          m.firstMismatch.kind = kTypeMismatch
+          # beware of the side-effects in 'prepareOperand'! So only do it for
+          # varargs matching. See tests/metatype/tstatic_overloading.
+          m.baseTypeMatch = false
+          m.typedescMatched = false
+          incl(marker, formal.position)
+          n[a] = prepareOperand(c, formal.typ, n[a])
+          arg = paramTypesMatch(m, formal.typ, n[a].typ,
+                                    n[a], nOrig[a])
+          if arg != nil and m.baseTypeMatch and container != nil:
+            container.add arg
+            incrIndexType(container.typ)
+            checkConstraint(n[a])
+          else:
+            noMatch()
+        else:
+          m.firstMismatch.kind = kExtraArg
+          noMatch()
+      else:
+        if m.callee.n[f].kind != nkSym:
+          internalError(c.config, n[a].info, "matches")
+          noMatch()
+        if flexibleOptionalParams in c.features and a >= firstArgBlock:
+          f = max(f, m.callee.n.len - (n.len - a))
+        formal = m.callee.n[f].sym
+        m.firstMismatch.kind = kTypeMismatch
+        if containsOrIncl(marker, formal.position) and container.isNil:
+          m.firstMismatch.kind = kPositionalAlreadyGiven
+          # positional param already in namedParams: (see above remark)
+          when false: localError(n[a].info, errCannotBindXTwice, formal.name.s)
+          noMatch()
+
+        if formal.typ.isVarargsUntyped:
+          if container.isNil:
+            container = newNodeIT(nkArgList, n[a].info, arrayConstr(c, n.info))
+            setSon(m.call, formal.position + 1, container)
+          else:
+            incrIndexType(container.typ)
+          container.add n[a]
+        else:
+          m.baseTypeMatch = false
+          m.typedescMatched = false
+          n[a] = prepareOperand(c, formal.typ, n[a])
+          arg = paramTypesMatch(m, formal.typ, n[a].typ,
+                                    n[a], nOrig[a])
+          if arg == nil:
+            noMatch()
+          if formal.typ.isVarargsTyped and m.calleeSym.kind in {skTemplate, skMacro}:
+            if container.isNil:
+              container = newNodeIT(nkBracket, n[a].info, arrayConstr(c, n.info))
+              setSon(m.call, formal.position + 1, implicitConv(nkHiddenStdConv, formal.typ, container, m, c))
+            else:
+              incrIndexType(container.typ)
+            container.add n[a]
+            f = max(f, formalLen - n.len + a + 1)
+          elif m.baseTypeMatch:
+            assert formal.typ.kind == tyVarargs
+            #assert(container == nil)
+            if container.isNil:
+              container = newNodeIT(nkBracket, n[a].info, arrayConstr(c, arg))
+              container.typ.flags.incl tfVarargs
+            else:
+              incrIndexType(container.typ)
+            container.add arg
+            setSon(m.call, formal.position + 1,
+                   implicitConv(nkHiddenStdConv, formal.typ, container, m, c))
+            #if f != formalLen - 1: container = nil
+
+            # pick the formal from the end, so that 'x, y, varargs, z' works:
+            f = max(f, formalLen - n.len + a + 1)
+          elif formal.typ.kind != tyVarargs or container == nil:
+            setSon(m.call, formal.position + 1, arg)
+            inc f
+            container = nil
+          else:
+            # we end up here if the argument can be converted into the varargs
+            # formal (e.g. seq[T] -> varargs[T]) but we have already instantiated
+            # a container
+            #assert arg.kind == nkHiddenStdConv # for 'nim check'
+            # this assertion can be off
+            localError(c.config, n[a].info, "cannot convert $1 to $2" % [
+              typeToString(n[a].typ), typeToString(formal.typ) ])
+            noMatch()
+        checkConstraint(n[a])
+
+    if m.state == csMatch and not (m.calleeSym != nil and m.calleeSym.kind in {skTemplate, skMacro}):
+      c.mergeShadowScope
+    else:
+      c.closeShadowScope
+
+    inc a
+  # for some edge cases (see tdont_return_unowned_from_owned test case)
+  m.firstMismatch.arg = a
+  m.firstMismatch.formal = formal
+
+proc partialMatch*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
+  # for 'suggest' support:
+  var marker = initIntSet()
+  matchesAux(c, n, nOrig, m, marker)
+
+proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
+  if m.magic in {mArrGet, mArrPut}:
+    m.state = csMatch
+    m.call = n
+    # Note the following doesn't work as it would produce ambiguities.
+    # Instead we patch system.nim, see bug #8049.
+    when false:
+      inc m.genericMatches
+      inc m.exactMatches
+    return
+  # initCandidate may have given csNoMatch if generic params didn't match:
+  if m.state == csNoMatch: return
+  var marker = initIntSet()
+  matchesAux(c, n, nOrig, m, marker)
+  if m.state == csNoMatch: return
+  # check that every formal parameter got a value:
+  for f in 1..<m.callee.n.len:
+    let formal = m.callee.n[f].sym
+    if not containsOrIncl(marker, formal.position):
+      if formal.ast == nil:
+        if formal.typ.kind == tyVarargs:
+          # For consistency with what happens in `matchesAux` select the
+          # container node kind accordingly
+          let cnKind = if formal.typ.isVarargsUntyped: nkArgList else: nkBracket
+          var container = newNodeIT(cnKind, n.info, arrayConstr(c, n.info))
+          setSon(m.call, formal.position + 1,
+                 implicitConv(nkHiddenStdConv, formal.typ, container, m, c))
+        else:
+          # no default value
+          m.state = csNoMatch
+          m.firstMismatch.kind = kMissingParam
+          m.firstMismatch.formal = formal
+          break
+      else:
+        # mirrored with updateDefaultParams:
+        if formal.ast.kind == nkEmpty:
+          # The default param value is set to empty in `instantiateProcType`
+          # when the type of the default expression doesn't match the type
+          # of the instantiated proc param:
+          pushInfoContext(c.config, m.call.info,
+            if m.calleeSym != nil: m.calleeSym.detailedInfo else: "")
+          typeMismatch(c.config, formal.ast.info, formal.typ, formal.ast.typ, formal.ast)
+          popInfoContext(c.config)
+          formal.ast.typ = errorType(c)
+        if nfDefaultRefsParam in formal.ast.flags:
+          m.call.flags.incl nfDefaultRefsParam
+        var defaultValue = copyTree(formal.ast)
+        if defaultValue.kind == nkNilLit:
+          defaultValue = implicitConv(nkHiddenStdConv, formal.typ, defaultValue, m, c)
+        # proc foo(x: T = 0.0)
+        # foo()
+        if {tfImplicitTypeParam, tfGenericTypeParam} * formal.typ.flags != {}:
+          let existing = idTableGet(m.bindings, formal.typ)
+          if existing == nil or existing.kind == tyTypeDesc:
+            # see bug #11600:
+            put(m, formal.typ, defaultValue.typ)
+        defaultValue.flags.incl nfDefaultParam
+        setSon(m.call, formal.position + 1, defaultValue)
+  # forget all inferred types if the overload matching failed
+  if m.state == csNoMatch:
+    for t in m.inferredTypes:
+      if t.len > 1: t.newSons 1
+
+proc argtypeMatches*(c: PContext, f, a: PType, fromHlo = false): bool =
+  var m = newCandidate(c, f)
+  let res = paramTypesMatch(m, f, a, c.graph.emptyNode, nil)
+  #instantiateGenericConverters(c, res, m)
+  # XXX this is used by patterns.nim too; I think it's better to not
+  # instantiate generic converters for that
+  if not fromHlo:
+    res != nil
+  else:
+    # pattern templates do not allow for conversions except from int literal
+    res != nil and m.convMatches == 0 and m.intConvMatches in [0, 256]
+
+
+proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo;
+                      op: TTypeAttachedOp; col: int): PSym =
+  var m = newCandidate(c, dc.typ)
+  if col >= dc.typ.len:
+    localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'")
+    return nil
+  var f = dc.typ[col]
+
+  if op == attachedDeepCopy:
+    if f.kind in {tyRef, tyPtr}: f = f.elementType
+  else:
+    if f.kind in {tyVar}: f = f.elementType
+  if typeRel(m, f, t) == isNone:
+    result = nil
+    localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'")
+  else:
+    result = c.semGenerateInstance(c, dc, m.bindings, info)
+    if op == attachedDeepCopy:
+      assert sfFromGeneric in result.flags
+
+include suggest
+
+when not declared(tests):
+  template tests(s: untyped) = discard
+
+tests:
+  var dummyOwner = newSym(skModule, getIdent("test_module"), nil, unknownLineInfo)
+
+  proc `|` (t1, t2: PType): PType =
+    result = newType(tyOr, dummyOwner)
+    result.rawAddSon(t1)
+    result.rawAddSon(t2)
+
+  proc `&` (t1, t2: PType): PType =
+    result = newType(tyAnd, dummyOwner)
+    result.rawAddSon(t1)
+    result.rawAddSon(t2)
+
+  proc `!` (t: PType): PType =
+    result = newType(tyNot, dummyOwner)
+    result.rawAddSon(t)
+
+  proc seq(t: PType): PType =
+    result = newType(tySequence, dummyOwner)
+    result.rawAddSon(t)
+
+  proc array(x: int, t: PType): PType =
+    result = newType(tyArray, dummyOwner)
+
+    var n = newNodeI(nkRange, unknownLineInfo)
+    n.add newIntNode(nkIntLit, 0)
+    n.add newIntNode(nkIntLit, x)
+    let range = newType(tyRange, dummyOwner)
+
+    result.rawAddSon(range)
+    result.rawAddSon(t)
+
+  suite "type classes":
+    let
+      int = newType(tyInt, dummyOwner)
+      float = newType(tyFloat, dummyOwner)
+      string = newType(tyString, dummyOwner)
+      ordinal = newType(tyOrdinal, dummyOwner)
+      any = newType(tyAnything, dummyOwner)
+      number = int | float
+
+    var TFoo = newType(tyObject, dummyOwner)
+    TFoo.sym = newSym(skType, getIdent"TFoo", dummyOwner, unknownLineInfo)
+
+    var T1 = newType(tyGenericParam, dummyOwner)
+    T1.sym = newSym(skType, getIdent"T1", dummyOwner, unknownLineInfo)
+    T1.sym.position = 0
+
+    var T2 = newType(tyGenericParam, dummyOwner)
+    T2.sym = newSym(skType, getIdent"T2", dummyOwner, unknownLineInfo)
+    T2.sym.position = 1
+
+    setup:
+      var c = newCandidate(nil, nil)
+
+    template yes(x, y) =
+      test astToStr(x) & " is " & astToStr(y):
+        check typeRel(c, y, x) == isGeneric
+
+    template no(x, y) =
+      test astToStr(x) & " is not " & astToStr(y):
+        check typeRel(c, y, x) == isNone
+
+    yes seq(any), array(10, int) | seq(any)
+    # Sure, seq[any] is directly included
+
+    yes seq(int), seq(any)
+    yes seq(int), seq(number)
+    # Sure, the int sequence is certainly
+    # part of the number sequences (and all sequences)
+
+    no seq(any), seq(float)
+    # Nope, seq[any] includes types that are not seq[float] (e.g. seq[int])
+
+    yes seq(int|string), seq(any)
+    # Sure
+
+    yes seq(int&string), seq(any)
+    # Again
+
+    yes seq(int&string), seq(int)
+    # A bit more complicated
+    # seq[int&string] is not a real type, but it's analogous to
+    # seq[Sortable and Iterable], which is certainly a subset of seq[Sortable]
+
+    no seq(int|string), seq(int|float)
+    # Nope, seq[string] is not included in not included in
+    # the seq[int|float] set
+
+    no seq(!(int|string)), seq(string)
+    # A sequence that is neither seq[int] or seq[string]
+    # is obviously not seq[string]
+
+    no seq(!int), seq(number)
+    # Now your head should start to hurt a bit
+    # A sequence that is not seq[int] is not necessarily a number sequence
+    # it could well be seq[string] for example
+
+    yes seq(!(int|string)), seq(!string)
+    # all sequnece types besides seq[int] and seq[string]
+    # are subset of all sequence types that are not seq[string]
+
+    no seq(!(int|string)), seq(!(string|TFoo))
+    # Nope, seq[TFoo] is included in the first set, but not in the second
+
+    no seq(!string), seq(!number)
+    # Nope, seq[int] in included in the first set, but not in the second
+
+    yes seq(!number), seq(any)
+    yes seq(!int), seq(any)
+    no seq(any), seq(!any)
+    no seq(!int), seq(!any)
+
+    yes int, ordinal
+    no  string, ordinal