simple unit test and better documentation for the user defined type classes

This commit is contained in:
Zahary Karadjov 2013-11-17 22:50:26 +02:00
commit a068aaed3c
8 changed files with 113 additions and 57 deletions

View file

@ -437,6 +437,13 @@ type
line*, col*: int16 line*, col*: int16
fileIndex*: int32 fileIndex*: int32
TErrorOutput* = enum
eStdOut
eStdErr
eInMemory
TErrorOutputs* = set[TErrorOutput]
ERecoverableError* = object of EInvalidValue ERecoverableError* = object of EInvalidValue
ESuggestDone* = object of EBase ESuggestDone* = object of EBase
@ -534,13 +541,27 @@ var
gHintCounter*: int = 0 gHintCounter*: int = 0
gWarnCounter*: int = 0 gWarnCounter*: int = 0
gErrorMax*: int = 1 # stop after gErrorMax errors gErrorMax*: int = 1 # stop after gErrorMax errors
gSilence*: int # == 0 if we produce any output at all
when useCaas: when useCaas:
var stdoutSocket*: TSocket 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) = proc SuggestWriteln*(s: string) =
if gSilence == 0: if eStdOut in errorOutputs:
when useCaas: when useCaas:
if isNil(stdoutSocket): Writeln(stdout, s) if isNil(stdoutSocket): Writeln(stdout, s)
else: else:
@ -549,6 +570,9 @@ proc SuggestWriteln*(s: string) =
else: else:
Writeln(stdout, s) Writeln(stdout, s)
if eInMemory in errorOutputs:
bufferedMsgs.safeAdd(s)
proc SuggestQuit*() = proc SuggestQuit*() =
if not isServing: if not isServing:
quit(0) quit(0)
@ -570,14 +594,6 @@ const
RawWarningFormat* = "Warning: $1" RawWarningFormat* = "Warning: $1"
RawHintFormat* = "Hint: $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 getInfoContextLen*(): int = return msgContext.len
proc setInfoContextLen*(L: int) = setLen(msgContext, L) proc setInfoContextLen*(L: int) = setLen(msgContext, L)
@ -642,14 +658,18 @@ proc addCheckpoint*(filename: string, line: int) =
proc OutWriteln*(s: string) = proc OutWriteln*(s: string) =
## Writes to stdout. Always. ## Writes to stdout. Always.
if gSilence == 0: Writeln(stdout, s) if eStdOut in errorOutputs: Writeln(stdout, s)
proc MsgWriteln*(s: string) = proc MsgWriteln*(s: string) =
## Writes to stdout. If --stdout option is given, writes to stderr instead. ## Writes to stdout. If --stdout option is given, writes to stderr instead.
if gSilence == 0: if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
if optStdout in gGlobalOptions: Writeln(stderr, s) if optStdout in gGlobalOptions:
else: Writeln(stdout, s) 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 = proc coordToStr(coord: int): string =
if coord == -1: result = "???" if coord == -1: result = "???"
@ -736,9 +756,6 @@ proc rawMessage*(msg: TMsgKind, args: openarray[string]) =
proc rawMessage*(msg: TMsgKind, arg: string) = proc rawMessage*(msg: TMsgKind, arg: string) =
rawMessage(msg, [arg]) rawMessage(msg, [arg])
var
lastError = UnknownLineInfo()
proc writeSurroundingSrc(info: TLineInfo) = proc writeSurroundingSrc(info: TLineInfo) =
const indent = " " const indent = " "
MsgWriteln(indent & info.sourceLine.ropeToStr) MsgWriteln(indent & info.sourceLine.ropeToStr)

View file

@ -36,7 +36,8 @@ proc semParamList(c: PContext, n, genericParams: PNode, s: PSym)
proc addParams(c: PContext, n: PNode, kind: TSymKind) proc addParams(c: PContext, n: PNode, kind: TSymKind)
proc maybeAddResult(c: PContext, s: PSym, n: PNode) proc maybeAddResult(c: PContext, s: PSym, n: PNode)
proc instGenericContainer(c: PContext, n: PNode, header: PType): PType 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 fixImmediateParams(n: PNode): PNode
proc activate(c: PContext, n: PNode) proc activate(c: PContext, n: PNode)
proc semQuoteAst(c: PContext, n: PNode): PNode proc semQuoteAst(c: PContext, n: PNode): PNode

