class Opal::Nodes::CallNode

Constants

OPERATORS

Operators that get optimized by compiler

SPECIALS

Attributes

arglist[R]
iter[R]
meth[R]
recvr[R]

Public Class Methods

add_special(name, options = {}, &handler) click to toggle source
# File lib/opal/nodes/call.rb, line 20
def self.add_special(name, options = {}, &handler)
  SPECIALS[name] = options
  define_method("handle_#{name}", &handler)
end
new(*) click to toggle source
Calls superclass method Opal::Nodes::Base::new
# File lib/opal/nodes/call.rb, line 25
def initialize(*)
  super
  @recvr, @meth, *args = *@sexp

  *rest, last_arg = *args

  if last_arg && %i[iter block_pass].include?(last_arg.type)
    @iter = last_arg
    args = rest
  else
    @iter = nil
  end

  @arglist = s(:arglist, *args)
end

Public Instance Methods

compile() click to toggle source
# File lib/opal/nodes/call.rb, line 41
def compile
  # handle some methods specially
  # some special methods need to skip compilation, so we pass the default as a block
  handle_special do
    compiler.record_method_call meth

    with_wrapper do
      if using_eval?
        # if trying to access an lvar in eval or irb mode
        compile_eval_var
      elsif using_irb?
        # if trying to access an lvar in irb mode
        compile_irb_var
      else
        default_compile
      end
    end
  end
end

Private Instance Methods

auto_await?() click to toggle source
# File lib/opal/nodes/call.rb, line 237
def auto_await?
  awaited_set = compiler.async_await

  awaited_set && awaited_set != true && awaited_set.match?(meth.to_s)
end
call_is_writer_that_needs_handling?() click to toggle source
# File lib/opal/nodes/call.rb, line 465
def call_is_writer_that_needs_handling?
  (expr? || recv?) && (meth.to_s =~ /^\w+=$/ || meth == :[]=)
end
compile_arguments(skip_comma = false) click to toggle source
# File lib/opal/nodes/call.rb, line 163
def compile_arguments(skip_comma = false)
  push ', ' unless skip_comma

  if @with_writer_temp
    push @with_writer_temp
  elsif splat?
    push expr(arglist)
  elsif arglist.children.empty?
    push '[]'
  else
    push '[', expr(arglist), ']'
  end
end
compile_block_pass() click to toggle source
# File lib/opal/nodes/call.rb, line 177
def compile_block_pass
  if iter
    push ', ', expr(iter)
  end
end
compile_eval_var() click to toggle source
# File lib/opal/nodes/call.rb, line 215
def compile_eval_var
  push meth.to_s
end
compile_irb_var() click to toggle source

Used to generate the code to use this sexp as an ivar var reference

# File lib/opal/nodes/call.rb, line 206
def compile_irb_var
  with_temp do |tmp|
    lvar = meth
    call = s(:send, s(:self), meth.intern, s(:arglist))
    ref = "(typeof #{lvar} !== 'undefined') ? #{lvar} : "
    push "((#{tmp} = Opal.irb_vars.#{lvar}) == null ? ", ref, expr(call), " : #{tmp})"
  end
end
compile_method_name() click to toggle source
# File lib/opal/nodes/call.rb, line 159
def compile_method_name
  push ", '#{meth}'"
end
compile_receiver() click to toggle source
# File lib/opal/nodes/call.rb, line 155
def compile_receiver
  push @conditional_recvr || recv(receiver_sexp)
end
compile_refinements() click to toggle source
# File lib/opal/nodes/call.rb, line 183
def compile_refinements
  refinements = scope.collect_refinements_temps.map { |i| s(:js_tmp, i) }
  push expr(s(:array, *refinements)), ', '
end
compile_simple_call_chain() click to toggle source
# File lib/opal/nodes/call.rb, line 188
def compile_simple_call_chain
  compile_receiver
  push method_jsid, '(', expr(arglist), ')'
end
compile_using_refined_send() click to toggle source

Compiles method call using ‘Opal.refined_send`

@example

a.b(c, &block)

Opal.refined_send(a, 'b', [c], block, [[Opal.MyRefinements]])
# File lib/opal/nodes/call.rb, line 143
def compile_using_refined_send
  helper :refined_send

  push '$refined_send('
  compile_refinements
  compile_receiver
  compile_method_name
  compile_arguments
  compile_block_pass
  push ')'
end
compile_using_send() click to toggle source

Compiles method call using ‘Opal.send`

@example

a.b(c, &block)

Opal.send(a, 'b', [c], block)
# File lib/opal/nodes/call.rb, line 125
def compile_using_send
  helper :send

  push '$send('
  compile_receiver
  compile_method_name
  compile_arguments
  compile_block_pass
  push ')'
end
csend?() click to toggle source

