Giter Site home page Giter Site logo

ruby-next / ruby-next Goto Github PK

View Code? Open in Web Editor NEW
723.0 16.0 45.0 899 KB

Ruby Next makes modern Ruby code run in older versions and alternative implementations

Home Page: https://ruby-next.github.io

License: MIT License

Ruby 99.85% Makefile 0.15%
ruby transpiler hacktoberfest

ruby-next's Introduction

Cult Of Martians Gem Version Build JRuby Build TruffleRuby Build

Ruby Next

Ruby Next is a transpiler and a collection of polyfills for supporting the latest and upcoming Ruby features (APIs and syntax) in older versions and alternative implementations. For example, you can use pattern matching and Kernel#then in Ruby 2.5 or mruby.

Who might be interested in Ruby Next?

  • Ruby gems maintainers who want to write code using the latest Ruby version but still support older ones.
  • Application developers who want to give new features a try without waiting for the final release (or, more often, for the first patch).
  • Users of non-MRI implementations such as mruby, JRuby, TruffleRuby, Natalie, Opal, RubyMotion, Artichoke, Prism.
  • Ruby syntax enthusiasts who want to experiment with custom syntax extensions πŸ‘©β€πŸ”¬πŸ‘¨β€πŸ”¬.

Ruby Next also aims to help the community to assess new, experimental, MRI features by making it easier to play with them. That's why Ruby Next implements the master features as fast as possible.

See also a companion library (extracted from Ruby Next) that provides code loading hooks for your needsβ€”require-hooks.

Read more about the motivation behind the Ruby Next in this post: Ruby Next: Make all Rubies quack alike.

Sponsored by Evil Martians

Posts

Talks

Examples

Please, submit a PR to add your project to the list!

Table of contents

Overview

Ruby Next consists of two parts: core and language.

Core provides polyfills for Ruby core classes APIs via Refinements (default strategy) or core extensions (optionally or for refinement-less environments).

Language is responsible for transpiling edge Ruby syntax into older versions. It could be done programmatically or via CLI. It also could be done in runtime.

Currently, Ruby Next supports Ruby versions 2.3+, including JRuby 9.2.8+ and TruffleRuby 20.1+ (with some limitations). Support for older versions (<2.5) slightly differs though (see below). Versions between 2.0 and 2.3 may work but we no longer test against them.

Please open an issue or join the discussion in the existing issues if you would like us to support older Ruby versions.

Quick start

The quickest way to start experimenting with Ruby Next is to install the gem and run a sample script. For example:

# Install Ruby Next globally
$ gem install ruby-next

# Call ruby with -ruby-next flag
$ ruby -ruby-next -e "
greet = proc do
  case it
    in hello: hello if hello =~ /human/i
      'πŸ™‚'
    in hello: 'martian'
      'πŸ‘½'
    end
end

puts greet.call(hello: 'martian')
"

=> πŸ‘½

Using only polyfills

First, install a gem:

# Gemfile
gem "ruby-next-core"

# gemspec
spec.add_dependency "ruby-next-core", "~> 1.0"

NOTE: we use a different gem for distribution, ruby-next-core, to provide a zero-dependency, polyfills-only version.

Then, all you need is to load Ruby Next:

require "ruby-next"

And activate the refinement in every file where you want to use it*:

using RubyNext

Ruby Next only refines core classes if necessary; thus, this line wouldn't have any effect in edge Ruby.

NOTE: Even if the runtime already contains a monkey-patch with the backported functionality, we consider the method as dirty and activate the refinement for it. Thus, you always have predictable behaviour. That's why we recommend using refinements for gem development.

Alternatively, you can go with monkey-patches. Just add this line:

require "ruby-next/core_ext"

