summaryrefslogtreecommitdiff
path: root/test/ruby/test_compile_prism.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_compile_prism.rb')
-rw-r--r--test/ruby/test_compile_prism.rb366
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