[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [objc-improvements] Just remembered something about multiple method

From: Ziemowit Laski
Subject: Re: [objc-improvements] Just remembered something about multiple method signatures...
Date: Mon, 8 Sep 2003 14:28:05 -0700

On Monday, Sep 8, 2003, at 03:44 US/Pacific, David Ayers wrote:

Ziemowit Laski wrote:

On Saturday, Sep 6, 2003, at 00:54 US/Pacific, David Ayers wrote:

Class1: -(id)test1Float:(float)f int:(int)i;
Class2: -(Object *)test1Float:(float)f int:(int)i;

In the old case, you could get a warning about the compiler picking the wrong prototype but the results were correct.

In this particular case, the compiler picked a method signature out of a hat that happened to work in that the intended codegen occurred. But just because the "results were correct" in this case does not make the compiler correct. :-( In fact, all you need to do here is add

  Class3: -(void)test1Float:(float)f int:(int)i;

to see just how "incorrect" things can get. :-)

:-) but this protoype is not just a conflicting prototype, it's an outright incompatible prototype, and as I believe Steve Naroff already suggested, it may be wiser to emit a hard error when prototypes are not just conflicting but really incompatible rather than second guessing any fallback prototype.

So now we need compiler logic to distinguish "conflicting" prototypes from "incompatible" ones. Are you offering to write it (and explain it to end-users)? :-)

But the new IMP promotion for id typed receivers will promote them to doubles and clobber not only of the float but all following arguments as well

I absolutely agree with you in that using the IMP prototype can also result in "bad" (i.e., unintended) codegen.

The 'IMP' assumption *will* result in "bad" code generation as soon as either return type or arguments contain floats. There is absolutly no ambiguity in that.

Don't follow you here.  Why?

If you're given a set of competing method signatures, each of them with conflicting codegen requirements, there is absolutely no way you can win here. :-( In the end, the user will simply have to type the receiver more strongly (possibly by casting it at the call site) to pick the intended signature, and I really don't see any other way out of this predicament.

Yes! So let's not assume anything in this case, let's make it a hard error. Assuming 'IMP' will to break more code than picking one did.

Personally, I'd have nothing against a hard error here.

So why did I bother with this IMP thing here?  There are two reasons:
(1) I wanted a consistent failure mode. In your test1Float:int: example above, you see how the compiler may wind up picking a different signature as more and more test1Float:int: methods are added to the mix. The signature that gets picked may happen to "work" in some cases and not work in others

Try my example with the old behavior. Change the order of the declaration. You will see that the old behavior (eventhough possibly not generating the warning) /always/ generates correct code because the test1Float:int: were 'compatible' even though they were conflicting in the exact return type.

It generates code which happens to be correct in this particular case. You must distinguish correctness from a lucky shot in the dark. :-)

(due to the float<->int promotions that both you and I discussed).

There is no 'float'<->'int' promotion. The varadic processing of 'IMP' promotes all floats to doubles. So the reciever will always get a double value instead of the expected float, yet it will not fit into the parameter. The code must break in all cases.

Of course, the IMP signature may also either work or not work :-); but at least now, it will do so consistently as new incarnations of test1Float:int: are pulled in from who knows where. So choosing IMP preserves consistency of the outcome regardless of the number/kind of conflicting signatures chosen.

No, the IMP assumption will break /all/ methods that expect float arguments, but have conflicting prototypes, even if they were compatible. There is no way for it to do the "right thing" anymore in the case of float arguments. (In fact there is no way to do the right thing for any return types that have a different size than id either.)

I'd love to have a fallback mechanism that is as simple, elegant and consistent as this.

(2) After making its method signature selection, the compiler should be able to proceed as gracefully as possible. These are, after all, warnings rather than errors. However, if a signature with return type 'void' is picked, the compiler will subsequently issue a hard error if the program attempts to use the return value of the message.

