class TypeDecision {

    // Record type decision for different type tuples.
    List<TypeContainer> containers;

    TypeDecision() {
      containers = new ArrayList<>();
    }

    public void addTypeDecision(TypeContainer container) {
      for (TypeContainer c : this.containers) {
        if ((typesEqual(container.lType, c.lType) && typesEqual(container.rType, c.rType)
            || (typesEqual(container.lType, c.rType) && typesEqual(container.rType, c.lType)))) {
          return;
        }
      }
      this.containers.add(container);
    }

    public Type getTypeDecision(Type l, Type r) {
      for (TypeContainer container : this.containers) {
        if (typesEqual(container.lType, l) && typesEqual(container.rType, r)) {
          return container.target;
        }
        if (typesEqual(container.lType, r) && typesEqual(container.rType, l)) {
          return container.target;
        }
      }
      return BottomType.v();
    }

    public TypeDecision copy() {
      TypeDecision decision = new TypeDecision();
      for (TypeContainer container : this.containers) {
        decision.containers.add(new TypeContainer(container.lType, container.rType, container.target));
      }
      return decision;
    }

    public void removeLast() {
      // Remove the last one.
      if (!this.containers.isEmpty()) {
        this.containers.remove(this.containers.size() - 1);
      }
    }
  }

  protected Collection<Typing> applyAssignmentConstraints(Typing tg, IEvalFunction ef, IHierarchy h) {<FILL_FUNCTION_BODY>

    final int numAssignments = this.assignments.size();
    if (numAssignments == 0) {
      return Collections.emptyList();
    }

    ArrayDeque<WorklistElement> sigma = createSigmaQueue();
    List<Typing> r = createResultList();

    final ITypingStrategy typingStrategy = getTypingStrategy();

    BitSet wl = new BitSet(numAssignments);
    wl.set(0, numAssignments);
    sigma.add(new WorklistElement(tg, wl, new TypeDecision()));

    Set<Type> throwable = null;

    while (!sigma.isEmpty()) {
      WorklistElement element = sigma.element();
      tg = element.typing;
      wl = element.worklist;
      TypeDecision ds = element.decision;
      int defIdx = wl.nextSetBit(0);
      if (defIdx == -1) {
        // worklist is empty
        r.add(tg);
        sigma.remove();
      } else {
        // Get the next definition statement
        wl.clear(defIdx);
        final DefinitionStmt stmt = this.assignments.get(defIdx);

        Value lhs = stmt.getLeftOp();
        Local v = (lhs instanceof Local) ? (Local) lhs : (Local) ((ArrayRef) lhs).getBase();
        Type told = tg.get(v);

        boolean isFirstType = true;
        for (Type t_ : ef.eval(tg, stmt.getRightOp(), stmt)) {
          if (lhs instanceof ArrayRef) {
            /*
             * We only need to consider array references on the LHS of assignments where there is supertyping between array
             * types, which is only for arrays of reference types and multidimensional arrays.
             */
            if (!(t_ instanceof RefType || t_ instanceof ArrayType || t_ instanceof WeakObjectType)) {
              continue;
            }

            t_ = t_.makeArrayType();
          }

          // Special handling for exception objects with phantom types
          final Collection<Type> lcas;
          if (!typesEqual(told, t_) && told instanceof RefType && t_ instanceof RefType
              && (((RefType) told).getSootClass().isPhantom() || ((RefType) t_).getSootClass().isPhantom())
              && (stmt.getRightOp() instanceof CaughtExceptionRef)) {
            if (throwable == null) {
              throwable = Collections.<Type>singleton(Scene.v().getBaseExceptionType());
            }
            lcas = throwable;
          } else {
            Type featureType = ds.getTypeDecision(told, t_);
            if (!typesEqual(featureType, BottomType.v())) {
              // Use feature type.
              lcas = Collections.singleton(featureType);
            } else {
              lcas = h.lcas(told, t_, true);
            }
          }
          boolean addFirstDecision = false;

          for (Type t : lcas) {
            if (!typesEqual(t, told)) {
              BitSet dependsV = this.depends.get(v);
              Typing tg_;
              BitSet wl_;
              TypeDecision ds_;
              if (/* (eval.size() == 1 && lcas.size() == 1) || */isFirstType) {
                // The types agree, we have a type we can directly use
                tg_ = tg;
                wl_ = wl;
                ds_ = ds;
              } else {
                // The types do not agree, add all supertype candidates
                tg_ = typingStrategy.createTyping(tg);
                wl_ = (BitSet) wl.clone();
                ds_ = ds.copy();
                if (addFirstDecision) {
                  ds_.removeLast();
                }
                WorklistElement e = new WorklistElement(tg_, wl_, ds_);

                sigma.add(e);
              }

              if (!typesEqual(told, BottomType.v()) && !typesEqual(t_, BottomType.v())) {
                // 't' is base class of type 'told' & 't_';
                // It will decide the feature type by target value.
                TypeContainer container = new TypeContainer(told, t_, t);
                ds_.addTypeDecision(container);

                // At first type, we will modify the base feature type decisions, and at the next type it will
                // copy the old feature type decisions and used the dirty data(so mark it and remove).
                addFirstDecision = isFirstType;
              }

              tg_.set(v, t);
              if (dependsV != null) {
                wl_.or(dependsV);
              }

            }
            isFirstType = false;
          }
        } // end for
      }
    }
    typingStrategy.minimize(r, h);
    return r;
  
<no_super_class>