opal 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +5 -3
  3. data/.github/workflows/build.yml +20 -18
  4. data/.rubocop.yml +1 -0
  5. data/CHANGELOG.md +64 -0
  6. data/UNRELEASED.md +3 -3
  7. data/benchmark-ips/bm_js_symbols_vs_strings.rb +39 -14
  8. data/docs/releasing.md +10 -2
  9. data/lib/opal/ast/matcher.rb +77 -0
  10. data/lib/opal/cache.rb +1 -1
  11. data/lib/opal/cli_runners/applescript.rb +2 -0
  12. data/lib/opal/compiler.rb +18 -9
  13. data/lib/opal/nodes/call.rb +73 -28
  14. data/lib/opal/nodes/def.rb +31 -27
  15. data/lib/opal/nodes/definitions.rb +2 -0
  16. data/lib/opal/nodes/helpers.rb +4 -23
  17. data/lib/opal/nodes/if.rb +222 -0
  18. data/lib/opal/nodes/iter.rb +41 -37
  19. data/lib/opal/nodes/literal.rb +2 -2
  20. data/lib/opal/nodes/masgn.rb +15 -17
  21. data/lib/opal/nodes/node_with_args/shortcuts.rb +100 -0
  22. data/lib/opal/nodes/node_with_args.rb +1 -0
  23. data/lib/opal/nodes/super.rb +9 -9
  24. data/lib/opal/nodes/top.rb +26 -10
  25. data/lib/opal/nodes.rb +0 -1
  26. data/lib/opal/parser/default_config.rb +3 -2
  27. data/lib/opal/repl.rb +1 -1
  28. data/lib/opal/rewriter.rb +13 -6
  29. data/lib/opal/rewriters/base.rb +12 -1
  30. data/lib/opal/rewriters/rubyspec/filters_rewriter.rb +1 -0
  31. data/lib/opal/version.rb +1 -1
  32. data/opal/corelib/array.rb +23 -28
  33. data/opal/corelib/binding.rb +14 -4
  34. data/opal/corelib/constants.rb +3 -3
  35. data/opal/corelib/hash.rb +2 -2
  36. data/opal/corelib/irb.rb +192 -0
  37. data/opal/corelib/math/polyfills.rb +127 -0
  38. data/opal/corelib/math.rb +14 -194
  39. data/opal/corelib/module.rb +23 -25
  40. data/opal/corelib/number.rb +63 -14
  41. data/opal/corelib/regexp.rb +2 -0
  42. data/opal/corelib/runtime.js +56 -20
  43. data/opal/corelib/string.rb +38 -59
  44. data/opal/corelib/time.rb +106 -68
  45. data/opal/opal/full.rb +0 -1
  46. data/opal/opal.rb +4 -1
  47. data/spec/filters/bugs/date.rb +0 -3
  48. data/spec/filters/bugs/datetime.rb +65 -0
  49. data/spec/filters/bugs/float.rb +0 -18
  50. data/spec/filters/bugs/hash.rb +0 -2
  51. data/spec/filters/bugs/language.rb +0 -3
  52. data/spec/filters/bugs/marshal.rb +0 -1
  53. data/spec/filters/bugs/string.rb +0 -30
  54. data/spec/filters/bugs/time.rb +18 -8
  55. data/spec/lib/cli_spec.rb +2 -2
  56. data/spec/lib/compiler_spec.rb +8 -8
  57. data/spec/lib/rewriters/base_spec.rb +1 -1
  58. data/spec/lib/rewriters/binary_operator_assignment_spec.rb +34 -59
  59. data/spec/lib/rewriters/block_to_iter_spec.rb +3 -6
  60. data/spec/lib/rewriters/dot_js_syntax_spec.rb +2 -5
  61. data/spec/lib/rewriters/for_rewriter_spec.rb +0 -1
  62. data/spec/lib/rewriters/forward_args_spec.rb +2 -3
  63. data/spec/lib/rewriters/js_reserved_words_spec.rb +2 -15
  64. data/spec/lib/rewriters/logical_operator_assignment_spec.rb +64 -89
  65. data/spec/lib/rewriters/numblocks_spec.rb +3 -5
  66. data/spec/lib/rewriters/opal_engine_check_spec.rb +2 -14
  67. data/spec/lib/rewriters/rubyspec/filters_rewriter_spec.rb +10 -2
  68. data/spec/opal/compiler/irb_spec.rb +4 -0
  69. data/spec/opal/core/language/super_spec.rb +47 -0
  70. data/spec/opal/core/regexp/assertions_spec.rb +19 -0
  71. data/spec/opal/core/string/to_proc_spec.rb +19 -0
  72. data/spec/ruby_specs +4 -0
  73. data/spec/support/rewriters_helper.rb +43 -23
  74. data/stdlib/date/date_time.rb +71 -0
  75. data/stdlib/date/formatters.rb +28 -0
  76. data/stdlib/date/infinity.rb +73 -0
  77. data/stdlib/date.rb +77 -214
  78. data/stdlib/opal/repl_js.rb +1 -1
  79. data/stdlib/{opal/replutils.rb → opal-replutils.rb} +3 -3
  80. data/stdlib/promise/v2.rb +0 -7
  81. data/stdlib/time.rb +39 -2
  82. data/stdlib/uri.rb +53 -0
  83. data/tasks/performance/asciidoctor_test.rb.erb +3 -1
  84. data/tasks/performance/optimization_status.rb +3 -2
  85. data/tasks/performance.rake +69 -35
  86. data/tasks/testing.rake +1 -0
  87. data/test/opal/test_uri.rb +35 -0
  88. data/yarn.lock +27 -5
  89. metadata +30 -16
  90. data/lib/opal/nodes/csend.rb +0 -24
  91. data/lib/opal/rewriters/explicit_writer_return.rb +0 -59
  92. data/spec/lib/rewriters/explicit_writer_return_spec.rb +0 -186
  93. data/stdlib/nodejs/irb.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec2d9bb3f02059898f9a494c018fbcb4f451c4c5130b3e9ff455bf3ce1561fc9
