Search code examples
rubybitwise-operatorsfixnum

TypeError: wrong argument type with Ruby &~ operator


I try to compare flags in my Ruby application.

I have this code :

if self.flag &~ flag == self.flag
        return false

But it won't run. I've narrowed the problem to this :

irb(main):020:0> my_user.flag
=> 1
irb(main):021:0> flag
=> 128
irb(main):022:0> my_user.flag.class
=> Fixnum
irb(main):023:0> flag.class
=> Fixnum
irb(main):024:0> my_user.flag &~ flag
TypeError: wrong argument type Fixnum (expected Proc)

That is really disturbing since it works like this :

irb(main):025:0> 1 &~ 128
=> 1

Solution

  • The difference between 1 &~ 128 and my_user.flag &~ flag is that the second expression involves a dot-method call. That changes the way the subsequent tokens are interpreted.

    Try this:

    # works
    my_user.flag() &~ flag
    
    # also works
    (my_user.flag) &~ flag
    
    # best
    my_user.flag & ~flag
    

    You'll find that it works. This is because adding () or moving ~ changes the order of operations to be more in line with what you expected.

    The original method call you're using is actually interpreted by Ruby as:

    # bad
    my_user.flag(&(~flag))
    

    This order of operations first flips the bits in flag by applying the ~ operator, then attempts to call to_proc on the resulting Fixnum because of the application of the & (cast-as-block) operator, and then finally attempts (had that not thrown a TypeError) to pass it as a block argument to the User#flag method.