ruby-next-core 0.7.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 293ae08c0e4ae536624da190361e2825a5b17ee9c0589626540c05ff87e6ef0a
4
- data.tar.gz: 45da1dcbc37d0c87c7f47cb300c38540dcd04085d8a85298747ca27e1c1ae99f
3
+ metadata.gz: 31bef7eea12c9a2c8b04f3daa62816e96b764bcbaf7cfc0f01d4bf1b040f34b9
4
+ data.tar.gz: 793834fef3c0a00944b7026a975f7ee4b97219ae42b2b3ebfb5327d4a3e5311c
5
5
  SHA512:
6
- metadata.gz: 2e93d7c73f2d90cdc5005c62d85aef1c55fdc05fc8ae333a05749518e0a61e4d6317d378d9df39ed5e4afc8ee2c6853f3fac714ad6751a7204c75f9a0754e244
7
- data.tar.gz: '053942e5c78cac2092a36491dc36d54e1f83aaa9a91dc4ecc021dfdc9c8c62572a549c063d1df70d618094694c1f8c4d13b69d3a50727551184a3bc5a38c40be'
6
+ metadata.gz: 690f42ac5954bbe4b2c6a0e4ee0a05f4842c87a4ade5bccb3f7be90d76b92df89e715a07c785f5286bb4137ac3357fe4341e90b41f010db8716fab5a9bdcfb83
7
+ data.tar.gz: 48bd02bbd66656cd9892cad1c8fe767c205bfba5eb47c59cfacdf3060af3c7cf886fbba6396283f3e19eb1da66cd53e9b6a5125595ec694cd8c37ad6ecb4e0f1
@@ -2,6 +2,41 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.9.2 (2020-06-24)
6
+
7
+ - Support passing rewriters to CLI. ([@sl4vr][])
8
+
9
+ Use `nextify --list-rewriters` to view all available rewriters.
10
+ Use `nextify` with `--rewrite=REWRITERS...` option to specify which particular rewriters to use.
11
+
12
+ ## 0.9.1 (2020-06-05)
13
+
14
+ - Keep `ruby-next` version in sync with `ruby-next-core`. ([@palkan][])
15
+
16
+ Require `ruby-next-core` of the same version as `ruby-next`.
17
+
18
+ ## 0.9.0 (2020-06-04)
19
+
20
+ - Add Ruby 2.3 support. ([@palkan][])
21
+
22
+ - Remove stale transpiled files when running `ruby-next nextify`. ([@palkan][])
23
+
24
+ - Add Ruby 2.4 support. ([@palkan][])
25
+
26
+ APIs for <2.5 must be backported via [backports][] gem. Refinements are not supported.
27
+
28
+ ## 0.8.0 (2020-05-01) 🚩
29
+
30
+ - Add right-hand assignment support. ([@palkan][])
31
+
32
+ It is real: `13.divmod(5) => a, b`.
33
+
34
+ - Add endless methods support. ([@palkan][])
35
+
36
+ Now you can write `def foo() = :bar`.
37
+
38
+ ## 0.7.0 (2020-04-29)
39
+
5
40
  - Try to auto-transpile the source code on load in `.setup_gem_load_path` if transpiled files are missing. ([@palkan][])
6
41
 
7
42
  This would make it possible to install gems from source if transpiled files do not exist in the repository.
@@ -166,3 +201,5 @@ p a #=> 1
166
201
  - Add `Kernel#then`. ([@palkan][])
167
202
 
168
203
  [@palkan]: https://github.com/palkan