View file

@ -73,7 +73,8 @@ type
libs*: TLinkedList # all libs used by this module libs*: TLinkedList # all libs used by this module
semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} 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.} semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
semOverloadedCall*: proc (c: PContext, n, nOrig: PNode, semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,

View file

@ -824,7 +824,7 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode =
proc semExprNoType(c: PContext, n: PNode): PNode = proc semExprNoType(c: PContext, n: PNode): PNode =
result = semExpr(c, n, {efWantStmt}) result = semExpr(c, n, {efWantStmt})
discardCheck(result) discardCheck(c, result)
proc isTypeExpr(n: PNode): bool = proc isTypeExpr(n: PNode): bool =
case n.kind case n.kind
@ -1218,7 +1218,7 @@ proc semProcBody(c: PContext, n: PNode): PNode =
a.sons[1] = result a.sons[1] = result
result = semAsgn(c, a) result = semAsgn(c, a)
else: else:
discardCheck(result) discardCheck(c, result)
closeScope(c) closeScope(c)
proc SemYieldVarResult(c: PContext, n: PNode, restype: PType) = proc SemYieldVarResult(c: PContext, n: PNode, restype: PType) =
@ -1439,12 +1439,12 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
newNode(nkCall, n.info, quotes)]) newNode(nkCall, n.info, quotes)])
result = semExpandToAst(c, result) 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: # watch out, hacks ahead:
let oldErrorCount = msgs.gErrorCounter let oldErrorCount = msgs.gErrorCounter
let oldErrorMax = msgs.gErrorMax let oldErrorMax = msgs.gErrorMax
inc c.InCompilesContext inc c.InCompilesContext
inc msgs.gSilence
# do not halt after first error: # do not halt after first error:
msgs.gErrorMax = high(int) msgs.gErrorMax = high(int)
@ -1453,6 +1453,8 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
openScope(c) openScope(c)
let oldOwnerLen = len(gOwners) let oldOwnerLen = len(gOwners)
let oldGenerics = c.generics let oldGenerics = c.generics
let oldErrorOutputs = errorOutputs
errorOutputs = if bufferErrors: {eInMemory} else: {}
let oldContextLen = msgs.getInfoContextLen() let oldContextLen = msgs.getInfoContextLen()
let oldInGenericContext = c.InGenericContext let oldInGenericContext = c.InGenericContext
@ -1475,7 +1477,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
setlen(gOwners, oldOwnerLen) setlen(gOwners, oldOwnerLen)
c.currentScope = oldScope c.currentScope = oldScope
dec c.InCompilesContext dec c.InCompilesContext
dec msgs.gSilence errorOutputs = oldErrorOutputs
msgs.gErrorCounter = oldErrorCount msgs.gErrorCounter = oldErrorCount
msgs.gErrorMax = oldErrorMax msgs.gErrorMax = oldErrorMax

View file

