ooc-lang / rock

:ocean: self-hosted ooc compiler that generates c99
http://ooc-lang.org/
MIT License
403 stars 40 forks source link

Extending generic classes #830

Open davidhesselbom opened 10 years ago

davidhesselbom commented 10 years ago
extend ArrayList {
    nop: func {
        // Do nothing
    }
}

gives the error No type parameters for ArrayList. It should match ArrayList, and

extend ArrayList<T> {
    nop: func {
        // Do nothing
    }
}

fails because Undefined type 'T'.

alexnask commented 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")
alexnask commented 10 years ago

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

fasterthanlime commented 10 years ago

@shamanas small beef: in your version block, gc should not be capitalized :)

alexnask commented 10 years ago

Ah, didn't test the code but I was pretty sure we had a GC version, apparently I was wrong :)

davidhesselbom commented 10 years ago

@shamanas I would advise against the printouts (printfln mallocs 700 bytes every time), but thanks for the suggestion! :)

alexnask commented 10 years ago

Yes, of course the print outs are just for debugging/example purposes, this wouldn't make production code :)

alexnask commented 10 years ago

@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

alexnask commented 10 years ago

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?

fasterthanlime commented 10 years ago
// ArrayList: class <T>
extend ArrayList<K> {
    noop: func {
        T name println() // shouldn't work - there's no such type parameter in this type extension
    }
}
alexnask commented 10 years ago

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 :/

fasterthanlime commented 10 years ago

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.

alexnask commented 10 years ago

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?

fasterthanlime commented 10 years ago

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.

alexnask commented 10 years ago

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)

alexnask commented 10 years ago

That is pretty much the same thing as modifying the VariableDecl names though, albeit a bit more organized.

fasterthanlime commented 10 years ago

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.

alexnask commented 10 years ago

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

marcusnaslund commented 8 years ago

Was this solved by #965 or is there more to do here?

alexnask commented 8 years ago

@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.

marcusnaslund commented 8 years ago

Awesome, best of luck.

alexnask commented 8 years ago

@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.