docs: private static boolean areEqualWithGenerics(ClassNode a, ClassNode b) {
          if (a==null) return b==null;
          if (!a.equals(b)) return false;
          if (a.isUsingGenerics() && !b.isUsingGenerics()) return false;
          GenericsType[] gta = a.getGenericsTypes();
          GenericsType[] gtb = b.getGenericsTypes();
          if (gta==null && gtb!=null) return false;
          if (gtb==null && gta!=null) return false;
          if (gta!=null && gtb!=null) {
              if (gta.length!=gtb.length) return false;
              for (int i = 0; i < gta.length; i++) {
                  GenericsType ga = gta[i];
                  GenericsType gb = gtb[i];
                  boolean result = ga.isPlaceholder()==gb.isPlaceholder() && ga.isWildcard()==gb.isWildcard();
                  result = result && ga.isResolved() && gb.isResolved();
                  result = result && ga.getName().equals(gb.getName());
                  result = result && areEqualWithGenerics(ga.getType(), gb.getType());
                  result = result && areEqualWithGenerics(ga.getLowerBound(), gb.getLowerBound());
                  if (result) {
                      ClassNode[] upA = ga.getUpperBounds();
                      if (upA!=null) {
                          ClassNode[] upB = gb.getUpperBounds();
                          if (upB==null || upB.length!=upA.length) return false;
                          for (int j = 0; j < upA.length; j++) {
                              if (!areEqualWithGenerics(upA[j],upB[j])) return false;
                          }
                      }
                  }
                  if (!result) return false;
              }
          }
          return true;
      }