Skip to content

Incorrectly flattening case statement with in in the in #3710

@jandudulski

Description

@jandudulski

I have such gist:

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "prism"
end

args = [:increased]
context = Data.define(:event).new(:decreased)

result =
  case args
  in [event]
    context.event in ^event
  end

puts result

source = <<~RUBY
  case args
  in [event]
    context.event in ^event
  end
RUBY

puts Prism.parse(source).inspect

Ruby prior to 3.4.6 returns false for a result. 3.4.6 and 3.4.7 returns :decreased. It looks like in Ruby 3.4 the inner in is flattened and treated as in in the case statement, see:

Ruby 3.2.2:

#<Prism::ParseResult:0x00007f5c3bd1b868 @value=@ ProgramNode (location: (1,0)-(4,3))
├── flags: ∅
├── locals: [:event]
└── statements:
    @ StatementsNode (location: (1,0)-(4,3))
    ├── flags: ∅
    └── body: (length: 1)
        └── @ CaseMatchNode (location: (1,0)-(4,3))
            ├── flags: newline
            ├── predicate:
            │   @ CallNode (location: (1,5)-(1,9))
            │   ├── flags: variable_call, ignore_visibility
            │   ├── receiver: ∅
            │   ├── call_operator_loc: ∅
            │   ├── name: :args
            │   ├── message_loc: (1,5)-(1,9) = "args"
            │   ├── opening_loc: ∅
            │   ├── arguments: ∅
            │   ├── closing_loc: ∅
            │   └── block: ∅
            ├── conditions: (length: 1)
            │   └── @ InNode (location: (2,0)-(3,25))
            │       ├── flags: ∅
            │       ├── pattern:
            │       │   @ ArrayPatternNode (location: (2,3)-(2,10))
            │       │   ├── flags: ∅
            │       │   ├── constant: ∅
            │       │   ├── requireds: (length: 1)
            │       │   │   └── @ LocalVariableTargetNode (location: (2,4)-(2,9))
            │       │   │       ├── flags: ∅
            │       │   │       ├── name: :event
            │       │   │       └── depth: 0
            │       │   ├── rest: ∅
            │       │   ├── posts: (length: 0)
            │       │   ├── opening_loc: (2,3)-(2,4) = "["
            │       │   └── closing_loc: (2,9)-(2,10) = "]"
            │       ├── statements:
            │       │   @ StatementsNode (location: (3,2)-(3,25))
            │       │   ├── flags: ∅
            │       │   └── body: (length: 1)
            │       │       └── @ MatchPredicateNode (location: (3,2)-(3,25))
            │       │           ├── flags: newline
            │       │           ├── value:
            │       │           │   @ CallNode (location: (3,2)-(3,15))
            │       │           │   ├── flags: ∅
            │       │           │   ├── receiver:
            │       │           │   │   @ CallNode (location: (3,2)-(3,9))
            │       │           │   │   ├── flags: variable_call, ignore_visibility
            │       │           │   │   ├── receiver: ∅
            │       │           │   │   ├── call_operator_loc: ∅
            │       │           │   │   ├── name: :context
            │       │           │   │   ├── message_loc: (3,2)-(3,9) = "context"
            │       │           │   │   ├── opening_loc: ∅
            │       │           │   │   ├── arguments: ∅
            │       │           │   │   ├── closing_loc: ∅
            │       │           │   │   └── block: ∅
            │       │           │   ├── call_operator_loc: (3,9)-(3,10) = "."
            │       │           │   ├── name: :event
            │       │           │   ├── message_loc: (3,10)-(3,15) = "event"
            │       │           │   ├── opening_loc: ∅
            │       │           │   ├── arguments: ∅
            │       │           │   ├── closing_loc: ∅
            │       │           │   └── block: ∅
            │       │           ├── pattern:
            │       │           │   @ PinnedVariableNode (location: (3,19)-(3,25))
            │       │           │   ├── flags: ∅
            │       │           │   ├── variable:
            │       │           │   │   @ LocalVariableReadNode (location: (3,20)-(3,25))
            │       │           │   │   ├── flags: ∅
            │       │           │   │   ├── name: :event
            │       │           │   │   └── depth: 0
            │       │           │   └── operator_loc: (3,19)-(3,20) = "^"
            │       │           └── operator_loc: (3,16)-(3,18) = "in"
            │       ├── in_loc: (2,0)-(2,2) = "in"
            │       └── then_loc: ∅
            ├── else_clause: ∅
            ├── case_keyword_loc: (1,0)-(1,4) = "case"
            └── end_keyword_loc: (4,0)-(4,3) = "end"
