class Steep::TypeInference::LocalVariableTypeEnv

def self.empty(subtyping:, self_type:)

def self.empty(subtyping:, self_type:)
  new(subtyping: subtyping, declared_types: {}, assigned_types: {}, self_type: self_type)
end

def [](var)

def [](var)
  entry(var)&.type
end

def annotate(collection)

def annotate(collection)
  decls = collection.var_type_annotations.each.with_object({}) do |(var, annotation), hash|
    type = collection.var_type(lvar: var)
    hash[var] = Entry.new(type: type, annotations: [annotation])
  end
  decls.each do |var, annot|
    inner_type = annot.type
    outer_type = self[var]
    if outer_type
      relation = Subtyping::Relation.new(sub_type: inner_type, super_type: outer_type)
      constraints = Subtyping::Constraints.new(unknowns: Set.new)
      subtyping.check(relation, constraints: constraints, self_type: self_type).else do |result|
        yield var, outer_type, inner_type, result
      end
    end
  end
  new_decls = declared_types.merge(decls)
  new_assigns = assigned_types.reject {|var, _| new_decls.key?(var) }
  update(declared_types: new_decls, assigned_types: new_assigns)
end

def assign(var, node:, type:)

def assign(var, node:, type:)
  declared_type = declared_types[var]&.type
  if declared_type
    relation = Subtyping::Relation.new(sub_type: type, super_type: declared_type)
    constraints = Subtyping::Constraints.new(unknowns: Set.new)
    subtyping.check(relation, constraints: constraints, self_type: self_type).else do |result|
      yield declared_type, type, result
    end
    self
  else
    assignments = { var => Entry.new(type: type, nodes: [node]) }
    update(assigned_types: assigned_types.merge(assignments))
  end
end

def assign!(var, node:, type:)

def assign!(var, node:, type:)
  declared_type = declared_types[var]&.type
  if declared_type
    relation = Subtyping::Relation.new(sub_type: type, super_type: declared_type)
    constraints = Subtyping::Constraints.new(unknowns: Set.new)
    subtyping.check(relation, constraints: constraints, self_type: self_type).else do |result|
      yield declared_type, type, result
    end
  end
  assignments = { var => Entry.new(type: type, nodes: [node]) }
  update(assigned_types: assigned_types.merge(assignments),
         declared_types: declared_types.reject {|k, _| k == var })
end

def each

def each
  if block_given?
    vars.each do |var|
      yield var, self[var]
    end
  else
    enum_for :each
  end
end

def entry(var)

def entry(var)
  declared_types[var] || assigned_types[var]
end

def except(variables)

def except(variables)
  update(
    declared_types: declared_types.reject {|var, _| variables.include?(var) },
    assigned_types: assigned_types.reject {|var, _| variables.include?(var) }
  )
end

def initialize(subtyping:, declared_types:, assigned_types:, self_type:)

def initialize(subtyping:, declared_types:, assigned_types:, self_type:)
  @subtyping = subtyping
  @self_type = self_type
  @declared_types = declared_types
  @assigned_types = assigned_types
  unless (intersection = Set.new(declared_types.keys) & Set.new(assigned_types.keys)).empty?
    raise "Declared types and assigned types should be disjoint: #{intersection}"
  end
end

def join(*envs)

def join(*envs)
  if envs.empty?
    self
  else
    env = envs.inject do |env1, env2|
      assigned_types = {}
      declared_types = {}
      (env1.vars + env2.vars).each do |var|
        e1 = env1.entry(var)
        e2 = env2.entry(var)
        je = join_entry(e1, e2)
        if env1.declared_types.key?(var) || env2.declared_types.key?(var)
          declared_types[var] = je
        else
          assigned_types[var] = je
        end
      end
      LocalVariableTypeEnv.new(
        subtyping: subtyping,
        self_type: self_type,
        declared_types: declared_types,
        assigned_types: assigned_types
      )
    end
    decls = env.declared_types.merge(declared_types)
    assignments = env.assigned_types.reject {|var, _| decls.key?(var) }
    update(
      declared_types: decls,
      assigned_types: assignments,
    )
  end
end

def join_entry(e1, e2)

def join_entry(e1, e2)
  case
  when e1 && e2
    e1 + e2
  when e1
    e1.optional
  when e2
    e2.optional
  else
    raise
  end
end

def pin_assignments

def pin_assignments
  update(
    declared_types: assigned_types.merge(declared_types),
    assigned_types: {}
  )
end

def to_s

def to_s
  ss = []
  vars.each do |var|
    ss << "#{var}: #{self[var].to_s}"
  end
  "{#{ss.join(", ")}}"
end

def update(declared_types: self.declared_types, assigned_types: self.assigned_types, self_type: self.self_type)

def update(declared_types: self.declared_types, assigned_types: self.assigned_types, self_type: self.self_type)
  self.class.new(
    subtyping: subtyping,
    declared_types: declared_types,
    assigned_types: assigned_types,
    self_type: self_type
  )
end

def vars

def vars
  @vars ||= Set.new(declared_types.keys + assigned_types.keys)
end