summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim55
-rw-r--r--compiler/ccgexprs.nim4
-rw-r--r--compiler/ccgtypes.nim8
-rw-r--r--compiler/ccgutils.nim4
-rw-r--r--compiler/commands.nim2
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/msgs.nim29
-rw-r--r--compiler/options.nim5
-rw-r--r--compiler/parampatterns.nim7
-rw-r--r--compiler/parser.nim20
-rw-r--r--compiler/pragmas.nim6
-rw-r--r--compiler/renderer.nim27
-rw-r--r--compiler/sem.nim1
-rw-r--r--compiler/semasgn.nim2
-rw-r--r--compiler/semcall.nim154
-rw-r--r--compiler/semdata.nim40
-rw-r--r--compiler/semexprs.nim116
-rw-r--r--compiler/seminst.nim3
-rw-r--r--compiler/semmagic.nim39
-rw-r--r--compiler/semstmts.nim35
-rw-r--r--compiler/semtypes.nim52
-rw-r--r--compiler/semtypinst.nim12
-rw-r--r--compiler/sigmatch.nim384
-rw-r--r--compiler/types.nim60
-rw-r--r--compiler/vmdeps.nim12
-rw-r--r--compiler/vmgen.nim3
-rw-r--r--compiler/wordrecg.nim4
-rw-r--r--doc/advopt.txt3
-rw-r--r--doc/manual/generics.txt431
-rw-r--r--lib/pure/typetraits.nim19
-rw-r--r--tests/concepts/matrix.nim15
-rw-r--r--tests/concepts/matrixalgo.nim28
-rw-r--r--tests/concepts/t1128.nim21
-rw-r--r--tests/concepts/t3330.nim41
-rw-r--r--tests/concepts/t976.nim32
-rw-r--r--tests/concepts/tconcepts_overload_precedence.nim69
-rw-r--r--tests/concepts/texplain.nim120
-rw-r--r--tests/concepts/tmapconcept.nim102
-rw-r--r--tests/concepts/tmatrixconcept.nim81
-rw-r--r--tests/concepts/tmatrixlib.nim31
-rw-r--r--tests/concepts/tmisc_issues.nim100
-rw-r--r--tests/concepts/tstackconcept.nim63
-rw-r--r--tests/concepts/tusertypeclasses.nim87
-rw-r--r--tests/concepts/tvectorspace.nim15
-rw-r--r--tests/generics/tgenericdotrettype.nim29
-rw-r--r--tests/parallel/tgc_unsafe2.nim6
-rw-r--r--tests/testament/tester.nim4
47 files changed, 2038 insertions, 345 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 26305cf3b..49ca1c5e0 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -252,6 +252,7 @@ type
     sfProcvar,        # proc can be passed to a proc var
     sfDiscriminant,   # field is a discriminant in a record/object
     sfDeprecated,     # symbol is deprecated
+    sfExplain,        # provide more diagnostics when this symbol is used
     sfError,          # usage of symbol should trigger a compile-time error
     sfShadowed,       # a symbol that was shadowed in some inner scope
     sfThread,         # proc will run as a thread
@@ -354,44 +355,52 @@ type
     tyUnused,
     tyProxy # used as errornous type (for idetools)
 
-    tyBuiltInTypeClass #\
+    tyBuiltInTypeClass
       # Type such as the catch-all object, tuple, seq, etc
 
-    tyUserTypeClass #\
+    tyUserTypeClass
       # the body of a user-defined type class
 
-    tyUserTypeClassInst #\
+    tyUserTypeClassInst
       # Instance of a parametric user-defined type class.
       # Structured similarly to tyGenericInst.
       # tyGenericInst represents concrete types, while
       # this is still a "generic param" that will bind types
       # and resolves them during sigmatch and instantiation.
 
-    tyCompositeTypeClass #\
+    tyCompositeTypeClass
       # Type such as seq[Number]
       # The notes for tyUserTypeClassInst apply here as well
       # sons[0]: the original expression used by the user.
       # sons[1]: fully expanded and instantiated meta type
       # (potentially following aliases)
 
-    tyAnd, tyOr, tyNot #\
+    tyInferred
+      # In the initial state `base` stores a type class constraining
+      # the types that can be inferred. After a candidate type is
+      # selected, it's stored in `lastSon`. Between `base` and `lastSon`
+      # there may be 0, 2 or more types that were also considered as
+      # possible candidates in the inference process (i.e. lastSon will
+      # be updated to store a type best conforming to all candidates)
+
+    tyAnd, tyOr, tyNot
       # boolean type classes such as `string|int`,`not seq`,
       # `Sortable and Enumable`, etc
 
-    tyAnything #\
+    tyAnything
       # a type class matching any type
 
-    tyStatic #\
+    tyStatic
       # a value known at compile type (the underlying type is .base)
 
-    tyFromExpr #\
+    tyFromExpr
       # This is a type representing an expression that depends
       # on generic parameters (the expression is stored in t.n)
       # It will be converted to a real type only during generic
       # instantiation and prior to this it has the potential to
       # be any type.
 
-    tyFieldAccessor #\
+    tyFieldAccessor
       # Expressions such as Type.field (valid in contexts such
       # as the `is` operator and magics like `high` and `low`).
       # Could be lifted to a single argument proc returning the
@@ -400,7 +409,7 @@ type
       # sons[1]: field type
       # .n: nkDotExpr storing the field name
 
-    tyVoid #\
+    tyVoid
       # now different from tyEmpty, hurray!
 
 static:
@@ -420,6 +429,7 @@ const
                     tyAnd, tyOr, tyNot, tyAnything}
 
   tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses
+  tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst}
 
 type
   TTypeKinds* = set[TTypeKind]
@@ -463,6 +473,8 @@ type
                       # can be attached to generic procs with free standing
                       # type parameters: e.g. proc foo[T]()
                       # depends on unresolved static params.
+    tfResolved        # marks a user type class, after it has been bound to a
+                      # concrete type (lastSon becomes the concrete type)
     tfRetType,        # marks return types in proc (used to detect type classes
                       # used as return types for return type inference)
     tfCapturesEnv,    # whether proc really captures some environment
@@ -482,6 +494,9 @@ type
     tfHasStatic
     tfGenericTypeParam
     tfImplicitTypeParam
+    tfInferrableStatic
+    tfExplicit        # for typedescs, marks types explicitly prefixed with the
+                      # `type` operator (e.g. type int)
     tfWildcard        # consider a proc like foo[T, I](x: Type[T, I])
                       # T and I here can bind to both typedesc and static types
                       # before this is determined, we'll consider them to be a
@@ -1036,6 +1051,9 @@ proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
   result = newNode(kind)
   result.strVal = strVal
 
+template previouslyInferred*(t: PType): PType =
+  if t.sons.len > 1: t.lastSon else: nil
+
 proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym,
              info: TLineInfo): PSym =
   # generates a symbol and initializes the hash field too
@@ -1062,6 +1080,9 @@ proc isMetaType*(t: PType): bool =
          (t.kind == tyStatic and t.n == nil) or
          tfHasMeta in t.flags
 
+proc isUnresolvedStatic*(t: PType): bool =
+  return t.kind == tyStatic and t.n == nil
+
 proc linkTo*(t: PType, s: PSym): PType {.discardable.} =
   t.sym = s
   s.typ = t
@@ -1278,6 +1299,8 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
     when debugIds: registerId(result)
   result.sym = t.sym          # backend-info should not be copied
 
+proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
+
 proc copySym*(s: PSym, keepId: bool = false): PSym =
   result = newSym(s.kind, s.name, s.owner, s.info)
   #result.ast = nil            # BUGFIX; was: s.ast which made problems
@@ -1561,7 +1584,7 @@ proc hasPattern*(s: PSym): bool {.inline.} =
   result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
 
 iterator items*(n: PNode): PNode =
-  for i in 0.. <n.len: yield n.sons[i]
+  for i in 0.. <n.safeLen: yield n.sons[i]
 
 iterator pairs*(n: PNode): tuple[i: int, n: PNode] =
   for i in 0.. <n.len: yield (i, n.sons[i])
@@ -1604,6 +1627,16 @@ proc toObject*(typ: PType): PType =
   if result.kind == tyRef:
     result = result.lastSon
 
+proc findUnresolvedStatic*(n: PNode): PNode =
+  if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil:
+    return n
+
+  for son in n:
+    let n = son.findUnresolvedStatic
+    if n != nil: return n
+
+  return nil
+
 when false:
   proc containsNil*(n: PNode): bool =
     # only for debugging
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 309fb1f20..6e10379e6 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -890,7 +890,7 @@ proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) =
               rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.s)
 
 proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) =
-  var ty = skipTypes(n.sons[0].typ, abstractVarRange)
+  var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses)
   if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange)
   case ty.kind
   of tyArray: genArrayElem(p, n.sons[0], n.sons[1], d)
@@ -1359,7 +1359,7 @@ proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) =
 proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   var a = e.sons[1]
   if a.kind == nkHiddenAddr: a = a.sons[0]
-  let typ = skipTypes(a.typ, abstractVar)
+  var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses)
   case typ.kind
   of tyOpenArray, tyVarargs:
     if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)")
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 8fdd97428..9915ad355 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -164,8 +164,11 @@ proc mapType(typ: PType): TCTypeKind =
   of tySet: result = mapSetType(typ)
   of tyOpenArray, tyArray, tyVarargs: result = ctArray
   of tyObject, tyTuple: result = ctStruct
+  of tyUserTypeClass, tyUserTypeClassInst:
+    internalAssert typ.isResolvedUserTypeClass
+    return mapType(typ.lastSon)
   of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal,
-     tyTypeDesc, tyAlias:
+     tyTypeDesc, tyAlias, tyInferred:
     result = mapType(lastSon(typ))
   of tyEnum:
     if firstOrd(typ) < 0:
@@ -787,7 +790,8 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
       of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)])
       else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n",
              [result, rope(getSize(t))])
-  of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias:
+  of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias,
+     tyUserTypeClass, tyUserTypeClassInst, tyInferred:
     result = getTypeDescAux(m, lastSon(t), check)
   else:
     internalError("getTypeDescAux(" & $t.kind & ')')
diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim
index ff8f768bd..c37a8fcdb 100644
--- a/compiler/ccgutils.nim
+++ b/compiler/ccgutils.nim
@@ -101,6 +101,8 @@ proc getUniqueType*(key: PType): PType =
         gCanonicalTypes[k] = key
         result = key
     of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr, tyFieldAccessor:
+      if key.isResolvedUserTypeClass:
+        return getUniqueType(lastSon(key))
       if key.sym != nil:
         internalError(key.sym.info, "metatype not eliminated")
       else:
@@ -108,7 +110,7 @@ proc getUniqueType*(key: PType): PType =
     of tyDistinct:
       if key.deepCopy != nil: result = key
       else: result = getUniqueType(lastSon(key))
-    of tyGenericInst, tyOrdinal, tyStatic, tyAlias:
+    of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tyInferred:
       result = getUniqueType(lastSon(key))
       #let obj = lastSon(key)
       #if obj.sym != nil and obj.sym.name.s == "TOption":
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 1b42a191e..22e4b5a2c 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -478,8 +478,6 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info)
   of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info)
   of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info)
-  of "reportconceptfailures":
-    processOnOffSwitchG({optReportConceptFailures}, arg, pass, info)
   of "threads":
     processOnOffSwitchG({optThreads}, arg, pass, info)
     #if optThreads in gGlobalOptions: incl(gNotes, warnGcUnsafe)
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 9c50a4fc9..d64e4f7dd 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -167,6 +167,8 @@ proc mapType(typ: PType): TJSTypeKind =
      tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor,
      tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias:
     result = etyNone
+  of tyInferred:
+    result = mapType(typ.lastSon)
   of tyStatic:
     if t.n != nil: result = mapType(lastSon t)
     else: result = etyNone
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 2db3646b5..3a97f1ed2 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -109,6 +109,7 @@ type
     errXCannotBeClosure, errXMustBeCompileTime,
     errCannotInferTypeOfTheLiteral,
     errCannotInferReturnType,
+    errCannotInferStaticParam,
     errGenericLambdaNotAllowed,
     errProcHasNoConcreteType,
     errCompilerDoesntSupportTarget,
@@ -132,7 +133,7 @@ type
     hintConditionAlwaysTrue, hintName, hintPattern,
     hintExecuting, hintLinking, hintDependency,
     hintSource, hintStackTrace, hintGCStats,
-    hintUser
+    hintUser, hintUserRaw
 
 const
   MsgKindToStr*: array[TMsgKind, string] = [
@@ -373,6 +374,7 @@ const
     errXMustBeCompileTime: "'$1' can only be used in compile-time context",
     errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1",
     errCannotInferReturnType: "cannot infer the return type of the proc",
+    errCannotInferStaticParam: "cannot infer the value of the static param `$1`",
     errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " &
                                 "it is used as an operand to another routine and the types " &
                                 "of the generic paramers can be inferred from the expected signature.",
@@ -432,10 +434,11 @@ const
     hintSource: "$1",
     hintStackTrace: "$1",
     hintGCStats: "$1",
-    hintUser: "$1"]
+    hintUser: "$1",
+    hintUserRaw: "$1"]
 
 const
-  WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape",
+  WarningsToStr* = ["CannotOpenFile", "OctalEscape",
     "XIsNeverRead", "XmightNotBeenInit",
     "Deprecated", "ConfigDeprecated",
     "SmallLshouldNotBeUsed", "UnknownMagic",
@@ -447,12 +450,12 @@ const
     "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
     "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"]
 
-  HintsToStr*: array[0..22, string] = ["Success", "SuccessX", "LineTooLong",
+  HintsToStr* = ["Success", "SuccessX", "LineTooLong",
     "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
     "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
     "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency",
     "Source", "StackTrace", "GCStats",
-    "User"]
+    "User", "UserRaw"]
 
 const
   fatalMin* = errUnknown
@@ -497,7 +500,6 @@ type
   TErrorOutput* = enum
     eStdOut
     eStdErr
-    eInMemory
 
   TErrorOutputs* = set[TErrorOutput]
 
@@ -651,6 +653,12 @@ var
   writelnHook*: proc (output: string) {.closure.}
   structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.}
 
+proc concat(strings: openarray[string]): string =
+  var totalLen = 0
+  for s in strings: totalLen += s.len
+  result = newStringOfCap totalLen
+  for s in strings: result.add s
+
 proc suggestWriteln*(s: string) =
   if eStdOut in errorOutputs:
     if isNil(writelnHook):
@@ -804,10 +812,7 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
     result.add(arg)
 
 template callWritelnHook(args: varargs[string, `$`]) =
-  var s = ""
-  for arg in args:
-    s.add arg
-  writelnHook s
+  writelnHook concat(args)
 
 template styledMsgWriteln*(args: varargs[typed]) =
   if not isNil(writelnHook):
@@ -922,7 +927,7 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
     if msg notin gNotes: return
     title = HintTitle
     color = HintColor
-    kind = HintsToStr[ord(msg) - ord(hintMin)]
+    if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
     inc(gHintCounter)
   let s = msgKindToString(msg) % args
 
@@ -990,7 +995,7 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
     ignoreMsg = optHints notin gOptions or msg notin gNotes
     title = HintTitle
     color = HintColor
-    kind = HintsToStr[ord(msg) - ord(hintMin)]
+    if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
     inc(gHintCounter)
   # NOTE: currently line info line numbers start with 1,
   # but column numbers start with 0, however most editors expect
diff --git a/compiler/options.nim b/compiler/options.nim
index 6372cddac..c4a57f41c 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -41,7 +41,6 @@ type                          # please make sure we have under 32 options
   TGlobalOption* = enum       # **keep binary compatible**
     gloptNone, optForceFullMake, optDeadCodeElim,
     optListCmd, optCompileOnly, optNoLinking,
-    optReportConceptFailures, # report 'compiles' or 'concept' matching failures
     optCDebug,                # turn on debugging information
     optGenDynLib,             # generate a dynamic library
     optGenStaticLib,          # generate a static library
@@ -73,8 +72,8 @@ type                          # please make sure we have under 32 options
   TGlobalOptions* = set[TGlobalOption]
 
 const
-  harmlessOptions* = {optForceFullMake, optNoLinking, optReportConceptFailures,
-    optRun, optUseColors, optStdout}
+  harmlessOptions* = {optForceFullMake, optNoLinking, optRun,
+                      optUseColors, optStdout}
 
 type
   TCommands* = enum           # Nim's commands
diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim
index c51d406ac..05b2d8f9c 100644
--- a/compiler/parampatterns.nim
+++ b/compiler/parampatterns.nim
@@ -235,6 +235,13 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
   of nkStmtList, nkStmtListExpr:
     if n.typ != nil:
       result = isAssignable(owner, n.lastSon, isUnsafeAddr)
