opal 1.4.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +5 -3
  3. data/.github/workflows/build.yml +1 -0
  4. data/.rubocop.yml +1 -0
  5. data/CHANGELOG.md +64 -8
  6. data/UNRELEASED.md +3 -2
  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/top.rb +26 -10
  24. data/lib/opal/nodes.rb +0 -1
  25. data/lib/opal/parser/default_config.rb +3 -2
  26. data/lib/opal/repl.rb +1 -1
  27. data/lib/opal/rewriter.rb +13 -6
  28. data/lib/opal/rewriters/base.rb +12 -1
  29. data/lib/opal/rewriters/rubyspec/filters_rewriter.rb +1 -0
  30. data/lib/opal/version.rb +1 -1
  31. data/opal/corelib/array.rb +23 -28
  32. data/opal/corelib/binding.rb +14 -4
  33. data/opal/corelib/constants.rb +3 -3
  34. data/opal/corelib/hash.rb +3 -3
  35. data/opal/corelib/irb.rb +192 -0
  36. data/opal/corelib/math/polyfills.rb +127 -0
  37. data/opal/corelib/math.rb +14 -194
  38. data/opal/corelib/module.rb +23 -25
  39. data/opal/corelib/number.rb +63 -14
  40. data/opal/corelib/regexp.rb +2 -0
  41. data/opal/corelib/runtime.js +56 -20
  42. data/opal/corelib/string.rb +38 -59
  43. data/opal/corelib/time.rb +111 -70
  44. data/opal/opal/full.rb +0 -1
  45. data/opal/opal.rb +4 -1
  46. data/spec/filters/bugs/date.rb +0 -3
  47. data/spec/filters/bugs/datetime.rb +65 -0
  48. data/spec/filters/bugs/float.rb +0 -18
  49. data/spec/filters/bugs/hash.rb +0 -2
  50. data/spec/filters/bugs/language.rb +0 -3
  51. data/spec/filters/bugs/marshal.rb +0 -1
  52. data/spec/filters/bugs/string.rb +0 -30
  53. data/spec/filters/bugs/stringscanner.rb +0 -1
  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 +26 -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/spec_helper.rb +4 -0
  74. data/spec/support/rewriters_helper.rb +43 -23
  75. data/stdlib/date/date_time.rb +71 -0
  76. data/stdlib/date/formatters.rb +28 -0
  77. data/stdlib/date/infinity.rb +73 -0
  78. data/stdlib/date.rb +77 -214
  79. data/stdlib/opal/repl_js.rb +1 -1
  80. data/stdlib/{opal/replutils.rb → opal-replutils.rb} +3 -3
  81. data/stdlib/strscan.rb +27 -49
  82. data/stdlib/time.rb +39 -2
  83. data/stdlib/uri.rb +53 -0
  84. data/tasks/performance/asciidoctor_test.rb.erb +3 -1
  85. data/tasks/performance/optimization_status.rb +3 -2
  86. data/tasks/performance.rake +69 -35
  87. data/tasks/testing.rake +9 -3
  88. data/test/opal/test_uri.rb +35 -0
  89. data/yarn.lock +27 -5
  90. metadata +29 -16
  91. data/lib/opal/nodes/csend.rb +0 -24
  92. data/lib/opal/rewriters/explicit_writer_return.rb +0 -59
  93. data/spec/lib/rewriters/explicit_writer_return_spec.rb +0 -186
  94. data/stdlib/nodejs/irb.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f199262b85612cb61bd3f3fa1d05ec8a7fb3fa6fabbd63a36baa03859e241c5c
4
- data.tar.gz: e4af60acb540708612fbed3e4aa04fef968b750b0ff588774bda6d979586fc8f
3
+ metadata.gz: 7e07c91ee89f49091b5245745dc0889d8266c6c437349f6c0a5f31c99ab34e61
4
+ data.tar.gz: bef7cf7822bd78100f39044d4243a588d8a752541bb9af584c324b7d9b31ed79
5
5
  SHA512:
6
- metadata.gz: a4ece0468468bc541c155cb53185550f35b4277c89e18f32990a7bb8abff6c20beaeb9f962f02f80b1afc251a75c0916d35dd8d2944fa8153aa4a6454a7ddf8a
7
- data.tar.gz: 81539b2993e44bfa8217cda753badaa292c26117de8366d04e50922309b95654f5c09f983a02f329b36ea1405affc52c3831c7122f4215a90e7a78c9084b9b31
6
+ metadata.gz: '05894c0ce50acfcda6bf2f333410639603c994ffdd56db7c892ae579601e7f739a13fb37b430e28048ae9ea84148956a5f3b37fc629506ccd11f913a1300cf7b'
7
+ data.tar.gz: deb8dfb2fecfb910d506a2707ad34a5306429debb693d4b54b61163195311807600b0b859db18e744669bdcfb4a07a610b31871050dbbb7df9f6dbf5ef712291
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
  };
