ruby-next-core 0.6.0 → 0.9.1

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: 5447b309915122d9f6dc9a366c34a566891c9f9cc569722c0999e020ef891d6b
4
- data.tar.gz: 9c2da837b1c9e4d2d36ea8665a90f7d3c004d6175b1d0e82262396846ac00f6a
3
+ metadata.gz: 5689b631e2b8dd6e9222a60b25434fcb323d008458342c395ff891015d9f651d
4
+ data.tar.gz: 6178612f34fbbe59ea3f3042fc62ea23cc3bede62a944e187791a7304164d819
5
5
  SHA512:
6
- metadata.gz: 7644e4703b233a730a3af183efaae78ea53cf5915ea70cc297ab8ebc8ff9346aad233c642cbf1d38ec82dc49deeab266e20ed35667e6c4069a7bb2f16b6c4e16
7
- data.tar.gz: 42fe6bf61bfd0cc97c1107455b873d97065eeb3f8c0c6ae3d7921b1fa208c155114e0e6012045d335fa31f1591093fabb58a3a2f1828c9fe668c2e798335ee80
6
+ metadata.gz: f24eef8dc28b0ad4e8f9d5073d1254f31111354291b9bfb60cb8c688fbf312f153ba8fe17886183798b9413280bac554f9012e2ec2b63d250f0bf3c34a3362eb
7
+ data.tar.gz: d9ab4a36680864b81cd545d438e001a51e4c6dfbca56336a971b557af50786b70c7b7f9eee37698fc0373480e56f51c153080b9e06ec218dc8d08e0da2dc79d6
@@ -2,6 +2,59 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.9.1 (2020-06-05)
6
+
7
+ - Keep `ruby-next` version in sync with `ruby-next-core`. ([@palkan][])
8
+
9
+ Require `ruby-next-core` of the same version as `ruby-next`.
10
+
11
+ ## 0.9.0 (2020-06-04)
12
+
13
+ - Add Ruby 2.3 support. ([@palkan][])
14
+
15
+ - Remove stale transpiled files when running `ruby-next nextify`. ([@palkan][])
16
+
17
+ - Add Ruby 2.4 support. ([@palkan][])
18
+
19
+ APIs for <2.5 must be backported via [backports][] gem. Refinements are not supported.
20
+
21
+ ## 0.8.0 (2020-05-01) 🚩
22
+
23
+ - Add right-hand assignment support. ([@palkan][])
24
+
25
+ It is real: `13.divmod(5) => a, b`.
26
+
27
+ - Add endless methods support. ([@palkan][])
28
+
29
+ Now you can write `def foo() = :bar`.
30
+
31
+ ## 0.7.0 (2020-04-29)
32
+
33
+ - Try to auto-transpile the source code on load in `.setup_gem_load_path` if transpiled files are missing. ([@palkan][])
34
+
35
+ This would make it possible to install gems from source if transpiled files do not exist in the repository.
36
+
37
+ - Use`./.rbnextrc` to define CLI args. ([@palkan][])
38
+
39
+ You can define CLI options in the configuration file to re-use them between environments or
40
+ simply avoid typing every time:
41
+
42
+ ```yml
43
+ # .rbnextrc
44
+ nextify: |
45
+ --transpiler-mode=rewrite
46
+ --edge
47
+ ```
48
+
49
+ - Add `--dry-run` option to CLI. ([@palkan][])
50
+
51
+ - Raise `SyntaxError` when parsing fails. ([@palkan][])
52
+
53
+ Previously, we let Parser to raise its `Parser::SyntaxError` but some exceptions
54
+ are not reported by Parser and should be handled by transpiler (and we raised `SyntaxError` in that case, as MRI does).
55
+
56
+ This change unifies the exceptions raised during transpiling.
57
+
5
58
  ## 0.6.0 (2020-04-23)
6
59
 
7
60
  - Changed the way edge/proposed features are activated. ([@palkan][])
