docs: private IncompatibleTypes compareTypes(Type expectedType, Type actualType, boolean ignoreBaseType) {
          // XXX equality not implemented for GenericObjectType
          // if (parmType.equals(argType)) return true;
          if (expectedType == actualType) {
              return IncompatibleTypes.SEEMS_OK;
          }
          // Compare type signatures instead
          String expectedString = GenericUtilities.getString(expectedType);
          String actualString = GenericUtilities.getString(actualType);
          if (expectedString.equals(actualString)) {
              return IncompatibleTypes.SEEMS_OK;
          }
  
          if (expectedType.equals(Type.OBJECT))
          {
              return IncompatibleTypes.SEEMS_OK;
              // if either type is java.lang.Object, then automatically true!
              // again compare strings...
          }
  
          String objString = GenericUtilities.getString(Type.OBJECT);
  
          if (expectedString.equals(objString)) {
              return IncompatibleTypes.SEEMS_OK;
          }
  
          // get a category for each type
          TypeCategory expectedCat = GenericUtilities.getTypeCategory(expectedType);
          TypeCategory argCat = GenericUtilities.getTypeCategory(actualType);
          if (actualString.equals(objString) && expectedCat == TypeCategory.TYPE_VARIABLE) {
              return IncompatibleTypes.SEEMS_OK;
          }
          if (expectedCat == TypeCategory.WILDCARD) {
              return IncompatibleTypes.SEEMS_OK;
          }
          if (ignoreBaseType) {
              if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
                  GenericObjectType parmGeneric = (GenericObjectType) expectedType;
                  GenericObjectType argGeneric = (GenericObjectType) actualType;
                  return compareTypeParameters(parmGeneric, argGeneric);
              }
              return IncompatibleTypes.SEEMS_OK;
          }
  
          if (actualType.equals(Type.OBJECT) && expectedCat == TypeCategory.ARRAY_TYPE) {
              return IncompatibleTypes.ARRAY_AND_OBJECT;
          }
  
          // -~- plain objects are easy
          if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PLAIN_OBJECT_TYPE) {
              return IncompatibleTypes.getPriorityForAssumingCompatible(expectedType, actualType, false);
          }
  
          if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PLAIN_OBJECT_TYPE) {
              return IncompatibleTypes.getPriorityForAssumingCompatible((GenericObjectType) expectedType, actualType);
          }
          if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PARAMETERIZED) {
              return IncompatibleTypes.getPriorityForAssumingCompatible((GenericObjectType) actualType, expectedType);
          }
  
          // -~- parmType is: "? extends Another Type" OR "? super Another Type"
          if (expectedCat == TypeCategory.WILDCARD_EXTENDS || expectedCat == TypeCategory.WILDCARD_SUPER) {
              return compareTypes(((GenericObjectType) expectedType).getExtension(), actualType, ignoreBaseType);
          }
  
          // -~- Not handling type variables
          if (expectedCat == TypeCategory.TYPE_VARIABLE || argCat == TypeCategory.TYPE_VARIABLE) {
              return IncompatibleTypes.SEEMS_OK;
          }
  
          // -~- Array Types: compare dimensions, then base type
          if (expectedCat == TypeCategory.ARRAY_TYPE && argCat == TypeCategory.ARRAY_TYPE) {
              ArrayType parmArray = (ArrayType) expectedType;
              ArrayType argArray = (ArrayType) actualType;
  
              if (parmArray.getDimensions() != argArray.getDimensions()) {
                  return IncompatibleTypes.ARRAY_AND_NON_ARRAY;
              }
  
              return compareTypes(parmArray.getBasicType(), argArray.getBasicType(), ignoreBaseType);
          }
          // If one is an Array Type and the other is not, then they
          // are incompatible. (We already know neither is java.lang.Object)
          if (expectedCat == TypeCategory.ARRAY_TYPE ^ argCat == TypeCategory.ARRAY_TYPE) {
              return IncompatibleTypes.ARRAY_AND_NON_ARRAY;
          }
  
          // -~- Parameter Types: compare base type then parameters
          if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
              GenericObjectType parmGeneric = (GenericObjectType) expectedType;
              GenericObjectType argGeneric = (GenericObjectType) actualType;
  
              // base types should be related
              {
                  IncompatibleTypes result = compareTypes(parmGeneric.getObjectType(), argGeneric.getObjectType(), ignoreBaseType);
                  if (!result.equals(IncompatibleTypes.SEEMS_OK)) {
                      return result;
                  }
              }
              return compareTypeParameters(parmGeneric, argGeneric);
  
              // XXX More to come
          }
  
          // -~- Wildcard e.g. List<*>.contains(...)
          if (expectedCat == TypeCategory.WILDCARD) {
              return IncompatibleTypes.SEEMS_OK;
          }
  
          // -~- Non Reference types
          // if ( parmCat == TypeCategory.NON_REFERENCE_TYPE ||
          // argCat == TypeCategory.NON_REFERENCE_TYPE )
          if (expectedType instanceof BasicType || actualType instanceof BasicType) {
              // this should not be possible, compiler will complain (pre 1.5)
              // or autobox primitive types (1.5 +)
              throw new IllegalArgumentException("checking for compatibility of " + expectedType + " with " + actualType);
          }
  
          return IncompatibleTypes.SEEMS_OK;
  
      }