@@ -51,6 +51,7 @@ jobs:
51
51
  command: bin/rake lint
52
52
  ruby: '3.0'
53
53
  - name: timezone
54
+ command: bin/rake mspec_nodejs TZ="Pacific/Fiji"
54
55
  ruby: '3.0'
55
56
  - name: performance
56
57
  ruby: '3.0'
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,23 +15,79 @@ Changes are grouped as follows:
15
15
 
16
16
 
17
17
 
18
- ## [1.4.1](https://github.com/opal/opal/compare/v1.4.0...v1.4.1) - 2022-01-12
18
+ ## [1.5.1](https://github.com/opal/opal/compare/v1.5.0...v1.5.1) - 2022-07-20
19
+
20
+
21
+ ### Fixed
22
+
23
+ - Make `Time.new` not depend on `Date.prototype.getTimezoneOffset()` ([#2426](https://github.com/opal/opal/pull/2426))
24
+ - Fix exception during `Hash#each_value` if keys get deleted during loop ([#2427](https://github.com/opal/opal/pull/2427))
25
+ - Fix scan_until and check_until implementation of StringScanner ([#2420](https://github.com/opal/opal/pull/2420))
19
26
 
20
27
 
28
+
29
+
30
+ ## [1.5.0](https://github.com/opal/opal/compare/v1.4.1...v1.5.0) - 2022-04-13
31
+
32
+
33
+ ### Added
34
+
35
+ * Introduce timezone support for Time by @hmdne in https://github.com/opal/opal/pull/2394
36
+ * DateTime and Date refactor by @hmdne in https://github.com/opal/opal/pull/2398
37
+ * Implement `Number#prev_float`/`#next_float` by @takaram in https://github.com/opal/opal/pull/2404
38
+ * Support `binding.irb` anywhere in the code, for both browsers and node by @hmdne in https://github.com/opal/opal/pull/2392 and https://github.com/opal/opal/pull/2408
39
+ * Added `URI.decode_www_form` by @HoneyryderChuck in https://github.com/opal/opal/pull/2387
40
+
21
41
  ### Changed
22
42
 
23
- - PromiseV2 is now declared a stable interface!
43
+ * Move `Math` IE11-supporting polyfills to a separate file by @hmdne in https://github.com/opal/opal/pull/2395
44
+ * `String` methods always return Strings even when overloaded by @hmdne in https://github.com/opal/opal/pull/2413
45
+ * Misc changes extracted from DCE work by @elia in https://github.com/opal/opal/pull/2414
46
+ - `alias` calls will not add the "old name" method to the list of stubs for method missing
47
+ * Optimize writer/setter methods (up to 4% performance gain!) by @hmdne in https://github.com/opal/opal/pull/2402
48
+ - Also fixed few edge cases of conditional calls combined with setters, e.g. `foo&.bar = 123`
49
+
50
+ ### Performance
51
+
52
+ * Runtime optimization by @hmdne in https://github.com/opal/opal/pull/2383
53
+ - Improve performance of argument coertion, fast-track `Integer`, `String`, and calling the designed coertion method
54
+ - Optimize `Array#[]=` by moving the implementation to JavaScript and inlining type checks
55
+ - Optimize internal runtime passing of block-options
56
+ * Compile `case` statements as `switch` whenever possible by @hmdne in https://github.com/opal/opal/pull/2411
57
+ * Improve performance with optimized common method/iter implementation shortcuts by @hmdne in https://github.com/opal/opal/pull/2401
24
58
 
25
59
  ### Fixed
26
60
 
27
- - Args named with JS reserved words weren't always renamed when _zsuper_ was involved ([#2385](https://github.com/opal/opal/pull/2385))
61
+ * Fix `Regexp.new`, replace `\A` to `^` and `\z` to `$` by @ysakasin in https://github.com/opal/opal/pull/2079
62
+ * Fix exception during `Hash#each` and `Hash#each_key` if keys get deleted during the loop by @janbiedermann in https://github.com/opal/opal/pull/2403
63
+ * Fix defining multiple methods with the same block by @elia in https://github.com/opal/opal/pull/2397
64
+ * Correct `String#to_proc` and `method_missing` compatibility by @hmdne in https://github.com/opal/opal/pull/2418
65
+ * Exit REPL respecting the exit status number by @janbiedermann in https://github.com/opal/opal/pull/2396
28
66
 
29
- <!--
30
- ### Added
31
- ### Removed
32
- ### Deprecated
33
67
  ### Internal
34
- -->
68
+
69
+ * Rewriters refactor, fix interaction between cache and inverted runner by @hmdne in https://github.com/opal/opal/pull/2400
70
+ * releasing.md: add a step to prepare for next release by @hmdne in https://github.com/opal/opal/pull/2407
71
+
72
+ ## New Contributors
73
+
74
+ * @HoneyryderChuck made their first contribution in https://github.com/opal/opal/pull/2387
75
+
76
+ **Full Changelog**: https://github.com/opal/opal/compare/v1.4.1...v1.5.0
77
+
78
+
79
+
80
+
81
+ ## [1.4.1](https://github.com/opal/opal/compare/v1.4.0...v1.4.1) - 2022-01-12
82
+
83
+
84
+ ### Changed
85
+
86
+ - PromiseV2 is now declared a stable interface! ([#2380](https://github.com/opal/opal/pull/2380))
87
+
88
+ ### Fixed
89
+
90
+ - Args named with JS reserved words weren't always renamed when _zsuper_ was involved ([#2385](https://github.com/opal/opal/pull/2385))
35
91
 
36
92
 
37
93
 
data/UNRELEASED.md CHANGED
@@ -1,8 +1,9 @@
1
1
  <!--
2
- ### Fixed
2
+ ### Internal
3
3
  ### Changed
4
4
  ### Added
5
5
  ### Removed
6
6
  ### Deprecated
7
- ### Internal
7
+ ### Performance
8
+ ### Fixed
8
9
  -->
@@ -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
@@ -10,6 +10,37 @@ module Opal
10
10
  children :mid, :inline_args, :stmts
11
11
 
12
12
  def compile
13
+ compile_body_or_shortcut
14
+
15
+ blockopts = []
16
+
17
+ blockopts << "$$arity: #{arity}"
18
+
19
+ if compiler.arity_check?
20
+ blockopts << "$$parameters: #{parameters_code}"
21
+ end
22
+
23
+ if compiler.parse_comments?
24
+ blockopts << "$$comments: #{comments_code}"
25
+ end
26
+
27
+ if compiler.enable_source_location?
28
+ blockopts << "$$source_location: #{source_location}"
29
+ end
30
+
31
+ if blockopts.length == 1
32
+ push ", #{arity}"
33
+ elsif blockopts.length > 1
34
+ push ', {', blockopts.join(', '), '}'
35
+ end
36
+
37
+ wrap_with_definition
38
+
39
+ scope.nesting if @define_nesting
40
+ scope.relative_access if @define_relative_access
41
+ end
42
+
43
+ def compile_body
13
44
  inline_params = nil
14
45
  scope_name = nil
15
46
 
@@ -51,33 +82,6 @@ module Opal
51
82
  unshift "async "
52
83
  end
53
84
  line '}'
54
-
55
- blockopts = []
56
-
57
- blockopts << "$$arity: #{arity}"
58
-
59
- if compiler.arity_check?
60
- blockopts << "$$parameters: #{parameters_code}"
61
- end
62
-
63
- if compiler.parse_comments?
64
- blockopts << "$$comments: #{comments_code}"
65
- end
66
-
67
- if compiler.enable_source_location?
68
- blockopts << "$$source_location: #{source_location}"
69
- end
70
-
71
- if blockopts.length == 1
72
- push ", #{arity}"
73
- elsif blockopts.length > 1
74
- push ', {', blockopts.join(', '), '}'
75
- end
76
-
77
- wrap_with_definition
78
-
79
- scope.nesting if @define_nesting
80
- scope.relative_access if @define_relative_access
81
85
  end
82
86
 
83
87
  def wrap_with_definition
@@ -31,6 +31,8 @@ module Opal
31
31
  push '$alias_gvar(', new_name_str, ', ', old_name_str, ')'
32
32
  when :dsym, :sym # This is a method alias: alias a b
33
33
  helper :alias
34
+ compiler.record_method_call old_name.children.last if old_name.type == :sym
35
+
34
36
  push "$alias(#{scope.self}, ", expr(new_name), ', ', expr(old_name), ')'
35
37
  else # Nothing else is available, but just in case, drop an error
36
38
  error "Opal doesn't know yet how to alias with #{new_name.type}"