+  of nkVarTy:
+    # XXX: The fact that this is here is a bit of a hack.
+    # The goal is to allow the use of checks such as "foo(var T)"
+    # within concepts. Semantically, it's not correct to say that
+    # nkVarTy denotes an lvalue, but the example above is the only
+    # possible code which will get us here
+    result = arLValue
   else:
     discard
 
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 362a5c286..8457adac7 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -66,6 +66,7 @@ proc parseSymbol*(p: var TParser, allowNil = false): PNode
 proc parseTry(p: var TParser; isExpr: bool): PNode
 proc parseCase(p: var TParser): PNode
 proc parseStmtPragma(p: var TParser): PNode
+proc parsePragma(p: var TParser): PNode
 # implementation
 
 proc getTok(p: var TParser) =
@@ -770,6 +771,13 @@ proc parseOperators(p: var TParser, headNode: PNode,
 
 proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
   result = primary(p, mode)
+  if p.tok.tokType == tkCurlyDotLe and
+     p.lex.lineNumber == result.info.line and
+     mode == pmNormal:
+    var pragmaExp = newNodeP(nkPragmaExpr, p)
+    pragmaExp.addSon result
+    pragmaExp.addSon p.parsePragma
+    result = pragmaExp
   result = parseOperators(p, result, limit, mode)
 
 proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
@@ -1793,8 +1801,16 @@ proc parseObject(p: var TParser): PNode =
   addSon(result, parseObjectPart(p))
 
 proc parseTypeClassParam(p: var TParser): PNode =
-  if p.tok.tokType in {tkOut, tkVar}:
-    result = newNodeP(nkVarTy, p)
+  let modifier = case p.tok.tokType
+    of tkOut, tkVar: nkVarTy
+    of tkPtr: nkPtrTy
+    of tkRef: nkRefTy
+    of tkStatic: nkStaticTy
+    of tkType: nkTypeOfExpr
+    else: nkEmpty
+
+  if modifier != nkEmpty:
+    result = newNodeP(modifier, p)
     getTok(p)
     result.addSon(p.parseSymbol)
   else:
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 387738f6d..b30b94b5d 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -55,7 +55,7 @@ const
     wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
     wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
     wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
-    wBorrow, wGcSafe, wExportNims, wPartial, wUsed}
+    wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain}
   fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
     wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed}
   varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
@@ -73,7 +73,7 @@ const
 proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
 # implementation
 
-proc invalidPragma(n: PNode) =
+proc invalidPragma*(n: PNode) =
   localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments}))
 
 proc pragmaAsm*(c: PContext, n: PNode): char =
@@ -773,6 +773,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
       of wProcVar:
         noVal(it)
         incl(sym.flags, sfProcvar)
+      of wExplain:
+        sym.flags.incl sfExplain
       of wDeprecated:
         if it.kind == nkExprColonExpr: deprecatedStmt(c, it)
         elif sym != nil: incl(sym.flags, sfDeprecated)
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index badcaea66..7d9536625 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -34,6 +34,7 @@ type
     pendingWhitespace: int
     comStack*: seq[PNode]  # comment stack
     flags*: TRenderFlags
+    inGenericParams: bool
     checkAnon: bool        # we're in a context that can contain sfAnon
     inPragma: int
 
@@ -83,7 +84,7 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) =
   g.flags = renderFlags
   g.pendingNL = -1
   g.pendingWhitespace = -1
-  g.checkAnon = false
+  g.inGenericParams = false
 
 proc addTok(g: var TSrcGen, kind: TTokType, s: string) =
   var length = len(g.tokens)
@@ -692,14 +693,14 @@ proc gproc(g: var TSrcGen, n: PNode) =
 
   if n.sons[patternPos].kind != nkEmpty:
     gpattern(g, n.sons[patternPos])
-  let oldCheckAnon = g.checkAnon
-  g.checkAnon = true
+  let oldInGenericParams = g.inGenericParams
+  g.inGenericParams = true
   if renderNoBody in g.flags and n[miscPos].kind != nkEmpty and
       n[miscPos][1].kind != nkEmpty:
     gsub(g, n[miscPos][1])
   else:
     gsub(g, n.sons[genericParamsPos])
-  g.checkAnon = oldCheckAnon
+  g.inGenericParams = oldInGenericParams
   gsub(g, n.sons[paramsPos])
   gsub(g, n.sons[pragmasPos])
   if renderNoBody notin g.flags:
@@ -765,7 +766,10 @@ proc gasm(g: var TSrcGen, n: PNode) =
     gsub(g, n.sons[1])
 
 proc gident(g: var TSrcGen, n: PNode) =
-  if g.checkAnon and n.kind == nkSym and sfAnon in n.sym.flags: return
+  if g.inGenericParams and n.kind == nkSym:
+    if sfAnon in n.sym.flags or
+      (n.typ != nil and tfImplicitTypeParam in n.typ.flags): return
+
   var t: TTokType
   var s = atom(n)
   if (s[0] in lexer.SymChars):
@@ -1315,9 +1319,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gcoms(g)
     gstmts(g, lastSon(n), c)
   of nkGenericParams:
-    put(g, tkBracketLe, "[")
-    gcomma(g, n)
-    put(g, tkBracketRi, "]")
+    proc hasExplicitParams(gp: PNode): bool =
+      for p in gp:
+        if p.typ == nil or tfImplicitTypeParam notin p.typ.flags:
+          return true
+      return false
+    
+    if n.hasExplicitParams:
+      put(g, tkBracketLe, "[")
+      gcomma(g, n)
+      put(g, tkBracketRi, "]")
   of nkFormalParams:
     put(g, tkParLe, "(")
     gsemicolon(g, n, 1)
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 6ad77e3fb..57b87e0bb 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -215,7 +215,6 @@ proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
 proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
 proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode
 proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
-proc isOpImpl(c: PContext, n: PNode): PNode
 proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
                      flags: TExprFlags = {}): PNode
 proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim
index 70765d087..f2144037c 100644
--- a/compiler/semasgn.nim
+++ b/compiler/semasgn.nim
@@ -226,7 +226,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) =
      tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt,
      tyTypeDesc, tyGenericInvocation, tyForward:
     internalError(c.info, "assignment requested for type: " & typeToString(t))
-  of tyOrdinal, tyRange,
+  of tyOrdinal, tyRange, tyInferred,
      tyGenericInst, tyFieldAccessor, tyStatic, tyVar, tyAlias:
     liftBodyAux(c, lastSon(t), body, x, y)
   of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux")
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index ffa940291..d6852859b 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -35,10 +35,11 @@ proc sameMethodDispatcher(a, b: PSym): bool =
 proc determineType(c: PContext, s: PSym)
 
 proc initCandidateSymbols(c: PContext, headSymbol: PNode,
-                       initialBinding: PNode,
-                       filter: TSymKinds,
-                       best, alt: var TCandidate,
-                       o: var TOverloadIter): seq[tuple[s: PSym, scope: int]] =
+                          initialBinding: PNode,
+                          filter: TSymKinds,
+                          best, alt: var TCandidate,
+                          o: var TOverloadIter,
+                          diagnostics: bool): seq[tuple[s: PSym, scope: int]] =
   result = @[]
   var symx = initOverloadIter(o, c, headSymbol)
   while symx != nil:
@@ -46,8 +47,10 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode,
       result.add((symx, o.lastOverloadScope))
     symx = nextOverloadIter(o, c, headSymbol)
   if result.len > 0:
-    initCandidate(c, best, result[0].s, initialBinding, result[0].scope)
-    initCandidate(c, alt, result[0].s, initialBinding, result[0].scope)
+    initCandidate(c, best, result[0].s, initialBinding,
+                  result[0].scope, diagnostics)
+    initCandidate(c, alt, result[0].s, initialBinding,
+                  result[0].scope, diagnostics)
     best.state = csNoMatch
 
 proc pickBestCandidate(c: PContext, headSymbol: PNode,
@@ -55,7 +58,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
                        initialBinding: PNode,
                        filter: TSymKinds,
                        best, alt: var TCandidate,
-                       errors: var CandidateErrors) =
+                       errors: var CandidateErrors,
+                       diagnosticsFlag = false) =
   var o: TOverloadIter
   var sym = initOverloadIter(o, c, headSymbol)
   var scope = o.lastOverloadScope
@@ -68,8 +72,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
   while sym != nil:
     if sym.kind in filter:
       # Initialise 'best' and 'alt' with the first available symbol
-      initCandidate(c, best, sym, initialBinding, scope)
-      initCandidate(c, alt, sym, initialBinding, scope)
+      initCandidate(c, best, sym, initialBinding, scope, diagnosticsFlag)
+      initCandidate(c, alt, sym, initialBinding, scope, diagnosticsFlag)
       best.state = csNoMatch
       break
     else:
@@ -82,14 +86,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
       scope = o.lastOverloadScope
       continue
     determineType(c, sym)
-    initCandidate(c, z, sym, initialBinding, scope)
+    initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag)
     if c.currentScope.symbols.counter == counterInitial or syms != nil:
       matches(c, n, orig, z)
-      if errors != nil:
-        errors.safeAdd((sym, int z.mutabilityProblem))
-        if z.errors != nil:
-          for err in z.errors:
-            errors.add(err)
       if z.state == csMatch:
         # little hack so that iterators are preferred over everything else:
         if sym.kind == skIterator: inc(z.exactMatches, 200)
@@ -99,10 +98,16 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
           var cmp = cmpCandidates(best, z)
           if cmp < 0: best = z   # x is better than the best so far
           elif cmp == 0: alt = z # x is as good as the best so far
+      elif errors != nil or z.diagnostics != nil:
+        errors.safeAdd(CandidateError(
+          sym: sym,
+          unmatchedVarParam: int z.mutabilityProblem,
+          diagnostics: z.diagnostics))
     else:
       # Symbol table has been modified. Restart and pre-calculate all syms
       # before any further candidate init and compare. SLOW, but rare case.
-      syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o)
+      syms = initCandidateSymbols(c, headSymbol, initialBinding, filter,
+                                  best, alt, o, diagnosticsFlag)
     if syms == nil:
       sym = nextOverloadIter(o, c, headSymbol)
       scope = o.lastOverloadScope
@@ -114,17 +119,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
     else:
       break
 
-proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
-  # Gives a detailed error message; this is separated from semOverloadedCall,
-  # as semOverlodedCall is already pretty slow (and we need this information
-  # only in case of an error).
-  if c.compilesContextId > 0 and optReportConceptFailures notin gGlobalOptions:
-    # fail fast:
-    globalError(n.info, errTypeMismatch, "")
-  if errors.isNil or errors.len == 0:
-    localError(n.info, errExprXCannotBeCalled, n[0].renderTree)
-    return
-
+proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
+                            (TPreferedDesc, string) =
+  var prefer = preferName
   # to avoid confusing errors like:
   #   got (SslPtr, SocketHandle)
   #   but expected one of:
@@ -132,11 +129,9 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
   # we do a pre-analysis. If all types produce the same string, we will add
   # module information.
   let proto = describeArgs(c, n, 1, preferName)
-
-  var prefer = preferName
-  for err, mut in items(errors):
+  for err in errors:
     var errProto = ""
-    let n = err.typ.n
+    let n = err.sym.typ.n
     for i in countup(1, n.len - 1):
       var p = n.sons[i]
       if p.kind == nkSym:
@@ -147,26 +142,40 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
       prefer = preferModuleInfo
       break
 
-  # now use the information stored in 'prefer' to produce a nice error message:
-  var result = msgKindToString(errTypeMismatch)
-  add(result, describeArgs(c, n, 1, prefer))
-  add(result, ')')
   var candidates = ""
-  for err, mut in items(errors):
-    if err.kind in routineKinds and err.ast != nil:
-      add(candidates, renderTree(err.ast,
-            {renderNoBody, renderNoComments,renderNoPragmas}))
+  for err in errors:
+    if err.sym.kind in routineKinds and err.sym.ast != nil:
+      add(candidates, renderTree(err.sym.ast,
+            {renderNoBody, renderNoComments, renderNoPragmas}))
     else:
-      add(candidates, err.getProcHeader(prefer))
+      add(candidates, err.sym.getProcHeader(prefer))
     add(candidates, "\n")
-    if mut != 0 and mut < n.len:
-      add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n")
+    if err.unmatchedVarParam != 0 and err.unmatchedVarParam < n.len:
+      add(candidates, "for a 'var' type a variable needs to be passed, but '" &
+                      renderTree(n[err.unmatchedVarParam]) & "' is immutable\n")
+    for diag in err.diagnostics:
+      add(candidates, diag & "\n")
+  
+  result = (prefer, candidates)
+
+proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
+  # Gives a detailed error message; this is separated from semOverloadedCall,
+  # as semOverlodedCall is already pretty slow (and we need this information
+  # only in case of an error).
+  if errorOutputs == {}:
+    # fail fast:
+    globalError(n.info, errTypeMismatch, "")
+  if errors.isNil or errors.len == 0:
+    localError(n.info, errExprXCannotBeCalled, n[0].renderTree)
+    return
+
+  let (prefer, candidates) = presentFailedCandidates(c, n, errors)
+  var result = msgKindToString(errTypeMismatch)
+  add(result, describeArgs(c, n, 1, prefer))
+  add(result, ')')
   if candidates != "":
     add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates)
-  if c.compilesContextId > 0 and optReportConceptFailures in gGlobalOptions:
-    globalError(n.info, errGenerated, result)
-  else:
-    localError(n.info, errGenerated, result)
+  localError(n.info, errGenerated, result)
 
 proc bracketNotFoundError(c: PContext; n: PNode) =
   var errors: CandidateErrors = @[]
@@ -175,7 +184,9 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
   var symx = initOverloadIter(o, c, headSymbol)
   while symx != nil:
     if symx.kind in routineKinds:
-      errors.add((symx, 0))
+      errors.add(CandidateError(sym: symx,
+                                unmatchedVarParam: 0,
+                                diagnostics: nil))
     symx = nextOverloadIter(o, c, headSymbol)
   if errors.len == 0:
     localError(n.info, "could not resolve: " & $n)
@@ -183,7 +194,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
     notFoundError(c, n, errors)
 
 proc resolveOverloads(c: PContext, n, orig: PNode,
-                      filter: TSymKinds;
+                      filter: TSymKinds, flags: TExprFlags,
                       errors: var CandidateErrors): TCandidate =
   var initialBinding: PNode
   var alt: TCandidate
@@ -197,7 +208,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
 
   template pickBest(headSymbol) =
     pickBestCandidate(c, headSymbol, n, orig, initialBinding,
-                      filter, result, alt, errors)
+                      filter, result, alt, errors, efExplain in flags)
   pickBest(f)
 
   let overloadsState = result.state
@@ -263,18 +274,13 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
           # clean up the inserted ops
           n.sons.delete(2)
           n.sons[0] = f
-
-        errors = @[]
-        pickBest(f)
-        #notFoundError(c, n, errors)
-
       return
   if alt.state == csMatch and cmpCandidates(result, alt) == 0 and
       not sameMethodDispatcher(result.calleeSym, alt.calleeSym):
     internalAssert result.state == csMatch
     #writeMatches(result)
     #writeMatches(alt)
-    if c.compilesContextId > 0:
+    if errorOutputs == {}:
       # quick error message for performance of 'compiles' built-in:
       globalError(n.info, errGenerated, "ambiguous call")
     elif gErrorCounter == 0:
@@ -289,7 +295,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
         getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym),
         args])
 
-
 proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
   if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym:
     let s = a.sons[0].sym
@@ -378,23 +383,40 @@ proc tryDeref(n: PNode): PNode =
   result.addSon(n)
 
 proc semOverloadedCall(c: PContext, n, nOrig: PNode,
-                       filter: TSymKinds): PNode =
-  var errors: CandidateErrors
-
-  var r = resolveOverloads(c, n, nOrig, filter, errors)
-  if r.state == csMatch: result = semResolvedCall(c, n, r)
+                       filter: TSymKinds, flags: TExprFlags): PNode =
+  var errors: CandidateErrors = if efExplain in flags: @[]
+                                else: nil
+  var r = resolveOverloads(c, n, nOrig, filter, flags, errors)
+  if r.state == csMatch:
+    # this may be triggered, when the explain pragma is used
+    if errors.len > 0:
+      let (_, candidates) = presentFailedCandidates(c, n, errors)
+      message(n.info, hintUserRaw,
+              "Non-matching candidates for " & renderTree(n) & "\n" &
+              candidates)
+    result = semResolvedCall(c, n, r)
   elif experimentalMode(c) and canDeref(n):
     # try to deref the first argument and then try overloading resolution again:
+    #
+    # XXX: why is this here?
+    # it could be added to the long list of alternatives tried
+    # inside `resolveOverloads` or it could be moved all the way
+    # into sigmatch with hidden conversion produced there
+    #
     n.sons[1] = n.sons[1].tryDeref
