bmx-ng / bcc

A next-generation bcc parser for BlitzMax
zlib License
33 stars 12 forks source link

Overriding "no return value"-methods within modules #55

Closed GWRon closed 8 years ago

GWRon commented 9 years ago

I just tried to compile maxmod again:

./bmk makemods -r -a maxmod2
Compiling:maxmod2.bmx
Compile Error: Overriding method does not match any overridden method.
[/BlitzMaxNG/mod/maxmod2.mod/maxmod2.mod/maxmod2.bmx;340;0]

The line is the following "Stop()" method:

Type TMaxModChannel Extends TChannel
...
    Method Stop()
        _channel.Stop()
    End Method

interestingly "TChannel" defines that method exactly the same (no explicit return value).

Changing it to "Stop:int()" in TChannel AND TMaxModChannel removes the error message (and goes on to the next :p).

So this means, bcc-ng currently seem to have problems with "void" methods in modules. Doing the same in a single file works as expected, same for normal "file imports" (import "bla.bmx").

woollybah commented 9 years ago

If you are interfacing with modules defined as Strict, your extending types will also need to be Strict, and not SuperStrict. Strict assumes undefined return types as Int.

GWRon commented 9 years ago

Maybe modules should get SuperStrict as much as possible - as this sounds as "superstrict + strict" is possible, while "strict + superstrict" isnt.

Thanks anyways

GWRon commented 9 years ago

Shouldnt this be possible:

Base: Strict Extended: SuperStrict

Base: Method Stop()

Extended Method Stop:int()

Why? Because you said in "strict" void returns default to ":int". If you blindly assume that for void returns, then the superstrict "Stop:int()" should be perfectly well defined in that case.

When doing this that way, we should get rid of many "overrridden method does not match" codes regarding old code flying around.

Are there disadvantages of this approach?

GWRon commented 9 years ago

Suggestion

What do you think about adding some kind of "strictmode"-property to function declaration?

All original classes get the property "strict:int = 0 | 1 | 2" based on the strictmode of their source file. If they extend a class, they do set the strict-property to the value of their parent (or just return the value of the original one).

Example

If the base file is "non"-strict, all declared classes there (if initial, not extending) get the property "strict:int = 0". Now, if you extend the classes in another file (this time strict), the validity checks use the property given in the initial class.

This gets rid of things like "Strict baseclass: Func()" and "SuperStrict extendedClass: Func:Int()".

woollybah commented 8 years ago

I don't think it is a good idea to "downgrade" a superstrict method to match the return type of its strict overridden parent.

However, I am not against "updgrading" a strict method to match the void return type of its superstrict overridden parent...

GWRon commented 8 years ago

upgrading

Isn't this the normal expected behaviour? I mean: parent is superstrict and expects ":void". As I am not sure if I got you correct: If a superstrict parent defines ":void" and a child defines ":int" you "upgrade" that child-definition to be ":void" ? How do you handle "return True" and "return False" then (which should be able for valid "nothing = :int"-constructions).

woollybah commented 8 years ago

Why on earth would you override a SuperStrict Void method with a Strict method and expect to be able to return an Int from it?

GWRon commented 8 years ago

The superstrict base module/source might not care for the result of a specific function and therefore do not return anything (aka "void").

Now I extend from this type and now I am interested in the result of the overridden function - so I rely on "nothing" becomes ":int" and do a return "myInterestedValue".

In short: a former "fire and forget" function call becomes (in the extended variant) something which enables checks for specific result values.

I know that this could get overcome with "GetLastResultID()" or similar approaches. Also do not get me wrong - I personally would adjust the base type to return ":int" in that case but I just thought about potential usage forms. If possible I would also see everything being "superstrict" by default - but as this needs a big overhaul on many modules, this is not an option.

GWRon commented 8 years ago

Again: I think I did not understand your

However, I am not against "updgrading" a strict method to match the void return type of its superstrict overridden parent...

So did you mean: base: function bla() extended: function bla:int() (or function bla() but in a not-superstrict-context aka "implicit-:int") And the extended function now becomes bla() automatically? If so, how do you then inform about "return True" not being possible in the extended bla:int()-function. For the average-joe-user this isn't self-explanatory then (he is reading ":int" - or assumes so because of "strict" - but is not alllowed to return an int...)

