Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
46 changes: 29 additions & 17 deletions ext/prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions test/prism/api/parse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down
14 changes: 5 additions & 9 deletions test/prism/result/source_location_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions test/prism/ruby/parameters_signature_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
Loading