-    var r = resolveOverloads(c, n, nOrig, filter, errors)
+    var r = resolveOverloads(c, n, nOrig, filter, flags, errors)
     if r.state == csMatch: result = semResolvedCall(c, n, r)
     else:
       # get rid of the deref again for a better error message:
       n.sons[1] = n.sons[1].sons[0]
       notFoundError(c, n, errors)
   else:
-    notFoundError(c, n, errors)
-  # else: result = errorNode(c, n)
+    if efExplain notin flags:
+      # repeat the overload resolution,
+      # this time enabling all the diagnostic output (this should fail again)
+      discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
+    else:
+      notFoundError(c, n, errors)
 
 proc explicitGenericInstError(n: PNode): PNode =
   localError(n.info, errCannotInstantiateX, renderTree(n))
@@ -450,6 +472,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
     if result.len == 1 and a.kind == nkClosedSymChoice:
       result = result[0]
     elif result.len == 0: result = explicitGenericInstError(n)
+    # candidateCount != 1: return explicitGenericInstError(n)
   else:
     result = explicitGenericInstError(n)
 
@@ -473,7 +496,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
       x = t.baseOfDistinct
     call.add(newNodeIT(nkEmpty, fn.info, x))
   if hasDistinct:
-    var resolved = semOverloadedCall(c, call, call, {fn.kind})
+    var resolved = semOverloadedCall(c, call, call, {fn.kind}, {})
     if resolved != nil:
       result = resolved.sons[0].sym
       if not compareTypes(result.typ.sons[0], fn.typ.sons[0], dcEqIgnoreDistinct):
@@ -481,4 +504,3 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
       elif result.magic in {mArrPut, mArrGet}:
         # cannot borrow these magics for now
         result = nil
-
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index ef23e40f2..023b85802 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -46,9 +46,10 @@ type
 
   TExprFlag* = enum
     efLValue, efWantIterator, efInTypeof,
-    efWantStmt, efAllowStmt, efDetermineType,
+    efWantStmt, efAllowStmt, efDetermineType, efExplain,
     efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
-    efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo
+    efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo,
+  
   TExprFlags* = set[TExprFlag]
 
   TTypeAttachedOp* = enum
@@ -84,12 +85,12 @@ type
     libs*: seq[PLib]           # all libs used by this module
     semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
     semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
-    semTryExpr*: proc (c: PContext, n: PNode,flags: TExprFlags = {}): PNode {.nimcall.}
+    semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
     semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.}
     semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
     semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
     semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
-                              filter: TSymKinds): PNode {.nimcall.}
+                              filter: TSymKinds, flags: TExprFlags): PNode {.nimcall.}
     semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
     semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode
     semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable,
@@ -230,6 +231,17 @@ proc makePtrType*(c: PContext, baseType: PType): PType =
   result = newTypeS(tyPtr, c)
   addSonSkipIntLit(result, baseType.assertNotNil)
 
+proc makeTypeWithModifier*(c: PContext,
+                           modifier: TTypeKind,
+                           baseType: PType): PType =
+  assert modifier in {tyVar, tyPtr, tyRef, tyStatic, tyTypeDesc}
+
+  if modifier in {tyVar, tyTypeDesc} and baseType.kind == modifier:
+    result = baseType
+  else:
+    result = newTypeS(modifier, c)
+    addSonSkipIntLit(result, baseType.assertNotNil)
+
 proc makeVarType*(c: PContext, baseType: PType): PType =
   if baseType.kind == tyVar:
     result = baseType
@@ -238,8 +250,11 @@ proc makeVarType*(c: PContext, baseType: PType): PType =
     addSonSkipIntLit(result, baseType.assertNotNil)
 
 proc makeTypeDesc*(c: PContext, typ: PType): PType =
-  result = newTypeS(tyTypeDesc, c)
-  result.addSonSkipIntLit(typ.assertNotNil)
+  if typ.kind == tyTypeDesc:
+    result = typ
+  else:
+    result = newTypeS(tyTypeDesc, c)
+    result.addSonSkipIntLit(typ.assertNotNil)
 
 proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
   let typedesc = makeTypeDesc(c, typ)
@@ -259,7 +274,8 @@ proc newTypeWithSons*(c: PContext, kind: TTypeKind,
 proc makeStaticExpr*(c: PContext, n: PNode): PNode =
   result = newNodeI(nkStaticExpr, n.info)
   result.sons = @[n]
-  result.typ = newTypeWithSons(c, tyStatic, @[n.typ])
+  result.typ = if n.typ != nil and n.typ.kind == tyStatic: n.typ
+               else: newTypeWithSons(c, tyStatic, @[n.typ])
 
 proc makeAndType*(c: PContext, t1, t2: PType): PType =
   result = newTypeS(tyAnd, c)
@@ -303,16 +319,14 @@ proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
   let intType = getSysType(tyInt)
   result = newTypeS(tyRange, c)
   result.sons = @[intType]
+  if n.typ != nil and n.typ.n == nil:
+    result.flags.incl tfUnresolved
   result.n = newNode(nkRange, n.info, @[
     newIntTypeNode(nkIntLit, 0, intType),
     makeStaticExpr(c, n.nMinusOne)])
 
-template rangeHasStaticIf*(t: PType): bool =
-  # this accepts the ranges's node
-  t.n != nil and t.n.len > 1 and t.n[1].kind == nkStaticExpr
-
-template getStaticTypeFromRange*(t: PType): PType =
-  t.n[1][0][1].typ
+template rangeHasUnresolvedStatic*(t: PType): bool =
+  tfUnresolved in t.flags
 
 proc errorType*(c: PContext): PType =
   ## creates a type representing an error state
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 80b844053..5f9263645 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -136,6 +136,7 @@ proc isCastable(dst, src: PType): bool =
   #  castableTypeKinds = {tyInt, tyPtr, tyRef, tyCstring, tyString,
   #                       tySequence, tyPointer, tyNil, tyOpenArray,
   #                       tyProc, tySet, tyEnum, tyBool, tyChar}
+  let src = src.skipTypes(tyUserTypeClasses)
   if skipTypes(dst, abstractInst-{tyOpenArray}).kind == tyOpenArray:
     return false
   if skipTypes(src, abstractInst-{tyTypeDesc}).kind == tyTypeDesc:
@@ -301,7 +302,7 @@ proc semOf(c: PContext, n: PNode): PNode =
   n.typ = getSysType(tyBool)
   result = n
 
-proc isOpImpl(c: PContext, n: PNode): PNode =
+proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode =
   internalAssert n.sonsLen == 3 and
     n[1].typ != nil and n[1].typ.kind == tyTypeDesc and
     n[2].kind in {nkStrLit..nkTripleStrLit, nkType}
@@ -318,16 +319,18 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
     else:
       result = newIntNode(nkIntLit, 0)
   else:
-    var t2 = n[2].typ.skipTypes({tyTypeDesc})
+    var rhsOrigType = n[2].typ
+    var t2 = rhsOrigType.skipTypes({tyTypeDesc})
     maybeLiftType(t2, c, n.info)
     var m: TCandidate
     initCandidate(c, m, t2)
+    if efExplain in flags: m.diagnostics = @[]
     let match = typeRel(m, t2, t1) >= isSubtype # isNone
     result = newIntNode(nkIntLit, ord(match))
 
   result.typ = n.typ
 
-proc semIs(c: PContext, n: PNode): PNode =
+proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode =
   if sonsLen(n) != 3:
     localError(n.info, errXExpectsTwoArguments, "is")
 
@@ -347,7 +350,7 @@ proc semIs(c: PContext, n: PNode): PNode =
     return
 
   # BUGFIX: don't evaluate this too early: ``T is void``
-  if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n)
+  if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n, flags)
 
 proc semOpAux(c: PContext, n: PNode) =
   const flags = {efDetermineType}
@@ -625,6 +628,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
 
 proc semStaticExpr(c: PContext, n: PNode): PNode =
   let a = semExpr(c, n.sons[0])
+  if a.findUnresolvedStatic != nil: return a
   result = evalStaticExpr(c.module, c.cache, a, c.p.owner)
   if result.isNil:
     localError(n.info, errCannotInterpretNodeX, renderTree(n))
@@ -640,10 +644,10 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
     # for typeof support.
     # for ``type(countup(1,3))``, see ``tests/ttoseq``.
     result = semOverloadedCall(c, n, nOrig,
-      {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator})
+      {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags)
   else:
     result = semOverloadedCall(c, n, nOrig,
-      {skProc, skMethod, skConverter, skMacro, skTemplate})
+      {skProc, skMethod, skConverter, skMacro, skTemplate}, flags)
 
   if result != nil:
     if result.sons[0].kind != nkSym:
@@ -751,7 +755,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
     # This is a proc variable, apply normal overload resolution
     let m = resolveIndirectCall(c, n, nOrig, t)
     if m.state != csMatch:
-      if c.compilesContextId > 0:
+      if errorOutputs == {}:
         # speed up error generation:
         globalError(n.info, errTypeMismatch, "")
         return emptyNode
@@ -912,20 +916,42 @@ const
 
 proc readTypeParameter(c: PContext, typ: PType,
                        paramName: PIdent, info: TLineInfo): PNode =
-  let ty = if typ.kind == tyGenericInst: typ.skipGenericAlias
-           else: (internalAssert(typ.kind == tyCompositeTypeClass);
-                  typ.sons[1].skipGenericAlias)
-  let tbody = ty.sons[0]
-  for s in countup(0, tbody.len-2):
-    let tParam = tbody.sons[s]
-    if tParam.sym.name.id == paramName.id:
-      let rawTyp = ty.sons[s + 1]
-      if rawTyp.kind == tyStatic:
-        return rawTyp.n
+  if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}:
+    for statement in typ.n:
+      case statement.kind
+      of nkTypeSection:
+        for def in statement:
+          if def[0].sym.name.id == paramName.id:
+            # XXX: Instead of lifting the section type to a typedesc
+            # here, we could try doing it earlier in semTypeSection.
+            # This seems semantically correct and then we'll be able
+            # to return the section symbol directly here
+            let foundType = makeTypeDesc(c, def[2].typ)
+            return newSymNode(copySym(def[0].sym).linkTo(foundType), info)
+
+      of nkConstSection:
+        for def in statement:
+          if def[0].sym.name.id == paramName.id:
+            return def[2]
+
       else:
-        let foundTyp = makeTypeDesc(c, rawTyp)
-        return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info)
-  #echo "came here: returned nil"
+        discard
+  
+  if typ.kind != tyUserTypeClass:
+    let ty = if typ.kind == tyCompositeTypeClass: typ.sons[1].skipGenericAlias
+             else: typ.skipGenericAlias
+    let tbody = ty.sons[0]
+    for s in countup(0, tbody.len-2):
+      let tParam = tbody.sons[s]
+      if tParam.sym.name.id == paramName.id:
+        let rawTyp = ty.sons[s + 1]
+        if rawTyp.kind == tyStatic:
+          return rawTyp.n
+        else:
+          let foundTyp = makeTypeDesc(c, rawTyp)
+          return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info)
+
+  return nil
 
 proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
   let s = getGenSym(c, sym)
@@ -1080,6 +1106,23 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   var ty = n.sons[0].typ
   var f: PSym = nil
   result = nil
+
+  template tryReadingGenericParam(t: PType) =
+    case t.kind
+    of tyTypeParamsHolders:
+      return readTypeParameter(c, t, i, n.info)
+    of tyUserTypeClasses:
+      if t.isResolvedUserTypeClass:
+        return readTypeParameter(c, t, i, n.info)
+      else:
+        n.typ = makeTypeFromExpr(c, copyTree(n))
+        return n
+    of tyGenericParam:
+      n.typ = makeTypeFromExpr(c, copyTree(n))
+      return n
+    else:
+      discard
+
   if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone):
     if ty.kind == tyTypeDesc: ty = ty.base
     ty = ty.skipTypes(tyDotOpTransparent)
@@ -1097,8 +1140,6 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
         markUsed(n.info, f, c.graph.usageSym)
         styleCheckUse(n.info, f)
         return
-    of tyTypeParamsHolders:
-      return readTypeParameter(c, ty, i, n.info)
     of tyObject, tyTuple:
       if ty.n != nil and ty.n.kind == nkRecList:
         let field = lookupInRecord(ty.n, i)
@@ -1107,8 +1148,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
           n.typ.n = copyTree(n)
           return n
     else:
-      # echo "TYPE FIELD ACCESS"
-      # debug ty
+      tryReadingGenericParam(ty)
       return
     # XXX: This is probably not relevant any more
     # reset to prevent 'nil' bug: see "tests/reject/tenumitems.nim":
@@ -1151,8 +1191,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # we didn't find any field, let's look for a generic param
   if result == nil:
     let t = n.sons[0].typ.skipTypes(tyDotOpTransparent)
-    if t.kind in tyTypeParamsHolders:
-      result = readTypeParameter(c, t, i, n.info)
+    tryReadingGenericParam(t)
 
 proc dotTransformation(c: PContext, n: PNode): PNode =
   if isSymChoice(n.sons[1]):
@@ -1718,7 +1757,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   let oldOwnerLen = len(c.graph.owners)
   let oldGenerics = c.generics
   let oldErrorOutputs = errorOutputs
-  errorOutputs = {}
+  if efExplain notin flags: errorOutputs = {}
   let oldContextLen = msgs.getInfoContextLen()
 
   let oldInGenericContext = c.inGenericContext
@@ -1731,8 +1770,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     result = semExpr(c, n, flags)
     if msgs.gErrorCounter != oldErrorCount: result = nil
   except ERecoverableError:
-    if optReportConceptFailures in gGlobalOptions:
-      err = getCurrentExceptionMsg()
+    discard
   # undo symbol table changes (as far as it's possible):
   c.compilesContextId = oldCompilesId
   c.generics = oldGenerics
@@ -1746,8 +1784,6 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   errorOutputs = oldErrorOutputs
   msgs.gErrorCounter = oldErrorCount
   msgs.gErrorMax = oldErrorMax
-  if optReportConceptFailures in gGlobalOptions and not err.isNil:
-    localError(n.info, err)
 
 proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # we replace this node by a 'true' or 'false' node:
@@ -1814,7 +1850,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
   of mLow: result = semLowHigh(c, setMs(n, s), mLow)
   of mHigh: result = semLowHigh(c, setMs(n, s), mHigh)
   of mSizeOf: result = semSizeof(c, setMs(n, s))
-  of mIs: result = semIs(c, setMs(n, s))
+  of mIs: result = semIs(c, setMs(n, s), flags)
   of mOf: result = semOf(c, setMs(n, s))
   of mShallowCopy: result = semShallowCopy(c, n, flags)
   of mExpandToAst: result = semExpandToAst(c, n, s, flags)
@@ -2327,8 +2363,20 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkCurlyExpr:
     result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags)
   of nkPragmaExpr:
-    # which pragmas are allowed for expressions? `likely`, `unlikely`
-    internalError(n.info, "semExpr() to implement") # XXX: to implement
+    var
+      expr = n[0]
+      pragma = n[1]
+      pragmaName = considerQuotedIdent(pragma[0])
+      flags = flags
+    
+    case whichKeyword(pragmaName)
+    of wExplain:
+      flags.incl efExplain
+    else:
+      # what other pragmas are allowed for expressions? `likely`, `unlikely`
+      invalidPragma(n)
+    
+    result = semExpr(c, n[0], flags)
   of nkPar:
     case checkPar(n)
     of paNone: result = errorNode(c, n)
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index 71752f5c3..874be8dd6 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -304,7 +304,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
       pragma(c, result, n.sons[pragmasPos], allRoutinePragmas)
     if isNil(n.sons[bodyPos]):
       n.sons[bodyPos] = copyTree(fn.getBody)
-    instantiateBody(c, n, fn.typ.n, result, fn)
+    if c.inGenericContext == 0:
+      instantiateBody(c, n, fn.typ.n, result, fn)
     sideEffectsCheck(c, result)
     paramsTypeCheck(c, result.typ)
   else:
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index 5eed1e702..3e1989eaf 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -24,7 +24,7 @@ proc semTypeOf(c: PContext; n: PNode): PNode =
   result = newNodeI(nkTypeOfExpr, n.info)
   let typExpr = semExprWithType(c, n, {efInTypeof})
   result.add typExpr
-  result.typ = makeTypeDesc(c, typExpr.typ.skipTypes({tyTypeDesc}))
+  result.typ = makeTypeDesc(c, typExpr.typ)
 
 type
   SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn
@@ -86,9 +86,32 @@ proc semInstantiationInfo(c: PContext, n: PNode): PNode =
   result.add(filename)
   result.add(line)
 