If you meant something different, please explain it to me in other words.

woollybah commented 8 years ago

Do you expect this to work?

SuperStrict
Framework brl.standardio

Type TBase
    Method getNothing()
    End Method
End Type

Type TImpl Extends TBase
    Method getNothing:Int()
    End Method
End Type
GWRon commented 8 years ago

in "vanilla" I would expect this to work, yes.

SuperStrict
Framework brl.standardio

Type TBase
    Method getNothing()
    End Method
End Type

Type TImpl Extends TBase
    Method getNothing:Int()
        return True
    End Method
End Type

global t:TImpl = new TImpl
if t.getNothing() then print "returned True"

output

returned True

As NG now supports void this becomes problematic. How would one use the ":void" return for something useful? Is there a reason to have ":void" when running a function-result-comparison ? I mean: ":void" can only be void - nothing else? So would it matter if a comparison of if not func() then ... assumes an integer of value 0 then? If it does not matter, then we could already do as if there was an :int instead of ":void".

woollybah commented 8 years ago

Right, because TBase getNothing() is really getNothing:Int() - since SuperStrict isn't really super-strict.

But now we are, so the rules are tightened up. If you were coding in Java, for example, the above example would not compile either, because the return type of the overridden method should match.

GWRon commented 8 years ago

I understood this part already: in a perfect scenario the extended function return type should be the same (or extending from the previous return type).

BUT ... I tried to suggest something which enables some "automagic" for strict source files extending superstrict-classes.

So:

superstrict.bmx

Superstrict
Type TSuperStrict
   Method a()
     return
   End Method
End Type

strict.bmx

Strict
Import "superstrict.bmx"

Type TStrict extends TSuperStrict
   Method a:int()
     return True
   End Method
End Type

local mySuperstrict:TSuperStrict = new TSuperStrict
local myStrict:TStrict = new TStrict

For the compiler "TSuperstrict.a" could be still a "void" thing... but once the user does a comparison (if mySuperstrict.a() then print "I run that method successfully") the compiler should allow that. So implicitely it converts that void to "0" then. So why should one call if mySuperStrict.a() then ? Because sometimes you do not know the exact type of an instance (there might be multiple types extending from TSuperStrict but you only store them in an array TSuperStrict[]).

It is more than just "hacky" because for other types the user expectations differ: :string in base class versus :int in extended class. The only difference here is, that vanilla does indeed ignore "void" and implicitely assumes "int = 0" then.

I wont worry keeping it the way it is for now: void <> int, no further discussion ;-) It is just questionable whether NG wants to try to stay compatible to vanilla in this special area.

woollybah commented 8 years ago

Your logic makes no sense to me :-)

GWRon commented 8 years ago

Seems we both have something in common then :-)

Dunno how to explain it understandable:

To allow this:

SuperStrict
Framework brl.standardio

Type TBase
    Method getNothing()
    End Method
End Type

Type TImpl Extends TBase
    Method getNothing:Int()
        return True
    End Method
End Type

global b:TBase = new TBase
global i:TImpl = new TImpl
print "b.getNothing() = " + b.getNothing()
print "i.getNothing() = " + i.getNothing()

outputting

b.getNothing() = 0
i.getNothing() = 1

NG needs to implicitely convert "void" to "0".

Question: Why does NG not allow overriding "void" with "int", isn't "int" some kind of child of "void" (which allows more than "0" as value)? Overriding Method get:TBaseClass() with Method get:TExtendedFromBaseClass() is possible too? Is it forbidden because of some C-code-interaction (when playing with external c-code from BlitzMax) ?

Edit: of course one could mention, that "0" <> "void" ... but I just try to make it work similar to vanilla while keeping the benefits you saw when adding void...

woollybah commented 8 years ago

Yes. And because Void should actually be void. Therefore, if you want a less strict programming style, use Strict. If you want to use SuperStrict, you need to live within its rules.

GWRon commented 8 years ago

You are right, but I was thinking of all those "beginners" who use a superstrict module / 3rd party code within their own strict-code.

BTW: feel free to close this issue (as I agree with having "void<>int")