Hey there!
I’ve been trying to mock a specific overload of the system.add function call using this mock template. I’ve had luck with mocking non-overloaded functions but not this one.
The main issue I’m facing (for now) is I’m not able to retrieve the specific overload I want to mock, system.add: proc (x: var seq[T], y: sink T){.noSideEffect.}. At least, I think that’d be the first step in mocking it.
When I try to retrieve it, like so:
let sysAdd = system.add
I get the following error:
Error: ambiguous identifier: 'add' -- you need a helper proc to disambiguate the following:
system.add: proc (x: var seq[T], y: sink T){.noSideEffect.}
system.add: proc (x: var string, y: string){.noSideEffect, gcsafe, locks: 0.}
system.add: proc (x: var string, y: char){.noSideEffect.}
system.add: proc (x: var seq[T], y: openArray[T]){.noSideEffect.}
system.add: proc (x: var string, y: cstring){.noSideEffect, gcsafe, locks: 0.}
So far so good. I’ve tried many methods to make it conform to the specific instance I want, but all my attempts have been unsuccessful. The closest one I’d say it’s this:
type
AddType=
proc(x: seq[NsContentTopic], y: sink NsContentTopic) {.noSideEffect.}
let nsAdd: AddType= system.add # Error: type mismatch: got 'None' for 'add' but expected 'NsContentTopicAdd = proc (x: seq[NsContentTopic], y: NsContentTopic){.closure, noSideEffect.}
Which is funny, because if I were to change it the type to fetch this other overload:
type
AddType=
proc(x: var seq[NsContentTopic], y: openArray[NsContentTopic]) {.
noSideEffect
.}
It’s able to fetch that instance (It does lead to other problems later regarding the mocking template, but that’s a later story, I think).
Does any of you have some clue as to how to achieve mocking this?
What you're trying to do is something I attempted (and failed at with mockingbird) and has been solved by ElegantBeef: Enabling replacing any proc as long as it is assigned a specific pragma.
https://github.com/beef331/nimtrest/blob/master/rickles.nim#L105-L123
I do not claim to understand it and have not used it yet and likely beef himself will need to write a bit more about it, but that would be a clue/source for how to achieve what you're looking for.
Personally I find a macro like the following to work the best for grabbing overloaded symbols
import std/macros
macro extractProc(t: typed): untyped =
if t.kind != nnkCall:
error("Expected a call", t)
t[0]
var myAdd = static:
var i: seq[int]
extractProc i.add(10)
The issue here is that Nim's add is not capable of being a pointer proc so you really just needed
var myAdd = proc(s: var seq[int], i: int){.nimcall.} = s.add(i)
Mocking code you did not author is not easy in any regard. I do not personally like the premise of mocking as you are not testing the actual program, but anyway to write more here. The way Rickles works is a simple mechanism. Any non generic procedure marked mockit gets turned into a var procName* = defaultImplementation this means you get the ability to mutate all non generic procedures to whatever you want. Generics are a bit more complicated and must use generic invokation syntax procName[...](...), for generic procedures it emits a var myProcName = ADistinctPointer(). When you use [] or []= on that myProcName you provide types to instantiate inside the []`s which are used to add to a global macrocache. This contains a hidden generated variable, presently it's a `{.global, nodecl.} to allow later rebinding. Finally a call to emitAllPointerProcs is required which generates all the variables using {.emit:["/*VARSECTION*/ typeof(", inst, ") *", name, " = &", inst, ";"].} since we used nodecl these pointer procs are called instead of the local variable. As it is annotated VARSECTION we put it before any procedure bodies are implemented.
You can also disambiguate the function call you need with a macro like so:
import macros
proc dummy() =
echo "normal dummy"
proc dummy(s: string) =
echo "string dummy " & s
macro getProc(sym: typed, types: varargs[typedesc]): untyped =
for procSym in sym:
let params = procSym.getImpl.params
block verifyCheck:
if (params.len-1) != types.len:
break verifyCheck
for i in 1 ..< params.len:
let paramTy = params[i]
# params len is 1 greater than types len
if paramTy[1] != types[i-1]:
break verifyCheck
# if all the params are the same
return procSym
proc main() =
let dummyProc = getProc(dummy, string)
let str = "test string"
# Calls the dummy proc with the string argument
dummyProc(str)
main()
Note that you will not be able to mock a lot of the system procs because they are defined in the compiler source and are thus "built-in".
For example, trying to do this:
proc main() =
let sysAdd = getProc(system.add, var string, string)
var str = "test string"
sysAdd(str, " adding")
echo str
main()
Results in the error:
Error: 'add' is a built-in and cannot be used as a first-class procedure
To start an arms race of "the best way to get a proc symbol" I enter my improved extractProc :P
import std/[macros, genasts]
macro extractProcImpl(call: typed): untyped =
call[0]
macro extractProc(prc: typed, params: varargs[typed]): untyped =
result = newCall(prc)
for param in params:
case param.kind
of nnkVarTy:
result.add:
genast(typ = param[^1]):
var param: typ
param
else:
result.add newCall("default", param)
result = newCall(bindSym"extractProcImpl", result)
proc doThing(a: var int) = discard
proc doThing[T](a: var seq[T]) = discard
proc doThing[T](a: var openArray[T]) = discard
var myDoThing = extractProc(doThing, var seq[int])
Thank you for all your responses, they were quite insightful!
So, I've tried running all your proposals and I always arrive at Error: 'add' cannot be passed to a procvar.
Which, I understand, means I can't mock them because they're built-in, as @jyapayne mentioned.
Out of curiosity, I take that it would work if system.add had been annotated with {.procvar.} or if I was running the mock in the same module. Or did I missunderstand something?