lib/syntax_tree/yarv/calldata.rb



# frozen_string_literal: true

module SyntaxTree
  module YARV
    # This is an operand to various YARV instructions that represents the
    # information about a specific call site.
    class CallData
      flags = %i[
        CALL_ARGS_SPLAT
        CALL_ARGS_BLOCKARG
        CALL_FCALL
        CALL_VCALL
        CALL_ARGS_SIMPLE
        CALL_KWARG
        CALL_KW_SPLAT
        CALL_TAILCALL
        CALL_SUPER
        CALL_ZSUPER
        CALL_OPT_SEND
        CALL_KW_SPLAT_MUT
      ]

      # Insert the legacy CALL_BLOCKISEQ flag for Ruby 3.2 and earlier.
      flags.insert(5, :CALL_BLOCKISEQ) if RUBY_VERSION < "3.3"

      # Set the flags as constants on the class.
      flags.each_with_index { |name, index| const_set(name, 1 << index) }

      attr_reader :method, :argc, :flags, :kw_arg

      def initialize(
        method,
        argc = 0,
        flags = CallData::CALL_ARGS_SIMPLE,
        kw_arg = nil
      )
        @method = method
        @argc = argc
        @flags = flags
        @kw_arg = kw_arg
      end

      def flag?(mask)
        (flags & mask) > 0
      end

      def to_h
        result = { mid: method, flag: flags, orig_argc: argc }
        result[:kw_arg] = kw_arg if kw_arg
        result
      end

      def inspect
        names = []
        names << :ARGS_SPLAT if flag?(CALL_ARGS_SPLAT)
        names << :ARGS_BLOCKARG if flag?(CALL_ARGS_BLOCKARG)
        names << :FCALL if flag?(CALL_FCALL)
        names << :VCALL if flag?(CALL_VCALL)
        names << :ARGS_SIMPLE if flag?(CALL_ARGS_SIMPLE)
        names << :KWARG if flag?(CALL_KWARG)
        names << :KW_SPLAT if flag?(CALL_KW_SPLAT)
        names << :TAILCALL if flag?(CALL_TAILCALL)
        names << :SUPER if flag?(CALL_SUPER)
        names << :ZSUPER if flag?(CALL_ZSUPER)
        names << :OPT_SEND if flag?(CALL_OPT_SEND)
        names << :KW_SPLAT_MUT if flag?(CALL_KW_SPLAT_MUT)

        parts = []
        parts << "mid:#{method}" if method
        parts << "argc:#{argc}"
        parts << "kw:[#{kw_arg.join(", ")}]" if kw_arg
        parts << names.join("|") if names.any?

        "<calldata!#{parts.join(", ")}>"
      end

      def self.from(serialized)
        new(
          serialized[:mid],
          serialized[:orig_argc],
          serialized[:flag],
          serialized[:kw_arg]
        )
      end
    end

    # A convenience method for creating a CallData object.
    def self.calldata(
      method,
      argc = 0,
      flags = CallData::CALL_ARGS_SIMPLE,
      kw_arg = nil
    )
      CallData.new(method, argc, flags, kw_arg)
    end
  end
end