diff options
Diffstat (limited to 'test/ruby/test_compile_prism.rb')
| -rw-r--r-- | test/ruby/test_compile_prism.rb | 366 |
1 files changed, 178 insertions, 188 deletions
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index 3dbc634828..c017111c0a 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -3,6 +3,12 @@ # This file is organized to match itemization in https://github.com/ruby/prism/issues/1335 module Prism class TestCompilePrism < Test::Unit::TestCase + def test_iseq_has_node_id + code = "proc { <<END }\n hello\nEND" + iseq = RubyVM::InstructionSequence.compile_prism(code) + assert_operator iseq.to_a[4][:node_id], :>, -1 + end + # Subclass is used for tests which need it class Subclass; end ############################################################################ @@ -207,6 +213,12 @@ module Prism assert_prism_eval("defined?(a(itself))") assert_prism_eval("defined?(itself(itself))") + # method chain with a block on the inside + assert_prism_eval("defined?(itself { 1 }.itself)") + + # method chain with parenthesized receiver + assert_prism_eval("defined?((itself).itself)") + # Method chain on a constant assert_prism_eval(<<~RUBY) class PrismDefinedNode @@ -362,110 +374,6 @@ module Prism assert_prism_eval("$pit = 1") end - def test_IndexAndWriteNode - assert_prism_eval("[0][0] &&= 1") - assert_prism_eval("[nil][0] &&= 1") - - # Testing `[]` with a block passed in - assert_prism_eval(<<-CODE) - class CustomHash < Hash - def []=(key, value, &block) - block ? super(block.call(key), value) : super(key, value) - end - end - - hash = CustomHash.new - - # Call the custom method with a block that modifies - # the key before assignment - hash["KEY"] = "test" - hash["key", &(Proc.new { _1.upcase })] &&= "value" - hash - CODE - - # Test with keyword arguments - assert_prism_eval(<<~RUBY) - h = Object.new - def h.[](**b) = 0 - def h.[]=(*a, **b); end - - h[foo: 1] &&= 2 - RUBY - - # Test with keyword splat - assert_prism_eval(<<~RUBY) - h = Object.new - def h.[](**b) = 1 - def h.[]=(*a, **b); end - - h[**{}] &&= 2 - RUBY - end - - def test_IndexOrWriteNode - assert_prism_eval("[0][0] ||= 1") - assert_prism_eval("[nil][0] ||= 1") - - # Testing `[]` with a block passed in - assert_prism_eval(<<-CODE) - class CustomHash < Hash - def []=(key, value, &block) - super(block.call(key), value) - end - end - - hash = CustomHash.new - - # Call the custom method with a block that modifies - # the key before assignment - hash["key", &(Proc.new { _1.upcase })] ||= "value" - hash - CODE - - # Test with keyword arguments - assert_prism_eval(<<~RUBY) - h = Object.new - def h.[](**b) = 0 - def h.[]=(*a, **b); end - - h[foo: 1] ||= 2 - RUBY - - # Test with keyword splat - assert_prism_eval(<<~RUBY) - h = Object.new - def h.[](**b) = nil - def h.[]=(*a, **b); end - - h[**{}] ||= 2 - RUBY - end - - def test_IndexOperatorWriteNode - assert_prism_eval("[0][0] += 1") - - # Testing `[]` with a block passed in - assert_prism_eval(<<-CODE) - class CustomHash < Hash - def [](key, &block) - block ? super(block.call(key)) : super(key) - end - - def []=(key, value, &block) - block ? super(block.call(key), value) : super(key, value) - end - end - - hash = CustomHash.new - - # Call the custom method with a block that modifies - # the key before assignment - hash["KEY"] = "test" - hash["key", &(Proc.new { _1.upcase })] &&= "value" - hash - CODE - end - def test_InstanceVariableAndWriteNode assert_prism_eval("@pit = 0; @pit &&= 1") end @@ -696,22 +604,16 @@ module Prism assert_prism_eval('$pit = 1; "1 #$pit 1"') assert_prism_eval('"1 #{1 + 2} 1"') assert_prism_eval('"Prism" "::" "TestCompilePrism"') - assert_prism_eval('("a""b").frozen?') - assert_prism_eval(<<-CODE) - # frozen_string_literal: true - - ("a""b").frozen? - CODE - assert_prism_eval(<<-CODE) + assert_prism_eval(<<-'RUBY') # frozen_string_literal: true - ("a""b""#{1}").frozen? - CODE - assert_prism_eval(<<-CODE) + !("a""b""#{1}").frozen? + RUBY + assert_prism_eval(<<-'RUBY') # frozen_string_literal: true - ("a""#{1}""b").frozen? - CODE + !("a""#{1}""b").frozen? + RUBY # Test encoding of interpolated strings assert_prism_eval(<<~'RUBY') @@ -724,6 +626,15 @@ module Prism RUBY end + def test_concatenated_StringNode + assert_prism_eval('("a""b").frozen?') + assert_prism_eval(<<-CODE) + # frozen_string_literal: true + + ("a""b").frozen? + CODE + end + def test_InterpolatedSymbolNode assert_prism_eval('$pit = 1; :"1 #$pit 1"') assert_prism_eval(':"1 #{1 + 2} 1"') @@ -777,7 +688,9 @@ module Prism def test_StringNode assert_prism_eval('"pit"') assert_prism_eval('"a".frozen?') + end + def test_StringNode_frozen_string_literal_true [ # Test that string literal is frozen <<~RUBY, @@ -794,6 +707,31 @@ module Prism end end + def test_StringNode_frozen_string_literal_false + [ + # Test that string literal is frozen + <<~RUBY, + # frozen_string_literal: false + !"a".frozen? + RUBY + # Test that two string literals with the same contents are the same string + <<~RUBY, + # frozen_string_literal: false + !"hello".equal?("hello") + RUBY + ].each do |src| + assert_prism_eval(src, raw: true) + end + end + + def test_StringNode_frozen_string_literal_default + # Test that string literal is chilled + assert_prism_eval('"a".frozen?') + + # Test that two identical chilled string literals aren't the same object + assert_prism_eval('!"hello".equal?("hello")') + end + def test_SymbolNode assert_prism_eval(":pit") @@ -842,6 +780,9 @@ module Prism assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]") assert_prism_eval("[[*1..2], 3, *4..5]") + elements = Array.new(64) { ":foo" } + assert_prism_eval("[#{elements.join(", ")}, bar: 1, baz: 2]") + # Test keyword splat inside of array assert_prism_eval("[**{x: 'hello'}]") @@ -916,6 +857,24 @@ module Prism assert_prism_eval("...2") assert_prism_eval("1..") assert_prism_eval("1...") + assert_prism_eval("a1 = 1; a2 = 2; a1..a2") + assert_prism_eval("a1 = 1; a2 = 2; a1...a2") + assert_prism_eval("a2 = 2; ..a2") + assert_prism_eval("a2 = 2; ...a2") + assert_prism_eval("a1 = 1; a1..") + assert_prism_eval("a1 = 1; a1...") + assert_prism_eval("1..2; nil") + assert_prism_eval("1...2; nil") + assert_prism_eval("..2; nil") + assert_prism_eval("...2; nil") + assert_prism_eval("1..; nil") + assert_prism_eval("1...; nil") + assert_prism_eval("a1 = 1; a2 = 2; a1..a2; nil") + assert_prism_eval("a1 = 1; a2 = 2; a1...a2; nil") + assert_prism_eval("a2 = 2; ..a2; nil") + assert_prism_eval("a2 = 2; ...a2; nil") + assert_prism_eval("a1 = 1; a1..; nil") + assert_prism_eval("a1 = 1; a1...; nil") end def test_SplatNode @@ -1087,13 +1046,19 @@ module Prism end def test_ForNode - assert_prism_eval("for i in [1,2] do; i; end") - assert_prism_eval("for @i in [1,2] do; @i; end") - assert_prism_eval("for $i in [1,2] do; $i; end") + assert_prism_eval("r = []; for i in [1,2] do; r << i; end; r") + assert_prism_eval("r = []; for @i in [1,2] do; r << @i; end; r") + assert_prism_eval("r = []; for $i in [1,2] do; r << $i; end; r") + + assert_prism_eval("r = []; for foo, in [1,2,3] do r << foo end; r") - assert_prism_eval("for foo, in [1,2,3] do end") + assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r") - assert_prism_eval("for i, j in {a: 'b'} do; i; j; end") + # Test splat node as index in for loop + assert_prism_eval("r = []; for *x in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for * in [[1,2], [3,4]] do; r << 'ok'; end; r") + assert_prism_eval("r = []; for x, * in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for x, *y in [[1,2], [3,4]] do; r << [x, y]; end; r") end ############################################################################ @@ -1242,6 +1207,27 @@ a res RUBY + + # Bug #21001 + assert_prism_eval(<<~RUBY) + RUN_ARRAY = [1,2] + + MAP_PROC = Proc.new do |&blk| + block_results = [] + RUN_ARRAY.each do |value| + block_value = blk.call(value) + block_results.push block_value + end + block_results + ensure + next block_results + end + + MAP_PROC.call do |value| + break if value > 1 + next value + end + RUBY end def test_NextNode @@ -1913,15 +1899,6 @@ end o.bar { :ok } RUBY - - # Test anonymous block forwarding from argument forwarding - assert_prism_eval(<<~RUBY) - o = Object.new - def o.foo = yield - def o.bar(...) = foo(&) - - o.bar { :ok } - RUBY end def test_BlockLocalVariableNode @@ -2012,24 +1989,6 @@ end obj[*[1]] = 3 RUBY - # Test passing block inside of []= - assert_prism_eval(<<~RUBY) - obj = Object.new - def obj.[]=(a); end - - p = proc {} - obj[&p] = 4 - RUBY - - # Test splat and block inside of []= - assert_prism_eval(<<~RUBY) - obj = Object.new - def obj.[]=(a, b); end - - p = proc {} - obj[*[1], &p] = 4 - RUBY - assert_prism_eval(<<-CODE) def self.prism_opt_var_trail_hash(a = nil, *b, c, **d); end prism_opt_var_trail_hash("a") @@ -2074,39 +2033,9 @@ end test_prism_call_node CODE - # Test opt_str_freeze instruction when calling #freeze on a string literal - assert_prism_eval(<<~RUBY) - "foo".freeze.equal?("foo".freeze) - RUBY - # Test encoding in opt_str_freeze - assert_prism_eval(<<~'RUBY', raw: true) - # -*- coding: us-ascii -*- - "\xff".freeze.encoding - RUBY - - # Test opt_aref_with instruction when calling [] with a string - assert_prism_eval(<<~RUBY) - ObjectSpace.count_objects - - h = {"abc" => 1} - before = ObjectSpace.count_objects[:T_STRING] - 5.times{ h["abc"] } - after = ObjectSpace.count_objects[:T_STRING] - - before == after - RUBY - - # Test opt_aset_with instruction when calling []= with a string key - assert_prism_eval(<<~RUBY) - ObjectSpace.count_objects - - h = {"abc" => 1} - before = ObjectSpace.count_objects[:T_STRING] - 5.times{ h["abc"] = 2} - after = ObjectSpace.count_objects[:T_STRING] - - before == after - RUBY + # Specialized instructions + assert_prism_eval(%{-"literal"}) + assert_prism_eval(%{"literal".freeze}) end def test_CallAndWriteNode @@ -2257,6 +2186,56 @@ end RUBY end + def test_ForwardingArgumentsNode_instruction_sequence_consistency + # Test that both parsers generate identical instruction sequences for forwarding arguments + # This prevents regressions like the one fixed in prism_compile.c for PM_FORWARDING_ARGUMENTS_NODE + + # Test case from the bug report: def bar(buz, ...) = foo(buz, ...) + source = <<~RUBY + def foo(*, &block) = block + def bar(buz, ...) = foo(buz, ...) + RUBY + + compare_instruction_sequences(source) + + # Test simple forwarding + source = <<~RUBY + def target(...) = nil + def forwarder(...) = target(...) + RUBY + + compare_instruction_sequences(source) + + # Test mixed forwarding with regular arguments + source = <<~RUBY + def target(a, b, c) = [a, b, c] + def forwarder(x, ...) = target(x, ...) + RUBY + + compare_instruction_sequences(source) + + # Test forwarding with splat + source = <<~RUBY + def target(a, b, c) = [a, b, c] + def forwarder(x, ...); target(*x, ...); end + RUBY + + compare_instruction_sequences(source) + end + + private + + def compare_instruction_sequences(source) + # Get instruction sequences from both parsers + parsey_iseq = RubyVM::InstructionSequence.compile_parsey(source) + prism_iseq = RubyVM::InstructionSequence.compile_prism(source) + + # Compare instruction sequences + assert_equal parsey_iseq.disasm, prism_iseq.disasm + end + + public + def test_ForwardingSuperNode assert_prism_eval("class Forwarding; def to_s; super; end; end") assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end") @@ -2599,6 +2578,9 @@ end assert_prism_eval("5 in foo") assert_prism_eval("1 in 2") + + # Bug: https://bugs.ruby-lang.org/issues/20956 + assert_prism_eval("1 in [1 | [1]]") end def test_MatchRequiredNode @@ -2637,6 +2619,7 @@ end assert_prism_eval("module Prism; @prism = 1; 1 in ^@prism; end") assert_prism_eval("$prism = 1; 1 in ^$prism") assert_prism_eval("prism = 1; 1 in ^prism") + assert_prism_eval("[1].each { 1 => ^it }") end ############################################################################ @@ -2711,7 +2694,7 @@ end # Errors # ############################################################################ - def test_MissingNode + def test_ErrorRecoveryNode # TODO end @@ -2738,31 +2721,38 @@ end assert_raise TypeError do RubyVM::InstructionSequence.compile_file_prism(nil) end + + assert_nothing_raised(Errno::EMFILE, Errno::ENFILE) do + 10000.times do + RubyVM::InstructionSequence.compile_file_prism(File::NULL) + end + end end private - def compare_eval(source, raw:) + def compare_eval(source, raw:, location:) source = raw ? source : "class Prism::TestCompilePrism\n#{source}\nend" - ruby_eval = RubyVM::InstructionSequence.compile(source).eval + ruby_eval = RubyVM::InstructionSequence.compile_parsey(source).eval prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval if ruby_eval.is_a? Proc - assert_equal ruby_eval.class, prism_eval.class + assert_equal ruby_eval.class, prism_eval.class, "@#{location.path}:#{location.lineno}" else - assert_equal ruby_eval, prism_eval + assert_equal ruby_eval, prism_eval, "@#{location.path}:#{location.lineno}" end end def assert_prism_eval(source, raw: false) + location = caller_locations(1, 1).first $VERBOSE, verbose_bak = nil, $VERBOSE begin - compare_eval(source, raw:) + compare_eval(source, raw:, location:) # Test "popped" functionality - compare_eval("#{source}; 1", raw:) + compare_eval("#{source}; 1", raw:, location:) ensure $VERBOSE = verbose_bak end |
