why doesn't the compiler complain?

  • I am so confused about something in Objective-C that I thought was perfectly
    clear and that I understood perfectly well. Please give me some kind of dope
    slap to get my brain back on track.

    The Objective-C docs say:

    "Methods in different classes that have the same selector (the same name)
    must also share the same return and argument types. This constraint is
    imposed by the compiler..."

    I thought I'd test this assertion, and just the opposite seems to be true:
    the compiler isn't imposing any constraints at all. This leaves me confused
    about what the rule is.

    Here's my test:

    @interface MyClass : NSObject {
    }
    - (void) tryme: (NSString*) s;
    @end

    @implementation MyClass
    - (void) tryme: (NSString*) s {
        ;
    }
    @end

    @interface MyClass2 : NSObject {
    }
    - (void) tryme: (NSArray*) s;
    @end

    @implementation MyClass2
    - (void) tryme: (NSArray*) s {
        ;
    }
    @end

    Those are methods in different classes with the same name but different
    argument types, right? Yet that code compiles just fine. What happened to
    the constraint imposed by the compiler?

    So now let's try actually calling tryme (in yet another class):

        MyClass* thing = [[MyClass alloc] init];
        NSString* s = @"Howdy";
        [(id)thing tryme: s];

    This, too, compiles with no problem. Why is the compiler not complaining
    that tryme: is ambiguous? Isn't that what it's supposed to do? (That is why
    I cast to an id, so that the compiler wouldn't be able to resolve tryme:.)

    I must be missing something unbelievably fundamental. What is it? Thx -

    m.

    --
    matt neuburg, phd = <matt...>, <http://www.tidbits.com/matt/>
    A fool + a tool + an autorelease pool = cool!
    AppleScript: the Definitive Guide - Second Edition!
    http://www.tidbits.com/matt/default.html#applescriptthings
  • On Apr 26, 2010, at 1:01 PM, Matt Neuburg wrote:

    > "Methods in different classes that have the same selector (the same
    > name)
    > must also share the same return and argument types. This constraint is
    > imposed by the compiler..."

    It's more like "should", and the reason is because of the ambiguity of
    sending the message when the receiver type isn't known. In my
    experience you just get a warning when the parser has seen two
    incompatible declarations of the same selector and tries to resolve
    its use in a message to 'id'.

    > MyClass* thing = [[MyClass alloc] init];
    > NSString* s = @"Howdy";
    > [(id)thing tryme: s];
    >
    > This, too, compiles with no problem. Why is the compiler not
    > complaining
    > that tryme: is ambiguous? Isn't that what it's supposed to do? (That
    > is why
    > I cast to an id, so that the compiler wouldn't be able to resolve
    > tryme:.)

    I would also expect a warning there. Had the parser seen both MyClass
    and MyClass2's interfaces by that point, i.e. did you #import both of
    their headers?

    —Jens
  • On Apr 26, 2010, at 1:01 PM, Matt Neuburg wrote:
    > I am so confused about something in Objective-C that I thought was perfectly
    > clear and that I understood perfectly well. Please give me some kind of dope
    > slap to get my brain back on track.
    >
    > The Objective-C docs say:
    >
    > "Methods in different classes that have the same selector (the same name)
    > must also share the same return and argument types. This constraint is
    > imposed by the compiler..."

    Note "imposed" by the compiler, not "enforced".

    The restriction exists because different parameter types can be passed in different ways at the machine code level. If the call site uses one set of parameter types and the implementation uses another set of types, then you may crash or get mangled data.

    On the other hand, sometimes you get mismatched types that are not-unsafe or even desirable, thanks to Objective-C's duck-typing ability. So "must" in the docs is merely "should", if you're careful.

    > I thought I'd test this assertion, and just the opposite seems to be true:
    > the compiler isn't imposing any constraints at all. This leaves me confused
    > about what the rule is.
    >
    > Here's my test:
    >
    > - (void) tryme: (NSString*) s;
    [...]
    > - (void) tryme: (NSArray*) s;
    >
    > Those are methods in different classes with the same name but different
    > argument types, right? Yet that code compiles just fine. What happened to
    > the constraint imposed by the compiler?
    >
    > So now let's try actually calling tryme (in yet another class):
    >
    > MyClass* thing = [[MyClass alloc] init];
    > NSString* s = @"Howdy";
    > [(id)thing tryme: s];
    >
    > This, too, compiles with no problem. Why is the compiler not complaining
    > that tryme: is ambiguous? Isn't that what it's supposed to do? (That is why
    > I cast to an id, so that the compiler wouldn't be able to resolve tryme:.)

    In this test, the mismatch is an NSString* parameter vs an NSArray* parameter. That mismatch is "safe": the compiled code for the call site looks the same either way. The compiler does not warn about this by default.

    If you turn on -Wstrict-selector-match for this test, you do get a warning:

        % cc -c test.m
        (no warning)
        % cc -c test.m -Wstrict-selector-match
        test.m: In function ‘main’:
        test.m:30: warning: multiple methods named ‘-tryme:’ found
        test.m:5: warning: using ‘-(void)tryme:(NSString *)s’
        test.m:16: warning: also found ‘-(void)tryme:(NSArray *)s’

    The compiler warns more aggressively for mismatches that do affect the call site's code. Those are more likely to be actual bugs, rather than correct but loosely-typed code. For example, if you change your example's parameter types to NSString* vs int, you'll get the warning even without any extra warning flags.

        % cc -c test.m
        test.m: In function ‘main’:
        test.m:30: warning: multiple methods named ‘-tryme:’ found
        test.m:5: warning: using ‘-(void)tryme:(NSString *)s’
        test.m:16: warning: also found ‘-(void)tryme:(int)s’

    --
    Greg Parker      <gparker...>    Runtime Wrangler
  • On or about 4/26/10 1:22 PM, thus spake "Greg Parker" <gparker...>:

    > On Apr 26, 2010, at 1:01 PM, Matt Neuburg wrote:
    >> Here's my test:
    >> MyClass:
    >> - (void) tryme: (NSString*) s;
    >> MyClass2:
    >> - (void) tryme: (NSArray*) s;
    >> MyClass* thing = [[MyClass alloc] init];
    >> NSString* s = @"Howdy";
    >> [(id)thing tryme: s];
    >>
    > In this test, the mismatch is an NSString* parameter vs an NSArray* parameter.
    > That mismatch is "safe": the compiled code for the call site looks the same
    > either way. The compiler does not warn about this by default.

    Thanks for this extensive reply; it taught me a lot.

    Here's something interesting. If I reverse the declaration order of MyClass
    and MyClass2, like this:

    >> MyClass2:
    >> - (void) tryme: (NSArray*) s;
    >> MyClass:
    >> - (void) tryme: (NSString*) s;
    >> MyClass* thing = [[MyClass alloc] init];
    >> NSString* s = @"Howdy";
    >> [(id)thing tryme: s];

    ...Now the compiler *does* complain about that last line - it ways I'm
    passing a string when an array was expected.

    So I guess the compiler treats the *first* declaration of a method
    name-and-signature that it encounters as the "real" one.

    Now, that's okay, I guess (especially since there's a warning I can turn on
    to detect even this level of conflict - thanks for explaining about that),
    as long as the compiler is correctly foreshadowing the behavior of the
    runtime. I take it from what you say about the compiled code being the same
    that it is... m.

    --
    matt neuburg, phd = <matt...>, http://www.tidbits.com/matt/
    pantes anthropoi tou eidenai oregontai phusei
    Among the 2007 MacTech Top 25, http://tinyurl.com/2rh4pf
    AppleScript: the Definitive Guide, 2nd edition
    http://www.tidbits.com/matt/default.html#applescriptthings
    Take Control of Exploring & Customizing Snow Leopard
    http://tinyurl.com/kufyy8
    RubyFrontier! http://www.apeth.com/RubyFrontierDocs/default.html
    TidBITS, Mac news and reviews since 1990, http://www.tidbits.com
  • > So I guess the compiler treats the *first* declaration of a
    > method
    > name-and-signature that it encounters as the "real" one.
    >
    > Now, that's okay ...

    Not really.  Without the warning flag it means that the type
    checking you would hope to get from the compiler is subverted.
    In your example it might let you pass an NSString to a method
    expecting an NSArray, which is likely to have unfortunate
    results.  Other, more insidious examples, are possible,
    including cases that could lead to a memory overwrite.  I think
    the fact that the warning is off by default is probably for
    historical reasons.  I'd like to see that changed.
    Unfortunately, setting it project-wide generates a warning
    (something like 'this flag pertains only to Objective-C') for .c
    and .cpp files, which is a bit of a pain, but one can probably
    set it with a #pragma.

    Paul Sanders.
  • On or about 4/26/10 1:22 PM, thus spake "Greg Parker" <gparker...>:

    > The compiler warns more aggressively for mismatches that do affect the call
    > site's code. Those are more likely to be actual bugs, rather than correct but
    > loosely-typed code. For example, if you change your example's parameter types
    > to NSString* vs int, you'll get the warning even without any extra warning
    > flags.

    Just to clarify: you get the warning, but only when you try to *call* the
    method that has multiple definitions (and then only when using dynamic
    typing, of course).

    In other words, this code by itself does not generate any warning:

    @interface MyClass : NSObject {
    }
    - (void) tryme: (NSString*) s;
    @end

    @implementation MyClass
    - (void) tryme: (NSString*) s {
        ;
    }
    @end
    @interface MyClass2 : NSObject {
    }
    - (void) tryme: (int) s;
    @end
    @implementation MyClass2
    - (void) tryme: (int) s {
        ;
    }
    @end

    I guess I was hoping and expecting that the above would be sufficient to
    generate a warning ("hey, you've defined methods with the same name but
    different signatures; this could lead to trouble"). But the compiler is
    silent.

    I see now, however, *why* the compiler is silent: it's because it weren't,
    it would be too chatty. I turned on -Wselector and got 114 warnings... :)

    m.

    --
    matt neuburg, phd = <matt...>, http://www.tidbits.com/matt/
    pantes anthropoi tou eidenai oregontai phusei
    Among the 2007 MacTech Top 25, http://tinyurl.com/2rh4pf
    AppleScript: the Definitive Guide, 2nd edition
    http://www.tidbits.com/matt/default.html#applescriptthings
    Take Control of Exploring & Customizing Snow Leopard
    http://tinyurl.com/kufyy8
    RubyFrontier! http://www.apeth.com/RubyFrontierDocs/default.html
    TidBITS, Mac news and reviews since 1990, http://www.tidbits.com
  • Hi Matt

    > I see now, however, *why* the compiler is silent: it's because it weren't,
    > it would be too chatty. I turned on -Wselector and got 114 warnings... :)

    You too eh? Wow, was that a surprise - my tutorial project gave me 3679!!!

    Joanna

    --
    Joanna Carter
    Carter Consulting
previous month april 2010 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Go to today