docs: private ConstraintSet solveEquality() {
      List<Constraint> current = eqConstraints;
      List<Constraint> next = Lists.newArrayList();
      boolean done = false;
      boolean solvable = this.solvable;
  
      TypeReplacement nextBindings = new TypeReplacement(bindings);
      while (!done) {
        done = true;
        // Iterative constraint solving algorithm with four cases
        for (Constraint c : current) {
          Type subtype = c.subtype.substitute(nextBindings.getBindings());
          Type supertype = c.supertype.substitute(nextBindings.getBindings());
          
          if (subtype.isFunctional() && supertype.isFunctional()) {
            // Case 1: <a,b> = <c,d> => a = c && b = d
            
            // Replace this constraint with the entailed subtype
            // constraints on argument/return types.
            next.add(new Constraint(supertype.getArgumentType(), subtype.getArgumentType()));
            next.add(new Constraint(subtype.getReturnType(), supertype.getReturnType()));
            done = false;
          } else if (!subtype.isFunctional() && supertype.isFunctional()) {
            // Case 2: A = <c,d> => A = <c,d> (if A is a type var, else fail)
            if (subtype.hasTypeVariables()) {
              nextBindings.add(subtype.getAtomicTypeVar(), supertype);
              done = false;
            } else {
              solvable = false;
            }
          } else if (subtype.isFunctional() && !supertype.isFunctional()) {
            // Case 3: <c,d> = A => A = <c,d> (if A is a type var, else fail)
            if (supertype.hasTypeVariables()) {
              nextBindings.add(supertype.getAtomicTypeVar(), subtype);
              done = false;
            } else {
              solvable = false;
            }
          } else {
            // Case 4: A = B => A = B
            if (subtype.hasTypeVariables() && supertype.hasTypeVariables()) {
              if (subtype.getAtomicTypeVar() != supertype.getAtomicTypeVar()) {
                nextBindings.add(subtype.getAtomicTypeVar(), supertype);
              }
            } else if (subtype.hasTypeVariables()) {
              nextBindings.add(subtype.getAtomicTypeVar(), supertype);
              done = false;
            } else if (supertype.hasTypeVariables()) {
              nextBindings.add(supertype.getAtomicTypeVar(), subtype);
              done = false;
            } else {
              if (!subtype.equals(supertype)) {
                solvable = false;
              }
            }
          }
        }
  
        current = next;
        next = Lists.newArrayList();
      }
      
      List<Constraint> newSubConstraints = Lists.newArrayList();
      for (Constraint c : subConstraints) {
        newSubConstraints.add(c.substitute(nextBindings.getBindings()));
      }
      return new ConstraintSet(Lists.newArrayList(), newSubConstraints, nextBindings,
          solvable, typeVarCounter);
    }