rubocop / rubocop-ast

RuboCop's AST extensions and NodePattern functionality
https://docs.rubocop.org/rubocop-ast
MIT License
112 stars 53 forks source link

Unification does not work inside any-order node #335

Open dvandersluis opened 3 weeks ago

dvandersluis commented 3 weeks ago

I'm trying to match something along the lines of:

(or <(send _receiver :bar) (send _receiver :baz)>)

However, the unification matches foo.baz || foo.bar but not foo.bar || foo.baz.

If the unification is removed:

(or <send _ :bar) (send _ :baz)>)

both forms match.

dvandersluis commented 3 weeks ago

I tried looking into this but I'm not really sure how to fix it. I wrote some test cases though:

Index: spec/rubocop/ast/node_pattern_spec.rb
<+>UTF-8
===================================================================
diff --git a/spec/rubocop/ast/node_pattern_spec.rb b/spec/rubocop/ast/node_pattern_spec.rb
--- a/spec/rubocop/ast/node_pattern_spec.rb (revision 1feb3b8216f0eb1079fd4f0c91480df8e63906d9)
+++ b/spec/rubocop/ast/node_pattern_spec.rb (date 1731087767421)
@@ -1696,6 +1696,44 @@
       end
     end

+    context 'with unification' do
+      let(:ruby) { 'foo.bar || foo.baz' }
+
+      context 'without capture' do
+        let(:pattern) { '(or <(send _receiver :bar) (send _receiver :baz)>)' }
+
+        it { expect(pattern).to match_code(node) }
+      end
+
+      context 'when reference matches in reverse' do
+        let(:ruby) { 'foo.baz || foo.bar' }
+        let(:pattern) { '(or <(send _receiver :bar) (send _receiver :baz)>)' }
+
+        it { expect(pattern).to match_code(node) }
+      end
+
+      context 'when reference does not match' do
+        let(:ruby) { 'foo.bar || bar.baz' }
+        let(:pattern) { '(or <(send _receiver :bar) (send _receiver :baz)>)' }
+
+        it_behaves_like 'nonmatching'
+      end
+
+      context 'with single capture' do
+        let(:pattern) { '(or <(send $_receiver :bar) (send _receiver :baz)>)' }
+        let(:captured_val) { s(:send, nil, :foo) }
+
+        it_behaves_like 'single capture'
+      end
+
+      context 'with multiple capture' do
+        let(:pattern) { '(or <(send $_receiver :bar) (send $_receiver :baz)>)' }
+        let(:captured_vals) { [s(:send, nil, :foo), s(:send, nil, :foo)] }
+
+        it_behaves_like 'multiple capture'
+      end
+    end
+
marcandre commented 3 weeks ago

Good catch. Agreed it's a bug. I'll have to check the code. Thanks for the issue and the test