+proc toNode(t: PType, i: TLineInfo): PNode =
+  result = newNodeIT(nkType, i, t)
+
+const 
+  # these are types that use the bracket syntax for instantiation
+  # they can be subjected to the type traits `genericHead` and 
+  # `Uninstantiated`
+  tyUserDefinedGenerics* = {tyGenericInst, tyGenericInvocation,
+                            tyUserTypeClassInst}
+
+  tyMagicGenerics* = {tySet, tySequence, tyArray, tyOpenArray}
+
+  tyGenericLike* = tyUserDefinedGenerics +
+                   tyMagicGenerics +
+                   {tyCompositeTypeClass}
+
+proc uninstantiate(t: PType): PType =
+  result = case t.kind
+    of tyMagicGenerics: t
+    of tyUserDefinedGenerics: t.base
+    of tyCompositeTypeClass: uninstantiate t.sons[1]
+    else: t
+
 proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode =
-  let typ = operand.skipTypes({tyTypeDesc})
-  case trait.sym.name.s.normalize
+  var typ = operand.skipTypes({tyTypeDesc})
+  case trait.sym.name.s
   of "name":
     result = newStrNode(nkStrLit, typ.typeToString(preferName))
     result.typ = newType(tyString, context)
@@ -97,6 +120,16 @@ proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode =
     result = newIntNode(nkIntLit, typ.len - ord(typ.kind==tyProc))
     result.typ = newType(tyInt, context)
     result.info = trait.info
+  of "genericHead":
+    var res = uninstantiate(typ)
+    if res == typ and res.kind notin tyMagicGenerics:
+      localError(trait.info,
+        "genericHead expects a generic type. The given type was " &
+        typeToString(typ))
+      return newType(tyError, context).toNode(trait.info)
+    result = res.base.toNode(trait.info)
+  of "stripGenericParams":
+    result = uninstantiate(typ).toNode(trait.info)
   else:
     internalAssert false
 
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index f4efcc0ff..5f025f943 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -501,6 +501,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
           #changeType(def.skipConv, typ, check=true)
       else:
         typ = skipIntLit(def.typ)
+        if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass:
+          typ = typ.lastSon
         if hasEmpty(typ):
           localError(def.info, errCannotInferTypeOfTheLiteral,
                      ($typ.kind).substr(2).toLowerAscii)
@@ -1554,6 +1556,16 @@ proc usesResult(n: PNode): bool =
       for c in n:
         if usesResult(c): return true
 
+proc inferConceptStaticParam(c: PContext, inferred, n: PNode) =
+  var typ = inferred.typ
+  let res = semConstExpr(c, n)
+  if not sameType(res.typ, typ.base):
+    localError(n.info,
+      "cannot infer the concept parameter '%s', due to a type mismatch. " &
+      "attempt to equate '%s' and '%s'.",
+      [inferred.renderTree, $res.typ, $typ.base])
+  typ.n = res
+
 proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # these must be last statements in a block:
   const
@@ -1605,10 +1617,21 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
       n.typ = n.sons[i].typ
       return
     else:
-      n.sons[i] = semExpr(c, n.sons[i])
-      if c.inTypeClass > 0 and n[i].typ != nil:
-        case n[i].typ.kind
+      var expr = semExpr(c, n.sons[i], flags)
+      n.sons[i] = expr
+      if c.inTypeClass > 0 and expr.typ != nil:
+        case expr.typ.kind
         of tyBool:
+          if expr.kind == nkInfix and
+             expr[0].kind == nkSym and
+             expr[0].sym.name.s == "==":
+            if expr[1].typ.isUnresolvedStatic:
+              inferConceptStaticParam(c, expr[1], expr[2])
+              continue
+            elif expr[2].typ.isUnresolvedStatic:
+              inferConceptStaticParam(c, expr[2], expr[1])
+              continue
+            
           let verdict = semConstExpr(c, n[i])
           if verdict.intVal == 0:
             localError(result.info, "type class predicate failed")
@@ -1632,8 +1655,12 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
           of nkPragma, nkCommentStmt, nkNilLit, nkEmpty: discard
           else: localError(n.sons[j].info, errStmtInvalidAfterReturn)
       else: discard
-  if result.len == 1 and result.sons[0].kind != nkDefer:
+  
+  if result.len == 1 and
+     c.inTypeClass == 0 and # concept bodies should be preserved as a stmt list
+     result.sons[0].kind != nkDefer:
     result = result.sons[0]
+
   when defined(nimfix):
     if result.kind == nkCommentStmt and not result.comment.isNil and
         not (result.comment[0] == '#' and result.comment[1] == '#'):
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index e86b527d6..422d2f0fa 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -135,7 +135,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
     let isCall = ord(n.kind in nkCallKinds+{nkBracketExpr})
     let n = if n[0].kind == nkBracket: n[0] else: n
     checkMinSonsLen(n, 1)
-    var base = semTypeNode(c, n.lastSon, nil)
+    var base = semTypeNode(c, n.lastSon, nil).skipTypes({tyTypeDesc})
     result = newOrPrevType(kind, prev, c)
     var isNilable = false
     # check every except the last is an object:
@@ -155,7 +155,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
 proc semVarType(c: PContext, n: PNode, prev: PType): PType =
   if sonsLen(n) == 1:
     result = newOrPrevType(tyVar, prev, c)
-    var base = semTypeNode(c, n.sons[0], nil)
+    var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc})
     if base.kind == tyVar:
       localError(n.info, errVarVarTypeNotAllowed)
       base = base.sons[0]
@@ -185,16 +185,21 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
   for i in 0..1:
     rangeT[i] = range[i].typ.skipTypes({tyStatic}).skipIntLit
 
-  if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
-    localError(n.info, errPureTypeMismatch)
-  elif not rangeT[0].isOrdinalType:
-    localError(n.info, errOrdinalTypeExpected)
-  elif enumHasHoles(rangeT[0]):
-    localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s)
+  let hasUnknownTypes = c.inGenericContext > 0 and
+    rangeT[0].kind == tyFromExpr or rangeT[1].kind == tyFromExpr
+
+  if not hasUnknownTypes:
+    if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
+      localError(n.info, errPureTypeMismatch)
+    elif not rangeT[0].isOrdinalType:
+      localError(n.info, errOrdinalTypeExpected)
+    elif enumHasHoles(rangeT[0]):
+      localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s)
 
   for i in 0..1:
     if hasGenericArguments(range[i]):
       result.n.addSon makeStaticExpr(c, range[i])
+      result.flags.incl tfUnresolved
     else:
       result.n.addSon semConstExpr(c, range[i])
 
@@ -227,7 +232,8 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType =
     result = newOrPrevType(tyError, prev, c)
 
 proc semArrayIndex(c: PContext, n: PNode): PType =
-  if isRange(n): result = semRangeAux(c, n, nil)
+  if isRange(n):
+    result = semRangeAux(c, n, nil)
   else:
     let e = semExprWithType(c, n, {efDetermineType})
     if e.typ.kind == tyFromExpr:
@@ -765,6 +771,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     let owner = if typeClass.sym != nil: typeClass.sym
                 else: getCurrOwner(c)
     var s = newSym(skType, finalTypId, owner, info)
+    if sfExplain in owner.flags: s.flags.incl sfExplain
     if typId == nil: s.flags.incl(sfAnon)
     s.linkTo(typeClass)
     typeClass.flags.incl tfImplicitTypeParam
@@ -843,9 +850,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
 
     for i in 0 .. paramType.sonsLen - 2:
       if paramType.sons[i].kind == tyStatic:
-        var x = copyNode(ast.emptyNode)
-        x.typ = paramType.sons[i]
-        result.rawAddSon makeTypeFromExpr(c, x) # aka 'tyUnknown'
+        var staticCopy = paramType.sons[i].exactReplica
+        staticCopy.flags.incl tfInferrableStatic
+        result.rawAddSon staticCopy
       else:
         result.rawAddSon newTypeS(tyAnything, c)
 
@@ -883,12 +890,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     for i in 1 .. <paramType.len:
       let lifted = liftingWalk(paramType.sons[i])
       if lifted != nil: paramType.sons[i] = lifted
-    when false:
+
+    if paramType.base.lastSon.kind == tyUserTypeClass:
       let expanded = instGenericContainer(c, info, paramType,
                                           allowMetaTypes = true)
       result = liftingWalk(expanded, true)
 
-  of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
+  of tyUserTypeClasses, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
     result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), true))
 
   of tyGenericParam:
@@ -1223,6 +1231,13 @@ proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) =
     result.sym = prev.sym
     assignType(prev, result)
 
+proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym =
+  if n.kind == nkType:
+    result = symFromType(n.typ, n.info)
+  else:
+    localError(n.info, errTypeExpected)
+    result = errorSym(c, n)
+
 proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   result = nil
   when defined(nimsuggest):
@@ -1237,6 +1252,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     let typExpr = semExprWithType(c, n.sons[0], {efInTypeof})
     fixupTypeOf(c, prev, typExpr)
     result = typExpr.typ
+    if result.kind == tyTypeDesc: result.flags.incl tfExplicit
   of nkPar:
     if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev)
     else:
@@ -1312,7 +1328,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     result = semTypeNode(c, whenResult, prev)
   of nkBracketExpr:
     checkMinSonsLen(n, 2)
-    var s = semTypeIdent(c, n.sons[0])
+    var head = n.sons[0]
+    var s = if head.kind notin nkCallKinds: semTypeIdent(c, head)
+            else: symFromExpectedTypeNode(c, semExpr(c, head))
     case s.magic
     of mArray: result = semArray(c, n, prev)
     of mOpenArray: result = semContainer(c, n, tyOpenArray, "openarray", prev)
@@ -1344,6 +1362,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     else: result = semGeneric(c, n, s, prev)
   of nkDotExpr:
     let typeExpr = semExpr(c, n)
+    if typeExpr.typ.kind == tyFromExpr:
+      return typeExpr.typ
     if typeExpr.typ.kind != tyTypeDesc:
       localError(n.info, errTypeExpected)
       result = errorType(c)
@@ -1403,7 +1423,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkDistinctTy: result = semDistinct(c, n, prev)
   of nkStaticTy:
     result = newOrPrevType(tyStatic, prev, c)
-    var base = semTypeNode(c, n.sons[0], nil)
+    var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc})
     result.rawAddSon(base)
     result.flags.incl tfHasStatic
   of nkIteratorTy:
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index 75dffb67f..fddcc7a24 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -122,6 +122,7 @@ proc isTypeParam(n: PNode): bool =
 proc hasGenericArguments*(n: PNode): bool =
   if n.kind == nkSym:
     return n.sym.kind == skGenericParam or
+           tfInferrableStatic in n.sym.typ.flags or
            (n.sym.kind == skType and
             n.sym.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {})
   else:
@@ -144,7 +145,7 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
       if isTypeParam(n[i]): needsFixing = true
     if needsFixing:
       n.sons[0] = newSymNode(n.sons[0].sym.owner)
-      return cl.c.semOverloadedCall(cl.c, n, n, {skProc})
+      return cl.c.semOverloadedCall(cl.c, n, n, {skProc}, {})
 
   for i in 0 .. <n.safeLen:
     n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i])
@@ -403,6 +404,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
   case t.kind
   of tyGenericInvocation:
     result = handleGenericInvocation(cl, t)
+    if result.lastSon.kind == tyUserTypeClass:
+      result.kind = tyUserTypeClassInst
 
   of tyGenericBody:
     localError(cl.info, errCannotInstantiateX, typeToString(t))
@@ -444,10 +447,10 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
     elif t.sons[0].kind != tyNone:
       result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0]))
 
-  of tyUserTypeClass:
+  of tyUserTypeClass, tyStatic:
     result = t
 
-  of tyGenericInst:
+  of tyGenericInst, tyUserTypeClassInst:
     bailout()
     result = instCopyType(cl, t)
     idTablePut(cl.localCache, t, result)
@@ -501,8 +504,9 @@ proc initTypeVars*(p: PContext, pt: TIdTable, info: TLineInfo;
   result.owner = owner
 
 proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode;
-                         owner: PSym): PNode =
+                         owner: PSym, allowMetaTypes = false): PNode =
   var cl = initTypeVars(p, pt, n.info, owner)
+  cl.allowMetaTypes = allowMetaTypes
   pushInfoContext(n.info)
   result = replaceTypeVarsN(cl, n)
   popInfoContext()
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 164fd0999..ff7b0ae72 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -22,7 +22,13 @@ type
   TCandidateState* = enum
     csEmpty, csMatch, csNoMatch
 
-  CandidateErrors* = seq[(PSym,int)]
+  CandidateError* = object
+    sym*: PSym
+    unmatchedVarParam*: int
+    diagnostics*: seq[string]
+  
+  CandidateErrors* = seq[CandidateError]
+
   TCandidate* = object
     c*: PContext
     exactMatches*: int       # also misused to prefer iters over procs
@@ -49,11 +55,20 @@ type
                              # 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 this is not nil, 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.
     mutabilityProblem*: uint8 # tyVar mismatch
     inheritancePenalty: int  # to prefer closest father object type
-    errors*: CandidateErrors # additional clarifications to be displayed to the
-                             # user if overload resolution fails
-
+  
   TTypeRelation* = enum      # order is important!
     isNone, isConvertible,
     isIntConv,
@@ -101,7 +116,7 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
   idTablePut(c.bindings, key, val.skipIntLit)
 
 proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
-                    binding: PNode, calleeScope = -1) =
+                    binding: PNode, calleeScope = -1, diagnostics = false) =
   initCandidateAux(ctx, c, callee.typ)
   c.calleeSym = callee
   if callee.kind in skProcKinds and calleeScope == -1:
@@ -116,9 +131,9 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
       c.calleeScope = 1
   else:
     c.calleeScope = calleeScope
+  c.diagnostics = if diagnostics: @[] else: nil
   c.magic = c.calleeSym.magic
   initIdTable(c.bindings)
-  c.errors = nil
   if binding != nil and callee.kind in routineKinds:
     var typeParams = callee.ast[genericParamsPos]
     for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1):
@@ -572,19 +587,22 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
     result = isNone
 
 proc matchUserTypeClass*(c: PContext, m: var TCandidate,
-                         ff, a: PType): TTypeRelation =
-  var body = ff.skipTypes({tyUserTypeClassInst})
+                         ff, a: PType): PType =
+  var
+    typeClass = ff.skipTypes({tyUserTypeClassInst})
+    body = typeClass.n[3]
   if c.inTypeClass > 4:
-    localError(body.n[3].info, $body.n[3] & " too nested for type matching")
-    return isNone
+    localError(body.info, $body & " too nested for type matching")
+    return nil
 
   openScope(c)
   inc c.inTypeClass
-
   defer:
     dec c.inTypeClass
     closeScope(c)
 
+  var typeParams: seq[(PSym, PType)]
+
   if ff.kind == tyUserTypeClassInst:
     for i in 1 .. <(ff.len - 1):
       var
@@ -593,44 +611,104 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
         param: PSym
 
       template paramSym(kind): untyped =
-        newSym(kind, typeParamName, body.sym, body.sym.info)
-
-      case typ.kind
-      of tyStatic:
-        param = paramSym skConst
-        param.typ = typ.base
-        param.ast = typ.n
-      of tyUnknown:
-        param = paramSym skVar
-        param.typ = typ
-      else:
-        param = paramSym skType
-        param.typ = makeTypeDesc(c, typ)
-
+        newSym(kind, typeParamName, 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
+          if typ.n == nil:
+            param.typ.flags.incl tfInferrableStatic
+          else:
+            param.ast = typ.n
+        of tyUnknown:
+          param = paramSym skVar
+          param.typ = typ.exactReplica
+        else:
+          param = paramSym skType
+          param.typ = if typ.isMetaType:
+                        c.newTypeWithSons(tyInferred, @[typ])
+                      else:
+                        makeTypeDesc(c, typ)
+
+        typeParams.safeAdd((param, typ))
+      
       addDecl(c, param)
-      #echo "A ", param.name.s, " ", typeToString(param.typ), " ", param.kind
 
-  for param in body.n[0]:
+  for param in typeClass.n[0]:
     var
       dummyName: PNode
       dummyType: PType
 
-    if param.kind == nkVarTy:
+    let modifier = case param.kind
+      of nkVarTy: tyVar
+      of nkRefTy: tyRef
+      of nkPtrTy: tyPtr
+      of nkStaticTy: tyStatic
+      of nkTypeOfExpr: tyTypeDesc
+      else: tyNone
+
+    if modifier != tyNone:
       dummyName = param[0]
-      dummyType = if a.kind != tyVar: makeVarType(c, a) else: a
+      dummyType = c.makeTypeWithModifier(modifier, a)
+      if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit
     else:
       dummyName = param
       dummyType = a
 
     internalAssert dummyName.kind == nkIdent
-    var dummyParam = newSym(skVar, dummyName.ident, body.sym, body.sym.info)
+    var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar,
+                            dummyName.ident, typeClass.sym, typeClass.sym.info)
     dummyParam.typ = dummyType
     addDecl(c, dummyParam)
