ruby-next-core 0.10.5 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +9 -14
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +1 -1
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +3 -3
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +19 -1
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +17 -14
- data/lib/.rbnext/2.7/ruby-next/core.rb +203 -0
- data/lib/ruby-next.rb +5 -38
- data/lib/ruby-next/commands/nextify.rb +1 -1
- data/lib/ruby-next/config.rb +45 -0
- data/lib/ruby-next/core.rb +5 -4
- data/lib/ruby-next/core/array/deconstruct.rb +1 -1
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
- data/lib/ruby-next/core_ext.rb +2 -0
- data/lib/ruby-next/language.rb +15 -4
- data/lib/ruby-next/language/bootsnap.rb +1 -1
- data/lib/ruby-next/language/edge.rb +0 -6
- data/lib/ruby-next/language/eval.rb +3 -3
- data/lib/ruby-next/language/parser.rb +19 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +8 -4
- data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
- data/lib/ruby-next/language/rewriters/base.rb +19 -1
- data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +17 -14
- data/lib/ruby-next/language/setup.rb +6 -3
- data/lib/ruby-next/language/unparser.rb +10 -0
- data/lib/ruby-next/rubocop.rb +9 -18
- data/lib/ruby-next/setup_self.rb +2 -2
- data/lib/ruby-next/version.rb +1 -1
- metadata +8 -6
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
- data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
# Mininum Ruby version supported by RubyNext
|
5
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
|
6
|
+
|
7
|
+
# Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
|
8
|
+
RUBY_NEXT_DIR = ".rbnext"
|
9
|
+
|
10
|
+
# Defines last minor version for every major version
|
11
|
+
LAST_MINOR_VERSIONS = {
|
12
|
+
2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
|
13
|
+
3 => 0
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
LATEST_VERSION = [3, 0].freeze
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# TruffleRuby claims it's 2.7.2 compatible but...
|
20
|
+
if defined?(TruffleRuby) && ::RUBY_VERSION =~ /^2\.7/
|
21
|
+
def current_ruby_version
|
22
|
+
"2.6.5"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
def current_ruby_version
|
26
|
+
::RUBY_VERSION
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_ruby_version(version = current_ruby_version)
|
31
|
+
major, minor = Gem::Version.new(version).segments.map(&:to_i)
|
32
|
+
|
33
|
+
return if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
|
34
|
+
|
35
|
+
nxt =
|
36
|
+
if LAST_MINOR_VERSIONS[major] == minor
|
37
|
+
"#{major + 1}.0.0"
|
38
|
+
else
|
39
|
+
"#{major}.#{minor + 1}.0"
|
40
|
+
end
|
41
|
+
|
42
|
+
Gem::Version.new(nxt)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/ruby-next/core.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
|
5
|
+
require "ruby-next/config"
|
5
6
|
require "ruby-next/utils"
|
6
7
|
|
7
8
|
module RubyNext
|
@@ -17,7 +18,7 @@ module RubyNext
|
|
17
18
|
# `core_ext` defines the strategy for core extensions:
|
18
19
|
# - :patch — extend class directly
|
19
20
|
# - :prepend — extend class by prepending a module (e.g., when needs `super`)
|
20
|
-
def initialize(mod = nil, method:, name: nil,
|
21
|
+
def initialize(mod = nil, method:, version:, name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
|
21
22
|
@mod = mod
|
22
23
|
@method_name = method
|
23
24
|
@version = version
|
@@ -122,8 +123,8 @@ module RubyNext
|
|
122
123
|
strategy == :backports
|
123
124
|
end
|
124
125
|
|
125
|
-
def patch(
|
126
|
-
patches << Patch.new(
|
126
|
+
def patch(...)
|
127
|
+
patches << Patch.new(...)
|
127
128
|
end
|
128
129
|
|
129
130
|
# Inject `using RubyNext` at the top of the source code
|
@@ -175,7 +176,7 @@ require "ruby-next/core/time/ceil"
|
|
175
176
|
# Core extensions required for pattern matching
|
176
177
|
# Required for pattern matching with refinements
|
177
178
|
unless defined?(NoMatchingPatternError)
|
178
|
-
class NoMatchingPatternError <
|
179
|
+
class NoMatchingPatternError < StandardError
|
179
180
|
end
|
180
181
|
end
|
181
182
|
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
end
|
10
10
|
|
11
11
|
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
12
|
-
if Gem::Version.new(
|
12
|
+
if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
|
13
13
|
RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
|
14
14
|
<<-RUBY
|
15
15
|
def respond_to?(mid, *)
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
end
|
10
10
|
|
11
11
|
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
12
|
-
if Gem::Version.new(
|
12
|
+
if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
|
13
13
|
RubyNext::Core.patch refineable: Hash, name: "HashRespondToDeconstructKeys", method: :deconstruct_keys, version: "2.7" do
|
14
14
|
<<-RUBY
|
15
15
|
def respond_to?(mid, *)
|
@@ -10,9 +10,9 @@ def deconstruct_keys(keys)
|
|
10
10
|
|
11
11
|
keys.each_with_object({}) do |k, acc|
|
12
12
|
# if k is Symbol and not a member of a Struct return {}
|
13
|
-
|
13
|
+
return {} if (Symbol === k || String === k) && !members.include?(k.to_sym)
|
14
14
|
# if k is Integer check that index is not ouf of bounds
|
15
|
-
|
15
|
+
return {} if Integer === k && k > size - 1
|
16
16
|
acc[k] = self[k]
|
17
17
|
end
|
18
18
|
end
|
@@ -20,7 +20,7 @@ end
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
23
|
-
if Gem::Version.new(
|
23
|
+
if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
|
24
24
|
RubyNext::Core.patch refineable: Struct, name: "StructRespondToDeconstruct", method: :deconstruct_keys, version: "2.7" do
|
25
25
|
<<-RUBY
|
26
26
|
def respond_to?(mid, *)
|
data/lib/ruby-next/core_ext.rb
CHANGED
data/lib/ruby-next/language.rb
CHANGED
@@ -101,9 +101,9 @@ module RubyNext
|
|
101
101
|
regenerate(*args, **kwargs)
|
102
102
|
end
|
103
103
|
rescue Unparser::UnknownNodeError
|
104
|
-
if Gem::Version.new(
|
105
|
-
RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support
|
106
|
-
"See https://github.com/mbj/unparser/
|
104
|
+
if Gem::Version.new(::RubyNext.current_ruby_version) >= Gem::Version.new("3.0.0")
|
105
|
+
RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 3.0+ AST yet.\n" \
|
106
|
+
"See https://github.com/mbj/unparser/issues/168"
|
107
107
|
self.mode = :rewrite
|
108
108
|
end
|
109
109
|
rewrite(*args, **kwargs)
|
@@ -169,7 +169,7 @@ module RubyNext
|
|
169
169
|
|
170
170
|
self.rewriters = []
|
171
171
|
self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
|
172
|
-
self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "
|
172
|
+
self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "rewrite").to_sym
|
173
173
|
|
174
174
|
require "ruby-next/language/rewriters/base"
|
175
175
|
|
@@ -182,6 +182,11 @@ module RubyNext
|
|
182
182
|
require "ruby-next/language/rewriters/args_forward"
|
183
183
|
rewriters << Rewriters::ArgsForward
|
184
184
|
|
185
|
+
# Must be added after general args forward rewriter to become
|
186
|
+
# no-op in Ruby <2.7
|
187
|
+
require "ruby-next/language/rewriters/args_forward_leading"
|
188
|
+
rewriters << Rewriters::ArgsForwardLeading
|
189
|
+
|
185
190
|
require "ruby-next/language/rewriters/numbered_params"
|
186
191
|
rewriters << Rewriters::NumberedParams
|
187
192
|
|
@@ -193,11 +198,17 @@ module RubyNext
|
|
193
198
|
require "ruby-next/language/rewriters/find_pattern"
|
194
199
|
rewriters << Rewriters::FindPattern
|
195
200
|
|
201
|
+
require "ruby-next/language/rewriters/in_pattern"
|
202
|
+
rewriters << Rewriters::InPattern
|
203
|
+
|
196
204
|
# Put endless range in the end, 'cause Parser fails to parse it in
|
197
205
|
# pattern matching
|
198
206
|
require "ruby-next/language/rewriters/endless_range"
|
199
207
|
rewriters << Rewriters::EndlessRange
|
200
208
|
|
209
|
+
require "ruby-next/language/rewriters/endless_method"
|
210
|
+
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
|
211
|
+
|
201
212
|
if ENV["RUBY_NEXT_EDGE"] == "1"
|
202
213
|
require "ruby-next/language/edge"
|
203
214
|
end
|
@@ -13,7 +13,7 @@ load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
|
|
13
13
|
if load_iseq.source_location[0].include?("/bootsnap/")
|
14
14
|
Bootsnap::CompileCache::ISeq.singleton_class.prepend(
|
15
15
|
Module.new do
|
16
|
-
def input_to_storage(source, path)
|
16
|
+
def input_to_storage(source, path, *)
|
17
17
|
return super unless RubyNext::Language.transformable?(path)
|
18
18
|
source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
|
19
19
|
|
@@ -1,9 +1,3 @@
|
|
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
|
@@ -20,7 +20,7 @@ module RubyNext
|
|
20
20
|
module InstanceEval # :nodoc:
|
21
21
|
refine Object do
|
22
22
|
def instance_eval(*args, &block)
|
23
|
-
return super(*args, &block) if
|
23
|
+
return super(*args, &block) if block
|
24
24
|
|
25
25
|
source = args.shift
|
26
26
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
@@ -33,7 +33,7 @@ module RubyNext
|
|
33
33
|
module ClassEval
|
34
34
|
refine Module do
|
35
35
|
def module_eval(*args, &block)
|
36
|
-
return super(*args, &block) if
|
36
|
+
return super(*args, &block) if block
|
37
37
|
|
38
38
|
source = args.shift
|
39
39
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
@@ -42,7 +42,7 @@ module RubyNext
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def class_eval(*args, &block)
|
45
|
-
return super(*args, &block) if
|
45
|
+
return super(*args, &block) if block
|
46
46
|
|
47
47
|
source = args.shift
|
48
48
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
@@ -4,8 +4,27 @@ require "parser/rubynext"
|
|
4
4
|
|
5
5
|
module RubyNext
|
6
6
|
module Language
|
7
|
+
module BuilderExt
|
8
|
+
def match_pattern(lhs, match_t, rhs)
|
9
|
+
n(:match_pattern, [lhs, rhs],
|
10
|
+
binary_op_map(lhs, match_t, rhs))
|
11
|
+
end
|
12
|
+
|
13
|
+
def match_pattern_p(lhs, match_t, rhs)
|
14
|
+
n(:match_pattern_p, [lhs, rhs],
|
15
|
+
binary_op_map(lhs, match_t, rhs))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
7
19
|
class Builder < ::Parser::Builders::Default
|
8
20
|
modernize
|
21
|
+
|
22
|
+
# Unparser doesn't support kwargs node yet
|
23
|
+
self.emit_kwargs = false if respond_to?(:emit_kwargs=)
|
24
|
+
|
25
|
+
unless method_defined?(:match_pattern_p)
|
26
|
+
include BuilderExt
|
27
|
+
end
|
9
28
|
end
|
10
29
|
|
11
30
|
class << self
|
@@ -5,8 +5,8 @@ module RubyNext
|
|
5
5
|
module Rewriters
|
6
6
|
class ArgsForward < Base
|
7
7
|
NAME = "args-forward"
|
8
|
-
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(
|
9
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("
|
8
|
+
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
10
10
|
|
11
11
|
REST = :__rest__
|
12
12
|
BLOCK = :__block__
|
@@ -28,14 +28,14 @@ module RubyNext
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def on_send(node)
|
31
|
-
fargs = node
|
31
|
+
fargs = extract_fargs(node)
|
32
32
|
return super(node) unless fargs
|
33
33
|
|
34
34
|
process_fargs(node, fargs)
|
35
35
|
end
|
36
36
|
|
37
37
|
def on_super(node)
|
38
|
-
fargs = node
|
38
|
+
fargs = extract_fargs(node)
|
39
39
|
return super(node) unless fargs
|
40
40
|
|
41
41
|
process_fargs(node, fargs)
|
@@ -43,6 +43,10 @@ module RubyNext
|
|
43
43
|
|
44
44
|
private
|
45
45
|
|
46
|
+
def extract_fargs(node)
|
47
|
+
node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
|
48
|
+
end
|
49
|
+
|
46
50
|
def process_fargs(node, fargs)
|
47
51
|
replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
|
48
52
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class ArgsForwardLeading < ArgsForward
|
7
|
+
NAME = "args-forward-leading"
|
8
|
+
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
10
|
+
|
11
|
+
attr_reader :leading_farg
|
12
|
+
alias leading_farg? leading_farg
|
13
|
+
|
14
|
+
def on_def(node)
|
15
|
+
@leading_farg = method_with_leading_arg(node)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_defs(node)
|
21
|
+
@leading_farg = method_with_leading_arg(node)
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_forward_arg(node)
|
27
|
+
return super if leading_farg?
|
28
|
+
|
29
|
+
node
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return super if leading_farg?
|
34
|
+
|
35
|
+
node
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_super(node)
|
39
|
+
return super if leading_farg?
|
40
|
+
|
41
|
+
node
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def send_with_leading_farg(node)
|
47
|
+
return false unless node.type == :send || node.type == :super
|
48
|
+
|
49
|
+
fargs = extract_fargs(node)
|
50
|
+
|
51
|
+
return false unless fargs
|
52
|
+
|
53
|
+
node.children.index(fargs) > (node.type == :send ? 2 : 0)
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_with_leading_arg(node)
|
57
|
+
find_child(node) { |child| child.type == :forward_arg } &&
|
58
|
+
(
|
59
|
+
def_with_leading_farg(node) ||
|
60
|
+
find_child(node) { |child| send_with_leading_farg(child) }
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def def_with_leading_farg(node)
|
65
|
+
args = node.type == :defs ? node.children[2] : node.children[1]
|
66
|
+
args = args.children
|
67
|
+
|
68
|
+
farg = args.detect { |child| child.type == :forward_arg }
|
69
|
+
|
70
|
+
args.index(farg) > 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -61,7 +61,7 @@ module RubyNext
|
|
61
61
|
eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
|
62
62
|
Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
|
63
63
|
false
|
64
|
-
rescue SyntaxError,
|
64
|
+
rescue SyntaxError, StandardError
|
65
65
|
true
|
66
66
|
ensure
|
67
67
|
$VERBOSE = save_verbose
|
@@ -94,6 +94,24 @@ module RubyNext
|
|
94
94
|
|
95
95
|
private
|
96
96
|
|
97
|
+
# BFS with predicate block
|
98
|
+
def find_child(node)
|
99
|
+
queue = [node]
|
100
|
+
|
101
|
+
loop do
|
102
|
+
break if queue.empty?
|
103
|
+
|
104
|
+
child = queue.shift
|
105
|
+
next unless child.is_a?(::Parser::AST::Node)
|
106
|
+
|
107
|
+
return child if yield child
|
108
|
+
|
109
|
+
queue.push(*child.children)
|
110
|
+
end
|
111
|
+
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
97
115
|
def replace(range, ast)
|
98
116
|
@source_rewriter&.replace(range, unparse(ast))
|
99
117
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby-next/language/rewriters/pattern_matching"
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
using RubyNext
|
9
|
+
|
10
|
+
# Separate pattern matching rewriter for Ruby 2.7 to
|
11
|
+
# transpile only `in` patterns
|
12
|
+
class InPattern < PatternMatching
|
13
|
+
NAME = "pattern-matching-in"
|
14
|
+
SYNTAX_PROBE = "1 in 2"
|
15
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
16
|
+
|
17
|
+
# Make case-match no-op
|
18
|
+
def on_case_match(node)
|
19
|
+
node
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_match_pattern_p(node)
|
23
|
+
context.track! self
|
24
|
+
|
25
|
+
@deconstructed_keys = {}
|
26
|
+
@predicates = Predicates::Noop.new
|
27
|
+
|
28
|
+
matchee =
|
29
|
+
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
30
|
+
|
31
|
+
pattern =
|
32
|
+
locals.with(
|
33
|
+
matchee: MATCHEE,
|
34
|
+
arr: MATCHEE_ARR,
|
35
|
+
hash: MATCHEE_HASH
|
36
|
+
) do
|
37
|
+
send(
|
38
|
+
:"#{node.children[1].type}_clause",
|
39
|
+
node.children[1]
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
node.updated(
|
44
|
+
:and,
|
45
|
+
[
|
46
|
+
matchee,
|
47
|
+
pattern
|
48
|
+
]
|
49
|
+
).tap do |new_node|
|
50
|
+
replace(node.loc.expression, inline_blocks(unparse(new_node)))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|