4
- data.tar.gz: a69e0554d43a0ef0ad6e7d439beb313536227615698487b8283321e879cf150d
3
+ metadata.gz: c7a74100fe9d5254983a2b7efde2482df462edae5aa85bdd691ce89c13de5081
4
+ data.tar.gz: 7c1841adacfc5dbf7156646bafd37592f1d9f54c31e07f98d7e2f77e4dc108e0
5
5
  SHA512:
6
- metadata.gz: 5a96aade4e8183b8f00136eb4c5e64ea1f6b12b03aec98096476de4dd414b03b9ecfb90658a29bef403b366e36db43283b6e175cac5dcc3796453535f9ad7bd0
7
- data.tar.gz: 8ea23169b1492a34b5bf98750259f9d9da636bb5070b50fad624a56a2eea65f4c5975a6385af3dfbdfad2a41985f5d32ba3aa583feea39b79b59ccc3cac20c43
6
+ metadata.gz: 9324444976198f22d216fec12cda5047389b6c4c0afacdf3d19d458e85f329f9339e7fe626de2659af51dd8acd3d8edabaea878789fb58ce05f67d67d254f9da
7
+ data.tar.gz: fe8b6678035e4160ee85328b60a59dfc68307e3a26b7a5e8e1273f3948017a0a7ad38c5858fedf1cce2fdfc7e93c9ea6d7cc210fbd0e7e916da599d2eec7b496
data/.eslintrc.js CHANGED
@@ -24,12 +24,14 @@ module.exports = {
24
24
  "no-control-regex": "off",
25
25
  },
26
26
  "globals": {
27
- "Opal": "readonly",
28
- "DataView": "readonly",
29
27
  "ArrayBuffer": "readonly",
28
+ "DataView": "readonly",
30
29
  "globalThis": "readonly",
31
- "Uint8Array": "readonly",
30
+ "Opal": "readonly",
32
31
  "Promise": "readonly",
32
+ "Proxy": "readonly",
33
+ "Reflect": "readonly",
34
+ "Uint8Array": "readonly",
33
35
  "WeakRef": "readonly",
34
36
  }
