From a068aaed3c5ddaf05a104f3f2d0f512bab2861c6 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sun, 17 Nov 2013 22:50:26 +0200 Subject: [PATCH] simple unit test and better documentation for the user defined type classes --- compiler/msgs.nim | 55 ++++++++++++++++++++++------------ compiler/sem.nim | 3 +- compiler/semdata.nim | 3 +- compiler/semexprs.nim | 12 ++++---- compiler/semstmts.nim | 16 ++++++---- compiler/sigmatch.nim | 22 +++++--------- doc/manual.txt | 31 ++++++++++++------- tests/run/tusertypeclasses.nim | 28 +++++++++++++++++ 8 files changed, 113 insertions(+), 57 deletions(-) create mode 100644 tests/run/tusertypeclasses.nim diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 5363442b40..895ba71f30 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 71951dd3f7..ea53afbeb4 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 31d2ce6bda..d02359d4ce 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 2cb6f20477..337224aef9 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 ed6787a167..f514a93d7f 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 1d502a205a..00f3b2b107 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 c63df03040..dabff3d69d 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 0000000000..4c2f07b853 --- /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)]) +