-    #echo "B ", dummyName.ident.s, " ", typeToString(dummyType), " ", dummyparam.kind
 
-  var checkedBody = c.semTryExpr(c, body.n[3].copyTree)
-  if checkedBody == nil: return isNone
-  return isGeneric
+  var
+    oldWriteHook: type(writelnHook)
+    diagnostics: seq[string]
+    errorPrefix: string
+    flags: TExprFlags = {}
+    collectDiagnostics = m.diagnostics != nil or
+                         sfExplain in typeClass.sym.flags
+  
+  if collectDiagnostics:
+    oldWriteHook = writelnHook
+    # XXX: we can't write to m.diagnostics directly, because
+    # Nim doesn't support capturing var params in closures
+    diagnostics = @[]
+    flags = {efExplain}
+    writelnHook = proc (s: string) =
+      if errorPrefix == nil: 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:
+    writelnHook = oldWriteHook
+    for msg in diagnostics: m.diagnostics.safeAdd msg
+  
+  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 = copyType(ff, ff.owner, true)
+
+  result.n = checkedBody
 
 proc shouldSkipDistinct(rules: PNode, callIdent: PIdent): bool =
   if rules.kind == nkWith:
@@ -649,16 +727,125 @@ proc maybeSkipDistinct(t: PType, callee: PSym): PType =
   else:
     result = t
 
-proc tryResolvingStaticExpr(c: var TCandidate, n: PNode): PNode =
+proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
+                            allowUnresolved = false): 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)
+  let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil,
+                                        allowMetaTypes = allowUnresolved)
   result = c.c.semExpr(c.c, instantiated)
 
+proc inferStaticParam*(lhs: PNode, rhs: BiggestInt): PType =
+  # 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 the inferred static type with the `n` field
+  #   populated with the inferred value.
+  #
+  #   `nil` will be returned if the inference was not possible
+  #
+  if lhs.kind in nkCallKinds and lhs[0].kind == nkSym:
+    case lhs[0].sym.magic
+    of mUnaryLt:
+      return inferStaticParam(lhs[1], rhs + 1)
+
+    of mAddI, mAddU, mInc, mSucc:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(lhs[2], rhs - lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(lhs[1], rhs - lhs[2].intVal)
+    
+    of mDec, mSubI, mSubU, mPred:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(lhs[2], lhs[1].intVal - rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(lhs[1], rhs + lhs[2].intVal)
+    
+    of mMulI, mMulU:
+      if lhs[1].kind == nkIntLit:
+        if rhs mod lhs[1].intVal == 0:
+          return inferStaticParam(lhs[2], rhs div lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        if rhs mod lhs[2].intVal == 0:
+          return inferStaticParam(lhs[1], rhs div lhs[2].intVal)
+    
+    of mDivI, mDivU:
+      if lhs[1].kind == nkIntLit:
+        if lhs[1].intVal mod rhs == 0:
+          return inferStaticParam(lhs[2], lhs[1].intVal div rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(lhs[1], lhs[2].intVal * rhs)
+    
+    of mShlI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(lhs[1], rhs shr lhs[2].intVal)
+    
+    of mShrI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(lhs[1], rhs shl lhs[2].intVal)
+    
+    of mUnaryMinusI:
+      return inferStaticParam(lhs[1], -rhs)
+    
+    of mUnaryPlusI, mToInt, mToBiggestInt:
+      return inferStaticParam(lhs[1], rhs)
+    
+    else: discard
+  
+  elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and lhs.typ.n == nil:
+    lhs.typ.n = newIntNode(nkIntLit, rhs)
+    return lhs.typ
+  
+  return nil
+
+proc failureToInferStaticParam(n: PNode) =
+  let staticParam = n.findUnresolvedStatic
+  let name = if staticParam != nil: staticParam.sym.name.s
+             else: "unknown"
+  localError(n.info, errCannotInferStaticParam, 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(c: var TCandidate, e: PNode, r: BiggestInt) =
+    var exp = e
+    var rhs = r
+    var inferred = inferStaticParam(exp, rhs)
+    if inferred != nil:
+      put(c, inferred, inferred)
+      return isGeneric
+    else:
+      failureToInferStaticParam exp
+
+  if lowerBound.kind == nkIntLit:
+    if upperBound.kind == nkIntLit:
+      if lengthOrd(concrete) == upperBound.intVal - lowerBound.intVal + 1:
+        return isGeneric
+      else:
+        return isNone
+    doInferStatic(c, upperBound, lengthOrd(concrete) + lowerBound.intVal - 1)
+  elif upperBound.kind == nkIntLit:
+    doInferStatic(c, lowerBound, upperBound.intVal + 1 - lengthOrd(concrete))
+
 template subtypeCheck() =
   if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar}:
     result = isNone
@@ -689,8 +876,49 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   assert(aOrig != nil)
 
+  var
+    useTypeLoweringRuleInTypeClass = c.c.inTypeClass > 0 and
+                                     not c.isNoCall and
+                                     f.kind != tyTypeDesc and
+                                     tfExplicit notin aOrig.flags
+
+    aOrig = if useTypeLoweringRuleInTypeClass:
+          aOrig.skipTypes({tyTypeDesc, tyFieldAccessor})
+        else:
+          aOrig
+
+  if aOrig.kind == tyInferred:
+    let prev = aOrig.previouslyInferred
+    if prev != nil:
+      return typeRel(c, f, prev)
+    else:
+      var candidate = f
+
+      case f.kind
+      of tyGenericParam:
+        var prev  = PType(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:
+          localError(f.n.info, errTypeExpected)
+      else:
+        discard
+
+      result = typeRel(c, aOrig.base, candidate)
+      if result != isNone:
+        c.inferredTypes.safeAdd aOrig
+        aOrig.sons.add candidate
+        result = isEqual
+      return
+
   # var and static arguments match regular modifier-free types
-  let a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym)
+  var a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(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
@@ -702,6 +930,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         tyGenericInst, tyGenericParam} + tyTypeClasses:
     return typeRel(c, f, lastSon(a))
 
+  if a.isResolvedUserTypeClass:
+    return typeRel(c, f, a.lastSon)
+
   template bindingRet(res) =
     if doBind:
       let bound = aOrig.skipTypes({tyRange}).skipIntLit
@@ -807,6 +1038,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
     case a.kind
     of tyArray:
       var fRange = f.sons[0]
+      var aRange = a.sons[0]
       if fRange.kind == tyGenericParam:
         var prev = PType(idTableGet(c.bindings, fRange))
         if prev == nil:
@@ -814,28 +1046,14 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
           fRange = a
         else:
           fRange = prev
-      result = typeRel(c, f.sons[1], a.sons[1])
+      result = typeRel(c, f.sons[1].skipTypes({tyTypeDesc}),
+                          a.sons[1].skipTypes({tyTypeDesc}))
       if result < isGeneric: return isNone
-      if rangeHasStaticIf(fRange):
-        if tfUnresolved in fRange.flags:
-          # This is a range from an array instantiated with a generic
-          # static param. We must extract the static param here and bind
-          # it to the size of the currently supplied array.
-          var
-            rangeStaticT = fRange.getStaticTypeFromRange
-            replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType])
-            inputUpperBound = a.sons[0].n[1].intVal
-          # we must correct for the off-by-one discrepancy between
-          # ranges and static params:
-          replacementT.n = newIntNode(nkIntLit, inputUpperBound + 1)
-          put(c, rangeStaticT, replacementT)
-          return isGeneric
-
-        let len = tryResolvingStaticExpr(c, fRange.n[1])
-        if len.kind == nkIntLit and len.intVal+1 == lengthOrd(a):
-          return # if we get this far, the result is already good
-        else:
-          return isNone
+      
+      if fRange.rangeHasUnresolvedStatic:
+        return inferStaticsInRange(c, fRange, a)
+      elif c.c.inTypeClass > 0 and aRange.rangeHasUnresolvedStatic:
+        return inferStaticsInRange(c, aRange, f)
       elif lengthOrd(fRange) != lengthOrd(a):
         result = isNone
     else: discard
@@ -1111,11 +1329,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       else:
         return isNone
 
-  of tyUserTypeClass, tyUserTypeClassInst:
-    considerPreviousT:
-      result = matchUserTypeClass(c.c, c, f, aOrig)
-      if result == isGeneric:
-        put(c, f, a)
+  of tyUserTypeClassInst, tyUserTypeClass:
+    if f.isResolvedUserTypeClass:
+      result = typeRel(c, f.lastSon, a)
+    else:
+      var matched = matchUserTypeClass(c.c, c, f, aOrig)
+      if matched != nil:
+        bindConcreteTypeToUserTypeClass(matched, a)
+        put(c, f, matched)
+        result = isGeneric
+      else:
+        result = isNone
 
   of tyCompositeTypeClass:
     considerPreviousT:
@@ -1212,6 +1436,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       # XXX endless recursion?
       #result = typeRel(c, prev, aOrig)
       result = isNone
+
+  of tyInferred:
+    let prev = f.previouslyInferred
+    if prev != nil:
+      result = typeRel(c, prev, a)
+    else:
+      result = typeRel(c, f.base, a)
+      if result != isNone:
+        c.inferredTypes.safeAdd f
+        f.sons.add a
+
   of tyTypeDesc:
     var prev = PType(idTableGet(c.bindings, f))
     if prev == nil:
@@ -1355,12 +1590,12 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) =
   of isEqual: inc(m.exactMatches)
   of isNone: discard
 
-proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
+proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
                         argSemantized, argOrig: PNode): PNode =
   var
     fMaybeStatic = f.skipTypes({tyDistinct})
     arg = argSemantized
-    argType = argType
+    a = a
     c = m.c
 
   if tfHasStatic in fMaybeStatic.flags:
@@ -1370,15 +1605,15 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
 
     # XXX: weaken tyGenericParam and call it tyGenericPlaceholder
     # and finally start using tyTypedesc for generic types properly.
-    if argType.kind == tyGenericParam and tfWildcard in argType.flags:
-      argType.assignType(f)
-      # put(m.bindings, f, argType)
+    if a.kind == tyGenericParam and tfWildcard in a.flags:
+      a.assignType(f)
+      # put(m.bindings, f, a)
       return argSemantized
 
-    if argType.kind == tyStatic:
+    if a.kind == tyStatic:
       if m.callee.kind == tyGenericBody and
-         argType.n == nil and
-         tfGenericTypeParam notin argType.flags:
+         a.n == nil and
+         tfGenericTypeParam notin a.flags:
         return newNodeIT(nkType, argOrig.info, makeTypeFromExpr(c, arg))
     else:
       var evaluated = c.semTryConstExpr(c, arg)
@@ -1386,9 +1621,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
         arg.typ = newTypeS(tyStatic, c)
         arg.typ.sons = @[evaluated.typ]
         arg.typ.n = evaluated
-        argType = arg.typ
+        a = arg.typ
 
-  var a = argType
   var r = typeRel(m, f, a)
 
   if r != isNone and m.calleeSym != nil and
@@ -1830,6 +2064,10 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
           def = implicitConv(nkHiddenStdConv, formal.typ, def, m, c)
         setSon(m.call, formal.position + 1, def)
     inc(f)
+  # forget all inferred types if the overload matching failed
+  if m.state == csNoMatch:
+    for t in m.inferredTypes:
+      if t.sonsLen > 1: t.sons.setLen 1
 
 proc argtypeMatches*(c: PContext, f, a: PType): bool =
   var m: TCandidate
diff --git a/compiler/types.nim b/compiler/types.nim
index f4ef75094..3f84548a1 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -55,16 +55,17 @@ const
   # TODO: Remove tyTypeDesc from each abstractX and (where necessary)
   # replace with typedescX
   abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal,
-                   tyTypeDesc, tyAlias}
+                   tyTypeDesc, tyAlias, tyInferred}
   abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc,
-                  tyAlias}
+                  tyAlias, tyInferred}
   abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc,
-                    tyAlias}
+                    tyAlias, tyInferred}
   abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal,
-                       tyTypeDesc, tyAlias}
-  abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias}
-
-  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias}
+                       tyTypeDesc, tyAlias, tyInferred}
+  abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias,
+                   tyInferred}
+  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias,
+               tyInferred}
   # typedescX is used if we're sure tyTypeDesc should be included (or skipped)
   typedescPtrs* = abstractPtrs + {tyTypeDesc}
   typedescInst* = abstractInst + {tyTypeDesc}
@@ -182,7 +183,7 @@ proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
   if result: return
   if not containsOrIncl(marker, t.id):
     case t.kind
-    of tyGenericInst, tyGenericBody, tyAlias:
+    of tyGenericInst, tyGenericBody, tyAlias, tyInferred:
       result = iterOverTypeAux(marker, lastSon(t), iter, closure)
     else:
       for i in countup(0, sonsLen(t) - 1):
@@ -410,12 +411,19 @@ const
     "unused0", "unused1",
     "unused2", "varargs[$1]", "unused", "Error Type",
     "BuiltInTypeClass", "UserTypeClass",
-    "UserTypeClassInst", "CompositeTypeClass",
+    "UserTypeClassInst", "CompositeTypeClass", "inferred",
     "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor",
     "void"]
 
 const preferToResolveSymbols = {preferName, preferModuleInfo, preferGenericArg}
 
+template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) =
+  tc.sons.safeAdd concrete
+  tc.flags.incl tfResolved
+
+template isResolvedUserTypeClass*(t: PType): bool =
+  tfResolved in t.flags
+
 proc addTypeFlags(name: var string, typ: PType) {.inline.} =
   if tfNotNil in typ.flags: name.add(" not nil")
 
@@ -460,6 +468,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       if t.n != nil: result.add "(" & renderTree(t.n) & ")"
   of tyUserTypeClass:
     internalAssert t.sym != nil and t.sym.owner != nil
+    if t.isResolvedUserTypeClass: return typeToString(t.lastSon)
     return t.sym.owner.name.s
   of tyBuiltInTypeClass:
     result = case t.base.kind:
@@ -476,6 +485,10 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       of tyTuple: "tuple"
       of tyOpenArray: "openarray"
       else: typeToStr[t.base.kind]
+  of tyInferred:
+    let concrete = t.previouslyInferred
+    if concrete != nil: result = typeToString(concrete)
+    else: result = "inferred[" & typeToString(t.base) & "]"
   of tyUserTypeClassInst:
     let body = t.base
     result = body.sym.name.s & "["
@@ -971,7 +984,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
     result = sameTypeOrNilAux(a.sons[0], b.sons[0], c) and
         sameValue(a.n.sons[0], b.n.sons[0]) and
         sameValue(a.n.sons[1], b.n.sons[1])
-  of tyGenericInst, tyAlias: discard
+  of tyGenericInst, tyAlias, tyInferred:
+    cycleCheck()
+    result = sameTypeAux(a.lastSon, b.lastSon, c)
   of tyNone: result = false
   of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags")
 
@@ -1118,7 +1133,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     result = nil
   of tyOrdinal:
     if kind != skParam: result = t
-  of tyGenericInst, tyDistinct, tyAlias:
+  of tyGenericInst, tyDistinct, tyAlias, tyInferred:
     result = typeAllowedAux(marker, lastSon(t), kind, flags)
   of tyRange:
     if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin
@@ -1305,14 +1320,20 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt =
     if result < 0: return
     if a < maxAlign: a = maxAlign
     result = align(result, a)
+  of tyInferred:
+    if typ.len > 1:
+      result = computeSizeAux(typ.lastSon, a)
   of tyGenericInst, tyDistinct, tyGenericBody, tyAlias:
     result = computeSizeAux(lastSon(typ), a)
+  of tyTypeClasses:
+    result = if typ.isResolvedUserTypeClass: computeSizeAux(typ.lastSon, a)
+             else: szUnknownSize
   of tyTypeDesc:
     result = computeSizeAux(typ.base, a)
   of tyForward: return szIllegalRecursion
   of tyStatic:
-    if typ.n != nil: result = computeSizeAux(lastSon(typ), a)
-    else: result = szUnknownSize
+    result = if typ.n != nil: computeSizeAux(typ.lastSon, a)
+             else: szUnknownSize
   else:
     #internalError("computeSizeAux()")
     result = szUnknownSize
@@ -1333,18 +1354,17 @@ proc getSize(typ: PType): BiggestInt =
   if result < 0: internalError("getSize: " & $typ.kind)
 
 proc containsGenericTypeIter(t: PType, closure: RootRef): bool =
-  if t.kind == tyStatic:
+  case t.kind
+  of tyStatic:
     return t.n == nil