35
37
  };
@@ -16,41 +16,44 @@ jobs:
16
16
  matrix:
17
17
  combo:
18
18
  - name: mspec-nodejs
19
- ruby: 3.0
19
+ ruby: '3.0'
20
20
  command: bin/rake mspec_nodejs
21
21
  - name: mspec-chrome
22
- ruby: 3.0
22
+ ruby: '3.0'
23
23
  command: bin/rake mspec_chrome
24
24
  - name: minitest
25
- ruby: 3.0
25
+ ruby: '3.0'
26
26
  command: bin/rake minitest
27
27
  - name: minitest-strict-mode
28
- ruby: 3.0
28
+ ruby: '3.0'
29
29
  command: bin/rake minitest
30
30
  strict: 'true'
31
+ - name: head-ruby
32
+ ruby: head
33
+ permissive: true
31
34
  - name: current-ruby
32
- ruby: 3.0
35
+ ruby: 3.1
33
36
  - name: previous-ruby
34
- ruby: 2.7
37
+ ruby: '3.0'
35
38
  - name: older-ruby
39
+ ruby: 2.7
40
+ - name: near-eol-ruby
36
41
  ruby: 2.6
37
- # - name: near-eol-ruby
38
- # ruby: 2.6
39
42
  - name: smoke-test
40
- ruby: 3.0
43
+ ruby: '3.0'
41
44
  command: bin/rake smoke_test
42
45
  - name: windows
43
46
  # These two fail because of broken stacktraces on windows: minitest_node_nodejs mspec_nodejs
44
47
  command: bundle exec rake rspec minitest_nodejs
45
- ruby: 3.0
48
+ ruby: '3.0'
46
49
  os: windows-latest
47
50
  - name: lint
48
51
  command: bin/rake lint
49
- ruby: 3.0
52
+ ruby: '3.0'
50
53
  - name: timezone
51
- ruby: 3.0
54
+ ruby: '3.0'
52
55
  - name: performance
53
- ruby: 3.0
56
+ ruby: '3.0'
54
57
  permissive: true
55
58
  fetchdepth: '0'
56
59
  command: bin/rake performance:compare
@@ -59,7 +62,6 @@ jobs:
59
62
  # Currently failing:
60
63
  # - ruby: truffleruby
61
64
  # - ruby: jruby
62
- # - ruby: ruby-head
63
65
 
64
66
  runs-on: ${{ matrix.combo.os || 'ubuntu-latest' }}
65
67
  continue-on-error: ${{ matrix.combo.permissive || false }}
@@ -77,11 +79,11 @@ jobs:
77
79
  - uses: actions/cache@v2
78
80
  with:
79
81
  path: ./vendor/bundle
80
- key: ${{ runner.os }}-${{ matrix.combo.ruby }}-gems-${{ github.ref }}-${{ hashFiles('**/Gemfile.lock') }}
82
+ key: ${{ runner.os }}-${{ matrix.combo.ruby }}-gem-${{ github.ref }}-${{ hashFiles('**/Gemfile.lock') }}
81
83
  restore-keys: |
82
- ${{ runner.os }}-${{ matrix.combo.ruby }}-gems-${{ github.ref }}
83
- ${{ runner.os }}-${{ matrix.combo.ruby }}-gems-master
84
- ${{ runner.os }}-${{ matrix.combo.ruby }}-gems-
84
+ ${{ runner.os }}-${{ matrix.combo.ruby }}-gem-${{ github.ref }}
85
+ ${{ runner.os }}-${{ matrix.combo.ruby }}-gem-master
86
+ ${{ runner.os }}-${{ matrix.combo.ruby }}-gem-
85
87
  - uses: actions/cache@v2
86
88
  with:
87
89
  path: ./node_modules