The following rule of thumb is recommended when choosing between refinements and monkey-patches:

  • Use refinements for library development (to avoid conflicts with others' code)
  • Using core extensions could be considered for application development (no need to think about using RubyNext); this approach could potentially lead to conflicts with dependencies (if these dependencies are not using refinements πŸ™‚)
  • Use core extensions if refinements are not supported by your platform

NOTE: Edge APIs (i.e., from the Ruby's master branch) are included by default.

The list of supported APIs.

Data backport

Ruby 3.2 has introduced a new core classβ€”Data. Ruby Next provides a backport functionality which is automatically activated when you require "ruby-next" if and only if the constant is undefined. If you want to use a custom backport, make sure you loaded it first.

If you want to opt-out from loading Data backport, you must set the RUBY_NEXT_DISABLE_DATA env variable to true.

Known limitations when using Data

Currently, passing Hash as a last positional argument to Data.new is not supported in Ruby <3.0 (due to the difference in keyword arguments handling). We recommend always using keyword arguments when initializing Data objects.

Transpiling

Ruby Next allows you to transpile* edge Ruby syntax to older versions.

Transpiler relies on two libraries: parser and unparser.

NOTE: The "official" parser gem only supports the latest stable Ruby version, while Ruby Next aims to support edge and experimental Ruby features. To enable them, you should use our version of Parser (see instructions below).

Installation:

# Gemfile
gem "ruby-next", "~> 1.0"

# gemspec
spec.add_dependency "ruby-next", "~> 1.0"
# or install globally
gem install ruby-next

The list of supported syntax features.

Transpiler modes

Since v1.0, Ruby Next only support the rewrite mode, i.e., the code transformations are applied directly to the original source code. This allows us to keep formatting as close as possible to the original code.

The main benefit of the rewrite mode is that it preserves the original code line numbers and layout, which is especially useful in debugging.

The legacy AST mode (regenerating source code from the modified abstract syntax tree) is deprecated (though still supported).

CLI

Ruby Next ships with the command-line interface (ruby-next) which provides the following functionality:

ruby-next nextify

This command allows you to transpile a file or directory into older Rubies (see, for example, the "Integrating into a gem development" section above).

It has the following interface:

$ ruby-next nextify
Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
    -o, --output=OUTPUT              Specify output directory or file or stdout
        --min-version=VERSION        Specify the minimum Ruby version to support
        --single-version             Only create one version of a file (for the earliest Ruby version)
        --overwrite                  Overwrites the original file with one version of --single-version (works only with --single-version or --rewrite)
        --edge                       Enable edge (master) Ruby features
        --proposed                   Enable proposed/experimental Ruby features
        --[no-]refine                Do not inject `using RubyNext`
        --list-rewriters             List available rewriters
        --rewrite=REWRITERS...       Specify particular Ruby features to rewrite
        --import-rewriter=PATHS...   Specify the paths to custom rewriters to load
    -h, --help                       Print help
    -V                               Turn on verbose mode
        --dry-run                    Print verbose output without generating files

The behaviour depends on whether you transpile a single file or a directory:

  • When transpiling a directory, the .rbnext subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., .rbnext/2.7, .rbnext/3.1, .rbnext/3.4, etc.). If you want to create only a single version (the smallest), you can also pass --single-version flag. In that case, no version directory is created (i.e., transpiled files go into .rbnext).

  • When transpiling a file and providing the output path as a file path, only a single version is created. For example:

$ ruby-next nextify my_ruby.rb -o my_ruby_next.rb -V
Ruby Next core strategy: refine
Generated: my_ruby_next.rb

ruby-next core_ext

This command could be used to generate a Ruby file with a configurable set of core extensions.

Use this command if you want to backport new Ruby features to Ruby implementations not compatible with RubyGems.

It has the following interface:

$ ruby-next core_ext
Usage: ruby-next core_ext [options]
    -o, --output=OUTPUT              Specify output file or stdout (default: ./core_ext.rb)
    -l, --list                       List all available extensions
        --min-version=VERSION        Specify the minimum Ruby version to support
    -n, --name=NAME                  Filter extensions by name
    -h, --help                       Print help
    -V                               Turn on verbose mode
        --dry-run                    Print verbose output without generating files

The most common use-case is to backport the APIs required by pattern matching. You can do this, for example, by including only monkey-patches containing the "deconstruct" in their names:

ruby-next core_ext -n deconstruct -o pattern_matching_core_ext.rb

To list all available (are matching if --min-version or --name specified) monkey-patches, use the -l switch:

$ ruby-next core_ext -l --name=filter --name=deconstruct
2.6 extensions:
  - ArrayFilter
  - EnumerableFilter
  - HashFilter

2.7 extensions:
  - ArrayDeconstruct
  - EnumerableFilterMap
  - EnumeratorLazyFilterMap
  - HashDeconstructKeys
  - StructDeconstruct

...

CLI configuration file

You can define CLI options in the .rbnextrc file located in the root of your project to avoid adding them every time you run ruby-next.

Configuration file is a YAML with commands as keys and options as multiline strings:

# ./.rbnextrc

nextify: |
  --min-version=2.7
  --edge

NOTE: The nextify section is also used by auto-transpiling when installing the gem from source and by runtime transpiling.

Integrating into a gem development

We recommend pre-transpiling source code to work with older versions before releasing it.

This is how you can do that with Ruby Next:

  • Write source code using the modern/edge Ruby syntax.

  • Generate transpiled code by calling ruby-next nextify ./lib (e.g., before releasing or pushing to VCS).

This will produce lib/.rbnext folder containing the transpiled files, lib/.rbnext/2.6, lib/.rbnext/2.7. The version in the path indicates which Ruby version is required for the original functionality. Only the source files containing new syntax are added to this folder.

NOTE: Do not edit these files manually, either run linters/type checkers/whatever against these files.

  • Add the following code to your gem's entrypoint (the file that is required first and contains other require-s):
require "ruby-next/language/setup"

RubyNext::Language.setup_gem_load_path

The setup_gem_load_path does the following:

  • Resolves the current ruby version.
  • Checks whether there are directories corresponding to the current and earlier* Ruby versions within the .rbnext folder.
  • Add the path to this directory to the $LOAD_PATH before the path to the gem's directory.

That's why need an entrypoint: all the subsequent require calls will load the transpiled files instead of the original ones due to the way feature resolving works in Ruby (scanning the $LOAD_PATH and halting as soon as the matching file is found).

NOTE: require_relative should be avoided due to the way we hijack the features loading mechanism.

If you're using runtime mode a long with setup_gem_load_path (e.g., in tests), the transpiled files are ignored (i.e., we do not modify $LOAD_PATH).

* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the .rbnext/2.7/path/to/file.rb is kept. That's why multiple entries are added to the $LOAD_PATH (.rbnext/2.6, .rbnext/2.7, and .rbnext/3.0 in the specified order for Ruby 2.5, and .rbnext/2.7 and .rbnext/3.0 for Ruby 2.6).

Transpiled files vs. VCS vs. installing from source

It's a best practice to not keep generated files in repositories. In case of Ruby Next, it's a lib/.rbnext folder.

We recommend adding this folder only to the gem package (i.e., it should be added to your spec.files) and ignore it in your VCS (e.g., echo ".rbnext/" >> .gitignore). That would make transpiled files available in releases without polluting your repository.

What if someone decides to install your gem from the VCS source? They would likely face some syntax errors due to the missing transpiled files.

To solve this problem, Ruby Next tries to transpile the source code when you call #setup_gem_load_path. It does this by calling bundle exec ruby-next nextify <lib_dir> -o <next_dir>. We make the following assumptions:

  • We are in the Bundler context (since that's the most common way of installing gems from source).
  • Our Gemfile contains ruby-next gem.
  • We use .rbnextrc for transpiling options.

If the command fails we warn the end user.

This feature, auto-transpiling, is disabled by default (will likely be enabled in future versions). You can enable it by calling RubyNext::Language.setup_gem_load_path(transpile: true).

Runtime usage

It is also possible to transpile Ruby source code in run-time via Ruby Next.

All you need is to require "ruby-next/language/runtime" to hijack Kernel#require and friends before loading the files you want to transpile. You can also automatically inject using RubyNext to every* loaded file by also adding require "ruby-next/core/runtime".

Runtime mode is backed by require-hooksβ€”a standalone gem which has been extracted from Ruby Next. Depending on the current runtime, it picks an optimal strategy for hijacking the loading mechanism. Please, refer to its documentation for more details.

* Ruby Next doesn't hijack every required file but only the configured directories: ./app/, ./lib/, ./spec/, ./test/ (relative to the pwd). It also excludes the ./vendor/bundle directory by default.

You can customize target files via the include_patterns and exclude_patterns configuration options:

RubyNext::Language.include_patterns << "path/to/other/dir/*.rb"
RubyNext::Language.exclude_patterns << "path/to/other/dir/subdir/*"

NOTE: Directories MUST be configured before requiring ruby-next/language/runtime.

Eval & similar

By default, we do not hijack Kernel.eval and similar methods due to some limitations (e.g., there is no easy and efficient way to access the caller's scope, or binding, and some evaluations relies on local variables).

If you want to support transpiling in eval-like methods, opt-in explicitly by activating the refinement:

using RubyNext::Language::Eval

uby-next

This is not a typo, that’s the way ruby -ruby-next works: it’s equal to ruby -r uby-next, and uby-next.rb is a special file that activates the runtime mode.

You can also enable runtime mode by requiring uby-next while running a Ruby executable:

ruby -ruby-next my_ruby_script.rb

# or
RUBYOPT="-ruby-next" ruby my_ruby_script.rb

# or
ruby -ruby-next -e "puts [2, 4, 5].tally"

NOTE: running Ruby scripts directly or executing code via -e option is not supported in TruffleRuby. You can still use -ruby-next to transpile required files, e.g.:

ruby -ruby-next -r my_ruby_script.rb -e "puts my_method"

Logging and debugging

Ruby Next prints some debugging information when fails to load a file in the runtime mode (and fallbacks to the built-in loading mechanism).

You can disable these warnings either by providing the RUBY_NEXT_WARN=false env variable or by setting RubyNext.silence_warnings = true in your code.

You can also enable transpiled source code debugging by setting the RUBY_NEXT_DEBUG=true env variable. When it's set, Ruby Next prints the transpiled code before loading it.

You can use a file pattern as the value for the env var to limit the output: for example, RUBY_NEXT_DEBUG=my_script.rb.

RuboCop

Since Ruby Next provides support for features not available in RuboCop yet, you need to add a patch for compatibility. In you .rubocop.yml add the following:

require:
  - ruby-next/rubocop

You must set TargetRubyVersion: next to make RuboCop use a Ruby Next parser.

Alternatively, you can load the patch from the command line by running: rubocop -r ruby-next/rubocop ....

We recommend using the latest RuboCop version, 'cause it has support for new nodes built-in.

Also, when pre-transpiling source code with ruby-next nextify, we suggest ignoring the transpiled files:

AllCops:
  Exclude:
    - 'lib/.rbnext/**/*'

NOTE: you need ruby-next gem available in the environment where you run RuboCop (having ruby-next-core is not enough).

IRB

Ruby Next supports IRB. In order to enable edge Ruby features for your REPL, add the following line to your .irbrc:

require "ruby-next/irb"

Alternatively, you can require it at startup:

irb -r ruby-next/irb
# or
irb -ruby-next/irb

Pry

Ruby Next supports Pry. In order to enable edge Ruby features for your REPL, add the following line to your .pryrc:

require "ruby-next/pry"

Alternatively, you can require it at startup:

pry -r ruby-next/pry
# or
pry -ruby-next/pry

Using with EOL Rubies

We currently provide support for Ruby 2.3+.

NOTE: By "support" here we mean using ruby-next CLI and runtime transpiling. Transpiled code may run on Ruby 2.0+.

Ruby Next itself relies on 2.5 features and contains polyfills only for version 2.5+ (and that won't change). Thus, to make it work with <2.5 we need to backport some APIs ourselves.

The recommended way of doing this is to use backports gem. You need to load backports before Ruby Next.

When using runtime features, you should do the following:

# first, require backports upto 2.5
require "backports/2.5"
# then, load Ruby Next
require "ruby-next"
# if you need 2.6+ APIs, add Ruby Next core_ext
require "ruby-next/core_ext"
# then, load runtime transpiling
require "ruby-next/language/runtime"
# or
require "ruby-next/language/bootsnap"

To load backports while using ruby-next nextify command, you must configure the environment variable:

RUBY_NEXT_CORE_STRATEGY=backports ruby-next nextify lib/

NOTE: Make sure you have backports gem installed globally or added to your bundle (if you're using bundle exec ruby-next ...).

NOTE: For Ruby 2.2, safe navigation operator (&.) and squiggly heredocs (<<~TXT) support is provided.

IMPORTANT: Unparser ~> 0.4.8 is required to run the transpiler on Ruby <2.4.

Proposed and edge features

Ruby Next aims to bring edge and proposed features to Ruby community before they (hopefully) reach an official Ruby release. This includes:

  • Features already merged to master (edge)
  • Features proposed in Ruby bug tracker (proposed)
  • Features once merged to master but got reverted.

These features are disabled by default, you must opt-in in one of the following ways:

  • Add --edge or --proposed option to nextify command when using CLI.
  • Enable programmatically when using a runtime mode:
# It's important to load language module first
require "ruby-next/language"

require "ruby-next/language/rewriters/edge"
# or
require "ruby-next/language/rewriters/proposed"

# and then activate the runtime mode
require "ruby-next/language/runtime"
# or require "ruby-next/language/bootsnap"
  • Set RUBY_NEXT_EDGE=1 or RUBY_NEXT_PROPOSED=1 environment variable.

Supported edge features

  • Implicit it block parameter (#19890).

Supported proposed features

  • Method reference operator (.:) (#13581).
  • Binding non-local variables in pattern matching (42 => @v) (#18408).

Custom syntax rewriters

Wonder what would happen if Ruby get a null coalescing operator (??=) or some other syntactic feature you want to try out? Ruby Next is here to help you!

Ruby Next allows you to write your own syntax rewriters. Full-featured rewriters (used by Ruby Next itself) operate on AST and usually require parser modifications. However, we also support text-based rewriters which can be used to experiment with new syntax much quicker without dealing with grammars, parsers and syntax trees.

Tip

You can experiment with Ruby Next rewriters at our online playground!

To implement a text-based rewriter, you need to create a new class inherited from RubyNext::Language::Rewriters::Text and implementing either #rewrite or #safe_rewrite method. For example, the method reference operator (.:) could be implemented as follows:

class MethodReferenceRewriter < RubyNext::Language::Rewriters::Text
  # Rewriter configuration includes its name, a syntax probe and a minimum supported Ruby version.
  # The latter two are used to determine whether the rewriter should be activated for the current file in runtime or when running `ruby-next nextify`.
  NAME = "method-reference"
  SYNTAX_PROBE = "Language.:transform"
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)

  def safe_rewrite(source)
    source.gsub(/\.:([\w_]+)/, '.method(:\1)')
  end
end

# Add the rewriter to the list of rewriters
RubyNext::Language.rewriters << MethodReferenceRewriter

The #safe_rewrite method operates on the normalized source code (i.e., without comments and string literals). It's useful when you want to avoid transpiling inside strings or comments. If you want to transpile the original contents, you can use the #rewrite method instead. For example, if you want to rewrite comments:

class NoteDateRewriter < RubyNext::Language::Rewriters::Text
  NAME = "note-comment-date"
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)

  def rewrite(source)
    source.gsub("# NOTE:") do |_match|
      context.track!(self)
      "# NOTE (#{Date.today}):"
    end
  end
end

RubyNext::Language.rewriters << NoteDateRewriter

Note that we use the context object in the example above. It is responsible for tracking if the rewriter was used for the current file. You must call the context.track! method to mark the file as dirty (i.e., it should be transpiled). The input parameter (source) is the Ruby source code of the file being transpiled and the output must be the transpiled source code. When using #safe_rewrite, marking content as dirty explicitly is not necessary.

Using parser combinators (Paco)

Under the hood, #safe_rewrite uses Paco to parse the source and separate string literals from the rest of the code. You can also leverage Paco in your text rewriters, if you want more control on the parsing process. For better experience, we provide a DSL to define a custom parser and the #parse method to use it. Here is an example of implementing the .: operator using a Paco parser:

class MethodReferenceRewriter < RubyNext::Language::Rewriters::Text
  NAME = "method-reference"
  SYNTAX_PROBE = "Language.:transform"

  parser do
    def default
      many(
        alt(
          method_ref,
          any_char
        )
      )
    end

    def method_ref
      seq(
        string(".:").result(""),
        method_name
      # IMPORTANT: Use `#track!` method to mark the file as dirty
      ).fmap { track! }.fmap { ".method(:#{_1})" }
    end

    def method_name = regexp(/[\w_]+/)
  end

  def safe_rewrite(source)
    parse(source).join
  end
end

# Add the rewriter to the list of rewriters
RubyNext::Language.rewriters << MethodReferenceRewriter

When using the ruby-next nextify command, you can load custom rewriters via the --import-rewriter option.

Known limitations

Ruby Next aims to be reasonably compatible with MRI. That means, some edge cases could be uncovered. Below is the list of known limitations.

For gem authors, we recommend testing against all supported versions on CI to make sure you're not hit by edge cases.

Enumerable methods

Using refinements (using RubyNext) for modules could lead to unexpected behaviour in case there is also a prepend for the same module in Ruby <2.7. To eliminate this, we also refine Array (when appropriate), but other enumerables could be affected.

See this issue for details.

Refinement#import_methods

  • Doesn't support importing methods generated with eval.
  • Doesn't support aliases (both alias and alias_method).
  • In JRuby, importing attribute accessors/readers/writers is not supported.
  • When using AST transpiling in runtime, likely fails to import methods from a transpiled files (due to the updated source location).

See the original PR for more details.

Other

See Parser's known issues.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-next/ruby-next.

See also the development guide.

Acknowledgments

License

The gem is available as open source under the terms of the MIT License.

ruby-next's People

Contributors

brandondrew avatar dapi avatar fargelus avatar kj avatar palkan avatar prog-supdex avatar skryukov avatar sl4vr avatar smaximov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ruby-next's Issues

Ruby 3.1 checklist

The features from Ruby 3.1 currently missing in Ruby Next.

Syntax

* We only need to support this for Ruby 3.0. Older Rubies are automatically supported (since Parser recognizes this syntax and we transpile it).

API

** Not sure we want to support this one; but that could be an interesting challenge πŸ™‚

Is transpiling to mruby available?

Hey @palkan first of all thanks for this awesome gem. I just started looking at it and I see you mention mruby in Readme so I wanted to try it out. I did ruby-next nextify --min-version=mruby tried also with a version specified after a dash but it gives me Malformed version number string mruby. It would be very useful if you could provide a list of all available versions and how to specify them in transpilation.

Using RubyNext in gems without a runtime dependency

From #100 (comment)

So we'd run ruby-next nextify to create the files, and paste something like $LOAD_PATH.unshift "#{__dir__}/.rbnext" but wouldn't need any require 'ruby-next'.
The reason is the transpiled files would only be used on some Ruby implementations, and we wouldn't to add a dependency to the gem for all Ruby implementations.

This mostly already works with ruby-next nextify -V --rewrite=pattern-matching --no-refine ./lib, but would need documentation in https://github.com/ruby-next/ruby-next#integrating-into-a-gem-development

can't require on Ruby 3.4.0dev

What did you do?

require 'ruby-next' doesn't work because of Thread::Backtrace::Location#label returns with the class name from Ruby 3.4.0dev.

      def build_location(trace_locations)
        # The caller_locations behaviour depends on implementaion,
        # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
        while trace_locations.first.base_label != "patch"
          trace_locations.shift
        end

trace_locations is a result of caller_locations (Array) and each entries are Thread::Backtrace::Location.
From Ruby 3.4.0dev we changed the return value of #label method (https://bugs.ruby-lang.org/issues/19117).

on my environment

["/home/ko1/ruby/install/trunk/lib/ruby/gems/3.4.0+0/gems/ruby-next-core-1.0.1/lib/ruby-next/core.rb:128:in 'Class#new'",
 "/home/ko1/ruby/install/trunk/lib/ruby/gems/3.4.0+0/gems/ruby-next-core-1.0.1/lib/ruby-next/core.rb:128:in 'RubyNext::Core.patch'",
 "/home/ko1/ruby/install/trunk/lib/ruby/gems/3.4.0+0/gems/ruby-next-core-1.0.1/lib/ruby-next/core/kernel/then.rb:6:in '<top (required)>'",
 "<internal:/home/ko1/ruby/install/trunk/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'",
 "<internal:/home/ko1/ruby/install/trunk/lib/ruby/3.4.0+0/rubygems/core_ext/kernel_require.rb>:136:in 'Kernel#require'"]

and #label returns RubyNext::Core.patch from Ruby 3.4.0dev.

To fix this issue, we simply need to use #base_label instead of #label.
(or use Regexp to match method name)


Now we are trying the new behavior of Thread::Backtrace::Location.
If you have further opinion about this specification, please comment on the redmine ticket.

ruby-next-core 0.13.0: No such file or directory @ rb_check_realpath_internal

What did you do?

I was updating my dependencies on https://github.com/Mayurifag/rails-vue-fullstack-ultimate-template/tree/update-deps project. After making bump to ruby 3.0.2 and bundle update I've got an issue with anyway_config and its dependence ruby-next-core-0.13.0/

After trying to do anything rails related, I've got this:

Creating rails-vue-fullstack-ultimate-template_backend_run ... done
/bundle/ruby/3.0.0/gems/ruby-next-core-0.13.0/lib/ruby-next/language/setup.rb:53:in `realpath': No such file or directory @ rb_check_realpath_internal - /bundle/ruby/3.0.0/gems/rails-6.1.4.1/lib (Errno::ENOENT)
	from /bundle/ruby/3.0.0/gems/ruby-next-core-0.13.0/lib/ruby-next/language/setup.rb:53:in `realpath'
	from /bundle/ruby/3.0.0/gems/ruby-next-core-0.13.0/lib/ruby-next/language/setup.rb:53:in `block in setup_gem_load_path'
	from /bundle/ruby/3.0.0/gems/ruby-next-core-0.13.0/lib/ruby-next/language/setup.rb:52:in `find_index'
	from /bundle/ruby/3.0.0/gems/ruby-next-core-0.13.0/lib/ruby-next/language/setup.rb:52:in `setup_gem_load_path'
	from /bundle/ruby/3.0.0/gems/anyway_config-2.1.0/lib/anyway_config.rb:6:in `<main>'
	from /bundle/ruby/3.0.0/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
       ...

Additional context

When I've added gem "ruby-next-core", "< 0.13.0" into gemfile, it probably fixed the issue.

Environment

Ruby version: 3.0.2 (not 2.x as in github template)

Resolve gem_load_path with cleaned path

Hi guys, first of all, thank you for reading this request and I apologize in advance if my understanding of Ruby is not deep / misjudged.

We have a project that uses anyway_config gem and thus have ruby-next as a indirect dependency.

My context is that:

I have a project of AWS Lambdas written in Ruby. As we are using the Serverless framework, and specifically the ruby-package plugin to help us package our external deps (gems), it was required for us to install the gems with Bundler's standalone mode.

In our project's case, we do bundle install --standalone --path vendor/bundle
As such, the generated bundler/setup.rb looks something like this (notice the use of relative path resolution /../):

# ./vendor/bundle/bundler/setup.rb
require 'rbconfig'
ruby_engine = RUBY_ENGINE
ruby_version = RbConfig::CONFIG["ruby_version"]
path = File.expand_path('..', __FILE__)
# ... [redacted]
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-next-core-0.10.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/anyway_config-2.0.6/lib"

I can see from Bundler's code base that for standalone mode, the gems' lib path are always relative paths to the setup.rb file

As such, $LOAD_PATH can have gem files' lib path to have non-fully-resolved paths (e.g., /../).

I was wondering if ruby-next's language/setup.rb should try to resolve path first before comparison?
https://github.com/ruby-next/ruby-next/blob/master/lib/ruby-next/language/setup.rb#L51-L53

-     current_index = $LOAD_PATH.index(dirname)
+     current_index = $LOAD_PATH.map { |p| Pathname.new(p).cleanpath.to_s }.index(dirname)

      raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?

Sorry for the long explanation, but this was coming from a discussion here:
palkan/anyway_config#71

Non-obvious syntax error

Unfortunately, I'm sure about this issue. Does it belong to ruby-next or to parser? That's why it's a feedback.

Ruby 3.1.2

we have this 2 lines

some_spec.rb

mock_board_api_request board, query: # line 5
mock_lists_api_request lists, query: # line 6

Definitely, it's syntax error. We have this error message with ruby-next. It doesn't have info about file and line with syntax error.

SyntaxError:
  unexpected token tIDENTIFIER
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language/parser.rb:43:in `rescue in parse'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language/parser.rb:36:in `parse'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language.rb:160:in `block in rewrite'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language.rb:156:in `each'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language.rb:156:in `inject'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language.rb:156:in `rewrite'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language.rb:103:in `transform'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language/runtime.rb:34:in `transform'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language/runtime.rb:25:in `load'
# /home/pavel/.rvm/gems/ruby-3.1.2/gems/ruby-next-core-0.15.3/lib/ruby-next/language/runtime.rb:112:in `load'
# ------------------
# --- Caused by: ---
# Parser::SyntaxError:
#   unexpected token tIDENTIFIER
#   /home/pavel/.rvm/gems/ruby-3.1.2/gems/parser-3.1.3.0/lib/parser/diagnostic/engine.rb:72:in `process'

Without ruby-next we have this message. Looks like it's better message

some_spec.rb:6: syntax error, unexpected string literal, expecting `do' or '{' or '('

Is it abour ruby-next or about parser?

An option to rewrite file inplace for `ruby-next nextify`

From #100 (comment)

It'd be really useful for experiments and in some usages to be able to just replace the files and e.g. desugar the pattern matching in them.

For instance if the files are tracked in git anyway, the easiest way to run locally some Ruby files using pattern matching would be to use this inplace rewrite, and then just throw away the changes when running on a Ruby which supports pattern matching.

Another example is if tests use pattern matching, it'd be convenient to just rewrite those tests inplace, and run them on a Ruby not supporting pattern matching.

It would also be convenient to benchmark desugared vs original for pattern matching.

Data#initialize doesn't support positional args

Hi @palkan,

I've noticed an inconsistency in the recent ruby-next release regarding the polyfill for Data classes.

While standard Ruby Data classes support initialization using both *args and **kwargs, the polyfill appears to only support **kwargs

def initialize(**kwargs)

I receive smth like this in my mock gem ruby-2.* CI builds

I'm eager to contribute a fix for this issue. However, I'm uncertain about the testing strategy for this behavior. I see some related specs

it "accepts positional arguments" do
data = DataSpecs::Measure.new(42, "km")
data.amount.should == 42
data.unit.should == "km"
end
it "accepts alternative positional arguments" do
data = DataSpecs::Measure[42, "km"]
data.amount.should == 42
data.unit.should == "km"
end
, but I'm not sure if they are enabled. Any guidance regarding it would be helpful πŸ™

Thank you!

ruby-next causes random segmentation faults in Ruby 2.7.x

What did you do?

Included the anyway_config gem into a Rails project.

What did you expect to happen?

It should not segfault.

What actually happened?

Segfaults happen randomly in code unrelated to anyway_config.

Additional context

I've uploaded a reproduction script to the Ruby bug tracker.

The segfaults do not happen using anyway_config v2.0.0.pre2, and do happen using v2.0.0.rc1. The newer version introduced ruby-next, so I'm assuming that is the cause.

Only happens on Ruby 2.7.x. Ruby 3.0.3 and 3.1.1 both appear to be ok.

I'm not sure if you'll be able to fix this or if it's a Ruby core bug, but I thought I would report it here anyway.

Environment

Ruby version: 2.7.5

Ruby Next version: ruby-next-core (0.14.1)

Parser version: parser (3.1.1.0)

Unparser version: not used.

$LOAD_PATH issue with RVM - JRuby

Hello,

I just ran into a troubling issue and tracked it back to here. I guess it could also be RVMβ€―issue.

What did you do?

When I did bundle install and bundle exec puma my app could not start on a brand new VM.
I got an error:

RuntimeError: Gem's lib is not in the $LOAD_PATH: /home/vagrant/.rvm/rubies/jruby-9.2.9.0/lib/ruby/gems/shared/gems/anyway_config-2.0.6/lib 

Additional context

Here is the error:

Puma starting in single mode...
* Version 5.0.4 (jruby 9.2.9.0 - ruby 2.5.7), codename: Spoony Bard
* Min threads: 0, max threads: 16
* Environment: development
! Unable to load application: RuntimeError: Gem's lib is not in the $LOAD_PATH: /home/vagrant/.rvm/rubies/jruby-9.2.9.0/lib/ruby/gems/shared/gems/anyway_config-2.0.6/lib

Solution

I traced back the problem using prints:β€―

"Looking at /home/vagrant/.rvm/rubies/jruby-9.2.9.0/lib/ruby/gems/shared/gems/anyway_config-2.0.6/lib in [\"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/bundler-2.2.18/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/websocket-1.2.8/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/slim-4.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/sinatra-contrib-2.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/sinatra-2.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/ruby-next-0.10.5/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/unparser-0.5.7/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/ruby-next-parser-3.0.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/redis-4.2.3/lib\",
 \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/rack-protection-2.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/rack-cors-1.1.1/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/rack-2.2.3/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/puma-5.0.4-java/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/parser-3.0.1.1/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/nio4r-2.5.4-java/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/mustermann-1.1.1/lib\",
 \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/ruby2_keywords-0.0.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/multi_json-1.15.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/mprelude-0.1.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/procto-0.0.3/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/litecable-0.7.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/haml-5.2.1/lib\", 
\"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/tilt-2.0.10/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/temple-0.8.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/diff-lcs-1.4.4/lib\", 
\"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/concord-0.1.6/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/ast-2.4.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/anyway_config-2.0.6/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0@global/gems/ruby-next-core-0.10.5/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/anima-0.3.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/equalizer-0.0.11/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/adamantium-0.2.0/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/memoizable-0.4.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/thread_safe-0.3.6-java/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/ice_nine-0.11.2/lib\", \"/home/vagrant/.rvm/gems/jruby-9.2.9.0/gems/abstract_type-0.0.7/lib\", \"/home/vagrant/.rvm/rubies/jruby-9.2.9.0/lib/ruby/2.5/site_ruby\", \"/home/vagrant/.rvm/rubies/jruby-9.2.9.0/lib/ruby/stdlib\"]"

I found out that I had two current gemsets and gems were not all available the $LOAD_PATH.

Thanks to StackOverflow MPapis’ answer:
https://stackoverflow.com/questions/12307217/rvm-list-all-gems-in-current-gemset-ignoring-global-default/16968309

I just found out that I had two environment variables: GEM_PATH and GEM_HOME and by tweaking them I solved my issue.

$ echo $GEM_HOME
> /home/vagrant/.rvm/gems/jruby-9.2.9.0
$ echo $GEM_PATH
> /home/vagrant/.rvm/gems/jruby-9.2.9.0:/home/vagrant/.rvm/gems/jruby-9.2.9.0@global
$ GEM_PATH=$GEM_HOME bundle install 

This installed the Gems using only one GEM_PATH entry, so now all the gems are available to Ruby-Next.

I did not try to solve the issue by patching, but I suppose it is not that complicated. Here is a solution for people encountering the same issueβ€―!β€―

Environment

Ruby version: JRuby 9.2.9.0 (2.5.7), OpenJDK

Ruby Next version: 0.10.5

Parser version: 3.0.1.1

Unparser version: 0.5.7

`TypeError: no implicit conversion from nil to integer` when `$LOAD_PATH` contains symlinks

What did you do?

Rake rake -T (or anything evokes bundler), when ruby-next's entry in $LOAD_PATH includes a symlink.

What did you expect to happen?

I expect the command to complete successfully without errors.

What actually happened?

TypeError: no implicit conversion from nil to integer @ lib/ruby-next/setup_self.rb#L17

Additional context

There are a couple places where ruby-next hunts for itself (or another gem) inside of $LOAD_PATH; however, the path it searches for is actually its realpath. If its entry in $LOAD_PATH includes a symlink, it will fail to find itself, resulting in an error.

Example

Say $LOAD_PATH == ["/srv/my-server/ruby/gems/2.6.0/gems/ruby-next-core-0.12.0/lib"], but /srv/my-server/ruby is a symlink to /var/lib/ruby.

Then the line lib_path = File.realpath(File.join(__dir__, "..")) (from setup_self.rb) will return /var/lib/ruby/gems/2.6.0/gems/ruby-next-core-0.12.0/lib. Then the next line ($LOAD_PATH.index(lib_path)) will return nil.

Potential solutions

I'm too inexperienced with this code to know what an ideal solution would be, but I would try:

  • Don't realise symlinks. Instead of File.realpath(File.join(__dir__, "..")), just do File.join(__dir__, "..").
  • When searching $LOAD_PATH, realise all its entries first. I have an example of this below.

Lame proof-of-concept patch

The very-naive patch below "fixes" this bug, but it's pretty lame. I would submit a pull-request, but I'm not familiar enough with this code not to make a mess of it. Hopefully this is enough to give you some ideas on a proper fix.

diff --git a/lib/ruby-next/language/setup.rb b/lib/ruby-next/language/setup.rb
index dc0f266..eee9667 100644
--- a/lib/ruby-next/language/setup.rb
+++ b/lib/ruby-next/language/setup.rb
@@ -49,7 +49,7 @@ module RubyNext
 
         GemTranspiler.maybe_transpile(File.dirname(dirname), lib_dir, next_dirname) if transpile
 
-        current_index = $LOAD_PATH.find_index do |load_path|
+        current_index = $LOAD_PATH.map { |p| File.realpath(p) rescue p }.find_index do |load_path|
           Pathname.new(load_path).cleanpath.to_s == dirname
         end
 
diff --git a/lib/ruby-next/setup_self.rb b/lib/ruby-next/setup_self.rb
index c745cbf..ce2a07d 100644
--- a/lib/ruby-next/setup_self.rb
+++ b/lib/ruby-next/setup_self.rb
@@ -6,7 +6,7 @@
 version = RubyNext.next_ruby_version
 next_dirname = File.join(__dir__, "..", ".rbnext")
 lib_path = File.realpath(File.join(__dir__, ".."))
-current_index = $LOAD_PATH.index(lib_path)
+current_index = $LOAD_PATH.map { |p| File.realpath(p) rescue p }.index(lib_path)
 
 loop do
   break unless version

Environment

Ruby version: 2.6.6

Ruby Next version: v0.12.0

Parser version: I'm not sure what this is.

Unparser version: My apologies, but I don't know what this refers to either.

Support for Ruby 2.4

I’m lead maintainer of https://github.com/fastlane/fastlane and I’m so excited about this. I’ve been wanting to use features from newer Ruby versions for so long but not able to since we have been supporting Ruby 2.1 and up for so long.

We have just recently bumped up our minimum to Ruby 2.4 and will probably keep at this for as long as we can. I totally want to respect your time but I am curious in what the effort level would be to support add support down to Ruby 2.4.

I really appreciate all the work you’ve done with this. I’m looking forward to adding this to fastlane as soon as we are able to 😊

Failed to transpile ./lib/syntax_tree/node.rb: SyntaxError β€” unexpected token tRBRACK

What did you do?

git clone https://github.com/kddnewton/syntax_tree.git
cd syntax_tree
git checkout cb72efc779aa8e9fdc812fc81b495c7998332f21
bundle

gem install ruby-next
ruby-next -V nextify ./lib

which shows:

RubyNext core strategy: refine
RubyNext transpile mode: rewrite
Remove old files: ./lib/.rbnext
Generated: ./lib/.rbnext/2.3/syntax_tree/basic_visitor.rb
Generated: ./lib/.rbnext/2.3/syntax_tree/cli.rb
Generated: ./lib/.rbnext/2.3/syntax_tree/formatter.rb
Generated: ./lib/.rbnext/2.7/syntax_tree/language_server/inlay_hints.rb
Generated: ./lib/.rbnext/2.7/syntax_tree/language_server.rb
Failed to transpile ./lib/syntax_tree/node.rb: SyntaxError β€” unexpected token tRBRACK

Additional Context

ruby-syntax-tree/syntax_tree#168

$ ruby -rparser/current -rbenchmark -e 'pp Parser::CurrentRuby.parse(File.read("./lib/syntax_tree/node.rb"))'

does work, so it seems parser can parse it.

Environment

Ruby version: I tried CRuby 3.0 and 3.1

Ruby Next version: 0.15.2

Parser version: 3.1.2.1

Unparser version: 0.6.5

Failed to transpile: SyntaxError β€” unexpected token tLCURLY

What did you do?

8b.rb is from https://github.com/eregon/adventofcode/blob/a5fea350d30370c6317e2f365338030ef096cbf6/2020/8b.rb

$ ruby -v 8b.rb
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
8b.rb:7: warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!
8b.rb:32: warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!
761
$ ruby-next -V --single-version --no-refine nextify 8b.rb
RubyNext core strategy: core_ext
RubyNext transpile mode: rewrite
Failed to transpile 8b.rb: SyntaxError β€” unexpected token tLCURLY

The &. of line 41 seems the issue, removing the & lets it parse.

Environment

Ruby version: 2.7.2
Ruby Next version: 0.11.1

Transpiling 2i to 2.i

I'm trying to run some small scripts on Ruby 2.0.0 (to compare performance), and one issue I'm seeing is that something like 1 + 2i doesn't parse on 2.0.
The 2i notation was added in Ruby 2.1.

Do you think this is something that would make sense to support in ruby-next?

Note that I don't request support for Ruby 2.0, just transpiling 2i to 2.i.
I actually got some script using case in working on 2.0 with:

ruby-next core_ext -o ruby-next.rb
ruby-next -V --min-version=2.0 nextify script.rb
ruby -r./backport.rb -r./ruby-next.rb .rbnext/2.7/script.rb

Support passing rewriters to CLI

Related to #41.

ruby-next nextify --rewrite=...

Currently, we can only specify the target Ruby version for transpiling. It works great for MRI-compatible Rubies but could be not so useful for others (e.g., mruby).

To make UX better, we can provide an alternative way of specifying the target: passing rewriters explicitly. For example:

ruby-next nextify ./lib --rewrite=pattern-matching --rewrite=endless-method

NOTE: --single-version is enforced in that case.

ruby-next --list-rewriters

In addition to that, we also need a helper command: ruby-next nextify --list-rewriters. It could be used to show the list of available rewriters:

$ ruby-next nextify --list-rewriters

endless-range ("[0, 1][1..]")
endless-method ("obj = Object.new; def obj.foo() = 42")
numbered-params ("proc { _1 }.call(1)")
pattern-matching ("case 0; in 0; true; else; 1; end")
...

We can use class names as identifiers (class_name == id.split("-").map(&:capitalize).join) and SYNTAX_PROBE as examples in the output.

NOTE: --min-version couldn't be used simultaneously with --rewrite.

Incorrect translation of &. evaluating LHS twice

test.rb:

p Object.new&.object_id
$ ruby-next -V --min-version=2.2.0 nextify test.rb         
RubyNext core strategy: refine
RubyNext transpile mode: rewrite
Remove old files: ./.rbnext
Generated: ./.rbnext/2.3/test.rb
$ cat ./.rbnext/2.3/test.rb
p ((!Object.new.nil? || nil) && Object.new.object_id)

But the LHS, Object.new should only be evaluated once.

Environment

Ruby version: 2.7

Ruby Next version: 0.11.1

Symbol arrays with percent notation

What did you do?

Trying to rewrite a Symbol array using percent notation, from this

%i[loader @loading]

to

%i[loader @loading_rewritten]

What actually happened?

The source before rewriting seems to replace the array with a few weird characters...

%i_A5Π―_ing]

and that is before anything is rewritten.

Additional context

Using this code to debug...

def safe_rewrite(source)
  pp source
  pp source.include?('%i[loader @loading]')
end

The include? call always returns false, even though the %i[loader @loading] exists in the source.

thx.

Rewrite not persisted

What did you do?

I have a custom rewriter...

require 'ruby-next/language'

module Proscenium
  module CssModule
    class Rewriter < RubyNext::Language::Rewriters::Text
      NAME = 'proscenium-css-module'

      def safe_rewrite(source)
        source.gsub(/:@([\w_]+)/) do |_|
          context.track! self

          match = ::Regexp.last_match(1)
          pp "Proscenium::CssModule::Name.new(:@#{match}, css_module(:#{match}))"
        end
      end
    end
  end
end

RubyNext::Language.rewriters << Proscenium::CssModule::Rewriter
require 'ruby-next/language/runtime'

What did you expect to happen?

This should replace :@something with Proscenium::CssModule::Name.new(:@something, css_module(:something)), and it does work on one of my Rails apps, but not in another. There are obviously differences between the two apps, but they are many and numerous, so it's impossible to run a like for like comparison.

It's almost like something else is reverting the rewrite somewhere.

What actually happened?

With a bit of puts logging, I can see that the rewriter is being called and is returning the "Proscenium::CssModule::Name.new(:@#{match}, css_module(:#{match}))" string. But when the page is rendered, the rewrite is somehow reverted.

So this...

:@breadcrumbs

Gets logged as...

Proscenium::CssModule::Name.new(:@breadcrumbs, css_module(:breadcrumbs))

but is rendered as

:@breadcrumbs

as if it was unchanged.

Environment

Ruby version: 3.2.2

thx

Problems with TargetRubyVersion under both standardrb and RuboCop

What did you do?

Attempted to Use Ruby-Next With Standardrb

With standardrb, I get an ArgumentError exception that says "Malformed version number string" when using "next" as the ruby_version as described in the documentation.

# .standard.yml
require:
  - ruby-next/rubocop
ruby_version: 'next'
parallel: true
ignore:
  - 'lib/.rbnext/**/*'
$ standardrb
Users/$USER/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/rubygems/version.rb:215:in `initialize': Malformed version number string next (ArgumentError)
from /Users/$USER/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/rubygems/version.rb:206:in `new'
from /Users/$USER/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/rubygems/version.rb:206:in `new'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/gems/standard-1.3.0/lib/standard/loads_yaml_config.rb:27:in `construct_config'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/gems/standard-1.3.0/lib/standard/loads_yaml_config.rb:12:in `call'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/gems/standard-1.3.0/lib/standard/builds_config.rb:19:in `call'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/gems/standard-1.3.0/lib/standard/cli.rb:13:in `run'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/gems/standard-1.3.0/exe/standardrb:7:in `<top (required)>'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/bin/standardrb:23:in `load'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/bin/standardrb:23:in `<main>'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/bin/ruby_executable_hooks:22:in `eval'
from /Users/$USER/.rvm/gems/ruby-2.7.3@tmo_ops_utils/bin/ruby_executable_hooks:22:in `<main>'

Attempted to Use Ruby-Next With RuboCop v1.20.0 and the Standardrb Configuration

# .rubocop.yml
require:
  - ruby-next/rubocop
  - standard

inherit_gem:
  standard: config/base.yml

AllCops:
  TargetRubyVersion: 'next'
  Exclude:
    - "lib/.rbnext/**/*"
# note that there was much trimming of output, which is quite verbose under rubocop,
# but it completed without actual exceptions.

$ rubocop
foo.gemspec:16:32: C: Gemspec/RequiredRubyVersion: required_ruby_version (2.7, declared in foo.gemspec) and TargetRubyVersion (3.01, which may be specified in .rubocop.yml) should be equal.
# almost (or possibly all) Lint/Syntax warnings:
(Using Ruby 3.01 parser; configure using TargetRubyVersion parameter, under AllCops`)

Please note that commenting out the inherit_gem directive and the standardrb YAML didn't change the result in any way, except to make RuboCop complain about more things.

What did you expect to happen?

  1. I expected standardrb to run with ruby_version: 'next' defined, similar to what's supposed to happen with RuboCop, without raising exceptions.
  2. I expected RuboCop not to complain about TargetRubyVersion when set to next per the README.
  3. I expected ruby-next to handle the use case where the gemspec specifies a minimum Ruby version without complaint, since Ruby-Next is in the runtime dependencies and should handle it smoothly.

What actually happened?

  1. I got exceptions when running standardrb.
  2. I got lots of version mismatch complaints from RuboCop when run directly.

Additional Comments

The addition or removal of additional RuboCop gems like rubocop-rake and rubocop-spec made no difference to the end result in any runs. The same errors were raised. However, it's not clear from the documentation how, where, or if additional RuboCop gems should be required within the YAML files.

Environment

  • macOS 11.6
  • GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)
  • RVM running Ruby 2.7.3p183
  • bundler 2.1.4
  • bundled gems include:
    • parser 3.0.2.0
    • rubocop 1.20.0
    • rubocop-rake 0.6.0
    • rubocop-rspec 2.5.0
    • ruby-next 0.13.1
    • ruby-next-core 0.13.1
    • standard 1.3.0
    • unparser 0.6.0

IRB/Pry support

It would be great to have runtime transpiling support for IRB/Pry.

For example:

$ irb
> x = 1
> y = 2
> d = {x:, y:}
{x: 1, y: 2}

Thoughts on implementation

It looks that the only way to do that is to monkey-patch eval somewhere inside the console code.

For example, IRB uses Workspace#evaluate: https://github.com/ruby/irb/blob/5af637b3c17f85c15a32416bc5b4579307873833/lib/irb/workspace.rb#L118

In order to activate the monkey-patch, we can use configuration files: .irbrc and .pryrc.

The open question is how to include refinements (using RubyNext); can we call binding.eval("using RubyNext") πŸ€”

Support for Ruby 2.2

Please, open an issue if you would like us to support older Ruby versions.

First of all, thanks for the hard work everyone! I'm super excited for this project. Unfortunately I'm stuck with Ruby 2.2 (MRI) at work. Would love to see support for our very old version.

Any plans to support rubies as far back as 2.2?

String#bytesplice

Introduced in Ruby 3.2. I came here looking for a polyfill so I can support it in my library πŸ˜….

Facing issues in new builds impacting production env

What did you do?

Done nothing, no code or config changes done.

What did you expect to happen?

Expected to easily deploy a new image given that there is NO change set in the associated repository/code.

What actually happened?

Getting unknown errors involving below 2 main problems, thereby not enabling a normal deployment with the same code:

  1. /usr/local/bundle/gems/ruby-next-core-1.0.0/lib/.rbnext/2.6/ruby-next/core/data.rb:5: warning: constant ::Data is deprecated
  2. /usr/local/bundle/gems/redis-rack-3.0.0/lib/rack/session/redis.rb:8:in module:Session': uninitialized constant Rack::Session::Abstract::PersistedSecure (NameError)`

Additional context

Upon deploying the previously stable image of our code (as of 25 September 2023), the k8s pods are successfully running. But, we are stuck here because we aren't able to push new changes to our stable code as the same stable code only is now NOT able to provide a stable service upon trying out a fresh deployment.
Below is the error that we are getting, causing the associated K8s service pod in CrashLoopBackOff state:
image
Adding the same error in text form below:
/usr/local/bundle/gems/ruby-next-core-1.0.0/lib/.rbnext/2.6/ruby-next/core/data.rb:5: warning: constant ::Data is deprecated /usr/local/bundle/gems/ruby-next-core-1.0.0/lib/.rbnext/2.6/ruby-next/core/data.rb:5: warning: constant ::Data is deprecated /usr/local/bundle/gems/ruby-next-core-1.0.0/lib/.rbnext/2.6/ruby-next/core/data.rb:5: warning: constant ::Data is deprecated /usr/local/bundle/gems/redis-rack-3.0.0/lib/rack/session/redis.rb:8:inmodule:Session': uninitialized constant Rack::Session::Abstract::PersistedSecure (NameError)
Did you mean? Rack::Session::Abstract::Persisted
from /usr/local/bundle/gems/redis-rack-3.0.0/lib/rack/session/redis.rb:7:in <module:Rack>' from /usr/local/bundle/gems/redis-rack-3.0.0/lib/rack/session/redis.rb:6:in <top (required)>'
from /usr/local/bundle/gems/redis-rack-3.0.0/lib/redis-rack.rb:3:in require' from /usr/local/bundle/gems/redis-rack-3.0.0/lib/redis-rack.rb:3:in <top (required)>'
from /usr/local/bundle/gems/redis-actionpack-5.4.0/lib/action_dispatch/middleware/session/redis_store.rb:4:in require' from /usr/local/bundle/gems/redis-actionpack-5.4.0/lib/action_dispatch/middleware/session/redis_store.rb:4:in <top (required)>'
from /usr/local/bundle/gems/redis-actionpack-5.4.0/lib/redis-actionpack.rb:4:in require' from /usr/local/bundle/gems/redis-actionpack-5.4.0/lib/redis-actionpack.rb:4:in <top (required)>'
from /usr/local/bundle/gems/redis-rails-5.0.2/lib/redis-rails.rb:4:in require' from /usr/local/bundle/gems/redis-rails-5.0.2/lib/redis-rails.rb:4:in <top (required)>'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:81:in require' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:81:in block (2 levels) in require'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:76:in each' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:76:in block in require'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:65:in each' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/runtime.rb:65:in require'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler.rb:114:in require' from /src/config/application.rb:7:in <top (required)>'
from /src/config/environment.rb:2:in require_relative' from /src/config/environment.rb:2:in <top (required)>'
from config.ru:3:in require_relative' from config.ru:3:in block in

'
from /usr/local/bundle/gems/rack-2.0.7/lib/rack/builder.rb:55:in instance_eval' from /usr/local/bundle/gems/rack-2.0.7/lib/rack/builder.rb:55:in initialize'
from config.ru:in new' from config.ru:in '
from /usr/local/bundle/gems/rack-2.0.7/lib/rack/builder.rb:49:in eval' from /usr/local/bundle/gems/rack-2.0.7/lib/rack/builder.rb:49:in new_from_string'
from /usr/local/bundle/gems/rack-2.0.7/lib/rack/builder.rb:40:in parse_file' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/configuration.rb:320:in load_rackup'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/configuration.rb:245:in app' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/runner.rb:157:in app'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/runner.rb:164:in start_server' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:275:in worker'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:139:in block (2 levels) in spawn_workers' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:139:in fork'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:139:in block in spawn_workers' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:135:in times'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:135:in spawn_workers' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:213:in check_workers'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cluster.rb:486:in run' from /usr/local/bundle/gems/puma-3.12.1/lib/puma/launcher.rb:186:in run'
from /usr/local/bundle/gems/puma-3.12.1/lib/puma/cli.rb:80:in run' from /usr/local/bundle/gems/puma-3.12.1/bin/puma:10:in <top (required)>'
from /usr/local/bundle/bin/puma:23:in load' from /usr/local/bundle/bin/puma:23:in <top (required)>'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:74:in load' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:74:in kernel_load'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli/exec.rb:28:in run' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:463:in exec'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/command.rb:27:in run' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/invocation.rb:126:in invoke_command'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor.rb:387:in dispatch' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:27:in dispatch'
from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/vendor/thor/lib/thor/base.rb:466:in start' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/cli.rb:18:in start'
from /usr/local/bin/bundle:30:in block in <main>' from /usr/local/lib/ruby/site_ruby/2.5.0/bundler/friendly_errors.rb:124:in with_friendly_errors'
from /usr/local/bin/bundle:22:in <main>'

Environment

Ruby version: 2.5.3

Ruby Next version: Unknown because we aren't directly using it via the Gemfile. It seems some other library is indirectly using it so we can't confirm the version.

Parser version: Unknown because we aren't directly using it via the Gemfile.

Unparser version: Unknown because we aren't directly using it via the Gemfile.

Works, but raises SyntaxError with JRuby 9.2.19.0 and OpenJDK

What did you do?

  1. Installed JRuby-9.2.19.0
  2. Installed Ruby-Next v0.13.1
  3. Ran the same endless def you have in the quickstart section of your README.

What did you expect to happen?

For the code to print πŸ‘½ without errors.

What actually happened?

The code appeared to execute properly, in the sense that the endless def printed the correct value, but JRuby still raised:

SyntaxError: -e:2: syntax error, unexpected '='
def greet(val) =
               ^
πŸ‘½

Environment

jruby --version

jruby 9.2.19.0 (2.5.8) 2021-06-15 55810c552b OpenJDK 64-Bit Server VM 17+0 on 17+0 +jit [darwin-x86_64]

jruby -e 'puts RUBY_VERSION'

2.5.8

gem list ruby-next

*** LOCAL GEMS ***

ruby-next (0.13.1)
ruby-next-core (0.13.1)
ruby-next-parser (3.0.1.0)

gem list unparser

*** LOCAL GEMS ***

unparser (0.6.0)

java --version

openjdk 17 2021-09-14
OpenJDK Runtime Environment Homebrew (build 17+0)
OpenJDK 64-Bit Server VM Homebrew (build 17+0, mixed mode, sharing)

uname -v

Darwin Kernel Version 20.6.0: Mon Aug 30 06:12:21 PDT 2021; root:xnu-7195.141.6~3/RELEASE_X86_64

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.