-
-  if t.kind == tyTypeDesc:
+  of tyTypeDesc:
     if t.base.kind == tyNone: return true
     if containsGenericTypeIter(t.base, closure): return true
     return false
-
-  if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}:
+  of GenericTypes + tyTypeClasses + {tyFromExpr}:
     return true
-
-  return false
+  else:
+    return false
 
 proc containsGenericType*(t: PType): bool =
   result = iterOverType(t, containsGenericTypeIter, nil)
diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim
index d684c4c32..8c7388643 100644
--- a/compiler/vmdeps.nim
+++ b/compiler/vmdeps.nim
@@ -175,7 +175,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
           result.add mapTypeToAst(t.sons[i], info)
     else:
       result = mapTypeToAstX(t.lastSon, info, inst, allowRecursion)
-  of tyGenericBody, tyOrdinal, tyUserTypeClassInst:
+  of tyGenericBody, tyOrdinal:
     result = mapTypeToAst(t.lastSon, info)
   of tyDistinct:
     if inst:
@@ -285,15 +285,19 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
   of tyProxy: result = atomicType("error", mNone)
   of tyBuiltInTypeClass:
     result = mapTypeToBracket("builtinTypeClass", mNone, t, info)
-  of tyUserTypeClass:
-    result = mapTypeToBracket("concept", mNone, t, info)
-    result.add t.n.copyTree
+  of tyUserTypeClass, tyUserTypeClassInst:
+    if t.isResolvedUserTypeClass:
+      result = mapTypeToAst(t.lastSon, info)
+    else:
+      result = mapTypeToBracket("concept", mNone, t, info)
+      result.add t.n.copyTree
   of tyCompositeTypeClass:
     result = mapTypeToBracket("compositeTypeClass", mNone, t, info)
   of tyAnd: result = mapTypeToBracket("and", mAnd, t, info)
   of tyOr: result = mapTypeToBracket("or", mOr, t, info)
   of tyNot: result = mapTypeToBracket("not", mNot, t, info)
   of tyAnything: result = atomicType("anything", mNone)
+  of tyInferred: internalAssert false
   of tyStatic, tyFromExpr, tyFieldAccessor:
     if inst:
       if t.n != nil: result = t.n.copyTree
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 125fe8ae0..c7d9be48c 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1678,7 +1678,8 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
       elif sfImportc in s.flags: c.importcSym(n.info, s)
       genLit(c, n, dest)
     of skConst:
-      gen(c, s.ast, dest)
+      let constVal = if s.ast != nil: s.ast else: s.typ.n
+      gen(c, constVal, dest)
     of skEnumField:
       if dest < 0: dest = c.getTemp(n.typ)
       if s.position >= low(int16) and s.position <= high(int16):
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 6072bd64c..98fd912d8 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -66,7 +66,7 @@ type
     wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit,
     wAsmNoStackFrame,
     wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks,
-    wPartial,
+    wPartial, wExplain,
 
     wAuto, wBool, wCatch, wChar, wClass,
     wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@@ -152,7 +152,7 @@ const
     "computedgoto", "injectstmt", "experimental",
     "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
     "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
-    "guard", "locks", "partial",
+    "guard", "locks", "partial", "explain",
 
     "auto", "bool", "catch", "char", "class",
     "const_cast", "default", "delete", "double",
diff --git a/doc/advopt.txt b/doc/advopt.txt
index 991f06397..9a9dab7e0 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -54,9 +54,6 @@ Advanced options:
   --embedsrc                embeds the original source code as comments
                             in the generated output
   --threadanalysis:on|off   turn thread analysis on|off
-  --reportConceptFailures:on|off
-                            show errors for 'system.compiles' and concept
-                            evaluation
   --tlsEmulation:on|off     turn thread local storage emulation on|off
   --taintMode:on|off        turn taint mode on|off
   --implicitStatic:on|off   turn implicit compile time evaluation on|off
diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt
index c1c6467e7..87fcb7828 100644
--- a/doc/manual/generics.txt
+++ b/doc/manual/generics.txt
@@ -116,7 +116,8 @@ type class           matches
 ``array``            any array type
 ``set``              any set type
 ``seq``              any seq type
-``any``              any type
+``auto``             any type
+``any``              distinct auto (see below)
 ==================   ===================================================
 
 Furthermore, every generic type automatically creates a type class of the same
@@ -148,8 +149,8 @@ as `type constraints`:idx: of the generic type parameter:
   onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time
 
 By default, during overload resolution each named type class will bind to
-exactly one concrete type. Here is an example taken directly from the system
-module to illustrate this:
+exactly one concrete type. We call such type classes `bind once`:idx: types.
+Here is an example taken directly from the system module to illustrate this:
 
 .. code-block:: nim
   proc `==`*(x, y: tuple): bool =
@@ -161,7 +162,8 @@ module to illustrate this:
       if a != b: result = false
 
 Alternatively, the ``distinct`` type modifier can be applied to the type class
-to allow each param matching the type class to bind to a different type.
+to allow each param matching the type class to bind to a different type. Such
+type classes are called `bind many`:idx: types.
 
 Procs written with the implicitly generic style will often need to refer to the
 type parameters of the matched generic type. They can be easily accessed using
@@ -211,34 +213,421 @@ Concepts are written in the following form:
     Comparable = concept x, y
       (x < y) is bool
 
-    Container[T] = concept c
-      c.len is Ordinal
-      items(c) is T
-      for value in c:
-        type(value) is T
+    Stack[T] = concept s, var v
+      s.pop() is T
+      v.push(T)
+
+      s.len is Ordinal
+
+      for value in s:
+        value is T
 
 The concept is a match if:
 
 a) all of the expressions within the body can be compiled for the tested type
-b) all statically evaluatable boolean expressions in the body must be true
+b) all statically evaluable boolean expressions in the body must be true
 
 The identifiers following the ``concept`` keyword represent instances of the
-currently matched type. These instances can act both as variables of the type,
-when used in contexts where a value is expected, and as the type itself when
-used in contexts where a type is expected.
+currently matched type. You can apply any of the standard type modifiers such
+as ``var``, ``ref``, ``ptr`` and ``static`` to denote a more specific type of
+instance. You can also apply the `type` modifier to create a named instance of
+the type itself:
+
+.. code-block:: nim
+  type
+    MyConcept = concept x, var v, ref r, ptr p, static s, type T
+      ...
+
+Within the concept body, types can appear in positions where ordinary values
+and parameters are expected. This provides a more convenient way to check for
+the presence of callable symbols with specific signatures:
+
+.. code-block:: nim
+  type
+    OutputStream = concept var s
+      s.write(string)
+
+In order to check for symbols accepting ``typedesc`` params, you must prefix
+the type with an explicit ``type`` modifier. The named instance of the type,
+following the ``concept`` keyword is also considered an explicit ``typedesc``
+value that will be matched only as a type.
+
+.. code-block:: nim
+  type
+    # Let's imagine a user-defined casting framework with operators
+    # such as `val.to(string)` and `val.to(JSonValue)`. We can test
+    # for these with the following concept:
+    MyCastables = concept x
+      x.to(type string)
+      x.to(type JSonValue)
+
+    # Let's define a couple of concepts, known from Algebra:
+    AdditiveMonoid* = concept x, y, type T
+      x + y is T
+      T.zero is T # require a proc such as `int.zero` or 'Position.zero'
+
+    AdditiveGroup* = concept x, y, type T
+      x is AdditiveMonoid
+      -x is T
+      x - y is T
 
 Please note that the ``is`` operator allows one to easily verify the precise
 type signatures of the required operations, but since type inference and
-default parameters are still applied in the provided block, it's also possible
-to encode usage protocols that do not reveal implementation details.
+default parameters are still applied in the concept body, it's also possible
+to describe usage protocols that do not reveal implementation details.
+
+Much like generics, concepts are instantiated exactly once for each tested type
+and any static code included within the body is executed only once.
+
+
+Concept diagnostics
+-------------------
+
+By default, the compiler will report the matching errors in concepts only when
+no other overload can be selected and a normal compilation error is produced.
+When you need to understand why the compiler is not matching a particular
+concept and, as a result, a wrong overload is selected, you can apply the
+``explain`` pragma to either the concept body or a particular call-site.
+
+.. code-block:: nim
+  type
+    MyConcept {.explain.} = concept ...
+
+  overloadedProc(x, y, z) {.explain.}
+
+This will provide Hints in the compiler output either every time the concept is
+not matched or only on the particular call-site.
+
+
+Generic concepts and type binding rules
+---------------------------------------
+
+The concept types can be parametric just like the regular generic types:
+
+.. code-block:: nim
+  ### matrixalgo.nim
+  
+  import typetraits
+
+  type
+    AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M
+      M.ValueType is T
+      M.Rows == R
+      M.Cols == C
+
+      m[int, int] is T
+      mvar[int, int] = T
+
+      type TransposedType = stripGenericParams(M)[C, R, T]
+
+    AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T]
+
+    AnyTransform3D* = AnyMatrix[4, 4, float]
+
+  proc transposed*(m: AnyMatrix): m.TransposedType =
+    for r in 0 .. <m.R:
+      for c in 0 .. <m.C:
+        result[r, c] = m[c, r]
+
+  proc determinant*(m: AnySquareMatrix): int =
+    ...
+
+  proc setPerspectiveProjection*(m: AnyTransform3D) =
+    ...
+
+  --------------
+  ### matrix.nim
+
+  type
+    Matrix*[M, N: static[int]; T] = object
+      data: array[M*N, T]
+
+  proc `[]`*(M: Matrix; m, n: int): M.T =
+    M.data[m * M.N + n]
+
+  proc `[]=`*(M: var Matrix; m, n: int; v: M.T) =
+    M.data[m * M.N + n] = v
+
+  # Adapt the Matrix type to the concept's requirements
+  template Rows*(M: type Matrix): expr = M.M
+  template Cols*(M: type Matrix): expr = M.N
+  template ValueType*(M: type Matrix): typedesc = M.T
+  
+  -------------
+  ### usage.nim
+
+  import matrix, matrixalgo
+
+  var
+    m: Matrix[3, 3, int]
+    projectionMatrix: Matrix[4, 4, float]
+
+  echo m.transposed.determinant
+  setPerspectiveProjection projectionMatrix
+
+When the concept type is matched against a concrete type, the unbound type
+parameters are inferred from the body of the concept in a way that closely
+resembles the way generic parameters of callable symbols are inferred on
+call sites.
+
+Unbound types can appear both as params to calls such as `s.push(T)` and
+on the right-hand side of the ``is`` operator in cases such as `x.pop is T`
+and `x.data is seq[T]`.
+
+Unbound static params will be inferred from expressions involving the `==`
+operator and also when types dependent on them are being matched:
+
+.. code-block:: nim
+  type
+    MatrixReducer[M, N: static[int]; T] = concept x
+      x.reduce(SquareMatrix[N, T]) is array[M, int]
+
+The Nim compiler includes a simple linear equation solver, allowing it to
+infer static params in some situations where integer arithmetic is involved.
+
+Just like in regular type classes, Nim discriminates between ``bind once``
+and ``bind many`` types when matching the concept. You can add the ``distinct``
+modifier to any of the otherwise inferable types to get a type that will be
+matched without permanently inferring it. This may be useful when you need
+to match several procs accepting the same wide class of types:
+
+.. code-block:: nim
+  type
+    Enumerable[T] = concept e
+      for v in e:
+        v is T
+
+  type
+    MyConcept = concept o
+      # this could be inferred to a type such as Enumerable[int]
+      o.foo is distinct Enumerable
+
+      # this could be inferred to a different type such as Enumerable[float]
+      o.bar is distinct Enumerable
+
+      # it's also possible to give an alias name to a `bind many` type class
+      type Enum = distinct Enumerable
+      o.baz is Enum
+
+On the other hand, using ``bind once`` types allows you to test for equivalent
+types used in multiple signatures, without actually requiring any concrete
+types, thus allowing you to encode implementation-defined types:
+
+.. code-block:: nim
+  type
+    MyConcept = concept x
+      type T1 = auto
+      x.foo(T1)
+      x.bar(T1) # both procs must accept the same type
+
+      type T2 = seq[SomeNumber]
+      x.alpha(T2)
+      x.omega(T2) # both procs must accept the same type
+                  # and it must be a numeric sequence
+
+As seen in the previous examples, you can refer to generic concepts such as
+`Enumerable[T]` just by their short name. Much like the regular generic types,
+the concept will be automatically instantiated with the bind once auto type
+in the place of each missing generic param.
+
+Please note that generic concepts such as `Enumerable[T]` can be matched
+against concrete types such as `string`. Nim doesn't require the concept
+type to have the same number of parameters as the type being matched.
+If you wish to express a requirement towards the generic parameters of
+the matched type, you can use a type mapping operator such as `genericHead`
+or `stripGenericParams` within the body of the concept to obtain the
+uninstantiated version of the type, which you can then try to instantiate
+in any required way. For example, here is how one might define the classic
+`Functor` concept from Haskell and then demonstrate that Nim's `Option[T]`
+type is an instance of it:
+
+.. code-block:: nim
+  import future, typetraits
+
+  type
+    Functor[A] = concept f
+      type MatchedGenericType = genericHead(f.type)
+        # `f` will be a value of a type such as `Option[T]`
+        # `MatchedGenericType` will become the `Option` type
+
+      f.val is A
+        # The Functor should provide a way to obtain
+        # a value stored inside it
+
+      type T = auto
+      map(f, A -> T) is MatchedGenericType[T]
+        # And it should provide a way to map one instance of
+        # the Functor to a instance of a different type, given
+        # a suitable `map` operation for the enclosed values
+
+  import options
+  echo Option[int] is Functor # prints true
+
+
+Concept derived values
+----------------------
+
+All top level constants or types appearing within the concept body are
+accessible through the dot operator in procs where the concept was successfully
+matched to a concrete type:
+
+.. code-block:: nim
+  type
+    DateTime = concept t1, t2, type T
+      const Min = T.MinDate
+      T.Now is T
+
+      t1 < t2 is bool
+
+      type TimeSpan = type(t1 - t2)
+      TimeSpan * int is TimeSpan
+      TimeSpan + TimeSpan is TimeSpan
+
+      t1 + TimeSpan is T
+
+  proc eventsJitter(events: Enumerable[DateTime]): float =
+    var
+      # this variable will have the inferred TimeSpan type for
+      # the concrete Date-like value the proc was called with:
+      averageInterval: DateTime.TimeSpan
+
+      deviation: float
+    ...
+
+
+Concept refinement
+------------------
+
+When the matched type within a concept is directly tested against a different
+concept, we say that the outer concept is a refinement of the inner concept and
+thus it is more-specific. When both concepts are matched in a call during
+overload resolution, Nim will assign a higher precedence to the most specific
+one. As an alternative way of defining concept refinements, you can use the
+object inheritance syntax involving the ``of`` keyword:
+
+.. code-block:: nim
+  type
+    Graph = concept g, type G of EqualyComparable, Copyable
+      type
+        VertexType = G.VertexType
+        EdgeType = G.EdgeType
+
+      VertexType is Copyable
+      EdgeType is Copyable
+
+      var
+        v: VertexType
+        e: EdgeType
+
+    IncidendeGraph = concept of Graph
+      # symbols such as variables and types from the refined
+      # concept are automatically in scope:
+
+      g.source(e) is VertexType
+      g.target(e) is VertexType
+
+      g.outgoingEdges(v) is Enumerable[EdgeType]
+
+    BidirectionalGraph = concept g, type G
+      # The following will also turn the concept into a refinement when it
+      # comes to overload resolution, but it doesn't provide the convenient
+      # symbol inheritance
+      g is IncidendeGraph
+
+      g.incomingEdges(G.VertexType) is Enumerable[G.EdgeType]
+
+  proc f(g: IncidendeGraph)
+  proc f(g: BidirectionalGraph) # this one will be preferred if we pass a type
+                                # matching the BidirectionalGraph concept
+
+
+Converter type classes
+----------------------
+
+Concepts can also be used to convert a whole range of types to a single type or
+a small set of simpler types. This is achieved with a `return` statement within
+the concept body:
+
+.. code-block:: nim
+  type
+    Stringable = concept x
+      $x is string
+      return $x
+
+    StringRefValue[CharType] = object
+      base: ptr CharType
+      len: int
+
+    StringRef = concept x
+      # the following would be an overloaded proc for cstring, string, seq and
+      # other user-defined types, returning either a StringRefValue[char] or
+      # StringRefValue[wchar]
+      return makeStringRefValue(x)
+
+  # the varargs param will here be converted to an array of StringRefValues
+  # the proc will have only two instantiations for the two character types
+  proc log(format: static[string], varargs[StringRef])
+
+  # this proc will allow char and wchar values to be mixed in
+  # the same call at the cost of additional instantiations
+  # the varargs param will be converted to a tuple
+  proc log(format: static[string], varargs[distinct StringRef])
+
+
+VTable types
+------------
+
+Concepts allow Nim to define a great number of algorithms, using only
+static polymorphism and without erasing any type information or sacrificing
+any execution speed. But when polymorphic collections of objects are required,
+the user must use one of the provided type erasure techniques - either common
+base types or VTable types.
+
+VTable types are represented as "fat pointers" storing a reference to an
+object together with a reference to a table of procs implementing a set of
+required operations (the so called vtable).
+
+In contrast to other programming languages, the vtable in Nim is stored
+externally to the object, allowing you to create multiple different vtable
+views for the same object. Thus, the polymorphism in Nim is unbounded -
+any type can implement an unlimited number of protocols or interfaces not
+originally envisioned by the type's author.
+
+Any concept type can be turned into a VTable type by using the ``vtref``
+or the ``vtptr`` compiler magics. Under the hood, these magics generate
+a converter type class, which converts the regular instances of the matching
+types to the corresponding VTable type.
+
+.. code-block:: nim
+  type
+    IntEnumerable = vtref Enumerable[int]
+
+    MyObject = object
+      enumerables: seq[IntEnumerable]
+      streams: seq[OutputStream.vtref]
+
+  proc addEnumerable(o: var MyObject, e: IntEnumerable) =
+    o.enumerables.add e
+
+  proc addStream(o: var MyObject, e: OutputStream.vtref) =
+    o.streams.add e
+
+The procs that will be included in the vtable are derived from the concept
+body and include all proc calls for which all param types were specified as
+concrete types. All such calls should include exactly one param of the type
+matched against the concept (not necessarily in the first position), which
+will be considered the value bound to the vtable.
+
+Overloads will be created for all captured procs, accepting the vtable type
+in the position of the captured underlying object.
 