data/.rubocop.yml CHANGED
@@ -365,6 +365,7 @@ Naming/FileName:
365
365
  - 'stdlib/opal-parser.rb'
366
366
  - 'stdlib/opal-platform.rb'
367
367
  - 'stdlib/opal-source-maps.rb'
368
+ - 'stdlib/opal-replutils.rb'
368
369
 
369
370
  Naming/ConstantName:
370
371
  Exclude:
data/CHANGELOG.md CHANGED
@@ -15,6 +15,70 @@ Changes are grouped as follows:
15
15
 
16
16
 
17
17
 
18
+ ## [1.5.0](https://github.com/opal/opal/compare/v1.4.1...v1.5.0) - 2022-04-13
19
+
20
+
21
+ ### Added
22
+
23
+ - Introduce timezone support for Time ([#2394](https://github.com/opal/opal/pull/2394))
24
+ - DateTime and Date refactor ([#2398](https://github.com/opal/opal/pull/2398))
25
+ - Implement `Number#prev_float`/`#next_float` ([#2404](https://github.com/opal/opal/pull/2404))
26
+ - Support `binding.irb` anywhere in the code, for both browsers and node ([#2392](https://github.com/opal/opal/pull/2392))
27
+ - Added `URI.decode_www_form` ([#2387](https://github.com/opal/opal/pull/2387))
28
+
29
+ ### Changed
30
+
31
+ - Move Math IE11-supporting polyfills to a separate file ([#2395](https://github.com/opal/opal/pull/2395))
32
+ - String methods always return Strings even when overloaded ([#2413](https://github.com/opal/opal/pull/2413))
33
+ - `alias` calls will not add the "old name" method to the list of stubs for method missing ([#2414](https://github.com/opal/opal/pull/2414))
34
+ - Optimize writer/setter methods, up to 4% performance gain ([#2402](https://github.com/opal/opal/pull/2402))
35
+
36
+ ### Performance
37
+
38
+ - Improve performance of argument coertion, fast-track `Integer`, `String`, and calling the designed coertion method ([#2383](https://github.com/opal/opal/pull/2383))
39
+ - Optimize `Array#[]=` by moving the implementation to JavaScript and inlining type checks ([#2383](https://github.com/opal/opal/pull/2383))
40
+ - Optimize internal runtime passing of block-options ([#2383](https://github.com/opal/opal/pull/2383))
41
+ - Compile `case` statements as `switch` whenever possible ([#2411](https://github.com/opal/opal/pull/2411))
42
+ - Improve performance with optimized common method/iter implementation shortcuts ([#2401](https://github.com/opal/opal/pull/2401))
43
+
44
+ ### Fixed
45
+
46
+ - Fix `Regexp.new`, previously `\A` and `\z` didn't match beginning and end of input ([#2079](https://github.com/opal/opal/pull/2079))
47
+ - Fix exception during `Hash#each` and `Hash#each_key` if keys get deleted during the loop ([#2403](https://github.com/opal/opal/pull/2403))
48
+ - Fix defining multiple methods with the same block ([#2397](https://github.com/opal/opal/pull/2397))
49
+ - A few edge cases of conditional calls combined with setters, e.g. `foo&.bar = 123` ([#2402](https://github.com/opal/opal/pull/2402))
50
+ - Correct String#to_proc and method_missing compatibility ([#2418](https://github.com/opal/opal/pull/2418))
51
+ - Exit REPL respecting the exit status number ([#2396](https://github.com/opal/opal/pull/2396))
52
+
53
+ ### Internal
54
+
55
+ - Rewriters refactor, fix interaction between cache and inverted runner ([#2400](https://github.com/opal/opal/pull/2400))
56
+
57
+ <!--
58
+ ### Internal
59
+ ### Changed
60
+ ### Added
61
+ ### Removed
62
+ ### Deprecated
63
+ -->
64
+
65
+
66
+
67
+
68
+ ## [1.4.1](https://github.com/opal/opal/compare/v1.4.0...v1.4.1) - 2022-01-12
69
+
70
+
71
+ ### Changed
72
+
73
+ - PromiseV2 is now declared a stable interface! ([#2380](https://github.com/opal/opal/pull/2380))
74
+
75
+ ### Fixed
76
+
77
+ - Args named with JS reserved words weren't always renamed when _zsuper_ was involved ([#2385](https://github.com/opal/opal/pull/2385))
78
+
79
+
80
+
81
+
18
82
  ## [1.4.0](https://github.com/opal/opal/compare/v1.3.2...v1.4.0) - 2021-12-24
19
83
 
20
84
 
data/UNRELEASED.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <!--
2
- ### Added
3
- ### Fixed
2
+ ### Internal
4
3
  ### Changed
4
+ ### Added
5
5
  ### Removed
6
6
  ### Deprecated
7
- ### Internal
7
+ ### Performance
8
8
  -->
@@ -1,33 +1,58 @@
1
1
  Benchmark.ips do |x|
2
2
  %x{
3
+ const c_foo = 'foo'
4
+ const v_foo = 'foo'
5
+ const cfoo = Symbol('foo')
6
+ var vfoo = Symbol('foo')
7
+ const cgfoo = Symbol.for('foo')
8
+ var vgfoo = Symbol.for('foo')
9
+
3
10
  var o = {}
4
- o[Symbol.for('foo')] = 123
5
- o.foo = 123
6
- var foo = Symbol('foo')
7
- var gfoo = Symbol.for('foo')
8
- o[foo] = 123
9
- var a = 0, b = 0, c = 0
11
+ o[cfoo] = 1
12
+ o[cgfoo] = 1
13
+ o[c_foo] = 1
14
+ o[vfoo] = 1
15
+
16
+ let a1 = 0, a2 = 0, a3 = 0, a4 = 0, a5 = 0, a6 = 0, a7 = 0, a8 = 0
10
17
  }
11
18
 
19
+ x.report('const string ref') do
20
+ `a1 += o[c_foo]`
21
+ end
22
+
23
+ x.report('var string ref') do
24
+ `a2 += o[v_foo]`
25
+ end
26
+
12
27
  x.report('live global symbol') do
13
- `a += o[Symbol.for('foo')]`
28
+ `a3 += o[Symbol.for('foo')]`
14
29
  end
15
30
 
16
- x.report('stored global symbol') do
17
- `a += o[gfoo]`
31
+ x.report('const global symbol') do
32
+ `a4 += o[cgfoo]`
18
33
  end
19
34
 
20
- x.report('stored symbol') do
21
- `a += o[foo]`
35
+ x.report('var global symbol') do
36
+ `a5 += o[vgfoo]`
37
+ end
38
+
39
+ x.report('const symbol') do
40
+ `a6 += o[cfoo]`
41
+ end
42
+
43
+ x.report('var symbol') do
44
+ `a6 += o[vfoo]`
22
45
  end
23
46
 
24
47
  x.report('ident') do
25
- `b += o.foo`
48
+ `a7 += o.foo`
26
49
  end
27
50
 
28
- x.report('string') do
29
- `c += o['foo']`
51
+ x.report('live string') do
52
+ `a8 += o['foo']`
30
53
  end
31
54
 
55
+ x.time = 10
56
+
32
57
  x.compare!
33
58
  end
data/docs/releasing.md CHANGED
@@ -10,8 +10,8 @@ _This guide is a work-in-progress._
10
10
  ## Updating the changelog
11
11
 
12
12
  - Ensure all the unreleased changes are documented in UNRELEASED.md
13
- - Run `bin/rake changelog VERSION=v1.2.3` specifying the version number you're about to release
14
- - Empty UNRELEASED.md
13
+ - [skip for pre-releases] Run `bin/rake changelog VERSION=v1.2.3` specifying the version number you're about to release
14
+ - [skip for pre-releases] Empty UNRELEASED.md
15
15
 
16
16
  ## The commit
17
17
 
@@ -34,3 +34,11 @@ _This guide is a work-in-progress._
34
34
  ## Opal CDN
35
35
 
36
36
  - Run `bin/release v1.2.3`
37
+
38
+ ## Prepare for the next release
39
+
40
+ - Skip this step if releasing a stable release
41
+ - Create a new pull request that:
42
+ - Updates a version to `v1.x.0.dev` in both `lib/opal/version.rb` and `opal/corelib/constants.rb`
43
+ - Remember to merge that PR before merging anything else next once we decide to not release any more point releases from `master`.
44
+
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ast'
4
+ require 'parser/ast/node'
5
+
6
+ module Opal
7
+ module AST
8
+ class Matcher
9
+ def initialize(&block)
10
+ @root = instance_exec(&block)
11
+ end
12
+
13
+ def s(type, *children)
14
+ Node.new(type, children)
15
+ end
16
+
17
+ def cap(capture)
18
+ Node.new(:capture, [capture])
19
+ end
20
+
21
+ def match(ast)
22
+ @captures = []
23
+ @root.match(ast, self) || (return false)
24
+ @captures
25
+ end
26
+
27
+ def inspect
28
+ "#<Opal::AST::Matcher: #{@root.inspect}>"
29
+ end
30
+
31
+ attr_accessor :captures
32
+
33
+ Node = Struct.new(:type, :children) do
34
+ def match(ast, matcher)
35
+ return false if ast.nil?
36
+
37
+ ast_parts = [ast.type] + ast.children
38
+ self_parts = [type] + children
39
+
40
+ return false if ast_parts.length != self_parts.length
41
+
42
+ ast_parts.length.times.all? do |i|
43
+ ast_elem = ast_parts[i]
44
+ self_elem = self_parts[i]
45
+
46
+ if self_elem.is_a?(Node) && self_elem.type == :capture
47
+ capture = true
48
+ self_elem = self_elem.children.first
49
+ end
50
+
51
+ res = case self_elem
52
+ when Node
53
+ self_elem.match(ast_elem, matcher)
54
+ when Array
55
+ self_elem.include?(ast_elem)
56
+ when :*
57
+ true
58
+ else
59
+ self_elem == ast_elem
60
+ end
61
+
62
+ matcher.captures << ast_elem if capture
63
+ res
64
+ end
65
+ end
66
+
67
+ def inspect
68
+ if type == :capture
69
+ "{#{children.first.inspect}}"
70
+ else
71
+ "s(#{type.inspect}, #{children.inspect[1..-2]})"
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
data/lib/opal/cache.rb CHANGED
@@ -41,7 +41,7 @@ module Opal
41
41
 
42
42
  data || begin
43
43
  compiler = yield
44
- cache.set(key, compiler)
44
+ cache.set(key, compiler) unless compiler.dynamic_cache_result
45
45
  compiler
46
46
  end
47
47
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'opal/cli_runners/system_runner'
4
+
3
5
  module Opal
4
6
  module CliRunners
5
7
  class Applescript
data/lib/opal/compiler.rb CHANGED
@@ -217,9 +217,16 @@ module Opal
217
217
  # Comments from the source code
218
218
  attr_reader :comments
219
219
 
220
+ # Method calls made in this file
221
+ attr_reader :method_calls
222
+
220
223
  # Magic comment flags extracted from the leading comments
221
224
  attr_reader :magic_comments
222
225
 
226
+ # Set if some rewritter caused a dynamic cache result, meaning it's not
227
+ # fit to be cached
228
+ attr_accessor :dynamic_cache_result
229
+
223
230
  def initialize(source, options = {})
224
231
  @source = source
225
232
  @indent = ''
@@ -227,8 +234,10 @@ module Opal
227
234
  @options = options
228
235
  @comments = Hash.new([])
229
236
  @case_stmt = nil
237
+ @method_calls = Set.new
230
238
  @option_values = {}
231
239
  @magic_comments = {}
240
+ @dynamic_cache_result = false
232
241
  end
233
242
 
234
243
  # Compile some ruby code to a string.
@@ -260,9 +269,12 @@ module Opal
260
269
  :main
261
270
  end
262
271
 
263
- @sexp = s(:top, sexp || s(:nil)).tap { |i| i.meta[:kind] = kind }
264
- @comments = ::Parser::Source::Comment.associate_locations(sexp, comments)
265
- @magic_comments = MagicComments.parse(sexp, comments)
272
+ @sexp = sexp.tap { |i| i.meta[:kind] = kind }
273
+
274
+ first_node = sexp.children.first if sexp.children.first.location
275
+
276
+ @comments = ::Parser::Source::Comment.associate_locations(first_node, comments)
277
+ @magic_comments = MagicComments.parse(first_node, comments)
266
278
  @eof_content = EofContent.new(tokens, @source).eof
267
279
  end
268
280
 
@@ -287,9 +299,8 @@ module Opal
287
299
  )
288
300
  end
289
301
 
290
- # Method calls made in this file
291
- def method_calls
292
- @method_calls ||= Set.new
302
+ def record_method_call(mid)
303
+ @method_calls << mid
293
304
  end
294
305
 
295
306
  alias async_await_before_typecasting async_await
@@ -353,9 +364,7 @@ module Opal
353
364
  @indent
354
365
  end
355
366
 
356
- # Create a new sexp using the given parts. Even though this just
357
- # returns an array, it must be used incase the internal structure
358
- # of sexps does change.
367
+ # Create a new sexp using the given parts.
359
368
  def s(type, *children)
360
369
  ::Opal::AST::Node.new(type, children)
361
370
  end
@@ -8,7 +8,7 @@ require 'opal/rewriters/break_finder'
8
8
  module Opal
9
9
  module Nodes
10
10
  class CallNode < Base
11
- handle :send
11
+ handle :send, :csend
12
12
 
13
13
  attr_reader :recvr, :meth, :arglist, :iter
14
14
 
@@ -43,15 +43,19 @@ module Opal
43
43
  # handle some methods specially
44
44
  # some special methods need to skip compilation, so we pass the default as a block
45
45
  handle_special do
46
- compiler.method_calls << meth.to_sym if record_method?
47
-
48
- # if trying to access an lvar in eval or irb mode
49
- return compile_eval_var if using_eval?
50
-
51
- # if trying to access an lvar in irb mode
52
- return compile_irb_var if using_irb?
53
-
54
- default_compile
46
+ compiler.record_method_call meth
47
+
48
+ with_wrapper do
49
+ if using_eval?
50
+ # if trying to access an lvar in eval or irb mode
51
+ compile_eval_var
52
+ elsif using_irb?
53
+ # if trying to access an lvar in irb mode
54
+ compile_irb_var
55
+ else
56
+ default_compile
57
+ end
58
+ end
55
59
  end
56
60
  end
57
61
 
@@ -81,13 +85,18 @@ module Opal
81
85
  # to a method body. This is some kind of protection from method calls
82
86
  # like 'a(a {}) { 1 }'.
83
87
  def invoke_using_send?
84
- iter || splat?
88
+ iter || splat? || call_is_writer_that_needs_handling?
85
89
  end
86
90
 
87
91
  def invoke_using_refinement?
88
92
  !scope.scope.collect_refinements_temps.empty?
89
93
  end
90
94
 
95
+ # Is it a conditional send, ie. `foo&.bar`?
96
+ def csend?
97
+ @sexp.type == :csend
98
+ end
99
+
91
100
  def default_compile
92
101
  if auto_await?
93
102
  push 'await '
@@ -143,17 +152,19 @@ module Opal
143
152
  end
144
153
 
145
154
  def compile_receiver
146
- push recv(receiver_sexp)
155
+ push @conditional_recvr || recv(receiver_sexp)
147
156
  end
148
157
 
149
158
  def compile_method_name
150
159
  push ", '#{meth}'"
151
160
  end
152
161
 
153
- def compile_arguments
154
- push ', '
162
+ def compile_arguments(skip_comma = false)
163
+ push ', ' unless skip_comma
155
164
 
156
- if splat?
165
+ if @with_writer_temp
166
+ push @with_writer_temp
167
+ elsif splat?
157
168
  push expr(arglist)
158
169
  elsif arglist.children.empty?
159
170
  push '[]'
@@ -197,16 +208,13 @@ module Opal
197
208
  mid_to_jsid meth.to_s
198
209
  end
199
210
 
200
- def record_method?
201
- true
202
- end
203
-
204
211
  # Used to generate the code to use this sexp as an ivar var reference
205
212
  def compile_irb_var
206
213
  with_temp do |tmp|
207
214
  lvar = meth
208
215
  call = s(:send, s(:self), meth.intern, s(:arglist))
209
- push "((#{tmp} = Opal.irb_vars.#{lvar}) == null ? ", expr(call), " : #{tmp})"
216
+ ref = "(typeof #{lvar} !== 'undefined') ? #{lvar} : "
217
+ push "((#{tmp} = Opal.irb_vars.#{lvar}) == null ? ", ref, expr(call), " : #{tmp})"
210
218
  end
211
219
  end
212
220
 
@@ -255,7 +263,7 @@ module Opal
255
263
  if invoke_using_refinement?
256
264
  compile_default.call
257
265
  elsif compiler.inline_operators?
258
- compiler.method_calls << operator.to_sym if record_method?
266
+ compiler.record_method_call operator
259
267
  helper :"rb_#{name}"
260
268
  lhs, rhs = expr(recvr), expr(arglist)
261
269
 
@@ -414,13 +422,8 @@ module Opal
414
422
 
415
423
  scope.nesting
416
424
  push "Opal.Binding.$new("
417
- push " function($code, $value) {"
418
- push " if (typeof $value === 'undefined') {"
419
- push " return eval($code);"
420
- push " }"
421
- push " else {"
422
- push " return eval($code + ' = $value');"
423
- push " }"
425
+ push " function($code) {"
426
+ push " return eval($code);"
424
427
  push " },"
425
428
  push " ", scope.scope_locals.map(&:to_s).inspect, ","
426
429
  push " ", scope.self, ","
@@ -450,6 +453,48 @@ module Opal
450
453
  )
451
454
  end
452
455
 
456
+ def with_wrapper(&block)
457
+ if csend? && !@conditional_recvr
458
+ handle_conditional_send do
459
+ with_wrapper(&block)
460
+ end
461
+ elsif call_is_writer_that_needs_handling?
462
+ handle_writer(&block)
463
+ else
464
+ yield
465
+ end
466
+ end
467
+
468
+ def call_is_writer_that_needs_handling?
469
+ (expr? || recv?) && (meth.to_s =~ /^\w+=$/ || meth == :[]=)
470
+ end
471
+
472
+ # Handle safe-operator calls: foo&.bar / foo&.bar ||= baz / ...
473
+ def handle_conditional_send
474
+ # temporary variable that stores method receiver
475
+ receiver_temp = scope.new_temp
476
+ push "#{receiver_temp} = ", expr(recvr)
477
+
478
+ # execute the sexp only if the receiver isn't nil
479
+ push ", (#{receiver_temp} === nil || #{receiver_temp} == null) ? nil : "
480
+ @conditional_recvr = receiver_temp
481
+ yield
482
+ wrap '(', ')'
483
+ end
484
+
485
+ def handle_writer
486
+ with_temp do |temp|
487
+ push "(#{temp} = "
488
+ compile_arguments(true)
489
+ push ", "
490
+ @with_writer_temp = temp
491
+ yield
492
+ @with_writer_temp = false
493
+ push ", "
494
+ push "#{temp}[#{temp}.length - 1])"
495
+ end
496
+ end
497
+
453
498
  class DependencyResolver
454
499
  def initialize(compiler, sexp, missing_dynamic_require = nil)
455
500
  @compiler = compiler