public boolean isSubtype(ConcreteType that) {
        if (rawType.desc.equals(RawType.coreBottom))
            return true;

        SuperType[] myGenericArgsForThat = rawType.superGenerics.get(that.rawType);
        if (myGenericArgsForThat == null)
            return false; // the raw types don't even fit

        // Check each generic argument.
        for (int i = 0; i < myGenericArgsForThat.length; ++i) {
            ConcreteType myGenericArg = myGenericArgsForThat[i].toConcrete(genericArgs);
            ConcreteType requiredGenericArg = that.genericArgs[i];
            switch (that.rawType.genericVariances[i]) {
                case COVARIANT:
                    if (!myGenericArg.isSubtype(requiredGenericArg))
                        return false;
                    break;
                case NONVARIANT:
                    if (!myGenericArg.equals(requiredGenericArg))
                        return false;
                    break;
                case CONTRAVARIANT:
                    if (!requiredGenericArg.isSubtype(myGenericArg))
                        return false;
                    break;
            }
            // TODO: check generic bounds here.
        }

        // If we got here, the generic arguments all fit.
        return true;
    }