-Much like generics, concepts are instantiated exactly
-once for each tested type and any static code included within them is also
-executed once.
+Under these rules, it's possible to obtain a vtable type for a concept with
+unbound type parameters or one instantiated with metatypes (type classes),
+but it will include a smaller number of captured procs. A completely empty
+vtable will be reported as an error.
 
-**Hint**: Since concepts are still very rough at the edges there is a
-command line switch ``--reportConceptFailures:on`` to make debugging
-concept related type failures more easy.
+The ``vtref`` magic produces types which can be bound to ``ref`` types and
+the ``vtptr`` magic produced types bound to ``ptr`` types.
 
 
 Symbol lookup in generics
diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim
index 2c3d872df..55c4bf038 100644
--- a/lib/pure/typetraits.nim
+++ b/lib/pure/typetraits.nim
@@ -19,7 +19,7 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".}
   ##
   ##   import typetraits
   ##
-  ##   proc `$`*[T](some:typedesc[T]): string = name(T)
+  ##   proc `$`*(T: typedesc): string = name(T)
   ##
   ##   template test(x): stmt =
   ##     echo "type: ", type(x), ", value: ", x
@@ -31,6 +31,21 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".}
   ##   test(@['A','B'])
   ##   # --> type: seq[char], value: @[A, B]
 
-
 proc arity*(t: typedesc): int {.magic: "TypeTrait".}
   ## Returns the arity of the given type
