Project

General

Profile

Actions

Bug #21984

open

RubyVM::AST can't tell if an array is passed to break/next/return

Bug #21984: RubyVM::AST can't tell if an array is passed to break/next/return

Added by kddnewton (Kevin Newton) 7 days ago. Updated 5 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:125208]

Description

irb(main):005> RubyVM::AbstractSyntaxTree.parse("tap { break [1, 2, 3] }")
=> (SCOPE@1:0-1:23 tbl: [] args: nil body: (ITER@1:0-1:23 (FCALL@1:0-1:3 :tap nil) (SCOPE@1:4-1:23 tbl: [] args: nil body: (BREAK@1:6-1:21 (LIST@1:12-1:21 (INTEGER@1:13-1:14 1) (INTEGER@1:16-1:17 2) (INTEGER@1:19-1:20 3) nil)))))
irb(main):006> RubyVM::AbstractSyntaxTree.parse("tap { break 1, 2, 3 }")
=> (SCOPE@1:0-1:21 tbl: [] args: nil body: (ITER@1:0-1:21 (FCALL@1:0-1:3 :tap nil) (SCOPE@1:4-1:21 tbl: [] args: nil body: (BREAK@1:6-1:19 (LIST@1:12-1:19 (INTEGER@1:12-1:13 1) (INTEGER@1:15-1:16 2) (INTEGER@1:18-1:19 3) nil)))))
irb(main):007> 

The issue is, break [1, 2, 3] has the same AST as break 1, 2, 3 (presumably because they are effectively the same thing). But a consumer has no way to know if an array was used short of re-parsing the source.

Updated by nobu (Nobuyoshi Nakada) 7 days ago ยท Edited Actions #1 [ruby-core:125210]

Changing this causes the rbs tests to fail.

https://github.com/ruby/ruby/actions/runs/24117591586/job/70364654030?pr=16678#step:14:828

  class Hello
-   def with_return: () -> (1 | "2" | :x)
?                              ^
+   def with_return: () -> (::Array[1] | ::Array["2"] | :x)
?                           ++++++++ +  ^^^^^^^^^   +
  
-   def with_untyped_return: () -> (untyped | :x)
+   def with_untyped_return: () -> (::Array[untyped] | :x)
?                                   ++++++++       +
  
-   def with_return_same_types: () -> 1
+   def with_return_same_types: () -> (::Array[1] | 1)
?                                     +++++++++ ++++++
  
    def with_return_without_value: () -> (nil | 42)
  
    def when_last_is_nil: () -> nil
  end

https://github.com/ruby/ruby/actions/runs/24117591586/job/70364654030?pr=16678#step:14:920

  class ReturnTypeWithIF
    def with_if: () -> (true | nil)
  
-   def with_if_and_block: () -> (false | true | nil)
+   def with_if_and_block: () -> (::Array[false] | true | nil)
?                                 ++++++++     +
  
    def with_else_and_bool: () -> (true | false)
  
    def with_else_and_elsif_and_bool_nil: () -> (nil | true | false)
  
    def with_nested_if: () -> (1 | 2 | 3 | 4)
  
    def with_unless: () -> (:sym | nil)
  end

Updated by kddnewton (Kevin Newton) 6 days ago Actions #2 [ruby-core:125212]

I don't think that fix works:

$ ./ruby -e 'pp RubyVM::AbstractSyntaxTree.parse("tap { break 1, 2 }")'
(SCOPE@1:0-1:18 tbl: [] args: nil body: (ITER@1:0-1:18 (FCALL@1:0-1:3 :tap nil) (SCOPE@1:4-1:18 tbl: [] args: nil body: (BREAK@1:6-1:16 (LIST@1:12-1:16 (INTEGER@1:12-1:13 1) (INTEGER@1:15-1:16 2) nil)))))
$ ./ruby -e 'pp RubyVM::AbstractSyntaxTree.parse("tap { break [1, 2] }")'
(SCOPE@1:0-1:20 tbl: [] args: nil body: (ITER@1:0-1:20 (FCALL@1:0-1:3 :tap nil) (SCOPE@1:4-1:20 tbl: [] args: nil body: (BREAK@1:6-1:18 (LIST@1:12-1:18 (LIST@1:12-1:18 (INTEGER@1:13-1:14 1) (INTEGER@1:16-1:17 2) nil) nil)))))
[10:15:45] kddnewton ~/Code/ruby/ruby @ast-ret_args $

That makes it looks like it's a nested array.

$ ./ruby -e 'pp RubyVM::AbstractSyntaxTree.parse("tap { break 1 }")'     
(SCOPE@1:0-1:15 tbl: [] args: nil body: (ITER@1:0-1:15 (FCALL@1:0-1:3 :tap nil) (SCOPE@1:4-1:15 tbl: [] args: nil body: (BREAK@1:6-1:13 (LIST@1:12-1:13 (INTEGER@1:12-1:13 1) nil)))))

Also, now when you have a single argument it looks like an array, which is probably what is breaking RBS.

Updated by tompng (tomoya ishida) 5 days ago Actions #3 [ruby-core:125219]

The same thing can be also said to a=[1,2] and a=1,2.
In Prism, these are both parsed as ArrayNode with/without opening and closing.
In AbstractSyntaxTree, the non-abstract parts (bracket) are dropped. I think it's just a restriction of abstract trees. Is this a problem to be fixed?

Updated by kddnewton (Kevin Newton) 5 days ago Actions #4 [ruby-core:125221]

The honest answer is, not really. It's probably fine to stay as is, and I would be okay with closing this.

For context, I've been building a translation layer from RubyVM::AbstractSyntaxTree to Prism, hoping it might help with a migration process. I'm running into problems like this one, #21986, #21985, #21983, #21980, etc when we don't have enough information to be able to reliably reproduce the source.

Actions

Also available in: PDF Atom