The goal of this macro
https://github.com/StefanSalewski/gintro/blob/master/gintro/gimpl.nim#L30
was to enable us to connect callbacks to widgets with type checking at compile time.
For most signals this works fine, see test in https://github.com/StefanSalewski/gintro/blob/master/examples/connect_args.nim
Problem is, that GTK uses not only plain object types, but sometimes define so called interfaces. Data types can provide such interfaces, and then can be regarded equivalent to the interfaces. (We discussed that on GTk mailing list in this thread: https://mail.gnome.org/archives/gtk-list/2017-July/msg00018.html)
The above macro generates code like this when Interfaces are involved:
import gintro/[gtk, gobject, glib]
var r: CellRenderer
proc handler2(renderer: CellRenderer, editable: CellEditable, path: string) = discard
proc connect_for_signal_cdecl_editing_started2(self: ptr CellRenderer00; editable: ptr CellEditable00; path: cstring; data: pointer) {.cdecl.} =
let h: pointer = g_object_get_qdata(self, Quark)
var editable1: CellEditable
new editable1
editable1.impl = editable
handler2(cast[CellRenderer](h), editable1, $(path))
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: CellEditable | SpinButton | ComboBox | SearchEntry | ComboBoxText | AppChooserButton | Entry; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
connect_for_signal_editing_started2(r, handler2)
The OR type class results from the fact that CellEditable is the Interface and all the other listed widgets provide this interface and are regarded compatible in this way. The handler can have one of these widgets type as argument, then it should compile. For other incompatible widgets it should of course not compile.
The above code does not compile at all currently, as that Or notation is not supported.
It seem that I can break down the proc with OR parameters into multiple procs like
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: SpinButton; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: CellEditable; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
Then the compiler selects the right one. But that is a lot of code generation in the macro.
Another approch may be to investigate the actual parameters of the provided proc (handler2 here) to check if parameters are compatible. That works, when the proc is passed as typed parameter to the macro -- then investigation starts with something like p.symbol.getImpl().
Unfortunately currently the proc is passed untyped to the macro. As typed parameter it does not compile.
Can I pass the same parameter to a macro as typed and untyped version?
Can I pass the same parameter to a macro as typed and untyped version?
If your proc itself takes parameters of concrete types then passing it to a macro in two different parameters -- one typed, one untyped -- should work. Not a very clean solution, though.
BTW: the CellEditable "interface" and its "implementations" look like a use case for a user defined type class, how about making it a concept? Maybe manually checking types will not be necessary then.
use case for a user defined type class, how about making it a concept?
I have never used concepts before, I know that Zachary was working on it...
When you think that concepts may work for this case then I will try that, thanks.
When you think that concepts may work for this case then I will try that, thanks.
Just an idea, didn't really analyze your case. To my experience: basic concepts work, generic and refined concepts can cause trouble as of now.
I have just tried manual patch with
type
CellEditableConcept* = CellEditable or SpinButton or ComboBox or SearchEntry or ComboBoxText or AppChooserButton or Entry
and
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: CellEditableConcept; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
connect_for_signal_editing_started2(r, handler2)
It seems that I get the same or very similar error message:
Error: internal error: getTypeDescAux(tyOr)
No stack traceback available
To create a stacktrace, rerun compilation with ./koch temp c <file>
So that notation is not supported.
And I have not manage to pass a proc twice as typed and untyped to a macro -- of course I do not want a call like
connect(widget, "delete_row", handler, handler)
but only one handler argument for the user. Using a template, which then calls the macro seems not to help.
A concept definition would look more like this:
type
CellEditableConcept* = concept c
# some compile-time-checkable criterion that all CellEditables have in common,
# e.g. a certain proc p (Note the concept-specific syntax).
c.p(string) is bool
Yes, I have seen that in the Nim manual.
Unfortunately such a concept construction is a bit difficult because all the code is automatically generated by use of gobject-introspection. Maybe I will make one manual test case to see if it would work. For now I have broken down the procs into multiple instances in the macro:
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: CellEditable; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: SpinButton; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: ComboBox; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: SearchEntry; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: ComboBoxText; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: AppChooserButton; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
proc connect_for_signal_editing_started2(self: CellRenderer; p: proc (self: CellRenderer; editable: Entry; path: string)): culong {.discardable.} =
scediting_started(self, connect_for_signal_cdecl_editing_started2, nil)
connect_for_signal_editing_started2(r, handler2)
Seems to work, due to overloading the compiler can select a proc definition which matches for handler2.
At least the test case which a user posted compiles now -- I have to wait for user reply: https://github.com/StefanSalewski/gintro/issues/14#issuecomment-330171244