204
+ [backports]: https://github.com/marcandre/backports
205
+ [@sl4vr]: https://github.com/sl4vr
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
+ [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/ruby-next-cli-rewriters.html#task)
1
2
  [![Gem Version](https://badge.fury.io/rb/ruby-next.svg)](https://rubygems.org/gems/ruby-next) [![Build](https://github.com/ruby-next/ruby-next/workflows/Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions)
2
- [![JRuby Build](https://github.com/ruby-next/ruby-next/workflows/JRuby%20Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions)
3
+ [![JRuby Build](https://github.com/ruby-next/ruby-next/workflows/JRuby%20Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions?query=workflow%3A%22TruffleRuby+Build%22)
4
+ [![TruffleRuby Build](https://github.com/ruby-next/ruby-next/workflows/TruffleRuby%20Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions?query=workflow%3A%22TruffleRuby+Build%22)
3
5
 
4
6
  # Ruby Next
5
7
 
@@ -17,13 +19,29 @@ Who might be interested in Ruby Next?
17
19
  Ruby Next also aims to help the community to assess new, _experimental_, MRI features by making it easier to play with them.
18
20
  That's why Ruby Next implements the `master` features as fast as possible.
19
21
 
22
+ Read more about the motivation behind the Ruby Next in this post: [Ruby Next: Make all Rubies quack alike](https://evilmartians.com/chronicles/ruby-next-make-all-rubies-quack-alike).
23
+
20
24
  <a href="https://evilmartians.com/?utm_source=ruby-next">
21
25
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
22
26
 
23
- ## Links
27
+ ## Posts
28
+
29
+ - [Ruby Next: Make all Rubies quack alike](https://evilmartians.com/chronicles/ruby-next-make-all-rubies-quack-alike)
30
+
31
+ ## Talks
24
32
 
25
33
  - [Ruby Next: Make old Rubies quack like a new one](https://noti.st/palkan/j3i2Dr/ruby-next-make-old-rubies-quack-like-a-new-one) (RubyConf 2019)
26
34
 
35
+ ## Examples
36
+
37
+ - Ruby gems
38
+ - [anyway_config](https://github.com/palkan/anyway_config)
39
+ - [graphql-fragment_cache](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache)
40
+ - mruby
41
+ - [ACLI](https://github.com/palkan/acli)
42
+
43
+ _Please, submit a PR to add your project to the list!_
44
+
27
45
  ## Table of contents
28
46
 
29
47
  - [Overview](#overview)
@@ -38,6 +56,7 @@ That's why Ruby Next implements the `master` features as fast as possible.
38
56
  - [`ruby -ruby-next`](#uby-next)
39
57
  - [Logging & Debugging](#logging-and-debugging)
40
58
  - [RuboCop](#rubocop)
59
+ - [Using with EOL Rubies](#using-with-eol-rubies)
41
60
  - [Proposed & edge features](#proposed-and-edge-features)
42
61
 
43
62
  ## Overview
@@ -49,8 +68,9 @@ Core provides **polyfills** for Ruby core classes APIs via Refinements (default
49
68
  Language is responsible for **transpiling** edge Ruby syntax into older versions. It could be done
50
69
  programmatically or via CLI. It also could be done in runtime.
51
70
 
52
- Currently, Ruby Next supports Ruby versions 2.5+ (including JRuby 9.2.8+).
53
- Please, [open an issue](https://github.com/ruby-next/ruby-next/issues/new/choose) if you would like us to support older Ruby versions.
71
+ Currently, Ruby Next supports Ruby versions 2.3+, including JRuby 9.2.8+ and TruffleRuby 20.1+ (with some limitations). Support for EOL versions (<2.5) slightly differs though ([see below](#using-with-eol-rubies)).
72
+
73
+ Please, [open an issue](https://github.com/ruby-next/ruby-next/issues/new/choose) or join the discussion in the existing ones if you would like us to support older Ruby versions.
54
74
 
55
75
  ## Quick start
56
76
 
@@ -62,12 +82,16 @@ $ gem install ruby-next
62
82
 
63
83
  # Call ruby with -ruby-next flag
64
84
  $ ruby -ruby-next -e "
65
- case {hello: 'martian'}
66
- in hello: hello if hello =~ /human/
67
- puts '🙂'
68
- in hello: 'martian'
69
- puts '👽'
70
- end
85
+ def greet(val) =
86
+ case val
87
+ in hello: hello if hello =~ /human/i
88
+ '🙂'
89
+ in hello: 'martian'
90
+ '👽'
91
+ end
92
+
93
+ greet(hello: 'martian') => greeting
94
+ puts greeting
71
95
  "
72
96
 
73
97
  => 👽
@@ -115,6 +139,8 @@ The following _rule of thumb_ is recommended when choosing between refinements a
115
139
  - 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 🙂)
116
140
  - Use core extensions if refinements are not supported by your platform
117
141
 
142
+ **NOTE:** TruffleRuby doesn't fully support refinements (refining modules is not supported as of v20.1.0).
143
+
118
144
  [**The list of supported APIs.**][features_core]
119
145
 
120
146
  ## Transpiling
@@ -160,6 +186,8 @@ You can change the transpiler mode:
160
186
  - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=rewrite`.
161
187
  - Via CLI option ([see below](#cli)).
162
188
 
189
+ **NOTE:** For the time being, Unparser [doesn't support](https://github.com/mbj/unparser/pull/142) new Ruby 2.7 AST nodes, so we always use rewrite mode in Ruby 2.7+.
190
+
163
191
  ## CLI
164
192
 
165
193
  Ruby Next ships with the command-line interface (`ruby-next`) which provides the following functionality:
@@ -173,12 +201,15 @@ It has the following interface:
173
201
  ```sh
174
202
  $ ruby-next nextify
175
203
  Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
176
- -o, --output=OUTPUT Specify output directory or file or stdout (use -o stdout for that)
204
+ -o, --output=OUTPUT Specify output directory or file or stdout
177
205
  --min-version=VERSION Specify the minimum Ruby version to support
178
206
  --single-version Only create one version of a file (for the earliest Ruby version)
179
- --enable-method-reference Enable reverted method reference syntax (requires custom parser)
207
+ --edge Enable edge (master) Ruby features
208
+ --proposed Enable proposed/experimental Ruby features
180
209
  --transpile-mode=MODE Transpiler mode (ast or rewrite). Default: ast
181
210
  --[no-]refine Do not inject `using RubyNext`
211
+ --list-rewriters List available rewriters
212
+ --rewrite=REWRITERS... Specify particular Ruby features to rewrite
182
213
  -h, --help Print help
183
214
  -V Turn on verbose mode
184
215
  --dry-run Print verbose output without generating files
@@ -291,17 +322,17 @@ If you're using [runtime mode](#runtime-usage) a long with `setup_gem_load_path`
291
322
 
292
323
  \* 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` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
293
324
 
294
- ### Transpiled files vs. VSC vs. installing from source
325
+ ### Transpiled files vs. VCS vs. installing from source
295
326
 
296
327
  It's a best practice to not keep generated files in repositories. In case of Ruby Next, it's a `lib/.rbnext` folder.
297
328
 
298
- 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 VSC (e.g., `echo ".rbnext/" >> .gitignore`). That would make transpiled files available in releases without polluting your repository.
329
+ 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.
299
330
 
300
- What if someone decides to install your gem from the VSC source? They would likely face some syntax errors due to the missing transpiled files.
331
+ 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.
301
332
 
302
333
  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:
303
334
 
304
- - We in the Bundler context (since that's the most common way of installing gems from source).
335
+ - We are in the Bundler context (since that's the most common way of installing gems from source).
305
336
  - Our Gemfile contains `ruby-next` gem.
306
337
  - We use [`.rbnextrc`](#CLI-configuration-file) for transpiling options.
307
338
 
@@ -366,6 +397,12 @@ RUBYOPT="-ruby-next" ruby my_ruby_script.rb
366
397
  ruby -ruby-next -e "puts [2, 4, 5].tally"
367
398
  ```
368
399
 
400
+ **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.:
401
+
402
+ ```sh
403
+ ruby -ruby-next -r my_ruby_script.rb -e "puts my_method"
404
+ ```
405
+
369
406
  ## Logging and debugging
370
407
 
371
408
  Ruby Next prints some debugging information when fails to load a file in the runtime mode (and fallbacks to the built-in loading mechanism).
@@ -400,6 +437,38 @@ AllCops:
400
437
 
401
438
  **NOTE:** you need `ruby-next` gem available in the environment where you run RuboCop (having `ruby-next-core` is not enough).
402
439
 
440
+ ## Using with EOL Rubies
441
+
442
+ We currently provide support for Ruby 2.3 and 2.4. Work on 2.2 is in progress.
443
+
444
+ Ruby Next itself relies on 2.5 features and contains polyfills only for version 2.5+ (and that won't change).
445
+ Thus, to make it work with <2.5 we need to backport some APIs ourselves.
446
+
447
+ The recommended way of doing this is to use [backports][] gem. You need to load backports **before Ruby Next**.
448
+
449
+ When using runtime features, you should do the following:
450
+
451
+ ```ruby
452
+ # first, require backports upto 2.5
453
+ require "backports/2.5"
454
+ # then, load Ruby Next
455
+ require "ruby-next"
456
+ # if you need 2.6+ APIs, add Ruby Next core_ext
457
+ require "ruby-next/core_ext"
458
+ # then, load runtime transpiling
459
+ require "ruby-next/language/runtime"
460
+ # or
461
+ require "ruby-next/language/bootsnap"
462
+ ```
463
+
464
+ To load backports while using `ruby-next nextify` command, you must configure the environment variable:
465
+
466
+ ```sh
467
+ RUBY_NEXT_CORE_STRATEGY=backports ruby-next nextify lib/
468
+ ```
469
+
470
+ **NOTE:** Make sure you have `backports` gem installed globally or added to your bundle (if you're using `bundle exec ruby-next ...`).
471
+
403
472
  ## Proposed and edge features
404
473
 
405
474
  Ruby Next aims to bring edge and proposed features to Ruby community before they (hopefully) reach an official Ruby release.
@@ -431,11 +500,13 @@ require "ruby-next/language/runtime"
431
500
 
432
501
  ### Supported edge features
433
502
 
434
- Not yet.
503
+ - "Endless" method definition (`def foo() = 42`) ([#16746](https://bugs.ruby-lang.org/issues/16746)).
504
+
505
+ - Right-hand assignment (`13.divmod(5) => a,b`) ([#15921](https://bugs.ruby-lang.org/issues/15921))
435
506
 
436
507
  ### Supported proposed features
437
508
 
438
- - _Method reference_ operator (`.:`) ([link](https://bugs.ruby-lang.org/issues/13581)).
509
+ - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
439
510
 
440
511
  ## Contributing
441
512
 
@@ -462,3 +533,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
462
533
  [next_parser]: https://github.com/ruby-next/parser
463
534
  [Bootsnap]: https://github.com/Shopify/bootsnap
464
535
  [rubocop]: https://github.com/rubocop-hq/rubocop
536
+ [backports]: https://github.com/marcandre/backports
@@ -5,11 +5,11 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require "bundler/setup"
7
7
 
8
- require "ruby-next/language"
8
+ ENV["RUBY_NEXT_EDGE"] = "1"
9
+ ENV["RUBY_NEXT_PROPOSED"] = "1"
9
10
 
10
- # optional parsers
11
- require "ruby-next/language/rewriters/method_reference"
12
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
11
+ require "ruby-next/language"
12
+ require "ruby-next/language/rewriters/runtime"
13
13
 
14
14
  contents =
15
15
  if File.exist?(ARGV[0])
@@ -18,4 +18,11 @@ contents =
18
18
  ARGV[0]
19
19
  end
20
20
 
21
- puts RubyNext::Language.transform(contents)
21
+ opts =
22
+ if ARGV[1] && ARGV[1] == "--current"
23
+ {rewriters: RubyNext::Language.current_rewriters}
24
+ else
25
+ {}
26
+ end
27
+
28
+ puts RubyNext::Language.transform(contents, **opts)
@@ -11,10 +11,10 @@ module RubyNext
11
11
 
12
12
  # Defines last minor version for every major version
13
13
  LAST_MINOR_VERSIONS = {
14
- 2 => 7
14
+ 2 => 8
15
15
  }.freeze
16
16
 
17
- LATEST_VERSION = [2, 7].freeze
17
+ LATEST_VERSION = [2, 8].freeze
18
18
 
19
19
  class << self
20
20
  def next_version(version = RUBY_VERSION)
@@ -34,5 +34,6 @@ module RubyNext
34
34
  end
35
35
 
36
36
  require_relative "ruby-next/core"
37
+ require_relative "ruby-next/core_ext" if RubyNext::Core.core_ext?
37
38
  require_relative "ruby-next/logging"
38
39
  end
@@ -69,7 +69,7 @@ module RubyNext
69
69
  RubyNext::Core.patches.extensions
70
70
  .values
71
71
  .flatten
72
- .filter do |patch|
72
+ .select do |patch|
73
73
  next if min_version && Gem::Version.new(patch.version) <= min_version
74
74
  next if filter && !filter.match?(patch.name)
75
75
  true
@@ -10,12 +10,16 @@ module RubyNext
10
10
  class Nextify < Base
11
11
  using RubyNext
12
12
 
13
- attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
13
+ attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters
14
14
 
15
15
  def run
16
16
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
17
17
  log "RubyNext transpile mode: #{RubyNext::Language.mode}"
18
18
 
19
+ remove_rbnext!
20
+
21
+ @min_version ||= MIN_SUPPORTED_VERSION
22
+
19
23
  paths.each do |path|
20
24
  contents = File.read(path)
21
25
  transpile path, contents
@@ -24,7 +28,8 @@ module RubyNext
24
28
 
25
29
  def parse!(args)
26
30
  print_help = false
27
- @min_version = MIN_SUPPORTED_VERSION
31
+ print_rewriters = false
32
+ rewriter_names = []
28
33
  @single_version = false
29
34
 
30
35
  optparser = base_parser do |opts|
@@ -61,6 +66,14 @@ module RubyNext
61
66
  Core.strategy = :core_ext unless val
62
67
  end
63
68
 
69
+ opts.on("--list-rewriters", "List available rewriters") do |val|
70
+ print_rewriters = true
71
+ end
72
+
73
+ opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
74
+ rewriter_names << val
75
+ end
76
+
64
77
  opts.on("-h", "--help", "Print help") do
65
78
  print_help = true
66
79
  end
@@ -75,12 +88,35 @@ module RubyNext
75
88
  exit 0
76
89
  end
77
90
 
91
+ if print_rewriters
92
+ Language.rewriters.each do |rewriter|
93
+ $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
94
+ end
95
+ exit 0
96
+ end
97
+
78
98
  unless lib_path&.then(&File.method(:exist?))
79
99
  $stdout.puts "Path not found: #{lib_path}"
80
100
  $stdout.puts optparser.help
81
101
  exit 2
82
102
  end
83
103
 
104
+ if rewriter_names.any? && min_version
105
+ $stdout.puts "--rewrite cannot be used with --min-version simultaneously"
106
+ exit 2
107
+ end
108
+
109
+ @specified_rewriters =
110
+ if rewriter_names.any?
111
+ begin
112
+ Language.select_rewriters(*rewriter_names)
113
+ rescue Language::RewriterNotFoundError => error
114
+ $stdout.puts error.message
115
+ $stdout.puts "Try --list-rewriters to see list of available rewriters"
116
+ exit 2
117
+ end
118
+ end
119
+
84
120
  @paths =
85
121
  if File.directory?(lib_path)
86
122
  Dir[File.join(lib_path, "**/*.rb")]
@@ -94,9 +130,10 @@ module RubyNext
94
130
  private
95
131
 
96
132
  def transpile(path, contents, version: min_version)
97
- rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
133
+ rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
98
134
 
99
135
  context = Language::TransformContext.new
136
+
100
137
  new_contents = Language.transform contents, context: context, rewriters: rewriters
101
138
 
102
139
  return unless context.dirty?
@@ -114,25 +151,17 @@ module RubyNext
114
151
  end
115
152
 
116
153
  def save(contents, path, version)
117
- return $stdout.puts(contents) if out_path == "stdout"
154
+ return $stdout.puts(contents) if stdout?
118
155
 
119
156
  paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
120
157
 
121
158
  paths.unshift(version.segments[0..1].join(".")) unless single_version?
122
159
 
123
160
  next_path =
124
- if out_path
125
- if out_path.end_with?(".rb")
126
- out_path
127
- else
128
- File.join(out_path, *paths)
129
- end
161
+ if next_dir_path.end_with?(".rb")
162
+ out_path
130
163
  else
131
- File.join(
132
- lib_path,
133
- RUBY_NEXT_DIR,
134
- *paths
135
- )
164
+ File.join(next_dir_path, *paths)
136
165
  end
137
166
 
138
167
  unless CLI.dry_run?
@@ -144,7 +173,26 @@ module RubyNext
144
173
  log "Generated: #{next_path}"
145
174
  end
146
175
 
147
- alias single_version? single_version
176
+ def remove_rbnext!
177
+ return if CLI.dry_run? || stdout?
178
+
179
+ return unless File.directory?(next_dir_path)
180
+
181
+ log "Remove old files: #{next_dir_path}"
182
+ FileUtils.rm_r(next_dir_path)
183
+ end
184
+
185
+ def next_dir_path
186
+ @next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
187
+ end
188
+
189
+ def stdout?
190
+ out_path == "stdout"
191
+ end
192
+
193
+ def single_version?
194
+ single_version || specified_rewriters
195
+ end
148
196
  end
149
197
  end
150
198
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "set"
4
4
 
5
+ require_relative "utils"
6
+
5
7
  module RubyNext
6
8
  module Core
7
9
  # Patch contains the extension implementation
@@ -95,7 +97,7 @@ module RubyNext
95
97
  end
96
98
 
97
99
  class << self
98
- STRATEGIES = %i[refine core_ext].freeze
100
+ STRATEGIES = %i[refine core_ext backports].freeze
99
101
 
100
102
  attr_reader :strategy
101
103
 
@@ -109,7 +111,11 @@ module RubyNext
109
111
  end
110
112
 
111
113
  def core_ext?
112
- strategy == :core_ext
114
+ strategy == :core_ext || strategy == :backports
115
+ end
116
+
117
+ def backports?
118
+ strategy == :backports
113
119
  end
114
120
 
115
121
  def patch(*args, **kwargs, &block)
@@ -136,6 +142,8 @@ module RubyNext
136
142
  end
137
143
  end
138
144
 
145
+ require "backports/2.5" if RubyNext::Core.backports?
146
+
139
147
  require_relative "core/kernel/then"
140
148
 
141
149
  require_relative "core/proc/compose"
@@ -176,6 +184,9 @@ require_relative "core/struct/deconstruct_keys"
176
184
  # Generate refinements
177
185
  RubyNext.module_eval do
178
186
  RubyNext::Core.patches.refined.each do |mod, patches|
187
+ # Only refine modules when supported
188
+ next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
189
+
179
190
  refine mod do
180
191
  patches.each do |patch|
181
192
  module_eval(patch.body, *patch.location)
@@ -3,7 +3,8 @@
3
3
  RubyNext::Core.patch Time, method: :ceil, version: "2.7" do
4
4
  <<~'RUBY'
5
5
  def ceil(den = 0)
6
- change = subsec.ceil(den) - subsec
6
+ sceil = (subsec * 10**den).ceil.to_r / 10**den
7
+ change = sceil - subsec
7
8
  self + change
8
9
  end
9
10
  RUBY
@@ -15,4 +15,4 @@ RubyNext::Core.patches.extensions.each do |mod, patches|
15
15
  end
16
16
  end
17
17
 
18
- RubyNext::Core.strategy = :core_ext
18
+ RubyNext::Core.strategy = :core_ext unless RubyNext::Core.core_ext?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- gem "ruby-next-parser", ">= 2.8.0.0"
3
+ gem "ruby-next-parser", ">= 2.8.0.3"
4
4
  gem "unparser", ">= 0.4.7"
5
5
 
6
6
  require "set"
@@ -23,6 +23,8 @@ module RubyNext
23
23
  require "ruby-next/language/parser"
24
24
  require "ruby-next/language/unparser"
25
25
 
26
+ RewriterNotFoundError = Class.new(StandardError)
27
+
26
28
  class TransformContext
27
29
  attr_reader :versions, :use_ruby_next
28
30
 
@@ -83,6 +85,8 @@ module RubyNext
83
85
  end
84
86
 
85
87
  def runtime!
88
+ require "ruby-next/language/rewriters/runtime"
89
+
86
90
  @runtime = true
87
91
  end
88
92
 
@@ -96,6 +100,13 @@ module RubyNext
96
100
  else
97
101
  regenerate(*args, **kwargs)
98
102
  end
103
+ rescue Unparser::UnknownEmitterError
104
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
105
+ RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 2.7+ AST yet.\n" \
106
+ "See https://github.com/mbj/unparser/pull/142"
107
+ self.mode = :rewrite
108
+ end
109
+ rewrite(*args, **kwargs)
99
110
  end
100
111
 
101
112
  def regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
@@ -141,6 +152,16 @@ module RubyNext
141
152
  @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
142
153
  end
143
154
 
155
+ # This method guarantees that rewriters will be returned in order they defined in Language module
156
+ def select_rewriters(*names)
157
+ rewriters_delta = names - rewriters.map { |rewriter| rewriter::NAME }
158
+ if rewriters_delta.any?
159
+ raise RewriterNotFoundError, "Rewriters not found: #{rewriters_delta.join(",")}"
160
+ end
161
+
162
+ rewriters.select { |rewriter| names.include?(rewriter::NAME) }
163
+ end
164
+
144
165
  private
145
166
 
146
167
  attr_writer :watch_dirs
@@ -1,3 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load edge Ruby features
4
+
5
+ require "ruby-next/language/rewriters/endless_method"
6
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
7
+
8
+ require "ruby-next/language/rewriters/right_hand_assignment"
9
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::RightHandAssignment
@@ -3,14 +3,16 @@
3
3
  module RubyNext
4
4
  module Language
5
5
  module KernelEval
6
- refine Kernel do
7
- def eval(source, bind = nil, *args)
8
- new_source = ::RubyNext::Language::Runtime.transform(
9
- source,
10
- using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
11
- )
12
- RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
13
- super new_source, bind, *args
6
+ if Utils.refine_modules?
7
+ refine Kernel do
8
+ def eval(source, bind = nil, *args)
9
+ new_source = ::RubyNext::Language::Runtime.transform(
10
+ source,
11
+ using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
12
+ )
13
+ RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
14
+ super new_source, bind, *args
15
+ end
14
16
  end
15
17
  end
16
18
  end
@@ -6,6 +6,9 @@ module RubyNext
6
6
  module Language
7
7
  class Builder < ::Parser::Builders::Default
8
8
  modernize
9
+
10
+ # FIXME: add support for 2.8 forward args
11
+ self.emit_forward_arg = false
9
12
  end
10
13
 
11
14
  class << self
@@ -4,6 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module Rewriters
6
6
  class ArgsForward < Base
7
+ NAME = "args-forward"
7
8
  SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
8
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
9
10
 
@@ -25,21 +26,23 @@ module RubyNext
25
26
  end
26
27
 
27
28
  def on_send(node)
28
- return unless node.children[2]&.type == :forwarded_args
29
+ return super(node) unless node.children[2]&.type == :forwarded_args
29
30
 
30
31
  replace(node.children[2].loc.expression, "*#{REST}, &#{BLOCK}")
31
32
 
32
- node.updated(
33
- nil,
34
- [
35
- *node.children[0..1],
36
- *forwarded_args
37
- ]
33
+ process(
34
+ node.updated(
35
+ nil,
36
+ [
37
+ *node.children[0..1],
38
+ *forwarded_args
39
+ ]
40
+ )
38
41
  )
39
42
  end
40
43
 
41
44
  def on_super(node)
42
- return unless node.children[0]&.type == :forwarded_args
45
+ return super(node) unless node.children[0]&.type == :forwarded_args
43
46
 
44
47
  replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
45
48
 
@@ -59,7 +59,7 @@ module RubyNext
59
59
  def unsupported_syntax?
60
60
  save_verbose, $VERBOSE = $VERBOSE, nil
61
61
  eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
62
- Kernel.send eval_mid, self::SYNTAX_PROBE
62
+ Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
63
63
  false
64
64
  rescue SyntaxError, NameError
65
65
  true
@@ -78,10 +78,6 @@ module RubyNext
78
78
  def transform(source)
79
79
  Language.transform(source, rewriters: [self], using: false)
80
80
  end
81
-
82
- def warn_custom_parser_required_for(feature)
83
- RubyNext.warn(CUSTOM_PARSER_REQUIRED % feature)
84
- end
85
81
  end
86
82
 
87
83
  attr_reader :locals
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class EndlessMethod < Base
7
+ NAME = "endless-method"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
10
+
11
+ def on_def_e(node)
12
+ context.track! self
13
+
14
+ replace(node.loc.assignment, "; ")
15
+ insert_after(node.loc.expression, "; end")
16
+
17
+ process(
18
+ node.updated(
19
+ :def,
20
+ node.children
21
+ )
22
+ )
23
+ end
24
+
25
+ def on_defs_e(node)
26
+ context.track! self
27
+
28
+ replace(node.loc.assignment, "; ")
29
+ insert_after(node.loc.expression, "; end")
30
+
31
+ process(
32
+ node.updated(
33
+ :defs,
34
+ node.children
35
+ )
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,6 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module Rewriters
6
6
  class EndlessRange < Base
7
+ NAME = "endless-range"
7
8
  SYNTAX_PROBE = "[0, 1][1..]"
8
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")
9
10
 
@@ -4,6 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module Rewriters
6
6
  class MethodReference < Base
7
+ NAME = "method-reference"
7
8
  SYNTAX_PROBE = "Language.:transform"
8
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
9
10
 
@@ -6,6 +6,7 @@ module RubyNext
6
6
  class NumberedParams < Base
7
7
  using RubyNext
8
8
 
9
+ NAME = "numbered-params"
9
10
  SYNTAX_PROBE = "proc { _1 }.call(1)"
10
11
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
11
12
 
@@ -216,6 +216,7 @@ module RubyNext
216
216
  end
217
217
 
218
218
  class PatternMatching < Base
219
+ NAME = "pattern-matching"
219
220
  SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
220
221
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
221
222
 
@@ -816,11 +817,12 @@ module RubyNext
816
817
  end
817
818
 
818
819
  def respond_to_missing?(mid, *)
819
- return true if mid.match?(/_(clause|array_element)/)
820
+ return true if mid.to_s.match?(/_(clause|array_element)/)
820
821
  super
821
822
  end
822
823
 
823
824
  def method_missing(mid, *args, &block)
825
+ mid = mid.to_s
824
826
  return case_eq_clause(*args) if mid.match?(/_clause$/)
825
827
  return case_eq_array_element(*args) if mid.match?(/_array_element$/)
826
828
  return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class RightHandAssignment < Base
7
+ NAME = "right-hand-assignment"
8
+ SYNTAX_PROBE = "1 + 2 => a"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
10
+
11
+ def on_rasgn(node)
12
+ context.track! self
13
+
14
+ val_node, asgn_node = *node
15
+
16
+ remove(val_node.loc.expression.end.join(asgn_node.loc.expression))
17
+ insert_before(val_node.loc.expression, "#{asgn_node.loc.expression.source} = ")
18
+
19
+ process(
20
+ asgn_node.updated(
21
+ nil,
22
+ asgn_node.children + [val_node]
23
+ )
24
+ )
25
+ end
26
+
27
+ def on_mrasgn(node)
28
+ context.track! self
29
+
30
+ lhs, rhs = *node
31
+
32
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
33
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
34
+
35
+ process(
36
+ node.updated(
37
+ :masgn,
38
+ [rhs, lhs]
39
+ )
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load rewriters only required for runtime transpiling
4
+
5
+ require "ruby-next/language/rewriters/runtime/dir"
6
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::Dir
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ # Special rewriter for Ruby <=2.3, which doesn't support __dir__ in iseq.eval
7
+ class Dir < Base
8
+ SYNTAX_PROBE = "defined?(RubyVM::InstructionSequence) && RubyVM::InstructionSequence.compile('raise SyntaxError if __dir__.nil?', 'test.rb').eval"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.4.0")
10
+
11
+ def on_send(node)
12
+ return super(node) unless node.children[1] == :__dir__
13
+
14
+ context.track! self
15
+
16
+ replace(node.loc.expression, "File.dirname(__FILE__)")
17
+
18
+ process(
19
+ node.updated(
20
+ nil,
21
+ [
22
+ s(:const, nil, :File),
23
+ :dirname,
24
+ s(:send, nil, "__FILE__")
25
+ ]
26
+ )
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -42,7 +42,7 @@ module RubyNext
42
42
  path
43
43
  end
44
44
 
45
- if defined?(JRUBY_VERSION)
45
+ if defined?(JRUBY_VERSION) || defined?(TruffleRuby)
46
46
  def evaluate(code, filepath)
47
47
  new_toplevel.eval(code, filepath)
48
48
  end
@@ -89,7 +89,8 @@ module Kernel
89
89
 
90
90
  alias_method :require_relative_without_ruby_next, :require_relative
91
91
  def require_relative(path)
92
- from = caller_locations(1..1).first.absolute_path || File.join(Dir.pwd, "main")
92
+ loc = caller_locations(1..1).first
93
+ from = loc.absolute_path || loc.path || File.join(Dir.pwd, "main")
93
94
  realpath = File.absolute_path(
94
95
  File.join(
95
96
  File.dirname(File.absolute_path(from)),
@@ -6,3 +6,17 @@ require "parser/current"
6
6
  $VERBOSE = save_verbose
7
7
 
8
8
  require "unparser"
9
+
10
+ unless defined?(Unparser::UnknownEmitterError)
11
+ module Unparser
12
+ class UnknownEmitterError < ArgumentError
13
+ end
14
+
15
+ Emitter.singleton_class.prepend(Module.new do
16
+ def emitter(node, parent)
17
+ raise UnknownEmitterError, "No emitter for node: #{node.type.inspect}" unless Emitter::REGISTRY.key?(node.type)
18
+ super
19
+ end
20
+ end)
21
+ end
22
+ end
@@ -84,6 +84,32 @@ module RuboCop
84
84
  send(:"on_#{child.type}", child)
85
85
  end
86
86
  end
87
+
88
+ unless method_defined?(:on_def_e)
89
+ def on_def_e(node)
90
+ _name, _args_node, body_node = *node
91
+ send(:"on_#{body_node.type}", body_node)
92
+ end
93
+
94
+ def on_defs_e(node)
95
+ _definee_node, _name, _args_node, body_node = *node
96
+ send(:"on_#{body_node.type}", body_node)
97
+ end
98
+ end
99
+
100
+ unless method_defined?(:on_rasgn)
101
+ def on_rasgn(node)
102
+ val_node, asgn_node = *node
103
+ send(:"on_#{asgn_node.type}", asgn_node)
104
+ send(:"on_#{val_node.type}", val_node)
105
+ end
106
+
107
+ def on_mrasgn(node)
108
+ lhs, rhs = *node
109
+ send(:"on_#{lhs.type}", lhs)
110
+ send(:"on_#{rhs.type}", rhs)
111
+ end
112
+ end
87
113
  end
88
114
  end
89
115
  end
@@ -33,5 +33,35 @@ module RubyNext
33
33
  lines.unshift " 0: # source: #{path}"
34
34
  end
35
35
  end
36
+
37
+ # Returns true if modules refinement is supported in current version
38
+ def refine_modules?
39
+ @refine_modules ||=
40
+ begin
41
+ # Make sure that including modules within refinements works
42
+ # See https://github.com/oracle/truffleruby/issues/2026
43
+ eval <<~RUBY, TOPLEVEL_BINDING, __FILE__, __LINE__ + 1
44
+ module RubyNext::Utils::A; end
45
+ class RubyNext::Utils::B
46
+ include RubyNext::Utils::A
47
+ end
48
+
49
+ using(Module.new do
50
+ refine RubyNext::Utils::A do
51
+ include(Module.new do
52
+ def i_am_refinement
53
+ "yes, you are!"
54
+ end
55
+ end)
56
+ end
57
+ end)
58
+
59
+ RubyNext::Utils::B.new.i_am_refinement
60
+ RUBY
61
+ true
62
+ rescue TypeError, NoMethodError
63
+ false
64
+ end
65
+ end
36
66
  end
37
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyNext
4
- VERSION = "0.7.0"
4
+ VERSION = "0.9.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-next-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-29 00:00:00.000000000 Z
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-parser
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.8.0.0
19
+ version: 2.8.0.7
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.8.0.0
26
+ version: 2.8.0.7
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: unparser
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -90,10 +90,14 @@ files:
90
90
  - lib/ruby-next/language/proposed.rb
91
91
  - lib/ruby-next/language/rewriters/args_forward.rb
92
92
  - lib/ruby-next/language/rewriters/base.rb
93
+ - lib/ruby-next/language/rewriters/endless_method.rb
93
94
  - lib/ruby-next/language/rewriters/endless_range.rb
94
95
  - lib/ruby-next/language/rewriters/method_reference.rb
95
96
  - lib/ruby-next/language/rewriters/numbered_params.rb
96
97
  - lib/ruby-next/language/rewriters/pattern_matching.rb
98
+ - lib/ruby-next/language/rewriters/right_hand_assignment.rb
99
+ - lib/ruby-next/language/rewriters/runtime.rb
100
+ - lib/ruby-next/language/rewriters/runtime/dir.rb
97
101
  - lib/ruby-next/language/runtime.rb
98
102
  - lib/ruby-next/language/setup.rb
99
103
  - lib/ruby-next/language/unparser.rb
@@ -119,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
123
  requirements:
120
124
  - - ">="
121
125
  - !ruby/object:Gem::Version
122
- version: 2.5.0
126
+ version: 2.3.0
123
127
  required_rubygems_version: !ruby/object:Gem::Requirement
124
128
  requirements:
125
129
  - - ">="