# frozen_string_literal: true require 'opal/nodes/base' module Opal module Nodes # This base class is used just to child the find_super_dispatcher method # body. This is then used by actual super calls, or a defined?(super) style # call. class BaseSuperNode < CallNode def initialize(*) super args = *@sexp *rest, last_child = *args if last_child && [:iter, :block_pass].include?(last_child.type) @iter = last_child args = rest else @iter = s(:js_tmp, 'null') end @arglist = s(:arglist, *args) @recvr = s(:self) end def compile_using_send helper :send push '$send(' compile_receiver compile_method compile_arguments compile_block_pass push ')' end private # Using super in a block inside a method is allowed, e.g. # def a # { super } # end # # This method finds returns a closest s(:def) (or s(:defs)) def def_scope @def_scope ||= scope.def? ? scope : scope.find_parent_def end def raise_exception? @sexp.type == :defined_super end def defined_check_param raise_exception? ? 'true' : 'false' end def implicit_args? @sexp.type == :zsuper end def implicit_arguments_param implicit_args? ? 'true' : 'false' end def method_id def_scope.mid.to_s end def def_scope_identity def_scope.identify!(def_scope.mid) end def super_method_invocation if def_scope.defs class_name = def_scope.parent.name ? "$#{def_scope.parent.name}" : 'self.$$class.$$proto' "Opal.find_super_dispatcher(self, '#{method_id}', #{def_scope_identity}, #{defined_check_param}, #{class_name})" else "Opal.find_super_dispatcher(self, '#{method_id}', #{def_scope_identity}, #{defined_check_param})" end end def super_block_invocation chain, cur_defn, mid = scope.get_super_chain trys = chain.map { |c| "#{c}.$$def" }.join(' || ') "Opal.find_iter_super_dispatcher(self, #{mid}, (#{trys} || #{cur_defn}), #{defined_check_param}, #{implicit_arguments_param})" end def compile_method push ", " if scope.def? push super_method_invocation elsif scope.iter? push super_block_invocation else raise 'super must be called from method body or block' end end end class DefinedSuperNode < BaseSuperNode handle :defined_super def compile compile_receiver compile_method # will never come back null with method missing on if compiler.method_missing? wrap '(!(', '.$$stub) ? "super" : nil)' else # TODO: With method_missing support off, something breaks in runtime.js's chain wrap '((', ') != null ? "super" : nil)' end end end # super with implicit args class SuperNode < BaseSuperNode handle :super def initialize(*) super if scope.def? scope.uses_block! end end def compile compile_using_send end end # super with explicit args class ZsuperNode < SuperNode handle :zsuper def initialize(*) super # preserve a block if we have one already but otherwise, assume a block is coming from higher # up the chain unless iter.type == :iter # Need to support passing block up even if it's not referenced in this method at all scope.uses_block! @iter = s(:js_tmp, '$iter') end end def compile if def_scope def_scope.uses_zuper = true implicit_args = [s(:js_tmp, '$zuper')] # If the method we're in has a block and we're using a default super call with no args, we need to grab the block # If an iter (block via braces) is provided, that takes precedence if (block_arg = formal_block_parameter) && !iter block_pass = s(:block_pass, s(:lvar, block_arg[1])) implicit_args << block_pass end @arglist = s(:arglist, *implicit_args) end compile_using_send end def compile_arguments push ", " if arglist.children.empty? push '[]' else push expr(arglist) end end def formal_block_parameter case def_scope when Opal::Nodes::IterNode def_scope.extract_block_arg when Opal::Nodes::DefNode def_scope.block_arg else raise "Don't know what to do with super in the scope #{def_scope}" end end end end end