Search code examples
rubymethodsoperator-precedence

What's the precedence of method calls with and without parentheses?


Previous answers

The answer to a similar question is wrong.

Method calls are mentionned neither in Ruby documentation nor in the community wiki.

Method call without parentheses

Higher than or

or seems to have a lower precedence than a method call without parentheses :

puts false or true

is equivalent to

( puts false ) or true

and displays false.

NOTE: I know or shouldn't be used. Still, it's a good example to show that some operators do have lower precedence than method calls.

Lower than ||

puts false || true

is equivalent to

puts (false || true)

and displays true.

Method call with parentheses

The parentheses used for method call don't seem to be grouping :

puts(false or true)
# SyntaxError: unexpected keyword_or
puts((false or true))
#=> true

Question

Where should method calls with and without parentheses be in this precedence table?

Bounty clarification

I'm looking for the exact location of method calls in the table. Preferably with examples proving it's lower than the previous one and higher than the next one.

The current answers also don't seem to mention method calls with parentheses.

Thanks in advance!


Solution

  • Prelude

    This aims to test all possible scenarios.

    Note that when saying "operator X has higher precedence than method invocation" what is meant is in arguments. Aka:

    invocation foo X bar
    

    as opposed to (call on object)

    X invocation
    

    As far as the second case is concerned, method calls always have higher precedence.


    Short answer

    It doesn't fit:

    • It causes SyntaxError in some cases
    • It has higher precedence than rescue, but lower than assignment

    Summary

    • not can't be used after method invocation regardless of brackets
    • Using brackets (()) with method invocations sometimes causes a SyntaxError. These cases are: and, or, if, unless, until, while and rescue
    • In cases when brackets don't cause an error, they don't change the precedence in any way
    • All operators, except for and, or, postfix if, unless, until, while, rescue have higher precedence than method invocation

    Lets try it:

    class Noone < BasicObject
      undef_method :!
    
      def initialize(order)
        @order = order
      end
    
      def method_missing(name, *args)
        @order << name
        self
      end
    end
    

    First unary:

    # + and - will become binary
    unary_operators = %i(! ~ not defined?)
    
    puts 'No brackets'
    unary_operators.each do |operator|
      puts operator
    
      order = []
      foo = Noone.new order
      bar = Noone.new order
      begin
        eval("foo.meta #{operator} bar")
      rescue SyntaxError => e
        puts e
      end
      p order
      puts '-----------'
    end
    
    puts 'Brackets'
    unary_operators.each do |operator|
      puts operator
    
      order = []
      foo = Noone.new order
      bar = Noone.new order
      begin
        eval("foo.meta(#{operator} bar)")
      rescue SyntaxError => e
        puts e
      end
      p order
      puts '-----------'
    end
    

    Points taken:

    • not after a method invocation is a SyntaxError
    • all unary operators have higher precedence than method invocation regardless of brackets

    Now binary:

    binary_operators = %i(
      **
      * / %
      + -
      << >>
      &
      | ^
      > >= < <=
      <=> == === =~
      .. ...
      or and
    )
    
    puts 'No brackets'
    binary_operators.each do |operator|
      order = []
      foo = Noone.new order
      bar = Noone.new order
      baz = Noone.new order
      begin
        eval("foo.meta bar #{operator} baz")
      rescue SyntaxError => e
        puts e
      end
      p order
    end
    
    puts 'Brackets'
    binary_operators.each do |operator|
      order = []
      foo = Noone.new order
      bar = Noone.new order
      baz = Noone.new order
      begin
        eval("foo.meta( bar #{operator} baz)")
      rescue SyntaxError => e
        puts e
      end
      p order
    end
    

    Points taken:

    • brackets around method invocation with and or or is a SyntaxError
    • we have to test and and or further without brackets
    • .. and ... call <=>. We have to test this further
    • we couldn't test a few other binary operators this way, namely &&, ||, ==, !=, modifier rescue, if, unless, until, while
    • other than the above mentioned, operators have higher precedence, regardless of brackets

    def yes
      puts 'yes'
      true
    end
    
    def no
      puts 'no'
      false
    end
    
    def anything(arg)
      puts 'Anything'
      arg
    end
    
    anything yes and no
    anything no or yes
    anything yes && no
    anything no || yes
    anything(yes && no)
    anything(no || yes)
    
    anything yes == no
    anything(yes == no)
    anything yes != no
    anything(yes != no)
    

    Points taken:

    • and and or have lower precedence without brackets
    • &&, ||, == and != have higher precedence regardless of brackets

    def five(*args)
      p args
      5
    end
    
    five 2..7
    five(2..7)
    five 2...7
    five(2...7)
    

    Points taken:

    • .. and ... have higher precedence regardless of brackets

    anything yes if no
    anything(yes if no)
    anything no unless yes
    anything(no unless yes)
    
    anything no until yes
    anything(no until yes)
    anything yes while no
    anything(yes while no)
    

    Points taken:

    • brackets with if, unless, until, while cause a SyntaxError
    • all of the above have lower precedence than method invocation without brackets

    def error
      puts 'Error'
      raise
    end
    
    anything error rescue yes
    anything(error rescue yes)
    

    Points taken:

    • brackets around rescue cause a SyntaxError
    • rescue has lower precedence if no brackets are present

    Ternary:

    anything yes ? no : 42
    anything(yes ? no : 42)
    

    Points taken:

    • ternary has higher precedence regardless of brackets

    Assignment (left for last as it changes yes and no):

    anything yes = no
    anything(no = five(42))
    

    Points taken:

    • Assignment has higher precedence than invocation

    Note that += and the like are just shortcuts for + and = so they exhibit the same behaviour.