@ -132,7 +132,7 @@ proc fixNilType(n: PNode) =
for it in n: fixNilType(it) for it in n: fixNilType(it)
n.typ = nil 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.typ != nil and result.typ.kind notin {tyStmt, tyEmpty}:
if result.kind == nkNilLit: if result.kind == nkNilLit:
result.typ = nil result.typ = nil
@ -142,6 +142,10 @@ proc discardCheck(result: PNode) =
while n.kind in skipForDiscardable: while n.kind in skipForDiscardable:
n = n.lastSon n = n.lastSon
n.typ = nil 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: elif result.typ.kind != tyError and gCmd != cmdInteractive:
if result.typ.kind == tyNil: if result.typ.kind == tyNil:
fixNilType(result) fixNilType(result)
@ -169,7 +173,7 @@ proc semIf(c: PContext, n: PNode): PNode =
typ = commonType(typ, it.sons[0].typ) typ = commonType(typ, it.sons[0].typ)
else: illFormedAst(it) else: illFormedAst(it)
if isEmptyType(typ) or typ.kind == tyNil or not hasElse: 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 result.kind = nkIfStmt
# propagate any enforced VoidContext: # propagate any enforced VoidContext:
if typ == EnforceVoidContext: result.typ = EnforceVoidContext if typ == EnforceVoidContext: result.typ = EnforceVoidContext
@ -230,7 +234,7 @@ proc semCase(c: PContext, n: PNode): PNode =
localError(n.info, errNotAllCasesCovered) localError(n.info, errNotAllCasesCovered)
closeScope(c) closeScope(c)
if isEmptyType(typ) or typ.kind == tyNil or not hasElse: 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: # propagate any enforced VoidContext:
if typ == EnforceVoidContext: if typ == EnforceVoidContext:
result.typ = EnforceVoidContext result.typ = EnforceVoidContext
@ -275,8 +279,8 @@ proc semTry(c: PContext, n: PNode): PNode =
typ = commonType(typ, a.sons[length-1].typ) typ = commonType(typ, a.sons[length-1].typ)
dec c.p.inTryStmt dec c.p.inTryStmt
if isEmptyType(typ) or typ.kind == tyNil: if isEmptyType(typ) or typ.kind == tyNil:
discardCheck(n.sons[0]) discardCheck(c, n.sons[0])
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)
if typ == EnforceVoidContext: if typ == EnforceVoidContext:
result.typ = EnforceVoidContext result.typ = EnforceVoidContext
else: else:
@ -1221,7 +1225,7 @@ proc semStmtList(c: PContext, n: PNode): PNode =
voidContext = true voidContext = true
n.typ = EnforceVoidContext n.typ = EnforceVoidContext
if i != last or voidContext: if i != last or voidContext:
discardCheck(n.sons[i]) discardCheck(c, n.sons[i])
else: else:
n.typ = n.sons[i].typ n.typ = n.sons[i].typ
if not isEmptyType(n.typ): if not isEmptyType(n.typ):

View file

@ -85,6 +85,7 @@ proc initCandidate*(c: var TCandidate, callee: PSym, binding: PNode,
c.calleeSym = callee c.calleeSym = callee
c.calleeScope = calleeScope c.calleeScope = calleeScope
initIdTable(c.bindings) initIdTable(c.bindings)
c.errors = nil
if binding != nil and callee.kind in RoutineKinds: if binding != nil and callee.kind in RoutineKinds:
var typeParams = callee.ast[genericParamsPos] var typeParams = callee.ast[genericParamsPos]
for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1): for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1):
@ -774,23 +775,16 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
addDecl(c, dummyParam) addDecl(c, dummyParam)
for stmt in f.n[3]: for stmt in f.n[3]:
var e = c.semTryExpr(c, copyTree(stmt)) var e = c.semTryExpr(c, copyTree(stmt), bufferErrors = false)
if e == nil: m.errors = bufferedMsgs
let expStr = renderTree(stmt, {renderNoComments}) clearBufferedMsgs()
m.errors.safeAdd("can't compile " & expStr & " for " & a.typeToString) if e == nil: return nil
return nil
case e.kind case e.kind
of nkReturnStmt: of nkReturnStmt: nil
nil
of nkTypeSection: nil of nkTypeSection: nil
of nkConstDef: nil of nkConstDef: nil
else: else: nil
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
result = arg result = arg
put(m.bindings, f, a) put(m.bindings, f, a)

View file

@ -3289,27 +3289,36 @@ Declarative type classes are written in the following form:
for value in c: for value in c:
type(value) is T type(value) is T
The type class will be matched if:
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:
a) all of the expressions within the body can be compiled for the tested type 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 evaluatable boolean expressions in the body must be true
Please note that the ``is`` operator allows you to easily verify the precise type The identifiers following the `generic` keyword represent instances of the
signatures of the required operations, but since type inference and default currently matched type. These instances can act both as variables of the type,
parameters are still applied in the provided block, it's also possible to encode when used in contexts, where a value is expected, and as the type itself, when
usage protocols that doesn't reveal implementation details. 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 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 once for each tested type and any static code included within them will also be
executed once. executed once.
Return Type Inference Return Type Inference
--------------------- ---------------------

View file

@ -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)])