class TypeProf::ActualArguments

Arguments from caller side

Attributes

blk_ty[R]
kw_tys[R]
lead_tys[R]
node_id[RW]
rest_ty[R]

Public Class Methods

new(lead_tys, rest_ty, kw_tys, blk_ty) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 4
def initialize(lead_tys, rest_ty, kw_tys, blk_ty)
  @lead_tys = lead_tys
  raise unless lead_tys
  @rest_ty = rest_ty
  @kw_tys = kw_tys # kw_tys should be {:key1 => Type, :key2 => Type, ...} or {nil => Type}
  raise if !kw_tys.is_a?(::Hash)
  @blk_ty = blk_ty
  raise unless blk_ty
end

Public Instance Methods

argument_error(given, exp_lower, exp_upper) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 103
def argument_error(given, exp_lower, exp_upper)
  case
  when !exp_upper then exp = "#{ exp_lower }+"
  when exp_lower == exp_upper then exp = "#{ exp_lower }"
  else exp = "#{ exp_lower }..#{ exp_upper }"
  end
  "wrong number of arguments (given #{ given }, expected #{ exp })"
end
consistent_with_method_signature?(msig) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 34
def consistent_with_method_signature?(msig)
  aargs = @lead_tys.dup

  # aargs: lead_tys, rest_ty
  # msig: lead_tys, opt_tys, rest_ty, post_tys
  if @rest_ty
    lower_bound = [0, msig.lead_tys.size + msig.post_tys.size - aargs.size].max
    upper_bound = [0, lower_bound + msig.opt_tys.size].max
    (lower_bound..upper_bound).each do |n|
      # BUG: @rest_ty is an Array, so need to squash
      tmp_aargs = ActualArguments.new(@lead_tys + [@rest_ty] * n, nil, @kw_tys, @blk_ty)
      subst = tmp_aargs.consistent_with_method_signature?(msig) # XXX: wrong subst handling in the loop!
      return subst if subst
    end
    return nil
  end

  subst = {}
  if msig.rest_ty
    return nil if aargs.size < msig.lead_tys.size + msig.post_tys.size
    aargs.shift(msig.lead_tys.size).zip(msig.lead_tys) do |aarg, farg|
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
    aargs.pop(msig.post_tys.size).zip(msig.post_tys) do |aarg, farg|
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
    msig.opt_tys.each do |farg|
      break if aargs.empty?
      aarg = aargs.shift
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
    aargs.each do |aarg|
      return nil unless subst2 = Type.match?(aarg, msig.rest_ty)
      subst = Type.merge_substitution(subst, subst2)
    end
  else
    return nil if aargs.size < msig.lead_tys.size + msig.post_tys.size
    return nil if aargs.size > msig.lead_tys.size + msig.post_tys.size + msig.opt_tys.size
    aargs.shift(msig.lead_tys.size).zip(msig.lead_tys) do |aarg, farg|
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
    aargs.pop(msig.post_tys.size).zip(msig.post_tys) do |aarg, farg|
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
    aargs.zip(msig.opt_tys) do |aarg, farg|
      return nil unless subst2 = Type.match?(aarg, farg)
      subst = Type.merge_substitution(subst, subst2)
    end
  end
  # XXX: msig.keyword_tys

  case msig.blk_ty
  when Type::Proc
    return nil if @blk_ty == Type.nil
  when Type.nil
    return nil if @blk_ty != Type.nil
  when Type::Any
  else
    raise "unknown type of formal block signature"
  end

  subst
end
for_method_missing(mid) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 14
def for_method_missing(mid)
  ActualArguments.new([mid] + @lead_tys, @rest_ty, @kw_tys, @blk_ty)
end
globalize(caller_env, visited, depth) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 21
def globalize(caller_env, visited, depth)
  lead_tys = @lead_tys.map {|ty| ty.globalize(caller_env, visited, depth) }
  rest_ty = @rest_ty.globalize(caller_env, visited, depth) if @rest_ty
  kw_tys = @kw_tys.to_h do |key, ty|
    [key, ty.globalize(caller_env, visited, depth)]
  end
  ActualArguments.new(lead_tys, rest_ty, kw_tys, @blk_ty)
