Skip to content

Commit 3d0f7c2

Browse files
authored
Merge pull request #395 from viralpraxis/fix-array-pattern-unparsing
Fix array pattern unparsing
2 parents ecf6158 + bb8478c commit 3d0f7c2

File tree

9 files changed

+216
-3
lines changed

9 files changed

+216
-3
lines changed

lib/unparser.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ def self.buffer(source, identification = '(string)')
295295
require 'unparser/emitter/match_pattern'
296296
require 'unparser/emitter/match_pattern_p'
297297
require 'unparser/writer'
298+
require 'unparser/writer/array'
298299
require 'unparser/writer/binary'
299300
require 'unparser/writer/dynamic_string'
300301
require 'unparser/writer/regexp'
@@ -308,6 +309,7 @@ def self.buffer(source, identification = '(string)')
308309
require 'unparser/node_details'
309310
require 'unparser/node_details/send'
310311
require 'unparser/cli'
312+
require 'unparser/util'
311313

312314
require 'unparser/validation'
313315
# make it easy for zombie

lib/unparser/emitter/in_pattern.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def dispatch
1616

1717
ws
1818

19-
visit(target)
19+
dispatch_target(target)
2020

2121
if unless_guard
2222
ws
@@ -31,6 +31,14 @@ def dispatch
3131
nl
3232
end
3333
end
34+
35+
def dispatch_target(target)
36+
if n_array?(target)
37+
writer_with(Writer::Array, node: target).emit_compact
38+
else
39+
visit(target)
40+
end
41+
end
3442
end # InPattern
3543
end # Emitter
3644
end # Unparser

lib/unparser/emitter/match_pattern.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ class MatchPattern < self
1414
def dispatch
1515
visit(target)
1616
write(' => ')
17-
visit(pattern)
17+
18+
if n_array?(pattern)
19+
writer_with(Writer::Array, node: pattern).emit_compact
20+
else
21+
visit(pattern)
22+
end
1823
end
1924
end # MatchPattern
2025
end # Emitter

lib/unparser/emitter/match_pattern_p.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ class MatchPatternP < self
1313
def dispatch
1414
visit(target)
1515
write(' in ')
16-
visit(pattern)
16+
17+
if n_array?(pattern)
18+
writer_with(Writer::Array, node: pattern).emit_compact
19+
else
20+
visit(pattern)
21+
end
1722
end
1823
end # MatchPatternP
1924
end # Emitter

lib/unparser/util.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module Unparser
4+
# Original code before vendoring and reduction from: https://github.com/mbj/mutant/blob/main/lib/mutant/util.rb
5+
module Util
6+
# Error raised by `Util.one` if size is not exactly one
7+
SizeError = Class.new(IndexError)
8+
9+
# Return only element in array if it contains exactly one member
10+
#
11+
# @param array [Array]
12+
#
13+
# @return [Object] first entry
14+
def self.one(array)
15+
case array
16+
in [value]
17+
value
18+
else
19+
fail SizeError, "expected size to be exactly 1 but size was #{array.size}"
20+
end
21+
end
22+
end
23+
end

lib/unparser/writer/array.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
module Unparser
4+
module Writer
5+
class Array
6+
include Writer, Adamantium
7+
8+
MAP = {
9+
dsym: '%I',
10+
sym: '%i',
11+
dstr: '%W',
12+
str: '%w'
13+
}.freeze
14+
private_constant(*constants(false))
15+
16+
def emit_compact # rubocop:disable Metrics/AbcSize
17+
children_generic_type = array_elements_generic_type
18+
19+
write(MAP.fetch(children_generic_type))
20+
21+
parentheses('[', ']') do
22+
delimited(children, ' ') do |child|
23+
if n_sym?(child) || n_str?(child)
24+
write(Util.one(child.children).to_s)
25+
else
26+
write('#{')
27+
emitter(unwrap_single_begin(Util.one(child.children))).write_to_buffer
28+
write('}')
29+
end
30+
end
31+
end
32+
end
33+
34+
private
35+
36+
def array_elements_generic_type
37+
children_types = children.to_set(&:type)
38+
39+
if children_types == Set[:sym, :dsym]
40+
:dsym
41+
elsif children_types == Set[:str, :dstr]
42+
:dstr
43+
elsif children_types == Set[]
44+
:sym
45+
else
46+
Util.one(children_types.to_a)
47+
end
48+
end
49+
end # Array
50+
end # Writer
51+
end # Unparser

spec/unit/unparser/util_spec.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Unparser::Util, '.one' do
4+
let(:item) { instance_double(Object) }
5+
6+
def apply
7+
described_class.one(array)
8+
end
9+
10+
context 'when array has exactly one element' do
11+
context 'and that element is nil' do
12+
let(:array) { [nil] }
13+
14+
it 'returns nil' do
15+
expect(apply).to be(nil)
16+
end
17+
end
18+
19+
context 'and that element is false' do
20+
let(:array) { [false] }
21+
22+
it 'returns false' do
23+
expect(apply).to be(false)
24+
end
25+
end
26+
27+
context 'and that element is a regular object' do
28+
let(:array) { [item] }
29+
30+
it 'returns first element' do
31+
expect(apply).to be(item)
32+
end
33+
end
34+
end
35+
36+
context 'when array is empty' do
37+
let(:array) { [] }
38+
39+
it 'raises expected error' do
40+
expect { apply }
41+
.to raise_error(described_class::SizeError)
42+
.with_message('expected size to be exactly 1 but size was 0')
43+
end
44+
end
45+
46+
context 'when array has more than one element' do
47+
let(:array) { [1, 2] }
48+
49+
it 'raises expected error' do
50+
expect { apply }
51+
.to raise_error(described_class::SizeError)
52+
.with_message('expected size to be exactly 1 but size was 2')
53+
end
54+
end
55+
end

test/corpus/literal/pattern.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,40 @@
4343
1 => [*]
4444
1 in [*, 42, *]
4545
1 in [*, a, *foo]
46+
a => %i[a b]
47+
a => %w[a b]
48+
a => ["a", "b"]
49+
a => [:a, :b]
50+
a => [:a, "b"]
51+
a => [true, false, nil]
52+
a => {a: 1, b: 2}
53+
a in %i[a b]
54+
a in %w[a b]
55+
a in ["a", "b"]
56+
a in [:a, :b]
57+
a in [:a, "b"]
58+
a in [true, false, nil]
59+
a in {a: 1, b: 2}
60+
case foo
61+
in %i[a b]
62+
end
63+
case foo
64+
in %w[a b]
65+
end
66+
case foo
67+
in [:a, "b"]
68+
end
69+
case foo
70+
in [1, 2]
71+
end
72+
case foo
73+
in [true, false, nil]
74+
end
75+
a => %I[a b #{foo(1)}]
76+
a => %W[a b #{foo(1)}]
77+
case a
78+
in %I[#{1 + 1}]
79+
end
80+
case foo
81+
in %i[a b c $FILE]
82+
end

test/corpus/semantic/pattern.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
case foo
2+
in %s[a b]
3+
end
4+
case foo
5+
in %r[foo]
6+
end
7+
case foo
8+
in %r[/foo.+bar/]
9+
end
10+
case foo
11+
in %r:/foo.+bar/:
12+
end
13+
case foo
14+
in %x(rm -rf /)
15+
end
16+
case foo
17+
in %i[]
18+
end
19+
case foo
20+
in %w[]
21+
end
22+
case foo
23+
in %q[a b c #{foo}]
24+
end
25+
case foo
26+
in %q[a b c $FILE]
27+
end

0 commit comments

Comments
 (0)