Open davidhesselbom opened 10 years ago
Ok so apparently rock tries to resolve the type access that should be a generic parameter and then proceeds to do nothing with the resulting ref (e.g. <T>)
What that means is that if you provide it with a class that actually exists as a generic parameter (e.g. <Int>), it will compile and 'T' will be accessible (as this T)
So, the following actually works (as a temporary workaround)
import structs/ArrayList
extend ArrayList<Int> {
nop: func (obj: T) {
match obj {
case i: Int => i toString() println()
case s: String => s println()
}
}
}
arr := [1, 2] as ArrayList<Int>
arr nop(42)
brr := ["hello", "world!"] as ArrayList<String>
brr nop("hi")
By the way @davidhesselbom, I would do something like that for a non-gc lib (actually if I rewrite the SDK I think I'll do it)
extend Object {
free: func {
version(!gc) {
__destroy__()
"Destroying obj %p" printfln(this)
gc_free(this)
}
}
}
Foo: class {
data: Int*
size: Int
init: func(=size) {
data = gc_malloc(size) as Int*
}
__destroy__: func {
"Destroying pointer's %p data" printfln(this)
gc_free(data)
}
}
f := Foo new(256)
f free()
EDIT: s/GC/gc
@shamanas small beef: in your version block, gc
should not be capitalized :)
Ah, didn't test the code but I was pretty sure we had a GC version, apparently I was wrong :)
@shamanas I would advise against the printouts (printfln
mallocs 700 bytes every time), but thanks for the suggestion! :)
Yes, of course the print outs are just for debugging/example purposes, this wouldn't make production code :)
@davidhesselbom 700 bytes is a lot, I wonder why so much memory is allocated. It seems the default buffer allocated by fromat ~main is 512 bytes Also
// allocate 20% + 10 bytes more than needed - just in case
capacity = (min * 120) / 100 + 10
In Buffer.ooc gets us to about 625 bytes... ouch
Ok I hit a bit of an issue.
I have working code, however there are a couple of complications.
To make this work, I essentially store the generic arguments as VariableDecl's, like TypeDecl does, in the addon and then add TypeAccesse's with the same names to the BaseType we extend and a resolveType for Addon wich suggests the VariableDecl's as ref's to the TypeAccesse's.
Everything is fine.
Then, in resolveAccess, I essentially "route" an access to one of the Addon's typeArgs to our base classe's typeArg declarations.
However, the fact remains that functions resolved in our addon must still call TypeDecl resolveAccess and that has one weird consequence, demonstrated below:
// ArrayList: class <T>
extend ArrayList<K> {
noop: func {
T name println() // K name println works as well
}
}
arr := ArrayList<Int> new()
arr noop() // prints Int, as expected
// This is the biggie
// HashMap: class <K, V>
extend HashMap<T, K> {
noop: func {
K name println() // What will happen? :D
}
}
hm := HashMap<Int, String> new()
hm noop() // Int is printed out because TypeDecl resolveAccess is called first
To fix that, three solutions are possible:
1) Disallow generic names that exist in the base class definition (bad) 2) Disallow generic names that exist in the base class definition in different positions (a little better) 3) Only allow the same generic names in the same positions as the base class definition (I personally prefer that one)
@fasterthanlime, What do you think?
// ArrayList: class <T>
extend ArrayList<K> {
noop: func {
T name println() // shouldn't work - there's no such type parameter in this type extension
}
}
This is the best solution but I think it is near impossible to implement without rewriting all of Addon.ooc and a big part of TypeDecl.ooc.
The issue here is that T is a field of ArrayList like any other.
Addon currently takes function declarations (and some properties) and basically resolves them as if there were part of the TypeDecl (their parent in the Trail is the TypeDecl), where the typeArgs have already been defined.
Perhaps this could be done with some really hackish name swapping, basically when TypeDecl resolve() is called, going in, changing the typeArg VariableDecl's names to reflect the Addon's typeArgs and reversing the process when we have ended up resolving this.
Then again, what happens if we pass our "K" variable to a function that expects a "T"?
I think it would work because they both point to the same VariableDecl but still :/
I'm aware of that :) If a rewrite is needed, then now seems like a good time.
Type parameter mapping was done in j/ooc (and still in rock, afaict) via 'phantom' args. I don't remember exactly where they are lurking in the code.
In TypeDecl.ooc, ghosting is just deleting variable declarations of typeArgs that are defined by a super type.
When you say "type parameter mapping" what are you referring to, resolving generic types to a calsse's variable decl?
When you say "type parameter mapping" what are you referring to, resolving generic types to a calsse's variable decl?
Yes, in that within extend ArrayList<K> { /* ... */ }
, K
should be mapped to T
.
I don't think rock does that anywhere else but that is fairly easy to accomplish, the difficult part is T not being mapped to T when K is mapped to T.
I guess I could make a GenericDecl that extends VariableDecl stores the index of the generic parameter (and its name in the class decl) for code generation purposes, then add a mapGenerics method for TypeDecls that changes tha mapping from name to GenericDecl (and finally change the mapping back when we resolve the TypeDecl itself)
That is pretty much the same thing as modifying the VariableDecl names though, albeit a bit more organized.
Well, when I wrote:
If a rewrite is needed, then now seems like a good time.
I had in mind a clean solution, not just piling more stuff on top of rock. I agree this takes more time, but at some point it'll be needed.
Yes I know, I'm still thinking on how this could be structured to make sense.
I mean, a Type Definition has type args which have names, the structure is pretty clear.
Extending a definition should be done in context of the original definition, so that accesses etc. are resolved from it, like we do currently.
A solution would be to transition to index-based solutions for generics everywhere, e.g. have a GenericAccess (extends VariableAccess) which points to an index whose ref is resolved to a GenericDefinition (which has a "true" name for code generation and an index).
Then, nodes that support generics all have generic "mappings" (basically arraylists of string) which essentially are the current names of the generic arguments.
Still a pain in the ass with "ghosting", we have to find out which indices are generics inherited from super types and pass on our mappings to them every time...
Anyway, sorry about that, just some brainstorming
Was this solved by #965 or is there more to do here?
@marcusnaslund With #965 you should be able to extend type template instances (not generic types), although I haven't tested it.
I will be looking at this next, I have some work on one of my branches, I will go through it and figure out where I was stuck.
Awesome, best of luck.
@marcusnaslund Thank you :)
I don't think I will have much time today but looking at the rate I've been solving bugs and adding features I would expect to be finished by tomorrow night.
Or perhaps this time I'm too cocky, I've crossed the line and the rock gods will punish me.
We will see either way, I'll post here if I have any significant breakthroughs.
gives the error
No type parameters for ArrayList. It should match ArrayList
, andfails because
Undefined type 'T'
.