end
limit_size(limit) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 30
def limit_size(limit)
  self
end
setup_formal_arguments(kind, locals, fargs_format) click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 127
def setup_formal_arguments(kind, locals, fargs_format)
  lead_num = fargs_format[:lead_num] || 0
  post_num = fargs_format[:post_num] || 0
  post_index = fargs_format[:post_start]
  rest_index = fargs_format[:rest_start]
  keyword = fargs_format[:keyword]
  kw_index = fargs_format[:kwbits] - keyword.size if keyword
  kwrest_index = fargs_format[:kwrest]
  block_index = fargs_format[:block_start]
  opt = fargs_format[:opt] || [0]
  ambiguous_param0 = fargs_format[:ambiguous_param0]

  lead_tys = @lead_tys
  rest_ty = @rest_ty

  if kind == :block
    # The rule of passing arguments to block:
    #
    # Let A is actual arguments and F is formal arguments.
    # If F is NOT ambiguous_param0, and if length(A) == 1, and if A[0] is an Array,
    # then replace A with A[0]. And then, F[i] = A[i] for all 0 <= i < length(F).

    # Handling the special case
    if !ambiguous_param0
      if lead_tys.size == 1 && !rest_ty && @kw_tys.empty? # length(A) == 1
        ty = lead_tys[0]
        case ty
        when Type::Array
          lead_tys = ty.elems.lead_tys
          rest_ty = ty.elems.rest_ty
        when Type::Union
          if ty.elems
            other_elems = {}
            ty.elems.each do |(container_kind, base_type), elems|
              if container_kind == Type::Array
                rest_ty = rest_ty ? rest_ty.union(elems.squash) : elems.squash
              else
                other_elems[[container_kind, base_type]] = elems
              end
            end
          end
          lead_tys = [Type::Union.new(ty.types, other_elems)]
        end
      end
    end
  end

  # Normal case: copy actual args to formal args
  if rest_ty
    ty = Type.bot
    additional_lead_size = nil
    rest_ty.each_child_global do |ty0|
      if ty0.is_a?(Type::Array)
        additional_lead_size = [additional_lead_size, ty0.elems.lead_tys.size].compact.min
      else
        additional_lead_size = 0
      end
    end
    additional_lead_tys = [Type.bot] * (additional_lead_size || 0)
    rest_ty.each_child_global do |ty0|
      if ty0.is_a?(Type::Array)
        tys, new_rest_ty = ty0.elems.take_first(additional_lead_size)
        tys.each_with_index do |ty00, i|
          additional_lead_tys[i] = additional_lead_tys[i].union(ty00)
        end
        ty = ty.union(new_rest_ty.elems.squash)
      else
        # XXX: to_ary?
        ty = ty.union(ty0)
      end
    end
    lead_tys += additional_lead_tys
    rest_ty = ty

    # XXX: Strictly speaking, this is needed, but it brings false positives. Which is better?
    #rest_ty = rest_ty.union(Type.nil)

    if rest_index
      # foo(a0, a1, a2, ...(rest_ty)) -->
      #            def foo(l0, l1, o0=, o1=, *rest, p0, p1)
      # lead_ty argc == 0:  -   -   -    -      -    -   -
      # lead_ty argc == 1: a0   -   -    -      -    -   -
      # lead_ty argc == 2: a0  a1   -    -      -    -   -
      # lead_ty argc == 3: a0  a1   -    -      -   a2   -
      # lead_ty argc == 4: a0  a1   -    -      -   a2  a3
      # lead_ty argc == 5: a0  a1  a2    -      -   a3  a4
      # lead_ty argc == 6: a0  a1  a2   a3      -   a4  a5
      # lead_ty argc == 7: a0  a1  a2   a3    a4    a5  a6
      # lead_ty argc == 8: a0  a1  a2   a3    a4|a5 a6  a7
      #
      # l0   = a0
      # l1   = a1
      # o0   = a2
      # o1   = a3
      # rest = a4|a5|...|rest_ty (= cum_lead_tys[4])
      # p0   = a2|a3|...|rest_ty (= cum_lead_tys[2])
      # p1   = a3|a4|...|rest_ty (= cum_lead_tys[3])

      cum_lead_tys = []
      ty = rest_ty
      lead_tys.reverse_each do |ty0|
        cum_lead_tys.unshift(ty = ty.union(ty0))
      end

      # l1, l2, o1, o2
      (lead_num + opt.size - 1).times {|i| locals[i] = lead_tys[i] || rest_ty }
      opt_count = opt.size - 1

      # rest
      ty = cum_lead_tys[lead_num + opt.size - 1] || rest_ty
      locals[rest_index] = Type::Array.new(Type::Array::Elements.new([], ty), Type::Instance.new(Type::Builtin[:ary]))

      # p0, p1
      off = [lead_num, lead_tys.size - post_num].max
      post_num.times {|i| locals[post_index + i] = cum_lead_tys[off + i] || rest_ty }
    else
      # foo(a0, a1, a2, ...(rest_ty)) -->
      #            def foo(l0, l1, o0=, o1=, p0, p1)
      # lead_ty argc == 0:  -   -   -    -    -   -
      # lead_ty argc == 1: a0   -   -    -    -   -
      # lead_ty argc == 2: a0  a1   -    -    -   -
      # lead_ty argc == 3: a0  a1   -    -   a2   -
      # lead_ty argc == 4: a0  a1   -    -   a2  a3
      # lead_ty argc == 5: a0  a1  a2    -   a3  a4
      # lead_ty argc == 6: a0  a1  a2   a3   a4  a5
      # lead_ty argc == 7: a0  a1  a2   a3   a4  a5 (if there is a6, report error if kind is method, or ignore if kind is block)
      #
      # l0 = a0
      # l1 = a1
      # o0 = a2
      # o1 = a3
      # p0 = a2|a3|a4
      # p1 = a3|a4|a5

      if kind == :method && lead_num + opt.size - 1 + post_num < lead_tys.size
        return argument_error(lead_tys.size, lead_num + post_num, lead_num + post_num + opt.size - 1)
      end

      # l1, l2, o1, o2
      (lead_num + opt.size - 1).times {|i| locals[i] = lead_tys[i] || rest_ty }
      opt_count = opt.size - 1

      # p0, p1
      post_num.times do |i|
        candidates = lead_tys[lead_num, opt.size] || []
        candidates << rest_ty if candidates.size < opt.size
        locals[post_index + i] = candidates.inject(&:union)
      end
    end
  else
    if rest_index
      # foo(a0, a1, a2) -->
      #            def foo(l0, l1, o0=, o1=, *rest, p0, p1)
      # lead_ty argc == 0:  -   -   -    -      -    -   - (error if kind is method)
      # lead_ty argc == 1: a0   -   -    -      -    -   - (error if kind is method)
      # lead_ty argc == 2: a0  a1   -    -      -    -   - (error if kind is method)
      # lead_ty argc == 3: a0  a1   -    -      -   a2   - (error if kind is method)
      # lead_ty argc == 4: a0  a1   -    -      -   a2  a3
      # lead_ty argc == 5: a0  a1  a2    -      -   a3  a4
      # lead_ty argc == 6: a0  a1  a2   a3      -   a4  a5
      # lead_ty argc == 7: a0  a1  a2   a3    a4    a5  a6
      # lead_ty argc == 8: a0  a1  a2   a3    a4|a5 a6  a7
      #
      # len(a) < 4 -> error
      #
      # l0   = a0
      # l1   = a1
      # o0   = a2
      # o1   = a3
      # rest = a4|a5|...|a[[4,len(a)-3].max]
      # p0   = a[[2,len(a)-2].max]
      # p1   = a[[3,len(a)-1].max]

      if kind == :method && lead_tys.size < lead_num + post_num
        return argument_error(lead_tys.size, lead_num + post_num, nil)
      end

      # l0, l1
      lead_num.times {|i| locals[i] = lead_tys[i] || Type.nil }

      # o0, o1
      opt_count = (lead_tys.size - lead_num - post_num).clamp(0, opt.size - 1)
      (opt.size - 1).times {|i| locals[lead_num + i] = i < opt_count ? lead_tys[lead_num + i] : Type.nil }

      # rest
      rest_b = lead_num + opt_count
      rest_e = [0, lead_tys.size - post_num].max
      locals[rest_index] = Type::Array.new(Type::Array::Elements.new(lead_tys[rest_b...rest_e] || [], Type.bot), Type::Instance.new(Type::Builtin[:ary]))

      # p0, p1
      off = [lead_num, lead_tys.size - post_num].max
      post_num.times {|i| locals[post_index + i] = lead_tys[off + i] || Type.nil }
    else
      # yield a0, a1, a2 -->
      #                do |l0, l1, o0=, o1=, p0, p1|
      # lead_ty argc == 0:  -   -   -    -    -   - (error if kind is method)
      # lead_ty argc == 1: a0   -   -    -    -   - (error if kind is method)
      # lead_ty argc == 2: a0  a1   -    -    -   - (error if kind is method)
      # lead_ty argc == 3: a0  a1   -    -   a2   - (error if kind is method)
      # lead_ty argc == 4: a0  a1   -    -   a2  a3
      # lead_ty argc == 5: a0  a1  a2    -   a3  a4
      # lead_ty argc == 6: a0  a1  a2   a3   a4  a5
      # lead_ty argc == 7: a0  a1  a2   a3   a4  a5 (if there is a6, report error if kind is method, or ignore if kind is block)
      #
      # l0 = a0
      # l1 = a1
      # o0 = a2
      # o1 = a3
      # p0 = a2|a3|a4
      # p1 = a3|a4|a5

      if kind == :method && (lead_tys.size < lead_num + post_num || lead_num + opt.size - 1 + post_num < lead_tys.size)
        return argument_error(lead_tys.size, lead_num + post_num, lead_num + post_num + opt.size - 1)
      end

      # l0, l1
      lead_num.times {|i| locals[i] = lead_tys[i] || Type.nil }

      # o0, o1
      opt_count = (lead_tys.size - lead_num - post_num).clamp(0, opt.size - 1)
      (opt.size - 1).times {|i| locals[lead_num + i] = i < opt_count ? lead_tys[lead_num + i] : Type.nil }

      # p0, p1
      off = lead_num + opt_count
      post_num.times {|i| locals[post_index + i] = lead_tys[off + i] || Type.nil }
    end
  end

  kw_tys = @kw_tys.dup
  if keyword
    keyword.each_with_index do |kw, i|
      case
      when kw.is_a?(Symbol) # required keyword
        key = kw
        req = true
      when kw.size == 2 # optional keyword (default value is a literal)
        key, default_ty = *kw
        default_ty = Type.guess_literal_type(default_ty)
        default_ty = default_ty.base_type if default_ty.is_a?(Type::Literal)
        req = false
      else # optional keyword (default value is an expression)
        key, = kw
        req = false
      end

      if kw_tys.key?(key)
        ty = kw_tys.delete(key)
      else
        ty = kw_tys[nil] || Type.bot
      end

      if ty == Type.bot && req
        return "no argument for required keywords"
      end

      ty = ty.union(default_ty) if default_ty
      locals[kw_index + i] = ty
    end
  end

  if kwrest_index
    if kw_tys.key?(nil)
      kw_rest_ty = Type.gen_hash {|h| h[Type.any] = kw_tys[nil] }
    else
      kw_rest_ty = Type.gen_hash do |h|
        kw_tys.each do |key, ty|
          sym = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
          h[sym] = ty
        end
      end
    end
    locals[kwrest_index] = kw_rest_ty
  else
    if !kw_tys.empty?
      return "unknown keyword: #{ kw_tys.keys.join(", ") }"
    end
  end

  if block_index
    locals[block_index] = @blk_ty
  end

  start_pcs = opt[0..opt_count]

  return @blk_ty, start_pcs
end
to_block_signature() click to toggle source
# File typeprof-0.21.9/lib/typeprof/arguments.rb, line 112
def to_block_signature
  if @rest_ty
    rest_ty = Type.bot
    @rest_ty.each_child_global do |ty|
      if ty.is_a?(Type::Array)
        rest_ty = rest_ty.union(ty.elems.squash)
      else
        # XXX: to_ary?
        rest_ty = rest_ty.union(ty)
      end
    end
  end
  BlockSignature.new(@lead_tys, [], rest_ty, @blk_ty)
end