+
+proc genericHead*(t: typedesc): typedesc {.magic: "TypeTrait".}
+  ## Accepts an instantiated generic type and returns its
+  ## uninstantiated form.
+  ##
+  ## For example:
+  ##   seq[int].genericHead will be just seq
+  ##   seq[int].genericHead[float] will be seq[float]
+  ##
+  ## A compile-time error will be produced if the supplied type
+  ## is not generic
+
+proc stripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".}
+  ## This trait is similar to `genericHead`, but instead of producing
+  ## error for non-generic types, it will just return them unmodified
+
diff --git a/tests/concepts/matrix.nim b/tests/concepts/matrix.nim
new file mode 100644
index 000000000..a30cbe4f3
--- /dev/null
+++ b/tests/concepts/matrix.nim
@@ -0,0 +1,15 @@
+type
+  Matrix*[M, N: static[int]; T] = object
+    data: array[M*N, T]
+
+proc `[]`*(M: Matrix; m, n: int): M.T =
+  M.data[m * M.N + n]
+
+proc `[]=`*(M: var Matrix; m, n: int; v: M.T) =
+  M.data[m * M.N + n] = v
+
+# Adapt the Matrix type to the concept's requirements
+template Rows*(M: type Matrix): expr = M.M
+template Cols*(M: type Matrix): expr = M.N
+template ValueType*(M: type Matrix): typedesc = M.T
+
diff --git a/tests/concepts/matrixalgo.nim b/tests/concepts/matrixalgo.nim
new file mode 100644
index 000000000..39cf16685
--- /dev/null
+++ b/tests/concepts/matrixalgo.nim
@@ -0,0 +1,28 @@
+import typetraits
+
+type
+  AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M
+    M.ValueType is T
+    M.Rows == R
+    M.Cols == C
+
+    m[int, int] is T
+    mvar[int, int] = T
+
+    type TransposedType = stripGenericParams(M)[C, R, T]
+
+  AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T]
+
+  AnyTransform3D* = AnyMatrix[4, 4, float]
+
+proc transposed*(m: AnyMatrix): m.TransposedType =
+  for r in 0 .. <m.R:
+    for c in 0 .. <m.C:
+      result[r, c] = m[c, r]
+
+proc determinant*(m: AnySquareMatrix): int =
+  return 0
+
+proc setPerspectiveProjection*(m: AnyTransform3D) =
+  discard
+
diff --git a/tests/concepts/t1128.nim b/tests/concepts/t1128.nim
new file mode 100644
index 000000000..7f7525a13
--- /dev/null
+++ b/tests/concepts/t1128.nim
@@ -0,0 +1,21 @@
+discard """
+  output: "true\ntrue"
+"""
+
+type
+  TFooContainer[T] = object
+  
+  TContainer[T] = generic var c
+    foo(c, T)
+
+proc foo[T](c: var TFooContainer[T], val: T) =
+  discard
+
+proc bar(c: var TContainer) =
+  discard
+
+var fooContainer: TFooContainer[int]
+echo fooContainer is TFooContainer # true.
+echo fooContainer is TFooContainer[int] # true.
+fooContainer.bar()
+
diff --git a/tests/concepts/t3330.nim b/tests/concepts/t3330.nim
new file mode 100644
index 000000000..04add2b6f
--- /dev/null
+++ b/tests/concepts/t3330.nim
@@ -0,0 +1,41 @@
+discard """
+errormsg: "type mismatch: got (Bar[system.int])"
+nimout: '''
+t3330.nim(40, 4) Error: type mismatch: got (Bar[system.int])
+but expected one of: 
+proc test(foo: Foo[int])
+t3330.nim(25, 8) Hint: Non-matching candidates for add(k, string, T)
+proc add[T](x: var seq[T]; y: T)
+proc add(result: var string; x: float)
+proc add(x: var string; y: string)
+proc add(x: var string; y: cstring)
+proc add(x: var string; y: char)
+proc add(result: var string; x: int64)
+proc add[T](x: var seq[T]; y: openArray[T])
+
+t3330.nim(25, 8) template/generic instantiation from here
+t3330.nim(32, 6) Foo: 'bar.value' cannot be assigned to
+t3330.nim(25, 8) template/generic instantiation from here
+t3330.nim(33, 6) Foo: 'bar.x' cannot be assigned to
+'''
+"""
+
+type
+  Foo[T] = concept k
+    add(k, string, T)
+
+  Bar[T] = object
+    value: T
+    x: string
+
+proc add[T](bar: Bar[T], x: string, val: T) =
+  bar.value = val
+  bar.x = x
+
+proc test(foo: Foo[int]) =
+  foo.add("test", 42)
+  echo(foo.x)
+
+var bar = Bar[int]()
+bar.test()
+
diff --git a/tests/concepts/t976.nim b/tests/concepts/t976.nim
new file mode 100644
index 000000000..cc0bbdc59
--- /dev/null
+++ b/tests/concepts/t976.nim
@@ -0,0 +1,32 @@
+import macros
+
+type
+  int1 = distinct int
+  int2 = distinct int
+
+  int1g = concept x
+    x is int1
+
+  int2g = concept x
+    x is int2
+
+proc take[T: int1g](value: int1) =
+  when T is int2:
+    static: error("killed in take(int1)")
+
+proc take[T: int2g](vale: int2) =
+  when T is int1:
+    static: error("killed in take(int2)")
+
+var i1: int1 = 1.int1
+var i2: int2 = 2.int2
+
+take[int1](i1)
+take[int2](i2)
+
+template reject(e) =
+  static: assert(not compiles(e))
+
+reject take[string](i2)
+reject take[int1](i2)
+
diff --git a/tests/concepts/tconcepts_overload_precedence.nim b/tests/concepts/tconcepts_overload_precedence.nim
new file mode 100644
index 000000000..9eed6256a
--- /dev/null
+++ b/tests/concepts/tconcepts_overload_precedence.nim
@@ -0,0 +1,69 @@
+discard """
+  output: '''x as ParameterizedType[T]
+x as ParameterizedType[T]
+x as ParameterizedType[T]
+x as ParameterizedType
+x as ParameterizedType
+x as CustomTypeClass'''
+"""
+
+type ParameterizedType[T] = object
+
+type CustomTypeClass = concept
+  true
+
+# 3 competing procs
+proc a[T](x: ParameterizedType[T]) =
+  echo "x as ParameterizedType[T]"
+
+proc a(x: ParameterizedType) =
+  echo "x as ParameterizedType"
+
+proc a(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+# the same procs in different order
+proc b(x: ParameterizedType) =
+  echo "x as ParameterizedType"
+
+proc b(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+proc b[T](x: ParameterizedType[T]) =
+  echo "x as ParameterizedType[T]"
+
+# and yet another order
+proc c(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+proc c(x: ParameterizedType) =
+  echo "x as ParameterizedType"
+
+proc c[T](x: ParameterizedType[T]) =
+  echo "x as ParameterizedType[T]"
+
+# remove the most specific one
+proc d(x: ParameterizedType) =
+  echo "x as ParameterizedType"
+
+proc d(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+# then shuffle the order again
+proc e(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+proc e(x: ParameterizedType) =
+  echo "x as ParameterizedType"
+
+# the least specific one is a match
+proc f(x: CustomTypeClass) =
+  echo "x as CustomTypeClass"
+
+a(ParameterizedType[int]())
+b(ParameterizedType[int]())
+c(ParameterizedType[int]())
+d(ParameterizedType[int]())
+e(ParameterizedType[int]())
+f(ParameterizedType[int]())
+
diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim
new file mode 100644
index 000000000..25a075fd1
--- /dev/null
+++ b/tests/concepts/texplain.nim
@@ -0,0 +1,120 @@
+discard """
+  cmd: "nim c --verbosity:0 --colors:off $file"
+  nimout: '''
+texplain.nim(99, 10) Hint: Non-matching candidates for e(y)
+proc e(i: int): int
+
+texplain.nim(102, 7) Hint: Non-matching candidates for e(10)
+proc e(o: ExplainedConcept): int
+texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo'
+texplain.nim(65, 6) ExplainedConcept: undeclared field: '.'
+texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called
+texplain.nim(65, 5) ExplainedConcept: type class predicate failed
+texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar'
+texplain.nim(66, 6) ExplainedConcept: undeclared field: '.'
+texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called
+texplain.nim(65, 5) ExplainedConcept: type class predicate failed
+
+texplain.nim(105, 10) Hint: Non-matching candidates for e(10)
+proc e(o: ExplainedConcept): int
+texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo'
+texplain.nim(65, 6) ExplainedConcept: undeclared field: '.'
+texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called
+texplain.nim(65, 5) ExplainedConcept: type class predicate failed
+texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar'
+texplain.nim(66, 6) ExplainedConcept: undeclared field: '.'
+texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called
+texplain.nim(65, 5) ExplainedConcept: type class predicate failed
+
+texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType)
+but expected one of: 
+proc e(o: ExplainedConcept): int
+texplain.nim(65, 5) ExplainedConcept: type class predicate failed
+proc e(i: int): int
+
+texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType)
+but expected one of: 
+proc r(o: RegularConcept): int
+texplain.nim(69, 5) RegularConcept: type class predicate failed
+proc r[T](a: SomeNumber; b: T; c: auto)
+proc r(i: string): int
+
+texplain.nim(111, 20) Hint: Non-matching candidates for r(y)
+proc r[T](a: SomeNumber; b: T; c: auto)
+proc r(i: string): int
+
+texplain.nim(119, 2) Error: type mismatch: got (MatchingType)
+but expected one of: 
+proc f(o: NestedConcept)
+texplain.nim(69, 6) RegularConcept: undeclared field: 'foo'
+texplain.nim(69, 6) RegularConcept: undeclared field: '.'
+texplain.nim(69, 6) RegularConcept: expression '.' cannot be called
+texplain.nim(69, 5) RegularConcept: type class predicate failed
+texplain.nim(70, 6) RegularConcept: undeclared field: 'bar'
+texplain.nim(70, 6) RegularConcept: undeclared field: '.'
+texplain.nim(70, 6) RegularConcept: expression '.' cannot be called
+texplain.nim(69, 5) RegularConcept: type class predicate failed
+texplain.nim(73, 5) NestedConcept: type class predicate failed
+'''
+  line: 119
+  errormsg: "type mismatch: got (MatchingType)"
+"""
+
+type
+  ExplainedConcept {.explain.} = concept o
+    o.foo is int
+    o.bar is string
+
+  RegularConcept = concept o
+    o.foo is int
+    o.bar is string
+
+  NestedConcept = concept o
+    o.foo is RegularConcept
+
+  NonMatchingType = object
+    foo: int
+    bar: int
+
+  MatchingType = object
+    foo: int
+    bar: string
+
+proc e(o: ExplainedConcept): int = 1
+proc e(i: int): int = i
+
+proc r[T](a: SomeNumber, b: T, c: auto) = discard
+proc r(o: RegularConcept): int = 1
+proc r(i: string): int = 1
+
+proc f(o: NestedConcept) = discard
+
+var n = NonMatchingType(foo: 10, bar: 20)
+var y = MatchingType(foo: 10, bar: "bar")
+
+# no diagnostic here:
+discard e(y)
+
+# explain that e(int) doesn't match
+discard e(y) {.explain.}
+
+# explain that e(ExplainedConcept) doesn't match
+echo(e(10) {.explain.}, 20)
+
+# explain that e(ExplainedConcept) doesn't again
+discard e(10)
+
+static:
+  # provide diagnostics why the compile block failed
+  assert(compiles(e(n)) {.explain.} == false)
+  assert(compiles(r(n)) {.explain.} == false)
+  assert(compiles(r(y)) {.explain.} == true)
+
+  # these should not produce any output
+  assert(compiles(r(10)) == false)
+  assert(compiles(e(10)) == true)
+
+# finally, provide multiple nested explanations for failed matching
+# of regular concepts, even when the explain pragma is not used
+f(y)
+
diff --git a/tests/concepts/tmapconcept.nim b/tests/concepts/tmapconcept.nim
new file mode 100644
index 000000000..81caed7c6
--- /dev/null
+++ b/tests/concepts/tmapconcept.nim
@@ -0,0 +1,102 @@
+discard """
+output: '''10
+10
+nil
+1'''
+msg: '''
+K=string V=int
+K=int64 V=string
+K=int V=int
+'''
+"""
+
+import tables, typetraits
+
+template ok(check) = assert check
+template no(check) = assert(not check)
+
+type
+  Enumerable[T] = concept e
+    for v in e:
+      v is T
+
+  Map[K, V] = concept m, var mvar
+    m[K] is V
+    mvar[K] = V
+    m.contains(K) is bool
+    m.valuesSeq is Enumerable[V]
+
+  TreeMap[K, V] = object
+    root: int
+
+  SparseSeq = object
+    data: seq[int]
+
+  JudyArray = object
+    data: SparseSeq
+
+static:
+  ok seq[int] is Enumerable[int]
+  ok seq[string] is Enumerable
+  ok seq[int] is Enumerable[SomeNumber]
+  ok SparseSeq.data is Enumerable
+  no seq[string] is Enumerable[int]
+  no int is Enumerable
+  no int is Enumerable[int]
+
+# Complete the map concept implementation for the Table type
+proc valuesSeq[K, V](t: Table[K, V]): seq[V]  =
+  result = @[]
+  for k, v in t:
+    result.add v
+
+# Map concept inplementation for TreeMap
+proc valuesSeq(t: TreeMap): array[1, TreeMap.V] =
+  var v: t.V
+  result = [v]
+
+proc contains[K, V](t: TreeMap[K, V], key: K): bool = true
+
+proc `[]=`[K, V](t: var TreeMap[K, V], key: K, val: V) = discard
+proc `[]`(t: TreeMap, key: t.K): TreeMap.V = discard
+
+# Map concept implementation for the non-generic JudyArray
+proc valuesSeq(j: JudyArray): SparseSeq = j.data
+
+proc contains(t: JudyArray, key: int): bool = true
+
+proc `[]=`(t: var JudyArray, key, val: int) = discard
+proc `[]`(t: JudyArray, key: int): int = discard
+
+iterator items(s: SparseSeq): int =
+  for i in s.data: yield i
+
+# Generic proc defined over map
+proc getFirstValue[K,V](m : Map[K,V]): V =
+  static: echo "K=", K.name, " V=", V.name
+
+  for i in m.valuesSeq:
+    return i
+
+  raise newException(RangeError, "no values")
+
+proc useConceptProcInGeneric[K, V](t: Table[K, V]): V =
+  return t.getFirstValue
+
+var t = initTable[string, int]()
+t["test"] = 10
+
+echo t.getFirstValue
+echo t.useConceptProcInGeneric
+
+var tm = TreeMap[int64, string](root: 0)
+echo getFirstValue(tm)
+
+var j = JudyArray(data: SparseSeq(data: @[1, 2, 3]))
+echo getFirstValue(j)
+
+static:
+  ok Table[int, float] is Map
+  ok Table[int, string] is Map[SomeNumber, string]
+  no JudyArray is Map[string, int]
+
diff --git a/tests/concepts/tmatrixconcept.nim b/tests/concepts/tmatrixconcept.nim
new file mode 100644
index 000000000..d2597a212
--- /dev/null
+++ b/tests/concepts/tmatrixconcept.nim
@@ -0,0 +1,81 @@
+discard """
+output: "0\n0\n0"
+msg: '''
+R=3 C=3 TE=9 FF=14 FC=20 T=int
+R=3 C=3 T=int
+'''
+"""
+
+import typetraits
+
+template ok(x) = assert x
+template no(x) = assert(not x)
+
+const C = 10
+
+type
+  Matrix[Rows, Cols, TotalElements, FromFoo, FromConst: static[int]; T] = concept m, var mvar, type M
+    M.M == Rows
+    Cols == M.N
+    M.T is T
+
+    m[int, int] is T
+    mvar[int, int] = T
+
+    FromConst == C * 2
+
+    # more complicated static param inference cases
+    m.data is array[TotalElements, T]
+    m.foo(array[0..FromFoo, type m[int, 10]])
+
+  MyMatrix[M, K: static[int]; T] = object
+    data: array[M*K, T]
+
+# adaptor for the concept's non-matching expectations
+template N(M: type MyMatrix): expr = M.K
+
+proc `[]`(m: MyMatrix; r, c: int): m.T =
+  m.data[r * m.K + c]
+
+proc `[]=`(m: var MyMatrix; r, c: int, v: m.T) =
+  m.data[r * m.K + c] = v
+
+proc foo(x: MyMatrix, arr: array[15, x.T]) = discard
+
+proc genericMatrixProc[R, C, TE, FF, FC, T](m: Matrix[R, C, TE, FF, FC, T]): T =
+  static:
+    echo "R=", R, " C=", C, " TE=", TE, " FF=", FF, " FC=", FC, " T=", T.name
+  
+  m[0, 0]
+
+proc implicitMatrixProc(m: Matrix): m.T =
+  static:
+    echo "R=", m.Rows,
+        " C=", m.Cols,
+        # XXX: fix these
+        #" TE=", m.TotalElements,
+        #" FF=", m.FromFoo,
+        #" FC=", m.FromConst,
+        " T=", m.T.name
+    
+  m[0, 0]
+
+proc myMatrixProc(x: MyMatrix): MyMatrix.T = genericMatrixProc(x)
+
+var x: MyMatrix[3, 3, int]
+
+static:
+  # ok x is Matrix
+  ok x is Matrix[3, 3, 9, 14, 20, int]
+
+  no x is Matrix[3, 3, 8, 15, 20, int]
+  no x is Matrix[3, 3, 9, 10, 20, int]
+  no x is Matrix[3, 3, 9, 15, 21, int]
+  no x is Matrix[3, 3, 9, 15, 20, float]
+  no x is Matrix[4, 3, 9, 15, 20, int]
+  no x is Matrix[3, 4, 9, 15, 20, int]
+
+echo x.myMatrixProc
+echo x.genericMatrixProc
+echo x.implicitMatrixProc
+
diff --git a/tests/concepts/tmatrixlib.nim b/tests/concepts/tmatrixlib.nim
new file mode 100644
index 000000000..a4ab04061
--- /dev/null
+++ b/tests/concepts/tmatrixlib.nim
@@ -0,0 +1,31 @@
+discard """
+output: "0"
+"""
+
+import matrix, matrixalgo
+
+import typetraits # XXX: this should be removed
+
+var m: Matrix[3, 3, int]
+var projectionMatrix: Matrix[4, 4, float]
+
+echo m.transposed.determinant
+setPerspectiveProjection projectionMatrix
+
+template ok(x) = assert x
+template no(x) = assert(not x)
+
+static:
+  ok projectionMatrix is AnyTransform3D
+  no m is AnyTransform3D
+  
+  type SquareStringMatrix = Matrix[5, 5, string]
+  
+  ok SquareStringMatrix is AnyMatrix
+  ok SquareStringMatrix is AnySquareMatrix
+  no SquareStringMatrix is AnyTransform3D
+  
+  ok Matrix[5, 10, int] is AnyMatrix
+  no Matrix[7, 15, float] is AnySquareMatrix
+  no Matrix[4, 4, int] is AnyTransform3D
+
diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim
new file mode 100644
index 000000000..10e072521
--- /dev/null
+++ b/tests/concepts/tmisc_issues.nim
@@ -0,0 +1,100 @@
+discard """
+output: '''true
+true
+true
+true
+p has been called.
+p has been called.
+implicit generic
+generic
+false
+true
+-1'''
+"""
+
+# https://github.com/nim-lang/Nim/issues/1147
+type TTest = object
+  vals: seq[int]
+
+proc add*(self: var TTest, val: int) =
+  self.vals.add(val)
+
+type CAddable = concept x
+  x[].add(int)
+
+echo((ref TTest) is CAddable) # true
+
+# https://github.com/nim-lang/Nim/issues/1570
+type ConcretePointOfFloat = object
+  x, y: float
+
+type ConcretePoint[Value] = object
+  x, y: Value
+
+type AbstractPointOfFloat = generic p
+  p.x is float and p.y is float
+
+let p1 = ConcretePointOfFloat(x: 0, y: 0)
+let p2 = ConcretePoint[float](x: 0, y: 0)
+
+echo p1 is AbstractPointOfFloat      # true
+echo p2 is AbstractPointOfFloat      # true
+echo p2.x is float and p2.y is float # true
+
+# https://github.com/nim-lang/Nim/issues/2018
+type ProtocolFollower = generic
+  true # not a particularly involved protocol
+
+type ImplementorA = object
+type ImplementorB = object
+
+proc p[A: ProtocolFollower, B: ProtocolFollower](a: A, b: B) =
+  echo "p has been called."
+
+p(ImplementorA(), ImplementorA())
+p(ImplementorA(), ImplementorB())
+
+# https://github.com/nim-lang/Nim/issues/2423
+proc put*[T](c: seq[T], x: T) = echo "generic"
+proc put*(c: seq) = echo "implicit generic"
+
+type
+  Container[T] = concept c
+    put(c)
+    put(c, T)
+
+proc c1(x: Container) = echo "implicit generic"
+c1(@[1])
+
+proc c2[T](x: Container[T]) = echo "generic"
+c2(@[1])
+
+# https://github.com/nim-lang/Nim/issues/2882
+type
+  Paper = object
+    name: string
+
+  Bendable = concept x
+    bend(x is Bendable)
+
+proc bend(p: Paper): Paper = Paper(name: "bent-" & p.name)
+
+var paper = Paper(name: "red")
+echo paper is Bendable
+
+type
+  A = concept self
+    size(self) is int
+
+  B = object
+
+proc size(self: B): int =
+  return -1  
+
+proc size(self: A): int =
+  return 0  
+
+let b = B()
+echo b is A
+echo b.size()
+
diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim
new file mode 100644
index 000000000..b6ead2c2b
--- /dev/null
+++ b/tests/concepts/tstackconcept.nim
@@ -0,0 +1,63 @@
+discard """
+output: "20\n10"
+msg: '''
+INFERRED int
+VALUE TYPE int
+VALUE TYPE NAME INT
+IMPLICIT INFERRED int int
+IMPLICIT VALUE TYPE int int
+IMPLICIT VALUE TYPE NAME INT INT
+'''
+"""
+
+import typetraits, strutils
+
+template reject(e: expr) =
+  static: assert(not compiles(e))
+
+type
+  ArrayStack = object
+    data: seq[int]
+
+proc push(s: var ArrayStack, item: int) =
+  s.data.add item
+
+proc pop(s: var ArrayStack): int =
+  return s.data.pop()
+
+type
+  Stack[T] = concept var s
+    s.push(T)
+    s.pop() is T
+
+    type ValueType = T
+    const ValueTypeName = T.name.toUpper
+
+proc genericAlgorithm[T](s: var Stack[T], y: T) =
+  static:
+    echo "INFERRED ", T.name
+    echo "VALUE TYPE ", s.ValueType.name
+    echo "VALUE TYPE NAME ", s.ValueTypeName
+
+  s.push(y)
+  echo s.pop
+
+proc implicitGeneric(s: var Stack): auto =
+  static:
+    echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name
+    echo "IMPLICIT VALUE TYPE ", s.ValueType.name, " ", Stack.ValueType.name
+    echo "IMPLICIT VALUE TYPE NAME ", s.ValueTypeName, " ", Stack.ValueTypeName
+
+  return s.pop()
+
+var s = ArrayStack(data: @[])
+
+s.push 10
+s.genericAlgorithm 20
+echo s.implicitGeneric
+
+reject s.genericAlgorithm "x"
+reject s.genericAlgorithm 1.0
+reject "str".implicitGeneric
+reject implicitGeneric(10)
+
diff --git a/tests/concepts/tusertypeclasses.nim b/tests/concepts/tusertypeclasses.nim
index 612556949..533bd528d 100644
--- a/tests/concepts/tusertypeclasses.nim
+++ b/tests/concepts/tusertypeclasses.nim
@@ -2,20 +2,22 @@ discard """
   output: '''Sortable
 Sortable
 Container
-true
-true
-false
-false
-false
+TObj
+int
 '''
 """
 
 import typetraits
 
+template reject(expr) = assert(not compiles(x))
+
 type
   TObj = object
     x: int
 
+  JSonValue = object
+    val: string
+
   Sortable = concept x, y
     (x < y) is bool
 
@@ -23,7 +25,7 @@ type
     C.len is Ordinal
     for v in items(C):
       v.type is tuple|object
-
+   
 proc foo(c: ObjectContainer) =
   echo "Container"
 
@@ -36,33 +38,62 @@ foo(@[TObj(x: 10), TObj(x: 20)])
 
 proc intval(x: int): int = 10
 
-# check real and virtual fields
 type
-  TFoo = concept T
-    T.x
-    y(T)
-    intval T.y
-    let z = intval(T.y)
+  TFoo = concept o, type T, ref r, var v, ptr p, static s
+    o.x
+    y(o) is int
 
-proc y(x: TObj): int = 10
+    var str: string
+    var intref: ref int
 
-proc testFoo(x: TFoo) = discard
-testFoo(TObj(x: 10))
+    refproc(ref T, ref int)
+    varproc(var T)
+    ptrproc(ptr T, str)
 
-type
-  Matrix[Rows, Cols: static[int]; T] = concept M
-    M.M == Rows
-    M.N == Cols
-    M.T is T
+    staticproc(static[T])
+
+    typeproc T
+    T.typeproc
+    typeproc o.type
+    o.type.typeproc
 
-  MyMatrix[M, N: static[int]; T] = object
-    data: array[M*N, T]
+    o.to(type string)
+    o.to(type JsonValue)
 
-var x: MyMatrix[3, 3, int]
+    refproc(r, intref)
+    varproc(v)
+    p.ptrproc(string)
+    staticproc s
+    typeproc(T)
 
-echo x is Matrix
-echo x is Matrix[3, 3, int]
-echo x is Matrix[3, 3, float]
-echo x is Matrix[4, 3, int]
-echo x is Matrix[3, 4, int]
+    const TypeName = T.name
+    type MappedType = type(o.y)
+
+    intval y(o)
+    let z = intval(o.y)
+
+    static:
+      assert T.name.len == 4
+      reject o.name
+      reject o.typeproc
+      reject staticproc(o)
+      reject o.varproc
+      reject T.staticproc
+      reject p.staticproc
+
+proc y(x: TObj): int = 10
+
+proc varproc(x: var TObj) = discard
+proc refproc(x: ref TObj, y: ref int) = discard
+proc ptrproc(x: ptr TObj, y: string) = discard
+proc staticproc(x: static[TObj]) = discard
+proc typeproc(t: type TObj) = discard
+proc to(x: TObj, t: type string) = discard
+proc to(x: TObj, t: type JSonValue) = discard
+
+proc testFoo(x: TFoo) =
+  echo x.TypeName
+  echo x.MappedType.name
+  
+testFoo(TObj(x: 10))
 
diff --git a/tests/concepts/tvectorspace.nim b/tests/concepts/tvectorspace.nim
new file mode 100644
index 000000000..74423e0d2
--- /dev/null
+++ b/tests/concepts/tvectorspace.nim
@@ -0,0 +1,15 @@
+type VectorSpace[K] = concept x, y
+  x + y is type(x)
+  zero(type(x)) is type(x)
+  -x is type(x)
+  x - y is type(x)
+  var k: K
+  k * x is type(x)
+
+proc zero(T: typedesc): T = 0
+
+static:
+  assert float is VectorSpace[float]
+  # assert float is VectorSpace[int]
+  # assert int is VectorSpace
+
diff --git a/tests/generics/tgenericdotrettype.nim b/tests/generics/tgenericdotrettype.nim
new file mode 100644
index 000000000..3e4614752
--- /dev/null
+++ b/tests/generics/tgenericdotrettype.nim
@@ -0,0 +1,29 @@
+discard """
+output: '''string
+int
+(int, string)
+'''
+"""
+
+import typetraits
+
+type
+  Foo[T, U] = object
+    x: T
+    y: U
+
+proc bar[T](a: T): T.U =
+  echo result.type.name
+
+proc bas(x: auto): x.T =
+  echo result.type.name
+
+proc baz(x: Foo): (Foo.T, x.U) =
+  echo result.type.name
+
+var
+  f: Foo[int, string]
+  x = bar f
+  z = bas f
+  y = baz f
+
diff --git a/tests/parallel/tgc_unsafe2.nim b/tests/parallel/tgc_unsafe2.nim
index 4a5f36f6d..40bfbdadb 100644
--- a/tests/parallel/tgc_unsafe2.nim
+++ b/tests/parallel/tgc_unsafe2.nim
@@ -1,8 +1,8 @@
 discard """
   line: 28
-  nimout: '''tgc_unsafe2.nim(22, 5) Warning: 'trick' is not GC-safe as it accesses 'global' which is a global using GC'ed memory
-tgc_unsafe2.nim(26, 5) Warning: 'track' is not GC-safe as it calls 'trick'
-tgc_unsafe2.nim(28, 5) Error: 'consumer' is not GC-safe as it calls 'track'
+  nimout: '''tgc_unsafe2.nim(22, 6) Warning: 'trick' is not GC-safe as it accesses 'global' which is a global using GC'ed memory [GcUnsafe2]
+tgc_unsafe2.nim(26, 6) Warning: 'track' is not GC-safe as it calls 'trick' [GcUnsafe2]
+tgc_unsafe2.nim(28, 6) Error: 'consumer' is not GC-safe as it calls 'track'
 '''
   errormsg: "'consumer' is not GC-safe as it calls 'track'"
 """
diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim
index d4a161dab..2d758ef0d 100644
--- a/tests/testament/tester.nim
+++ b/tests/testament/tester.nim
@@ -63,6 +63,8 @@ let
 
 var targets = {low(TTarget)..high(TTarget)}
 
+proc normalizeMsg(s: string): string = s.strip.replace("\C\L", "\L")
+
 proc callCompiler(cmdTemplate, filename, options: string,
                   target: TTarget): TSpec =
   let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
@@ -184,6 +186,8 @@ proc addResult(r: var TResults, test: TTest,
 proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) =
   if strip(expected.msg) notin strip(given.msg):
     r.addResult(test, expected.msg, given.msg, reMsgsDiffer)
+  elif expected.nimout.len > 0 and expected.nimout.normalizeMsg notin given.nimout.normalizeMsg:
+    r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer)
   elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and
       "internal error:" notin expected.msg:
     r.addResult(test, expected.file, given.file, reFilesDiffer)