lib/opal/nodes/args/arity_check.rb
# frozen_string_literal: true
require 'opal/nodes/base'
require 'opal/rewriters/arguments'
module Opal
module Nodes
class ArityCheckNode < Base
handle :arity_check
children :args_node
def initialize(*)
super
arguments = Rewriters::Arguments.new(args_node.children)
@args = arguments.args
@optargs = arguments.optargs
@restarg = arguments.restarg
@postargs = arguments.postargs
@kwargs = arguments.kwargs
@kwoptargs = arguments.kwoptargs
@kwrestarg = arguments.kwrestarg
@kwnilarg = arguments.kwnilarg
end
def compile
scope.arity = arity
return unless compiler.arity_check?
unless arity_checks.empty?
helper :ac
meth = scope.mid.to_s.inspect
line 'var $arity = arguments.length;'
push " if (#{arity_checks.join(' || ')}) { $ac($arity, #{arity}, this, #{meth}); }"
end
end
def kwargs
[*@kwargs, *@kwoptargs, @kwrestarg].compact
end
def all_args
@all_args ||= [*@args, *@optargs, @restarg, *@postargs, *kwargs].compact
end
# Returns an array of JS conditions for raising and argument
# error caused by arity check
def arity_checks
return @arity_checks if defined?(@arity_checks)
arity = all_args.size
arity -= @optargs.size
arity -= 1 if @restarg
arity -= kwargs.size
arity = -arity - 1 if !@optargs.empty? || !kwargs.empty? || @restarg
@arity_checks = []
if arity < 0 # splat or opt args
min_arity = -(arity + 1)
max_arity = all_args.size
@arity_checks << "$arity < #{min_arity}" if min_arity > 0
@arity_checks << "$arity > #{max_arity}" unless @restarg
else
@arity_checks << "$arity !== #{arity}"
end
@arity_checks
end
def arity
if @restarg || @optargs.any? || has_only_optional_kwargs?
negative_arity
else
positive_arity
end
end
def negative_arity
required_plain_args = all_args.select do |arg|
%i[arg mlhs].include?(arg.type)
end
result = required_plain_args.size
if has_required_kwargs?
result += 1
end
result = -result - 1
result
end
def positive_arity
result = all_args.size
result -= kwargs.size
result += 1 if kwargs.any?
result
end
def has_only_optional_kwargs?
kwargs.any? && kwargs.all? { |arg| %i[kwoptarg kwrestarg].include?(arg.type) }
end
def has_required_kwargs?
kwargs.any? { |arg| arg.type == :kwarg }
end
end
class IterArityCheckNode < ArityCheckNode
handle :iter_arity_check
def compile
scope.arity = arity
return unless compiler.arity_check?
unless arity_checks.empty?
parent_scope = scope
until parent_scope.def? || parent_scope.class_scope? || parent_scope.top?
parent_scope = parent_scope.parent
end
context =
if parent_scope.top?
"'<main>'"
elsif parent_scope.def?
"'#{parent_scope.mid}'"
elsif parent_scope.class?
"'<class:#{parent_scope.name}>'"
elsif parent_scope.module?
"'<module:#{parent_scope.name}>'"
end
identity = scope.identity
line "if (#{identity}.$$is_lambda || #{identity}.$$define_meth) {"
line ' var $arity = arguments.length;'
line " if (#{arity_checks.join(' || ')}) { Opal.block_ac($arity, #{arity}, #{context}); }"
line '}'
end
end
end
end
end