summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorZahary Karadjov <zahary@gmail.com>2013-11-17 22:50:26 +0200
committerZahary Karadjov <zahary@gmail.com>2013-11-17 22:50:26 +0200
commita068aaed3c5ddaf05a104f3f2d0f512bab2861c6 (patch)
tree4f15fc2599ef28934ac6276ceb9f8a388d63f9a2
parent4cea15d2748de610715311497110136ba11c7ce9 (diff)
downloadNim-a068aaed3c5ddaf05a104f3f2d0f512bab2861c6.tar.gz
simple unit test and better documentation for the user defined type classes
-rw-r--r--compiler/msgs.nim55
-rw-r--r--compiler/sem.nim3
-rw-r--r--compiler/semdata.nim3
-rw-r--r--compiler/semexprs.nim12
-rw-r--r--compiler/semstmts.nim16
-rw-r--r--compiler/sigmatch.nim22
-rw-r--r--doc/manual.txt31
-rw-r--r--tests/run/tusertypeclasses.nim28
8 files changed, 113 insertions, 57 deletions
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 5363442b4..895ba71f3 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -436,7 +436,14 @@ type
                                # only 8 bytes.
     line*, col*: int16
     fileIndex*: int32
-    
+  
+  TErrorOutput* = enum
+    eStdOut
+    eStdErr
+    eInMemory
+
+  TErrorOutputs* = set[TErrorOutput]
+
   ERecoverableError* = object of EInvalidValue
   ESuggestDone* = object of EBase
 
@@ -534,13 +541,27 @@ var
   gHintCounter*: int = 0
   gWarnCounter*: int = 0
   gErrorMax*: int = 1         # stop after gErrorMax errors
-  gSilence*: int              # == 0 if we produce any output at all 
 
 when useCaas:
   var stdoutSocket*: TSocket
 
+proc UnknownLineInfo*(): TLineInfo =
+  result.line = int16(-1)
+  result.col = int16(-1)
+  result.fileIndex = -1
+
+var 
+  msgContext: seq[TLineInfo] = @[]
+  lastError = UnknownLineInfo()
+  bufferedMsgs*: seq[string]
+
+  errorOutputs* = {eStdOut, eStdErr}
+
+proc clearBufferedMsgs* =
+  bufferedMsgs = nil
+
 proc SuggestWriteln*(s: string) =
-  if gSilence == 0:
+  if eStdOut in errorOutputs:
     when useCaas:
       if isNil(stdoutSocket): Writeln(stdout, s)
       else:
@@ -548,6 +569,9 @@ proc SuggestWriteln*(s: string) =
         stdoutSocket.send(s & "\c\L")
     else:
       Writeln(stdout, s)
+  
+  if eInMemory in errorOutputs:
+    bufferedMsgs.safeAdd(s)
 
 proc SuggestQuit*() =
   if not isServing:
@@ -570,14 +594,6 @@ const
   RawWarningFormat* = "Warning: $1"
   RawHintFormat* = "Hint: $1"
 
-proc UnknownLineInfo*(): TLineInfo = 
-  result.line = int16(-1)
-  result.col = int16(-1)
-  result.fileIndex = -1
-
-var 
-  msgContext: seq[TLineInfo] = @[]
-
 proc getInfoContextLen*(): int = return msgContext.len
 proc setInfoContextLen*(L: int) = setLen(msgContext, L)
 
@@ -642,14 +658,18 @@ proc addCheckpoint*(filename: string, line: int) =
 
 proc OutWriteln*(s: string) = 
   ## Writes to stdout. Always.
-  if gSilence == 0: Writeln(stdout, s)
+  if eStdOut in errorOutputs: Writeln(stdout, s)
  
 proc MsgWriteln*(s: string) = 
   ## Writes to stdout. If --stdout option is given, writes to stderr instead.
-  if gSilence == 0:
-    if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
-    if optStdout in gGlobalOptions: Writeln(stderr, s)
-    else: Writeln(stdout, s)
+  if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
+
+  if optStdout in gGlobalOptions:
+    if eStdErr in errorOutputs: Writeln(stderr, s)
+  else:
+    if eStdOut in errorOutputs: Writeln(stdout, s)
+  
+  if eInMemory in errorOutputs: bufferedMsgs.safeAdd(s)
 
 proc coordToStr(coord: int): string = 
   if coord == -1: result = "???"
@@ -736,9 +756,6 @@ proc rawMessage*(msg: TMsgKind, args: openarray[string]) =
 proc rawMessage*(msg: TMsgKind, arg: string) = 
   rawMessage(msg, [arg])
 
