When casting, a procedure is not properly selected when two procedures with different signatures have the same name.
The examples below use types from the __iup__ and __nimpy__ libraries. I included all the relevant parts from the libraries and shortened the code a bit:
# types imported from the iup library
type
Ihandle = object
PtrIhandle* = ptr Ihandle
Icallback* = proc (arg: PtrIhandle): cint {.cdecl.}
# types imported from the nimpy library
type
PPyObject* = distinct pointer
PyObject* = ref object
rawPyObj: PPyObject
# mymodule.nim
proc turn_on*(bulb: PyObject) =
discard
# main.nim
import mymodule
proc turn_on(button: PtrIhandle): cint =
discard
def main() =
#[
some code
]#
# Setting a callback for a button from the iup library
btn_on.setCallback("ACTION", cast[Icallback](turn_on)) # Error line
The following error is thrown when compiling with nim c main.nim:
main.nim(9, 34) Error: expression cannot be cast to Icallback=proc (arg: PtrIhandle): cint{.cdecl.}
The compiler selects the mymodule.turn_on proc instead of main.turn_on in the line:
btn_on.setCallback("ACTION", cast[Icallback](turn_on))
First off, I didn't know that you can reference the module you are inside in. Cool. I tried it, and it works:
btn_on.setCallback("ACTION", cast[Icallback](main.turn_on))
But my feeling is that the compiler should know which is the correct procedure based on the signature or at least say that it doesn't know which one it should pick.
Should I report this issue or am I missing something?
am I missing something?
yes, you're missing one small thing :). Let's look at the definition of Icallback:
Icallback* = proc (arg: PtrIhandle): cint {.cdecl.}
See the {.cdecl.}, it defines a type of function pointer, with the calling definition set to cdecl. I don't know how familiar you are with these kind of things, but basically each procedure/function(in all compiled languages) has a set calling convention, which specifies how in which processor registers/where on the stack arguments and return values are passed when calling it. By default the calling convention of Nim procs is set to nimcall and the compiler applies exactly that kind of behaviour you described, so actually none of the two possible overloads match the required type.
Though that leaves one things open: what's about "workaround" you presented. The reason for this lies in the fact that you used cast. In Nim castT means interpret the given data as if it was of type T, completely breaking all safety measures, it's not needed in this situation. And that explains why it can't decide which overload to use, because it has no expectations. E.g. this would compile as well:
setCallback("ACTION", cast[Icallback](42))
And in this case you forcefully convert a proc with the nimcall calling convention into a cdecl, which obviously goes wrong, since a call to a nimcall proc could be setup in a completely different way than a cdecl proc call, you don't know.@doofenstein
Right, I forgot the {.cdecl.}, good catch.
The reason why the cast is there, is that the iup library has only one function to set the callbacks, which in Nim is:
# Callback definition
Icallback* = proc (arg: PtrIhandle): cint {.cdecl.}
# Function for setting a callback
proc setCallback*(ih: PtrIhandle, name: cstring, fn: Icallback): Icallback {.importc: "IupSetCallback",
cdecl, discardable.}`
and in C it's:
// Callback definition
typedef int (*Icallback)(Ihandle*);
// Function for setting a callback
Icallback IupSetCallback(Ihandle* ih, const char *name, Icallback func);
Which works great for callbacks that have the exact same signature as Icallback. But some callbacks have additional parameters! And in C example code for the library they just cast the different callback signatures with (Icallback). Example from the IUP official docs:
// Functions
int btn_on_off_cb(Ihandle *self)
{
// code
}
int btn_image_button_cb( Ihandle *self,int b, int e )
{
// code
}
// Setting up the callbacks
IupSetCallback( btn_on_off, "ACTION", (Icallback) btn_on_off_cb );
IupSetCallback( btn_image, "ACTION", (Icallback) btn_image_button_cb );
So I'm just doing the same as these examples in C, because I don't know of a way to do this better in Nim. Any suggestions would be greatly appreciated? But some callbacks have additional parameters! And in C example code for the library they just cast the different callback signatures with (Icallback).
In this case you have no other choice then to use cast to call the C functions and it's completly justified. I don't know much about iup, though you could try something like that to make it safer:
proc setCallbackClick(ih: PtrIHandle, handler: proc (arg: PtrIhandle, mousebutton: cint): cint {.cdecl.}) =
ih.setCallback("CLICK", cast[Icallback](handler))