From: duerst via ruby-core <ruby-core@...>
Date: 2023-12-09T01:03:57+00:00
Subject: [ruby-core:115668] [Ruby master Bug#19392] Endless method and parsing priorities

Issue #19392 has been updated by duerst (Martin D�rst).





> Because `a = b and c` is interpreted as `(a = b) and c`, it is natural that `def a = b and c` is interpreted as `(def a = b) and c`



I agree with others that there's not much (if anything) natural about this. In `def a = b and c`, the most important thing that virtually every Ruby programmer will see is `def`.



To formalize this, we can think about a special `def=` operator. This `def=` operator has lower precedence than `and` or `if` or `unless`,... As a result, `def a = b and c` is interpreted as `def a = (b and c)`, which is easier to understand and more useful (because it is the more frequent usecase).



Parsing this so that `def=` has lower precedence may not exactly be easy. But the Ruby parser goes through many contortions to make Ruby a language useful for human programmers, with very little concern for 'parsing difficulty'.[1] It would be weird if we gave up in this case because of 'parser difficulty'. `def=` has the huge advantage that `def` is prefix, which is much easier to handle in parsers that an infix or postfix operator.



[1] The opposite of this is Pascal and other languages created by Niklaus Wirth. Wirths main goal when designing a language was to make the compiler easy to implement.



----------------------------------------

Bug #19392: Endless method and parsing priorities

https://bugs.ruby-lang.org/issues/19392#change-105604



* Author: zverok (Victor Shepelev)

* Status: Closed

* Priority: Normal

* Backport: 2.7: DONTNEED, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN

----------------------------------------

**Initial description**



[Discovered](https://twitter.com/lucianghinda/status/1617783952353406977) by Lucian Ghinda: 



```ruby

def test = puts("foo") and puts("bar")

# prints "bar" immediately

test

# prints "foo"

```



It seems that it is a parser error, right?..

```ruby

RubyVM::AbstractSyntaxTree.parse('def test = puts("foo") and puts("bar")')

#  => 

# (SCOPE@1:0-1:38                                                         

#  tbl: []                                                                

#  args: nil                                                              

#  body:                                                                  

#    (AND@1:0-1:38                                                        

#       (DEFN@1:0-1:22                                                    

#        mid: :test                                                       

#        body:                                                            

#          (SCOPE@1:0-1:22                                                

#           tbl: []                                                       

#           args:                                                         

#             (ARGS@1:0-1:8 pre_num: 0 pre_init: nil opt: nil first_post: nil post_num: 0 post_init: nil rest: nil kw: nil kwrest: nil block: nil)

#           body: (FCALL@1:11-1:22 :puts (LIST@1:16-1:21 (STR@1:16-1:21 "foo") nil))))

#       (FCALL@1:27-1:38 :puts (LIST@1:32-1:37 (STR@1:32-1:37 "bar") nil)))) 

```



E.g. it is parsed as 

```ruby

(def test = puts("foo")) and (puts("bar"))

```

...which is hardly intentional or have any practical use. The rightly parsed code in this case _can_ have practical use, like

```ruby

def write(data) = File.write(@filename, data) == data.size or raise "Something went wrong"

```



**Additional cases of what seems to be the same problem**



```ruby

def save = File.write(name, self.to_yaml) unless invalid?

# Parsed as:

(def save = File.write(name, self.to_yaml)) unless invalid?

```

...which makes it very hard for the users to diagnose the real reason, see #19731



```ruby

def initialize(a, b) = @a, b = a, b

# syntax error, unexpected ',', expecting end-of-input (SyntaxError)                                                 

# def initialize(a, b) = @a, b = a, b                            

#                          ^         



# Again, parsed as

(def initialize(a, b) = @a), b = a, b

```

While this one is at least diagnosed early, in pathological cases, it might lead to very subtle bugs:

```ruby

private def start = @operation, @conversion = :print, :to_s

```

This code doesn't throw a syntax error, but its effect is very far from expected. Again, it is parsed as

```ruby

private( (def start = @operation), @conversion = :print, :to_s )

```

...and ends up in:

* defining a private method `start`

* making private methods `:print` and `:to_s`









-- 

https://bugs.ruby-lang.org/

 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/