@@ -141,3 +194,4 @@ p a #=> 1
141
194
  - Add `Kernel#then`. ([@palkan][])
142
195
 
143
196
  [@palkan]: https://github.com/palkan
197
+ [backports]: https://github.com/marcandre/backports
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  [![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)
2
+ [![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)
3
+ [![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
4
 
4
5
  # Ruby Next
5
6
 
@@ -17,13 +18,29 @@ Who might be interested in Ruby Next?
17
18
  Ruby Next also aims to help the community to assess new, _experimental_, MRI features by making it easier to play with them.
18
19
  That's why Ruby Next implements the `master` features as fast as possible.
19
20
 
21
+ 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).
22
+
20
23
  <a href="https://evilmartians.com/?utm_source=ruby-next">
21
24
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
22
25
 
23
- ## Links
26
+ ## Posts
27
+
28
+ - [Ruby Next: Make all Rubies quack alike](https://evilmartians.com/chronicles/ruby-next-make-all-rubies-quack-alike)
29
+
30
+ ## Talks
24
31
 
25
32
  - [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
33
 
34
+ ## Examples
35
+
36
+ - Ruby gems
37
+ - [anyway_config](https://github.com/palkan/anyway_config)
38
+ - [graphql-fragment_cache](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache)
39
+ - mruby
40
+ - [ACLI](https://github.com/palkan/acli)
41
+
42
+ _Please, submit a PR to add your project to the list!_
43
+
27
44
  ## Table of contents
28
45
 
29
46
  - [Overview](#overview)
@@ -38,6 +55,7 @@ That's why Ruby Next implements the `master` features as fast as possible.
38
55
  - [`ruby -ruby-next`](#uby-next)
39
56
  - [Logging & Debugging](#logging-and-debugging)
40
57
  - [RuboCop](#rubocop)
58
+ - [Using with EOL Rubies](#using-with-eol-rubies)
41
59
  - [Proposed & edge features](#proposed-and-edge-features)
42
60
 
43
61
  ## Overview
@@ -49,8 +67,9 @@ Core provides **polyfills** for Ruby core classes APIs via Refinements (default
49
67
  Language is responsible for **transpiling** edge Ruby syntax into older versions. It could be done
50
68
  programmatically or via CLI. It also could be done in runtime.
51
69
 
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.
70
+ 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)).
71
+
72
+ 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
73
 
55
74
  ## Quick start
56
75
 
@@ -62,12 +81,16 @@ $ gem install ruby-next
62
81
 
63
82
  # Call ruby with -ruby-next flag
64
83
  $ 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
84
+ def greet(val) =
85
+ case val
86
+ in hello: hello if hello =~ /human/i
87
+ '🙂'
88
+ in hello: 'martian'
89
+ '👽'
90
+ end
91
+
92
+ greet(hello: 'martian') => greeting
93
+ puts greeting
71
94
  "
72
95
 
73
96
  => 👽
@@ -115,6 +138,8 @@ The following _rule of thumb_ is recommended when choosing between refinements a
115
138
  - 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
139
  - Use core extensions if refinements are not supported by your platform
117
140
 
141
+ **NOTE:** TruffleRuby doesn't fully support refinements (refining modules is not supported as of v20.1.0).
142
+
118
143
  [**The list of supported APIs.**][features_core]
119
144
 
120
145
  ## Transpiling
@@ -160,6 +185,8 @@ You can change the transpiler mode:
160
185
  - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=rewrite`.
161
186
  - Via CLI option ([see below](#cli)).
162
187
 
188
+ **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+.
189
+
163
190
  ## CLI
164
191
 
165
192
  Ruby Next ships with the command-line interface (`ruby-next`) which provides the following functionality:
@@ -181,6 +208,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
181
208
  --[no-]refine Do not inject `using RubyNext`
182
209
  -h, --help Print help
183
210
  -V Turn on verbose mode
211
+ --dry-run Print verbose output without generating files
184
212
  ```
185
213
 
186
214
  The behaviour depends on whether you transpile a single file or a directory:
@@ -212,6 +240,7 @@ Usage: ruby-next core_ext [options]
212
240
  -n, --name=NAME Filter extensions by name
213
241
  -h, --help Print help
214
242
  -V Turn on verbose mode
243
+ --dry-run Print verbose output without generating files
215
244
  ```
216
245
 
217
246
  The most common use-case is to backport the APIs required by pattern matching. You can do this, for example,
@@ -238,7 +267,21 @@ $ ruby-next core_ext -l --name=filter --name=deconstruct
238
267
  - StructDeconstruct
239
268
  ```
240
269
 
241
- ### Integrating into a gem development
270
+ ### CLI configuration file
271
+
272
+ 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`.
273
+
274
+ Configuration file is a YAML with commands as keys and options as multiline strings:
275
+
276
+ ```yml
277
+ # ./.rbnextrc
278
+
279
+ nextify: |
280
+ --transpiler-mode=rewrite
281
+ --edge
282
+ ```
283
+
284
+ ## Integrating into a gem development
242
285
 
243
286
  We recommend _pre-transpiling_ source code to work with older versions before releasing it.
244
287
 
@@ -275,6 +318,24 @@ If you're using [runtime mode](#runtime-usage) a long with `setup_gem_load_path`
275
318
 
276
319
  \* 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).
277
320
 
321
+ ### Transpiled files vs. VCS vs. installing from source
322
+
323
+ It's a best practice to not keep generated files in repositories. In case of Ruby Next, it's a `lib/.rbnext` folder.
324
+
325
+ 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.
326
+
327
+ 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.
328
+
329
+ 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:
330
+
331
+ - We are in the Bundler context (since that's the most common way of installing gems from source).
332
+ - Our Gemfile contains `ruby-next` gem.
333
+ - We use [`.rbnextrc`](#CLI-configuration-file) for transpiling options.
334
+
335
+ If the command fails we warn the end user.
336
+
337
+ 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)`.
338
+
278
339
  ## Runtime usage
279
340
 
280
341
  It is also possible to transpile Ruby source code in run-time via Ruby Next.
@@ -332,6 +393,12 @@ RUBYOPT="-ruby-next" ruby my_ruby_script.rb
332
393
  ruby -ruby-next -e "puts [2, 4, 5].tally"
333
394
  ```
334
395
 
396
+ **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.:
397
+
398
+ ```sh
399
+ ruby -ruby-next -r my_ruby_script.rb -e "puts my_method"
400
+ ```
401
+
335
402
  ## Logging and debugging
336
403
 
337
404
  Ruby Next prints some debugging information when fails to load a file in the runtime mode (and fallbacks to the built-in loading mechanism).
@@ -364,6 +431,40 @@ AllCops:
364
431
  - 'lib/.rbnext/**/*'
365
432
  ```
366
433
 
434
+ **NOTE:** you need `ruby-next` gem available in the environment where you run RuboCop (having `ruby-next-core` is not enough).
435
+
436
+ ## Using with EOL Rubies
437
+
438
+ We currently provide support for Ruby 2.3 and 2.4. Work on 2.2 is in progress.
439
+
440
+ Ruby Next itself relies on 2.5 features and contains polyfills only for version 2.5+ (and that won't change).
441
+ Thus, to make it work with <2.5 we need to backport some APIs ourselves.
442
+
443
+ The recommended way of doing this is to use [backports][] gem. You need to load backports **before Ruby Next**.
444
+
445
+ When using runtime features, you should do the following:
446
+
447
+ ```ruby
448
+ # first, require backports upto 2.5
449
+ require "backports/2.5"
450
+ # then, load Ruby Next
451
+ require "ruby-next"
452
+ # if you need 2.6+ APIs, add Ruby Next core_ext
453
+ require "ruby-next/core_ext"
454
+ # then, load runtime transpiling
455
+ require "ruby-next/language/runtime"
456
+ # or
457
+ require "ruby-next/language/bootsnap"
458
+ ```
459
+
460
+ To load backports while using `ruby-next nextify` command, you must configure the environment variable:
461
+
462
+ ```sh
463
+ RUBY_NEXT_CORE_STRATEGY=backports ruby-next nextify lib/
464
+ ```
465
+
466
+ **NOTE:** Make sure you have `backports` gem installed globally or added to your bundle (if you're using `bundle exec ruby-next ...`).
467
+
367
468
  ## Proposed and edge features
368
469
 
369
470
  Ruby Next aims to bring edge and proposed features to Ruby community before they (hopefully) reach an official Ruby release.
@@ -395,11 +496,13 @@ require "ruby-next/language/runtime"
395
496
 
396
497
  ### Supported edge features
397
498
 
398
- Not yet.
499
+ - "Endless" method definition (`def foo() = 42`) ([#16746](https://bugs.ruby-lang.org/issues/16746)).
500
+
501
+ - Right-hand assignment (`13.divmod(5) => a,b`) ([#15921](https://bugs.ruby-lang.org/issues/15921))
399
502
 
400
503
  ### Supported proposed features
401
504
 
402
- - _Method reference_ operator (`.:`) ([link](https://bugs.ruby-lang.org/issues/13581)).
505
+ - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
403
506
 
404
507
  ## Contributing
405
508
 
@@ -426,3 +529,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
426
529
  [next_parser]: https://github.com/ruby-next/parser
427
530
  [Bootsnap]: https://github.com/Shopify/bootsnap
428
531
  [rubocop]: https://github.com/rubocop-hq/rubocop
532
+ [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
@@ -10,10 +10,14 @@ module RubyNext
10
10
  # Command line interface for RubyNext
11
11
  class CLI
12
12
  class << self
13
- attr_accessor :verbose
13
+ attr_accessor :verbose, :dry_run
14
+
15
+ alias verbose? verbose
16
+ alias dry_run? dry_run
14
17
  end
15
18
 
16
19
  self.verbose = false
20
+ self.dry_run = false
17
21
 
18
22
  COMMANDS = {
19
23
  "nextify" => Commands::Nextify,
@@ -36,6 +40,8 @@ module RubyNext
36
40
 
37
41
  args.delete(command)
38
42
 
43
+ args.unshift(*load_args_from_rc(command))
44
+
39
45
  COMMANDS.fetch(command) do
40
46
  raise "Unknown command: #{command}. Available commands: #{COMMANDS.keys.join(",")}"
41
47
  end.run(args)
@@ -89,5 +95,15 @@ module RubyNext
89
95
  end
90
96
  end
91
97
  end
98
+
99
+ def load_args_from_rc(command)
100
+ return [] unless File.file?(".rbnextrc")
101
+
102
+ require "yaml"
103
+ command_args = YAML.load_file(".rbnextrc")[command]
104
+ return [] unless command_args
105
+
106
+ command_args.lines.flat_map { |line| line.chomp.split(/\s+/) }
107
+ end
92
108
  end
93
109
  end
@@ -11,6 +11,9 @@ module RubyNext
11
11
  end
12
12
  end
13
13
 
14
+ attr_reader :dry_run
15
+ alias dry_run? dry_run
16
+
14
17
  def initialize(args)
15
18
  parse! args
16
19
  end
@@ -24,8 +27,13 @@ module RubyNext
24
27
  end
25
28
 
26
29
  def log(msg)
27
- return unless CLI.verbose
28
- $stdout.puts msg
30
+ return unless CLI.verbose?
31
+
32
+ if CLI.dry_run?
33
+ $stdout.puts "[DRY RUN] #{msg}"
34
+ else
35
+ $stdout.puts msg
36
+ end
29
37
  end
30
38
 
31
39
  def base_parser
@@ -35,6 +43,11 @@ module RubyNext
35
43
  opts.on("-V", "Turn on verbose mode") do
36
44
  CLI.verbose = true
37
45
  end
46
+
47
+ opts.on("--dry-run", "Print verbose output without generating files") do
48
+ CLI.dry_run = true
49
+ CLI.verbose = true
50
+ end
38
51
  end
39
52
  end
40
53
  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
@@ -136,8 +136,10 @@ module RubyNext
136
136
 
137
137
  return $stdout.puts(contents) if out_path == "stdout"
138
138
 
139
- FileUtils.mkdir_p File.dirname(out_path)
140
- File.write(out_path, contents)
139
+ unless CLI.dry_run?
140
+ FileUtils.mkdir_p File.dirname(out_path)
141
+ File.write(out_path, contents)
142
+ end
141
143
 
142
144
  log "Generated: #{out_path}"
143
145
  end
@@ -14,6 +14,10 @@ module RubyNext
14
14
 
15
15
  def run
16
16
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
17
+ log "RubyNext transpile mode: #{RubyNext::Language.mode}"
18
+
19
+ remove_rbnext!
20
+
17
21
  paths.each do |path|
18
22
  contents = File.read(path)
19
23
  transpile path, contents
@@ -64,6 +68,8 @@ module RubyNext
64
68
  end
65
69
  end
66
70
 
71
+ optparser.parse!(args)
72
+
67
73
  @lib_path = args[0]
68
74
 
69
75
  if print_help
@@ -72,12 +78,11 @@ module RubyNext
72
78
  end
73
79
 
74
80
  unless lib_path&.then(&File.method(:exist?))
81
+ $stdout.puts "Path not found: #{lib_path}"
75
82
  $stdout.puts optparser.help
76
83
  exit 2
77
84
  end
78
85
 
79
- optparser.parse!(args)
80
-
81
86
  @paths =
82
87
  if File.directory?(lib_path)
83
88
  Dir[File.join(lib_path, "**/*.rb")]
@@ -94,7 +99,13 @@ module RubyNext
94
99
  rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
95
100
 
96
101
  context = Language::TransformContext.new
97
- new_contents = Language.transform contents, context: context, rewriters: rewriters
102
+
103
+ new_contents =
104
+ if Gem::Version.new(version) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
105
+ Language.rewrite contents, context: context, rewriters: rewriters
106
+ else
107
+ Language.transform contents, context: context, rewriters: rewriters
108
+ end
98
109
 
99
110
  return unless context.dirty?
100
111
 
@@ -111,34 +122,45 @@ module RubyNext
111
122
  end
112
123
 
113
124
  def save(contents, path, version)
114
- return $stdout.puts(contents) if out_path == "stdout"
125
+ return $stdout.puts(contents) if stdout?
115
126
 
116
127
  paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
117
128
 
118
129
  paths.unshift(version.segments[0..1].join(".")) unless single_version?
119
130
 
120
131
  next_path =
121
- if out_path
122
- if out_path.end_with?(".rb")
123
- out_path
124
- else
125
- File.join(out_path, *paths)
126
- end
132
+ if next_dir_path.end_with?(".rb")
133
+ out_path
127
134
  else
128
- File.join(
129
- lib_path,
130
- RUBY_NEXT_DIR,
131
- *paths
132
- )
135
+ File.join(next_dir_path, *paths)
133
136
  end
134
137
 
135
- FileUtils.mkdir_p File.dirname(next_path)
138
+ unless CLI.dry_run?
139
+ FileUtils.mkdir_p File.dirname(next_path)
136
140
 
137
- File.write(next_path, contents)
141
+ File.write(next_path, contents)
142
+ end
138
143
 
139
144
  log "Generated: #{next_path}"
140
145
  end
141
146
 
147
+ def remove_rbnext!
148
+ return if CLI.dry_run? || stdout?
149
+
150
+ return unless File.directory?(next_dir_path)
151
+
152
+ log "Remove old files: #{next_dir_path}"
153
+ FileUtils.rm_r(next_dir_path)
154
+ end
155
+
156
+ def next_dir_path
157
+ @next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
158
+ end
159
+
160
+ def stdout?
161
+ out_path == "stdout"
162
+ end
163
+
142
164
  alias single_version? single_version
143
165
  end
144
166
  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"
@@ -83,6 +83,14 @@ module RubyNext
83
83
  end
84
84
 
85
85
  def runtime!
86
+ require "ruby-next/language/rewriters/runtime"
87
+
88
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
89
+ RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 2.7 AST yet.\n" \
90
+ "See https://github.com/mbj/unparser/pull/142"
91
+ self.mode = :rewrite
92
+ end
93
+
86
94
  @runtime = true
87
95
  end
88
96
 
@@ -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
@@ -23,6 +23,8 @@ module RubyNext
23
23
  end
24
24
 
25
25
  parser.parse(buffer)
26
+ rescue ::Parser::SyntaxError => e
27
+ raise ::SyntaxError, e.message
26
28
  end
27
29
 
28
30
  def parse_with_comments(source, file = "(string)")
@@ -31,6 +33,8 @@ module RubyNext
31
33
  end
32
34
 
33
35
  parser.parse_with_comments(buffer)
36
+ rescue ::Parser::SyntaxError => e
37
+ raise ::SyntaxError, e.message
34
38
  end
35
39
  end
36
40
  end
@@ -25,21 +25,23 @@ module RubyNext
25
25
  end
26
26
 
27
27
  def on_send(node)
28
- return unless node.children[2]&.type == :forwarded_args
28
+ return super(node) unless node.children[2]&.type == :forwarded_args
29
29
 
30
30
  replace(node.children[2].loc.expression, "*#{REST}, &#{BLOCK}")
31
31
 
32
- node.updated(
33
- nil,
34
- [
35
- *node.children[0..1],
36
- *forwarded_args
37
- ]
32
+ process(
33
+ node.updated(
34
+ nil,
35
+ [
36
+ *node.children[0..1],
37
+ *forwarded_args
38
+ ]
39
+ )
38
40
  )
39
41
  end
40
42
 
41
43
  def on_super(node)
42
- return unless node.children[0]&.type == :forwarded_args
44
+ return super(node) unless node.children[0]&.type == :forwarded_args
43
45
 
44
46
  replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
45
47
 
@@ -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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class EndlessMethod < Base
7
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
8
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
9
+
10
+ def on_def_e(node)
11
+ context.track! self
12
+
13
+ replace(node.loc.assignment, "; ")
14
+ insert_after(node.loc.expression, "; end")
15
+
16
+ process(
17
+ node.updated(
18
+ :def,
19
+ node.children
20
+ )
21
+ )
22
+ end
23
+
24
+ def on_defs_e(node)
25
+ context.track! self
26
+
27
+ replace(node.loc.assignment, "; ")
28
+ insert_after(node.loc.expression, "; end")
29
+
30
+ process(
31
+ node.updated(
32
+ :defs,
33
+ node.children
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -28,12 +28,6 @@ module RubyNext
28
28
  ]
29
29
  )
30
30
  end
31
-
32
- begin
33
- transform(SYNTAX_PROBE)
34
- rescue ::Parser::SyntaxError
35
- warn_custom_parser_required_for("method reference")
36
- end
37
31
  end
38
32
  end
39
33
  end
@@ -816,11 +816,12 @@ module RubyNext
816
816
  end
817
817
 
818
818
  def respond_to_missing?(mid, *)
819
- return true if mid.match?(/_(clause|array_element)/)
819
+ return true if mid.to_s.match?(/_(clause|array_element)/)
820
820
  super
821
821
  end
822
822
 
823
823
  def method_missing(mid, *args, &block)
824
+ mid = mid.to_s
824
825
  return case_eq_clause(*args) if mid.match?(/_clause$/)
825
826
  return case_eq_array_element(*args) if mid.match?(/_array_element$/)
826
827
  return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class RightHandAssignment < Base
7
+ SYNTAX_PROBE = "1 + 2 => a"
8
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")
9
+
10
+ def on_rasgn(node)
11
+ context.track! self
12
+
13
+ val_node, asgn_node = *node
14
+
15
+ remove(val_node.loc.expression.end.join(asgn_node.loc.expression))
16
+ insert_before(val_node.loc.expression, "#{asgn_node.loc.expression.source} = ")
17
+
18
+ process(
19
+ asgn_node.updated(
20
+ nil,
21
+ asgn_node.children + [val_node]
22
+ )
23
+ )
24
+ end
25
+
26
+ def on_mrasgn(node)
27
+ context.track! self
28
+
29
+ lhs, rhs = *node
30
+
31
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
32
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
33
+
34
+ process(
35
+ node.updated(
36
+ :masgn,
37
+ [rhs, lhs]
38
+ )
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ 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)),
@@ -5,6 +5,21 @@ require "ruby-next"
5
5
 
6
6
  module RubyNext
7
7
  module Language
8
+ # Module responsible for transpiling a library at load time
9
+ module GemTranspiler
10
+ def self.maybe_transpile(root_dir, lib_dir, target_dir)
11
+ return if File.directory?(target_dir)
12
+
13
+ Dir.chdir(root_dir) do
14
+ unless system("bundle exec ruby-next nextify ./#{lib_dir} -o #{target_dir} > /dev/null 2>&1")
15
+ RubyNext.warn "Traspiled files are missing in: #{target_dir}. \n" \
16
+ "Make sure you have gem 'ruby-next' in your Gemfile to auto-transpile the required files from source on load. " \
17
+ "Otherwise the code from #{root_dir} may not work correctly."
18
+ end
19
+ end
20
+ end
21
+ end
22
+
8
23
  class << self
9
24
  unless method_defined?(:runtime?)
10
25
  def runtime?
@@ -12,7 +27,7 @@ module RubyNext
12
27
  end
13
28
  end
14
29
 
15
- def setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR)
30
+ def setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR, transpile: false)
16
31
  called_from = caller_locations(1, 1).first.path
17
32
  dirname = File.dirname(called_from)
18
33
 
@@ -29,6 +44,10 @@ module RubyNext
29
44
 
30
45
  return if Language.runtime? && Language.watch_dirs.include?(dirname)
31
46
 
47
+ next_dirname = File.join(dirname, rbnext_dir)
48
+
49
+ GemTranspiler.maybe_transpile(File.dirname(dirname), lib_dir, next_dirname) if transpile
50
+
32
51
  current_index = $LOAD_PATH.index(dirname)
33
52
 
34
53
  raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?
@@ -38,7 +57,7 @@ module RubyNext
38
57
  loop do
39
58
  break unless version
40
59
 
41
- version_dir = File.join(dirname, rbnext_dir, version.segments[0..1].join("."))
60
+ version_dir = File.join(next_dirname, version.segments[0..1].join("."))
42
61
 
43
62
  if File.exist?(version_dir)
44
63
  $LOAD_PATH.insert current_index, version_dir
@@ -38,6 +38,6 @@ module RubyNext
38
38
  attr_reader :debug_filter
39
39
  end
40
40
 
41
- self.silence_warnings = ENV["RUBY_NEXT_WARN"] != "false"
41
+ self.silence_warnings = ENV["RUBY_NEXT_WARN"] == "false"
42
42
  self.debug_enabled = ENV["RUBY_NEXT_DEBUG"]
43
43
  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.6.0"
4
+ VERSION = "0.9.1"
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.6.0
4
+ version: 0.9.1
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-23 00:00:00.000000000 Z
11
+ date: 2020-06-05 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.6
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.6
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
  - - ">="