, @comments=[], @magic_comments=[], @data_loc=nil, @errors=[], @warnings=[], @source=#<Prism::ASCIISource:0x00007f5c3bcbd178 @source="case args\nin [event]\n  context.event in ^event\nend\n", @start_line=1, @offsets=[0, 10, 21, 47, 51]>>

Ruby 3.4.7:

#<Prism::ParseResult:0x00007f05afebc768 @value=@ ProgramNode (location: (1,0)-(4,3))
├── flags: ∅
├── locals: [:event]
└── statements:
    @ StatementsNode (location: (1,0)-(4,3))
    ├── flags: ∅
    └── body: (length: 1)
        └── @ CaseMatchNode (location: (1,0)-(4,3))
            ├── flags: newline
            ├── predicate:
            │   @ CallNode (location: (1,5)-(1,9))
            │   ├── flags: variable_call, ignore_visibility
            │   ├── receiver: ∅
            │   ├── call_operator_loc: ∅
            │   ├── name: :args
            │   ├── message_loc: (1,5)-(1,9) = "args"
            │   ├── opening_loc: ∅
            │   ├── arguments: ∅
            │   ├── closing_loc: ∅
            │   └── block: ∅
            ├── conditions: (length: 2)
            │   ├── @ InNode (location: (2,0)-(3,15))
            │   │   ├── flags: ∅
            │   │   ├── pattern:
            │   │   │   @ ArrayPatternNode (location: (2,3)-(2,10))
            │   │   │   ├── flags: ∅
            │   │   │   ├── constant: ∅
            │   │   │   ├── requireds: (length: 1)
            │   │   │   │   └── @ LocalVariableTargetNode (location: (2,4)-(2,9))
            │   │   │   │       ├── flags: ∅
            │   │   │   │       ├── name: :event
            │   │   │   │       └── depth: 0
            │   │   │   ├── rest: ∅
            │   │   │   ├── posts: (length: 0)
            │   │   │   ├── opening_loc: (2,3)-(2,4) = "["
            │   │   │   └── closing_loc: (2,9)-(2,10) = "]"
            │   │   ├── statements:
            │   │   │   @ StatementsNode (location: (3,2)-(3,15))
            │   │   │   ├── flags: ∅
            │   │   │   └── body: (length: 1)
            │   │   │       └── @ CallNode (location: (3,2)-(3,15))
            │   │   │           ├── flags: newline
            │   │   │           ├── receiver:
            │   │   │           │   @ CallNode (location: (3,2)-(3,9))
            │   │   │           │   ├── flags: variable_call, ignore_visibility
            │   │   │           │   ├── receiver: ∅
            │   │   │           │   ├── call_operator_loc: ∅
            │   │   │           │   ├── name: :context
            │   │   │           │   ├── message_loc: (3,2)-(3,9) = "context"
            │   │   │           │   ├── opening_loc: ∅
            │   │   │           │   ├── arguments: ∅
            │   │   │           │   ├── closing_loc: ∅
            │   │   │           │   └── block: ∅
            │   │   │           ├── call_operator_loc: (3,9)-(3,10) = "."
            │   │   │           ├── name: :event
            │   │   │           ├── message_loc: (3,10)-(3,15) = "event"
            │   │   │           ├── opening_loc: ∅
            │   │   │           ├── arguments: ∅
            │   │   │           ├── closing_loc: ∅
            │   │   │           └── block: ∅
            │   │   ├── in_loc: (2,0)-(2,2) = "in"
            │   │   └── then_loc: ∅
            │   └── @ InNode (location: (3,16)-(3,25))
            │       ├── flags: ∅
            │       ├── pattern:
            │       │   @ PinnedVariableNode (location: (3,19)-(3,25))
            │       │   ├── flags: ∅
            │       │   ├── variable:
            │       │   │   @ LocalVariableReadNode (location: (3,20)-(3,25))
            │       │   │   ├── flags: ∅
            │       │   │   ├── name: :event
            │       │   │   └── depth: 0
            │       │   └── operator_loc: (3,19)-(3,20) = "^"
            │       ├── statements: ∅
            │       ├── in_loc: (3,16)-(3,18) = "in"
            │       └── then_loc: ∅
            ├── else_clause: ∅
            ├── case_keyword_loc: (1,0)-(1,4) = "case"
            └── end_keyword_loc: (4,0)-(4,3) = "end"
, @comments=[], @magic_comments=[], @data_loc=nil, @errors=[], @warnings=[], @source=#<Prism::ASCIISource:0x00007f05b002ceb8 @source="case args\nin [event]\n  context.event in ^event\nend\n", @start_line=1, @offsets=[0, 10, 21, 47, 51]>>

I also tested on 3.4.5 which has the same tree but returns false as expected.

Filled a bug in upstream, not sure where it belongs https://bugs.ruby-lang.org/issues/21674

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions