diff --git a/CHANGELOG.md b/CHANGELOG.md index eb419ebb5a..255a93790f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Highlights of this release: - Significant changes to the exposed C API and language bindings. ### Breaking Changes +- The Ruby API now defaults to `version: "nearest"` instead of `version: "latest"`, so code is parsed with the syntax of the running Ruby (or the closest version prism supports). Pass `version: "latest"` to keep the previous behavior. [#4247](https://github.com/ruby/prism/issues/4247) - Optional locations like `StringNode#closing_loc` in `'foo` that were empty for syntax-invalid code will now be `nil` instead. - `MissingNode` has been replaced by `ErrorRecoveryNode`. `ErrorRecoveryNode` wraps unexpected nodes, so when walking syntax-invalid code you will now only need to handle this one specific node type. - Require arguments for `Prism::Source.for`. The second and third positional parameters are no longer optional. diff --git a/ext/prism/extension.c b/ext/prism/extension.c index 8d24a99a7b..e6768deef4 100644 --- a/ext/prism/extension.c +++ b/ext/prism/extension.c @@ -200,6 +200,23 @@ build_options_scopes(pm_options_t *options, VALUE scopes) { } } +/** + * Set the version option to the syntax version of the currently running Ruby, + * or to the closest version prism supports if it does not know that one. + */ +static void +options_version_set_nearest(pm_options_t *options) { + if (!pm_options_version_set(options, ruby_version, 3)) { + // Prism doesn't know this specific version. Is it lower? + if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { + pm_options_version_set_lowest(options); + } else { + // Must be higher. + pm_options_version_set_highest(options); + } + } +} + /** * An iterator function that is called for each key-value in the keywords hash. */ @@ -231,15 +248,7 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version)); } } else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) { - if (!pm_options_version_set(options, ruby_version, 3)) { - // Prism doesn't know this specific version. Is it lower? - if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { - pm_options_version_set_lowest(options); - } else { - // Must be higher. - pm_options_version_set_highest(options); - } - } + options_version_set_nearest(options); } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); } @@ -345,6 +354,7 @@ build_options(VALUE argument) { static void extract_options(pm_options_t *options, VALUE filepath, VALUE keywords) { pm_options_line_set(options, 1); /* default */ + options_version_set_nearest(options); /* default */ if (!NIL_P(keywords)) { struct build_options_data data = { .options = options, .keywords = keywords }; @@ -1151,14 +1161,16 @@ parse_input(const uint8_t *input, size_t input_length, const pm_options_t *optio * parsed. This should be an array of arrays of symbols or nil. Scopes are * ordered from the outermost scope to the innermost one. * * `version` - the version of Ruby syntax that prism should used to parse Ruby - * code. By default prism assumes you want to parse with the latest - * version of Ruby syntax (which you can trigger with `nil` or - * `"latest"`). You may also restrict the syntax to a specific version of - * Ruby, e.g., with `"3.3.0"`. To parse with the same syntax version that - * the current Ruby is running use `version: "current"`. To parse with the - * nearest version to the current Ruby that is running, use - * `version: "nearest"`. Raises ArgumentError if the version is not - * currently supported by Prism. + * code. By default prism parses with the syntax version nearest to the + * current Ruby that is running (which you can trigger with `nil` or + * `"nearest"`): the running version if prism supports it, otherwise the + * closest version that it does support. You may also restrict the syntax + * to a specific version of Ruby, e.g., with `"3.3.0"`. To parse with + * exactly the same syntax version that the current Ruby is running use + * `version: "current"`, which raises Prism::CurrentVersionError if prism + * does not support it. To parse with the latest version of Ruby syntax + * that prism supports, use `version: "latest"`. Raises ArgumentError if + * the version is not currently supported by Prism. */ static VALUE parse(int argc, VALUE *argv, VALUE self) { diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 013f02ca8c..a0948beddb 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -565,9 +565,9 @@ def dump_options_version(version) case version when "current" version_string_to_number(RUBY_VERSION) || raise(CurrentVersionError, RUBY_VERSION) - when "latest", nil + when "latest" 0 # Handled in pm_parser_init - when "nearest" + when "nearest", nil dump = version_string_to_number(RUBY_VERSION) return dump if dump if RUBY_VERSION < "3.3" diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 6d487e6ce9..c66a69caf9 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -173,6 +173,17 @@ def test_nearest assert Prism.parse_success?("1 + 1", version: "nearest") end + def test_version_default + # Trailing commas in method parameters are only valid as of Ruby 4.1. + source = "def foo(a,); end" + expected = RUBY_VERSION >= "4.1" + + assert_equal expected, Prism.parse_success?(source) + assert_equal expected, Prism.parse_success?(source, version: nil) + assert_equal expected, Prism.parse_success?(source, version: "nearest") + assert Prism.parse_success?(source, version: "latest") + end + def test_scopes assert_kind_of Prism::CallNode, Prism.parse_statement("foo") assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]]) diff --git a/test/prism/result/source_location_test.rb b/test/prism/result/source_location_test.rb index a8d27b95a8..493ce7729c 100644 --- a/test/prism/result/source_location_test.rb +++ b/test/prism/result/source_location_test.rb @@ -536,25 +536,21 @@ def test_InterpolatedXStringNode end def test_ItLocalVariableReadNode - assert_location(ItLocalVariableReadNode, "-> { it }", 5...7) do |node| + assert_location(ItLocalVariableReadNode, "-> { it }", 5...7, version: "3.4.0") do |node| node.body.body.first end - assert_location(ItLocalVariableReadNode, "foo { it }", 6...8) do |node| + assert_location(ItLocalVariableReadNode, "foo { it }", 6...8, version: "3.4.0") do |node| node.block.body.body.first end assert_location(CallNode, "-> { it }", 5...7, version: "3.3.0") do |node| node.body.body.first end - - assert_location(ItLocalVariableReadNode, "-> { it }", 5...7, version: "3.4.0") do |node| - node.body.body.first - end end def test_ItParametersNode - assert_location(ItParametersNode, "-> { it }", &:parameters) + assert_location(ItParametersNode, "-> { it }", version: "3.4.0", &:parameters) end def test_KeywordHashNode @@ -651,7 +647,7 @@ def test_NilNode end def test_NoBlockParameterNode - assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12) { |node| node.parameters.block } + assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12, version: "4.1") { |node| node.parameters.block } end def test_NoKeywordsParameterNode @@ -701,7 +697,7 @@ def test_PinnedExpressionNode def test_PinnedVariableNode assert_location(PinnedVariableNode, "bar = 1; foo in ^bar", 16...20, &:pattern) - assert_location(PinnedVariableNode, "proc { 1 in ^it }.call(1)", 12...15) do |node| + assert_location(PinnedVariableNode, "proc { 1 in ^it }.call(1)", 12...15, version: "3.4.0") do |node| node.receiver.block.body.body.first.pattern end end diff --git a/test/prism/ruby/parameters_signature_test.rb b/test/prism/ruby/parameters_signature_test.rb index 1ca2b144a9..03355b3111 100644 --- a/test/prism/ruby/parameters_signature_test.rb +++ b/test/prism/ruby/parameters_signature_test.rb @@ -51,8 +51,7 @@ def test_nokey end def test_noblock - # FIXME: `compare: RUBY_VERSION >= "4.1"` once builds are available - assert_parameters([[:noblock]], "&nil", compare: false) + assert_parameters([[:noblock]], "&nil", compare: RUBY_VERSION >= "4.1", version: "4.1") end def test_keyrest_anonymous @@ -86,9 +85,9 @@ def test_invalid_syntax private - def assert_parameters(expected, source, compare: true) + def assert_parameters(expected, source, compare: true, version: nil) # Compare against our expectation. - assert_equal(expected, signature(source)) + assert_equal(expected, signature(source, version: version)) return unless compare # Compare against Ruby's expectation. @@ -97,8 +96,8 @@ def assert_parameters(expected, source, compare: true) assert_equal(expected, object.method(:m).parameters) end - def signature(source) - program = Prism.parse("def m(#{source}); end").value + def signature(source, version: nil) + program = Prism.parse("def m(#{source}); end", version: version).value program.statements.body.first.parameters.signature end end