lib/steep/interface/type_param.rb



module Steep
  module Interface
    class TypeParam
      attr_reader :name
      attr_reader :upper_bound
      attr_reader :variance
      attr_reader :unchecked
      attr_reader :location
      attr_reader :default_type

      def initialize(name:, upper_bound:, variance:, unchecked:, location: nil, default_type:)
        @name = name
        @upper_bound = upper_bound
        @variance = variance
        @unchecked = unchecked
        @location = location
        @default_type = default_type
      end

      def ==(other)
        other.is_a?(TypeParam) &&
          other.name == name &&
          other.upper_bound == upper_bound &&
          other.variance == variance &&
          other.unchecked == unchecked &&
          other.default_type == default_type
      end

      alias eql? ==

      def hash
        name.hash ^ upper_bound.hash ^ variance.hash ^ unchecked.hash ^ default_type.hash
      end

      def self.rename(params, conflicting_names = params.map(&:name), new_names = conflicting_names.map {|n| AST::Types::Var.fresh_name(n) })
        unless conflicting_names.empty?
          hash = conflicting_names.zip(new_names).to_h
          new_types = new_names.map {|n| AST::Types::Var.new(name: n) }

          subst = Substitution.build(conflicting_names, new_types)

          [
            params.map do |param|
              if hash.key?(param.name) || param.upper_bound
                TypeParam.new(
                  name: hash[param.name] || param.name,
                  upper_bound: param.upper_bound&.subst(subst),
                  variance: param.variance,
                  unchecked: param.unchecked,
                  location: param.location,
                  default_type: param.default_type&.subst(subst)
                )
              else
                param
              end
            end,
            subst
          ]
        else
          [params, Substitution.empty]
        end
      end

      def to_s
        buf = ""

        if unchecked
          buf << "unchecked "
        end

        case variance
        when :covariant
          buf << "out "
        when :contravariant
          buf << "in "
        end

        buf << name.to_s

        if upper_bound
          buf << " < #{upper_bound}"
        end

        buf
      end

      def update(name: self.name, upper_bound: self.upper_bound, variance: self.variance, unchecked: self.unchecked, location: self.location, default_type: self.default_type)
        TypeParam.new(
          name: name,
          upper_bound: upper_bound,
          variance: variance,
          unchecked: unchecked,
          location: location,
          default_type: default_type
        )
      end

      def subst(s)
        if u = upper_bound
          ub = u.subst(s)
        end

        if d = default_type
          dt = d.subst(s)
        end

        if ub || dt
          update(upper_bound: ub, default_type: dt)
        else
          self
        end
      end
    end
  end
end