Hello everyone, so I'm trying to make a simple C compiler in Nim. I needed to change AST node types, so I wrote something like this:
type
Ast = ref object of RootObj
A = ref object of Ast
B = ref object of Ast
B1 = ref object of B
B2 = ref object of B
C = ref object of Ast
b: B
method change(ast: var Ast) {.base.} =
discard
method change(ast: var B) =
if ast of B1:
ast = B2()
method change(ast: var C) =
ast.b.change()
This worked well, so I added D:
...
type D = ref object of Ast
bs: seq[B]
...
method change(ast: var D) =
for b in ast.bs.mitems:
b.change()
And this also worked well, so I decided to move this into iterator:
type
Ast = ...
iterator msub(ast: Ast): var Ast =
if ast of C:
yield ast.C.b.Ast
elif ast of D:
for b in ast.D.bs.mitems:
yield b.Ast
method change(ast: var Ast) {.base.} =
for sub in ast.msub:
sub.change()
method change(ast: var B) =
if ast of B1:
ast = B2()
And suddenly this didn't work. Also it raised error not in the Nim compiler but in the gcc. Here is the error for the sake of completeness:
error: '*b' is a pointer; did you mean to use '->'?
sub = &b->Sup;
^~
->
I read some related issues, and found out that accroding to Araq var Ast and var B have no subtype relation, so all of this was invalid.
While I don't understand why that should be that way, I also don't understand how things like that should be written in Nim. Or should I return changed version of AST node? This is still strange to me. Also, why the compiler allowed me to use that before, but didn't allow it when I switched to iterators? Could someone please explain that to me?
An illustration.
type
A = ref object of RootObj
B = ref object of A
method m(v: A) {.base.} = echo "mA"
method m(v: B) = echo "mB"
proc p(v: A) = echo "pA"
proc p(v: B) = echo "pB"
iterator items(v: A): string = yield "iA"
iterator items(v: B): string = yield "iB"
var b: A = B()
m b # => mB
p b # => pA
for i in b: echo i # => iA
No corresponding language feature to combine both behaviours, of method and of iterator.
But you may workaround this by having separate iterators for types:
# continuing the previous example
method mi(v: A) {.base.} =
for i in v: echo "mi A, " & i # ``iterator items(v: A)`` is called
method mi(v: B) =
for i in v: echo "mi B, " & i # ``iterator items(v: B)`` is called
mi b # => mi B, iB
var a = A()
mi a # => mi A, iA
Thanks for the response! Though I do not try to use dynamic dispatch for iterators, am I wrong? I directly check runtime type inside of the iterator, so it is always using the only iterator version , Ast one, isn't it?
Anyway, I found my original problem. It was because type D can only hold instances of type B by definition, but I'm returning var Ast, so I could potentially assign anything (for example A) to it, which would be invalid.
As a solution I changed var Ast in iterator return type to var B and removed all conversions to Ast inside. In my original problem, not this simplified one, it required 4 iterators with different return types, but oh well. Thanks anyway!