diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 35b91e41b2..a0da0b6195 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -7,6 +7,10 @@ require "rbconfig" require "ffi" +# We want to eagerly load this file if there are Ractors so that it does not get +# autoloaded from within a non-main Ractor. +require "prism/serialize" if defined?(Ractor) + module Prism module LibRubyParser # :nodoc: extend FFI::Library @@ -159,6 +163,9 @@ module Prism class PrismString # :nodoc: SIZEOF = LibRubyParser.pm_string_sizeof + PLATFORM_EXPECTS_UTF8 = + RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) + attr_reader :pointer, :length def initialize(pointer, length, from_string) @@ -193,8 +200,7 @@ module Prism # On Windows and Mac, it's expected that filepaths will be encoded in # UTF-8. If they are not, we need to convert them to UTF-8 before # passing them into pm_string_mapped_init. - if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) && - (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8 + if PLATFORM_EXPECTS_UTF8 && (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8 filepath = filepath.encode(Encoding::UTF_8) end @@ -223,7 +229,7 @@ module Prism private_constant :LibRubyParser # The version constant is set by reading the result of calling pm_version. - VERSION = LibRubyParser.pm_version.read_string + VERSION = LibRubyParser.pm_version.read_string.freeze class << self # Mirror the Prism.dump API by using the serialization API. diff --git a/prism/extension.c b/prism/extension.c index dca2ee67a1..1533ca7bb3 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -1331,6 +1331,11 @@ Init_prism(void) { ); } +#ifdef HAVE_RB_EXT_RACTOR_SAFE + // Mark this extension as Ractor-safe. + rb_ext_ractor_safe(true); +#endif + // Grab up references to all of the constants that we're going to need to // reference throughout this extension. rb_cPrism = rb_define_module("Prism"); diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 0b9e83eb0a..104b60f484 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -592,7 +592,7 @@ module Prism <%- tokens.each do |token| -%> <%= token.name.to_sym.inspect %>, <%- end -%> - ] + ].freeze private_constant :MAJOR_VERSION, :MINOR_VERSION, :PATCH_VERSION private_constant :ConstantPool, :FastStringIO, :Loader, :TOKEN_TYPES diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb new file mode 100644 index 0000000000..68fc0508c8 --- /dev/null +++ b/test/prism/ractor_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +return unless defined?(Ractor) + +require_relative "test_helper" + +return if Prism::TestCase.windows? + +module Prism + class RactorTest < TestCase + def test_version + assert_match(/\A\d+\.\d+\.\d+\z/, with_ractor { Prism::VERSION }) + end + + def test_parse_file + assert_equal("Prism::ParseResult", with_ractor(__FILE__) { |filepath| Prism.parse_file(filepath).class }) + end + + def test_lex_file + assert_equal("Prism::LexResult", with_ractor(__FILE__) { |filepath| Prism.lex_file(filepath).class }) + end + + def test_parse_file_comments + assert_equal("Array", with_ractor(__FILE__) { |filepath| Prism.parse_file_comments(filepath).class }) + end + + def test_parse_lex_file + assert_equal("Prism::ParseLexResult", with_ractor(__FILE__) { |filepath| Prism.parse_lex_file(filepath).class }) + end + + def test_parse_success + assert_equal("true", with_ractor("1 + 1") { |source| Prism.parse_success?(source) }) + end + + def test_parse_failure + assert_equal("true", with_ractor("1 +") { |source| Prism.parse_failure?(source) }) + end + + def test_string_query_local + assert_equal("true", with_ractor("foo") { |source| StringQuery.local?(source) }) + end + + def test_string_query_constant + assert_equal("true", with_ractor("FOO") { |source| StringQuery.constant?(source) }) + end + + def test_string_query_method_name + assert_equal("true", with_ractor("foo?") { |source| StringQuery.method_name?(source) }) + end + + if !ENV["PRISM_BUILD_MINIMAL"] + def test_dump_file + result = with_ractor(__FILE__) { |filepath| Prism.dump_file(filepath) } + assert_operator(result, :start_with?, "PRISM") + end + end + + private + + # Note that this must be done in a subprocess, otherwise it can mess up + # CRuby's test suite. + def with_ractor(*arguments, &block) + reader, writer = IO.pipe + + pid = fork do + reader.close + writer.puts(ignore_warnings { Ractor.new(*arguments, &block) }.take) + end + + writer.close + result = reader.gets.chomp + + Process.wait(pid) + result + end + end +end diff --git a/test/prism/result/warnings_test.rb b/test/prism/result/warnings_test.rb index 04542dbada..4643fb134f 100644 --- a/test/prism/result/warnings_test.rb +++ b/test/prism/result/warnings_test.rb @@ -339,7 +339,7 @@ module Prism assert_warning("tap { redo; foo }", "statement not reached") end - if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i) + if windows? def test_shebang_ending_with_carriage_return refute_warning("#!ruby\r\np(123)\n", compare: false) end diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index 8a137cb636..13da37b687 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -212,6 +212,11 @@ module Prism yield Encoding::EUC_TW, codepoints_euc_tw end + # True if the current platform is Windows. + def self.windows? + RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i) + end + private if RUBY_ENGINE == "ruby" && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism