ooc-lang / rock

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

The number of super constructor arguments do not need to match #974

Open marcusnaslund opened 8 years ago

marcusnaslund commented 8 years ago

Here is a simple example:

Base: abstract class {
    value: Int
    init: func (=value)
}

Derived: class extends Base {
    init: func (v: Int) {
        super()
    }
}

x := Derived new(3)
x value toString() println()

Even though Base has no zero-argument constructor, this code still compiles and runs just fine. The v passed to Derived constructor magically ends up in value.

vendethiel commented 8 years ago

Isn't super ruby-like just forwarding its arguments?

horasal commented 8 years ago

The compiler does forward all the arguments. You can find the related lines around rock/middle/FunctionCall.ooc: line 428. But I don't know what the "expected action" should be.

davidhesselbom commented 8 years ago

What if a zero-argument constructor is added in Base? Should the value still "magically" end up in x value, or will the constructor in Derived have to be modified to call super(v) instead of super() for that to happen?

horasal commented 8 years ago

If change the test code to

Base: abstract class {
    value: Int
    init: func (=value)
    init: func~nodefault ()
}

Derived: class extends Base {
    init: func (v: Int) {
        super()
    }
}

x := Derived new(3)
x value toString() println()

It wil print 0 instead of 3, because init: func has higher matching score than init: func(int).

davidhesselbom commented 8 years ago

Thanks for clearing that up - I don't recall it being possible to call super() unless there was a matching zero-argument function in the base class.

So... modifying Base by adding a zero-argument constructor will make Derived call a different function. If this were my program, I think I'd want to be explicit about which function super refers to by calling super(v) instead of super(), then, instead of relying on the arguments being forwarded and possibly calling the wrong function if the base class changes.

horasal commented 8 years ago

Yes, if you call super(v) explictly, it will finally match init: func(int), and the forwarding will not happen.

However, currently compiler will ignore the suffixes of super, so if we have

Base: abstract class {
    init: func~a(arg1: Int)
    init: func~b(arg2: Int)
}

super~b() may no result a call of init~b as both init~a and init~b have the same score.

horasal commented 8 years ago

The following patch makes super+~suffix available.

diff --git a/source/rock/middle/FunctionCall.ooc b/source/rock/middle/FunctionCall.ooc
index 1049553..4a831b2 100644
--- a/source/rock/middle/FunctionCall.ooc
+++ b/source/rock/middle/FunctionCall.ooc
@@ -417,7 +417,7 @@ FunctionCall: class extends Expression {
                 fDecl := trail get(trail find(FunctionDecl), FunctionDecl)
                 superTypeDecl := fDecl owner getSuperRef()
                 finalScore := 0
-                ref = superTypeDecl getMeta() getFunction(fDecl getName(), null, this, finalScore&)
+                ref = superTypeDecl getMeta() getFunction(fDecl getName(), this getSuffix(), this, finalScore&)
                 if(finalScore == -1) {
                     res wholeAgain(this, "something in our typedecl's functions needs resolving!")
                     return Response OK
alexnask commented 8 years ago

By the way, I would suggest using the super modifier in the function prototype if you need to call the super function anyways.

If you do need to call it conditionally, using all the arguments would obviously be better form, since the addition of a zero argument version of the function would alter behavior.

davidhesselbom commented 8 years ago

"the super modifier"?

alexnask commented 8 years ago

Yes, you can do something like:


Foo: class {
    init: func

    doThing: func (x: Int) { "Called Foo doThing with #{x}" println() }
}

Bar: class extends Foo {
    init: super func

    doThing: super func
}

It doesn't allow you to add call though, it's pretty much only useful to inherit constructors, since you are required to do it in some cases.

vendethiel commented 8 years ago

IIRC the tutorial mentions this as a "hackish workaround"?

fasterthanlime commented 8 years ago

IIRC the tutorial mentions this as a "hackish workaround"?

I don't remember exactly, but perhaps the issue with the super modifier isn't the spec but its implementation.

alexnask commented 8 years ago

The implementation looks fine (does what I expect) to me and I don't particularly recall it being considered poor style.
Anyway, the use case is rare, I just think it's more elegant to inherit a constructor that way than just using a super call in the body.