Is it a conditional send, ie. ‘foo&.bar`?

# File lib/opal/nodes/call.rb, line 93
def csend?
  @sexp.type == :csend
end
default_compile() click to toggle source
# File lib/opal/nodes/call.rb, line 97
def default_compile
  if auto_await?
    push '(await '
    scope.await_encountered = true
  end

  push_closure(Closure::SEND) if iter_has_break?
  if invoke_using_refinement?
    compile_using_refined_send
  elsif invoke_using_send?
    compile_using_send
  else
    compile_simple_call_chain
  end
  pop_closure if iter_has_break?

  if auto_await?
    push ')'
  end
end
handle_conditional_send() { || ... } click to toggle source

Handle safe-operator calls: foo&.bar / foo&.bar ||= baz / …

# File lib/opal/nodes/call.rb, line 470
def handle_conditional_send
  # temporary variable that stores method receiver
  receiver_temp = scope.new_temp
  push "#{receiver_temp} = ", expr(receiver_sexp)

  # execute the sexp only if the receiver isn't nil
  push ", (#{receiver_temp} === nil || #{receiver_temp} == null) ? nil : "
  @conditional_recvr = receiver_temp
  yield
  wrap '(', ')'
end
handle_special() { || ... } click to toggle source

Handle “special” method calls, e.g. require(). Subclasses can override this method. If this method returns nil, then the method will continue to be generated by CallNode.

# File lib/opal/nodes/call.rb, line 246
def handle_special(&compile_default)
  if SPECIALS.include? meth
    method = method("handle_#{meth}")
    method.arity == 1 ? method[compile_default] : method[]
  else
    yield # i.e. compile_default.call
  end
end
handle_writer() { || ... } click to toggle source
# File lib/opal/nodes/call.rb, line 482
def handle_writer
  with_temp do |temp|
    push "(#{temp} = "
    compile_arguments(true)
    push ", "
    @with_writer_temp = temp
    yield
    @with_writer_temp = false
    push ", "
    push "#{temp}[#{temp}.length - 1])"
  end
end
invoke_using_refinement?() click to toggle source
# File lib/opal/nodes/call.rb, line 88
def invoke_using_refinement?
  !scope.scope.collect_refinements_temps.empty?
end
invoke_using_send?() click to toggle source

Opal has a runtime helper ‘Opal.send_method_name’ that assigns provided block to a ‘$$p’ property of the method body and invokes a method using ‘apply’.

We have to compile a method call using this ‘Opal.send_method_name’ when a method:

  1. takes a splat

  2. takes a block

Arguments that contain splat must be handled in a different way. @see compile_arguments

When a method takes a block we have to calculate all arguments before assigning ‘$$p’ property (that stores a passed block) to a method body. This is some kind of protection from method calls like ‘a(a {}) { 1 }’.

# File lib/opal/nodes/call.rb, line 84
def invoke_using_send?
  iter || splat? || call_is_writer_that_needs_handling?
end
iter_has_break?() click to toggle source
# File lib/opal/nodes/call.rb, line 63
def iter_has_break?
  return false unless iter

  iter.meta[:has_break]
end
method_jsid() click to toggle source
# File lib/opal/nodes/call.rb, line 201
def method_jsid
  mid_to_jsid meth.to_s
end
push_nesting?() click to toggle source
# File lib/opal/nodes/call.rb, line 442
def push_nesting?
  recv = children.first

  children.size == 2 && (           # only receiver and method
    recv.nil? || (                  # and no receiver
      recv.type == :const &&        # or receiver
      recv.children.last == :Module # is Module
    )
  )
end
receiver_sexp() click to toggle source
# File lib/opal/nodes/call.rb, line 197
def receiver_sexp
  recvr || s(:self)
end
sexp_with_arglist() click to toggle source
# File lib/opal/nodes/call.rb, line 233
def sexp_with_arglist
  @sexp.updated(nil, [recvr, meth, arglist])
end
splat?() click to toggle source
# File lib/opal/nodes/call.rb, line 193
def splat?
  arglist.children.any? { |a| a.type == :splat }
end
using_eval?() click to toggle source
# File lib/opal/nodes/call.rb, line 225
def using_eval?
  @compiler.eval? && scope.top? && @compiler.scope_variables.include?(meth)
end
using_irb?() click to toggle source

a variable reference in irb mode in top scope might be a var ref, or it might be a method call

# File lib/opal/nodes/call.rb, line 221
def using_irb?
  @compiler.irb? && scope.top? && variable_like?
end
using_refinement(arg) click to toggle source
# File lib/opal/nodes/call.rb, line 355
def using_refinement(arg)
  prev, curr = *scope.refinements_temp
  if prev
    push "(#{curr} = #{prev}.slice(), #{curr}.push(", expr(arg), "), #{scope.self})"
  else
    push "(#{curr} = [", expr(arg), "], #{scope.self})"
  end
end
variable_like?() click to toggle source
# File lib/opal/nodes/call.rb, line 229
def variable_like?
  arglist == s(:arglist) && recvr.nil? && iter.nil?
end
with_wrapper() { || ... } click to toggle source
# File lib/opal/nodes/call.rb, line 453
def with_wrapper(&block)
  if csend? && !@conditional_recvr
    handle_conditional_send do
      with_wrapper(&block)
    end
  elsif call_is_writer_that_needs_handling?
    handle_writer(&block)
  else
    yield
  end
end