-var
-  lastError = UnknownLineInfo()
-
 proc writeSurroundingSrc(info: TLineInfo) =
   const indent = "  "
   MsgWriteln(indent & info.sourceLine.ropeToStr)
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 71951dd3f..ea53afbeb 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -36,7 +36,8 @@ proc semParamList(c: PContext, n, genericParams: PNode, s: PSym)
 proc addParams(c: PContext, n: PNode, kind: TSymKind)
 proc maybeAddResult(c: PContext, s: PSym, n: PNode)
 proc instGenericContainer(c: PContext, n: PNode, header: PType): PType
-proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
+proc tryExpr(c: PContext, n: PNode,
+             flags: TExprFlags = {}, bufferErrors = false): PNode
 proc fixImmediateParams(n: PNode): PNode
 proc activate(c: PContext, n: PNode)
 proc semQuoteAst(c: PContext, n: PNode): PNode
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 31d2ce6bd..d02359d4c 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -73,7 +73,8 @@ type
     libs*: TLinkedList         # 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 = {},
+                       bufferErrors = false): 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,
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 2cb6f2047..337224aef 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -824,7 +824,7 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode =
 
 proc semExprNoType(c: PContext, n: PNode): PNode =
   result = semExpr(c, n, {efWantStmt})
-  discardCheck(result)
+  discardCheck(c, result)
   
 proc isTypeExpr(n: PNode): bool = 
   case n.kind
@@ -1218,7 +1218,7 @@ proc semProcBody(c: PContext, n: PNode): PNode =
       a.sons[1] = result
       result = semAsgn(c, a)
   else:
-    discardCheck(result)
+    discardCheck(c, result)
   closeScope(c)
 
 proc SemYieldVarResult(c: PContext, n: PNode, restype: PType) =
@@ -1439,12 +1439,12 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
     newNode(nkCall, n.info, quotes)])
   result = semExpandToAst(c, result)
 
-proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
+proc tryExpr(c: PContext, n: PNode,
+             flags: TExprFlags = {}, bufferErrors = false): PNode =
   # watch out, hacks ahead:
   let oldErrorCount = msgs.gErrorCounter
   let oldErrorMax = msgs.gErrorMax
   inc c.InCompilesContext
-  inc msgs.gSilence
   # do not halt after first error:
   msgs.gErrorMax = high(int)
   
@@ -1453,6 +1453,8 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   openScope(c)
   let oldOwnerLen = len(gOwners)
   let oldGenerics = c.generics
+  let oldErrorOutputs = errorOutputs
+  errorOutputs = if bufferErrors: {eInMemory} else: {}
   let oldContextLen = msgs.getInfoContextLen()
   
   let oldInGenericContext = c.InGenericContext
@@ -1475,7 +1477,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   setlen(gOwners, oldOwnerLen)
   c.currentScope = oldScope
   dec c.InCompilesContext
-  dec msgs.gSilence
+  errorOutputs = oldErrorOutputs
   msgs.gErrorCounter = oldErrorCount
   msgs.gErrorMax = oldErrorMax
 
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index ed6787a16..f514a93d7 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -132,7 +132,7 @@ proc fixNilType(n: PNode) =
     for it in n: fixNilType(it)
   n.typ = nil
 
-proc discardCheck(result: PNode) =
+proc discardCheck(c: PContext, result: PNode) =
   if result.typ != nil and result.typ.kind notin {tyStmt, tyEmpty}:
     if result.kind == nkNilLit:
       result.typ = nil
@@ -142,6 +142,10 @@ proc discardCheck(result: PNode) =
       while n.kind in skipForDiscardable:
         n = n.lastSon
         n.typ = nil
+    elif c.InTypeClass > 0 and result.typ.kind == tyBool:
+      let verdict = semConstExpr(c, result)
+      if verdict.intVal == 0:
+        localError(result.info, "type class predicate failed.")
     elif result.typ.kind != tyError and gCmd != cmdInteractive:
       if result.typ.kind == tyNil:
         fixNilType(result)
@@ -169,7 +173,7 @@ proc semIf(c: PContext, n: PNode): PNode =
       typ = commonType(typ, it.sons[0].typ)
     else: illFormedAst(it)
   if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
-    for it in n: discardCheck(it.lastSon)
+    for it in n: discardCheck(c, it.lastSon)
     result.kind = nkIfStmt
     # propagate any enforced VoidContext:
     if typ == EnforceVoidContext: result.typ = EnforceVoidContext
@@ -230,7 +234,7 @@ proc semCase(c: PContext, n: PNode): PNode =
       localError(n.info, errNotAllCasesCovered)
   closeScope(c)
   if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
