So I ran into a funny problem today. Let me to illustrate with a contrived example:
{.experimental.}
type
Vec2 = tuple[x, y: float]
Kinetic = concept g
g.pos is Vec2
g.vel is Vec2
IKinetic = object
converter conToInter[T: Kinetic](x: var T): IKinetic =
IKinetic()
template `.`(x: IKinetic; field: untyped): untyped =
123
conToInter(5)
This seems to throw the compiler into an infinite loop (until it gives up). Here's my understanding of what's happening: the conToInter call triggers the Kinetic concept checks, which use the dot operator, so the compiler tries to find an applicable . implementation. In doing so, it runs into the one taking an IKinetic, so now it has to check if there's an applicable conversion, which leads it back to conToInter, triggering the concept checks again. The obvious way to fix this is to avoid the dot operator inside the concept body (which I don't mind, as it generates ugly and unhelpful errors when the checks fail), but what should I use instead? I suppose I could search the type's AST representation, but that seems like a slow and fragile way to do it. Are there any alternatives?Do you really need to overload .? That would make any further development of your codebase really tricky.
If you want custom getters and setters, you can follow this: https://nim-lang.org/docs/manual.html#procedures-properties
Otherwise you might want to look into parameter constraints You can make sure that your custom dot template is only called for identifier for example.
I'm overloading it for a sort of proxy object which contains a pointer to a vtable with field offsets and type information, to give seamless access to the fields of the target through the proxy. The entire thing is generated by a macro, so I could simply generate getters and setters as you suggest, but wouldn't the same problem still occur? The compiler will look for a pos getter, find one with takes an IKinetic, check if g is convertible to that and re-trigger the concept checks.
That would make any further development of your codebase really tricky.
Could you please elaborate on that?
Otherwise you might want to look into parameter constraints You can make sure that your custom dot template is only called for identifier for example.
Well, what I really want is for it to not get called inside the concept. The key is to avoid anything that may trigger a convertibility check (into IKinetic) from the concept. I'll check your link and see if I can cook something up.
This seems to work, but it's kinda dirty, and the error messages aren't great:
{.experimental.}
type
Vec2 = tuple[x, y: float]
Kinetic = concept g, type T
T.pos is Vec2
T.vel is Vec2
IKinetic = object
Box = object
pos: Vec2
vel: Vec2
converter conToInter[T: not typedesc and Kinetic](x: var T): IKinetic =
IKinetic()
template `.`(x: IKinetic, field: untyped): untyped =
123
proc dummy(k: IKinetic): IKinetic = k
var x = Box()
var y = 3
var a = dummy(x) # This is okay
var b = dummy(y) # This gives a compiler error, as it should
It avoids the infinite recursion by checking the fields on the instance type, and excluding typedescs from the converter. I'll be happy to consider alternatives, if there exist any.