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) about 18 hours ago. Updated about 6 hours 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) about 16 hours 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) about 6 hours 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.

Actions

Also available in: PDF Atom