-    for i in 1..n.len-1: discardCheck(n.sons[i].lastSon)
+    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
     # propagate any enforced VoidContext:
     if typ == EnforceVoidContext:
       result.typ = EnforceVoidContext
@@ -275,8 +279,8 @@ proc semTry(c: PContext, n: PNode): PNode =
     typ = commonType(typ, a.sons[length-1].typ)
   dec c.p.inTryStmt
   if isEmptyType(typ) or typ.kind == tyNil:
-    discardCheck(n.sons[0])
-    for i in 1..n.len-1: discardCheck(n.sons[i].lastSon)
+    discardCheck(c, n.sons[0])
+    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
     if typ == EnforceVoidContext:
       result.typ = EnforceVoidContext
   else:
@@ -1221,7 +1225,7 @@ proc semStmtList(c: PContext, n: PNode): PNode =
         voidContext = true
         n.typ = EnforceVoidContext
       if i != last or voidContext:
-        discardCheck(n.sons[i])
+        discardCheck(c, n.sons[i])
       else:
         n.typ = n.sons[i].typ
         if not isEmptyType(n.typ):
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 1d502a205..00f3b2b10 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -85,6 +85,7 @@ proc initCandidate*(c: var TCandidate, callee: PSym, binding: PNode,
   c.calleeSym = callee
   c.calleeScope = calleeScope
   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):
@@ -774,23 +775,16 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
     addDecl(c, dummyParam)
 
   for stmt in f.n[3]:
-    var e = c.semTryExpr(c, copyTree(stmt))
-    if e == nil:
-      let expStr = renderTree(stmt, {renderNoComments})
-      m.errors.safeAdd("can't compile " & expStr & "  for " & a.typeToString)
-      return nil
+    var e = c.semTryExpr(c, copyTree(stmt), bufferErrors = false)
+    m.errors = bufferedMsgs
+    clearBufferedMsgs()
+    if e == nil: return nil
+
     case e.kind
-    of nkReturnStmt:
-      nil
+    of nkReturnStmt: nil
     of nkTypeSection: nil
     of nkConstDef: nil
-    else:
-      if e.typ != nil and e.typ.kind == tyBool:
-        let verdict = c.semConstExpr(c, e)
-        if verdict.intVal == 0:
-          let expStr = renderTree(stmt, {renderNoComments})
-          m.errors.safeAdd(expStr & " doesn't hold for " & a.typeToString)
-          return nil
+    else: nil
   
   result = arg
   put(m.bindings, f, a)
diff --git a/doc/manual.txt b/doc/manual.txt
index c63df0304..dabff3d69 100644
--- a/doc/manual.txt
+++ b/doc/manual.txt
@@ -3289,27 +3289,36 @@ Declarative type classes are written in the following form:
       for value in c:
         type(value) is T
 
-
-The identifiers following the `generic` keyword are treated as variables of
-the matched type and the body of the type class consists of arbitrary code that
-must be valid under these circumstances.
-
-Specifically, the type class will be matched if:
+The type class will be matched 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
 
-Please note that the ``is`` operator allows you 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 doesn't reveal implementation details.
+The identifiers following the `generic` 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 a contexts, where a type is expected.
+
+Please note that the ``is`` operator allows you 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 doesn't reveal implementation details.
+
+As a special rule providing further convenience when writing type classes, any
+type value appearing in a callable expression will be treated as a variable of
+the designated type for overload resolution purposes, unless the type value was
+passed in its explicit ``typedesc[T]`` form:
+
+.. code-block:: nimrod
+  type
+    OutputStream = generic S
+      write(var S, string)
 
 Much like generics, the user defined type classes will be instantiated exactly
 once for each tested type and any static code included within them will also be
 executed once.
 
 
-
 Return Type Inference
 ---------------------
 
diff --git a/tests/run/tusertypeclasses.nim b/tests/run/tusertypeclasses.nim
new file mode 100644
index 000000000..4c2f07b85
--- /dev/null
+++ b/tests/run/tusertypeclasses.nim
@@ -0,0 +1,28 @@
+discard """
+  output: "Sortable\nSortable\nContainer"
+"""
+
+import typetraits
+
+type
+  TObj = object
+    x: int
+
+  Sortable = generic x, y
+    (x < y) is bool
+
+  ObjectContainer = generic C
+    C.len is ordinal
+    for v in items(C):
+      v.type is tuple|object
+
+proc foo(c: ObjectContainer) =
+  echo "Container"
+
+proc foo(x: Sortable) =
+  echo "Sortable"
+
+foo 10
+foo "test"
+foo(@[TObj(x: 10), TObj(x: 20)])
+