<commit_before>require_relative 'type'

module RDL::Type
  class UnionType < Type
    attr_reader :types

    @@cache = {}

    class << self
      alias :__new__ :new
    end

    def self.new(*types)
      ts = []
      types.each { |t|
        if t.instance_of? TopType
          ts = [t]
          break
        elsif t.instance_of? UnionType
          ts.concat t.types
        else
          raise RuntimeError, "Attempt to create union type with non-type" unless t.is_a? Type
          ts << t
        end
      }

      ts.sort! { |a, b| a.object_id <=> b.object_id }
      ts.uniq!

      if ts.member? $__rdl_nil_type
        # nil shouldn't be in union *unless* there are only otherwise singleton types, since
        # nil is not a subtype of singleton types
        ts.delete $__rdl_nil_type unless ts.all? { |t| t.is_a? SingletonType }
      end

      return $__rdl_nil_type if ts.size == 0
      return ts[0] if ts.size == 1

      t = @@cache[ts]
      return t if t
      t = UnionType.__new__(ts)
      return (@@cache[ts] = t) # assignment evaluates to t
    end

    def initialize(types)
      @types = types
      super()
    end

    def to_s  # :nodoc:
      "#{@types.map { |t| t.to_s }.join(' or ')}"
    end

    def eql?(other)
      self == other
    end

    def ==(other)  # :nodoc:
      return (other.instance_of? UnionType) && (other.types == @types)
    end

    def match(other)
      other = other.type if other.instance_of? AnnotatedArgType
      return true if other.instance_of? WildQuery
      return false if @types.length != other.types.length
      @types.all? { |t| other.types.any? { |ot| t.match(ot) } }
    end

    def <=(other)
      @types.all? { |t| t <= other }
    end

    def member?(obj, *args)
      @types.any? { |t| t.member?(obj, *args) }
    end

    def instantiate(inst)
      return UnionType.new(*(@types.map { |t| t.instantiate(inst) }))
    end

    def hash  # :nodoc:
      41 + @types.hash
    end
  end
end<commit_msg>remove union caching, replace with lazy caoninical representation, better == comparison that does not rely on types being in a certain order.<commit_after>module RDL::Type
  class UnionType < Type
    attr_reader :types

    class << self
      alias :__new__ :new
    end

    def self.new(*types)
      return $__rdl_nil_type if types.size == 0
      ts = []
      # flatten nested unions, check that all args are types
      types.each { |t|
        if t.instance_of? UnionType
          ts.concat t.types
        else
          raise RuntimeError, "Attempt to create union type with non-type" unless t.is_a? Type
          ts << t
        end
      }
      return ts[0] if ts.size == 1
      return UnionType.__new__(ts)
    end

    def initialize(types)
      @types = types
      @canonical = false
      @canonicalized = false
      @hash = 41 + @types.hash # don't rehash if @types changes
      super()
    end

    def canonical
      canonicalize!
      return @canonical if @canonical
      return self
    end

    def canonicalize!
      return if @canonicalized
      # for any type such that a supertype is already in ts, set its position to nil
      for i in 0..(@types.length-1)
        for j in (i+1)..(@types.length-1)
          next if (@types[j].nil?) || (@types[i].nil?)
          (@types[i] = nil; break) if @types[i] <= @types[j]
          (@types[j] = nil) if @types[j] <= @types[i]
        end
      end
      @types.delete(nil) # eliminate any "deleted" elements
      @types.sort! { |a, b| a.object_id <=> b.object_id } # canonicalize order
      @types.uniq!
      @canonical = @types[0] if @types.size == 1
      @canonicalized = true
    end

    def to_s  # :nodoc:
      return @canonical.to_s if @canonical
      return "#{@types.map { |t| t.to_s }.join(' or ')}"
    end

    def ==(other)  # :nodoc:
      return false if other.nil?
      canonicalize!
      return @canonical == other if @canonical
      other = other.canonical
      return false unless other.instance_of? UnionType
      other.canonicalize!
      return false unless @types.length == other.types.length
      return @types.all? { |t| other.types.any? { |ot| t == ot } }
    end

    alias eql? ==

    def match(other)
      canonicalize!
      return @canonical.match(other) if @canonical
      other = other.canonical
      other = other.type if other.instance_of? AnnotatedArgType
      return true if other.instance_of? WildQuery
      return false if @types.length != other.types.length
      @types.all? { |t| other.types.any? { |ot| t.match(ot) } }
    end

    def <=(other)
      canonicalize!
      return @canonical <= other if @canonical
      other = other.canonical
      @types.all? { |t| t <= other }
    end

    def member?(obj, *args)
      canonicalize!
      return @canonical.member?(obj, *args) if @canonical
      @types.any? { |t| t.member?(obj, *args) }
    end

    def instantiate(inst)
      canonicalize!
      return @canonical.instantiate(inst) if @canonical
      return UnionType.new(*(@types.map { |t| t.instantiate(inst) }))
    end

    def hash  # :nodoc:
      return @hash
    end
  end
end
{'fraction_non_alphanumeric': 0.1165692007797271, 'fraction_numerical': 0.0031189083820662767, 'mean_word_length': 2.130567419158023, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4189742', 'n_tokens_mistral': 1714, 'n_tokens_neox': 1643, 'n_words': 498}