In the case of conflicting prototypes that are actually incompatible, as the 'void'/'id' return type example, the hard error is a "Good Thing"! In fact the /real/ issue is that it could potentially pick the prototype that declares the 'id' return value, but the message is actually sent to the object that doesn't return anything. That will most like result in a runtime error or undefined behavior at least. In this case the better solution would be a hard compile time error.

A milder version of this occurs when the compiler produces a string of warnings about incompatible pointer types of arguments; these warnings are purely a result of the fact that there was a conflict to begin with, and tend to confuse users (as evidenced by bug reports filed against Apple's gcc).

Please reconsider the validity of these bug reports. If the compiler cannot determine the correct prototype to generate code for, the developer must provide a meaning full cast (and potential runtime checks using isKindOfClass:/conformsToProtocol:/methodSignatureForSelector: with alternative execution paths where each path casts the reciever to the corresponding type and the compiler can then generate code for the correct prototype in the corresponding paths.

I think that your suggestion about itteratively comparing the return value and then the arguments before falling back to varadic processing might be a good solution, as long as 'compatible' return values/arguments are "merged" to a general type (such as id/class) before falling back to varadic processing. (The warnings should remain, and if you like, with a flag to turn them off :-) )

Doing this will increase the probability (vs. both the current and the IMP approaches) that the compiler will happen to do "the right thing" (codegen-wise) when encountering conflicts, although of course it won't make it a certainty -- no scheme can do that. The down side is that this scheme lacks the simple elegance (and hence end-user understandability) of the IMP approach. :-(

We are trying to find an elegant way to generate correct code for ambiguous declarations. The more I think about it, the less I like even this compromise. I'm starting to lean heavily toward emiting a hard error where prototypes are not just conflicting but also incompatible, as this means that the compiler is forced to generate bad code for some cases. And the case you qoute with conflicting return types 'id'/'void' would definitely fall into that category. This behavior should meet your requirement of having a "consistent failure mode".

The conflicting declarations ('id'/'void' return type), the IMP assumption tries to generate code for is far more serious that the conflicting but compatible declarations that were handled correctly by the previous behavior. We would be exchanging one set of errors for another. Even worse, the prototype conflicts we are breaking can have a lot more validity than the new ones we'd be allowing. (see the covariant/contravariant return type/arguments discussion on gnustep-discuss)

As I said, the primary virtue of the IMP approach is that it is a consistent failure mode. But it _is_ a failure, just as the pick-a-signature-out-of-a-hat approach was. You can and will get bad codegen with both cases at times. And just because the current approach may _happen_ to pick the desired result with a higher probability, that does _not_ make it correct! :-)

Thanks to Chris Hanson (very much appreciated the clairification) I understand that this issue wasn't as apparent on G5/Dawin as it would be on other architectures. But none the less these are serious > issues.

Yes, they are -- and neither the existing implementation nor my proposal (nor any _other_ proposal) can address it. :-(

PS: The "overkill" solution to this is:
a) to generate code for adding something like the "signatureForSelector:" test
b) generate code for all seen prototypes
c) select the code path at runtime
But I'd definitely would prefer to emit hard errors and require casting.

Yes, I'd prefer that solution also. Actually, I just conversed with Steve Naroff who suggested I investigate whether turning this into a hard error will not be too painful (i.e., if there are not too many existing selector conflicts in MacOS X sources :-) ). This would apply to the basic 'id' case, but also to cases like 'id<Foo, Bar>' (where Foo and Bar have conflicting signatures) or if a category conflicts with the main interface.

In this case, adding support for 'Class <MyProtocol>' should accompany that change in behavior.


I'm looking into this, but as I'm rather new to compiler hacking it might take a bit, before I come up with something.

If we develop consensus around the 'hard error' proposal (which I must admit I like even better than the IMP approach), I'll tackle the implementation, while you guys can come up with creative test cases. :-)

Ziemowit Laski                 1 Infinite Loop, MS 301-2K
Mac OS X Compiler Group        Cupertino, CA USA  95014-2083
Apple Computer, Inc.           +1.408.974.6229  Fax .5477

reply via email to

[Prev in Thread] Current Thread [Next in Thread]