private TypeConstants.BoundCheckStatus internalBoundCheck(
      Substitution substitution, TypeBinding argumentType, Scope scope, ASTNode location) {
    if (argumentType == TypeBinding.NULL || TypeBinding.equalsEquals(argumentType, this)) {
      return BoundCheckStatus.OK;
    }
    boolean hasSubstitution = substitution != null;
    if (!(argumentType instanceof ReferenceBinding || argumentType.isArrayType()))
      return BoundCheckStatus.MISMATCH;
    // special case for re-entrant source types (selection, code assist, etc)...
    // can request additional types during hierarchy walk that are found as source types that also
    // 'need' to connect their hierarchy
    if (this.superclass == null) return BoundCheckStatus.OK;

    if (argumentType.kind() == Binding.WILDCARD_TYPE) {
      WildcardBinding wildcard = (WildcardBinding) argumentType;
      switch (wildcard.boundKind) {
        case Wildcard.EXTENDS:
          TypeBinding wildcardBound = wildcard.bound;
          if (TypeBinding.equalsEquals(wildcardBound, this)) return BoundCheckStatus.OK;
          boolean isArrayBound = wildcardBound.isArrayType();
          if (!wildcardBound.isInterface()) {
            TypeBinding substitutedSuperType =
                hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass;
            if (substitutedSuperType.id != TypeIds.T_JavaLangObject) {
              if (isArrayBound) {
                if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope))
                  return BoundCheckStatus.MISMATCH;
              } else {
                TypeBinding match =
                    wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType);
                if (match != null) {
                  if (substitutedSuperType.isProvablyDistinct(match)) {
                    return BoundCheckStatus.MISMATCH;
                  }
                } else {
                  match = substitutedSuperType.findSuperTypeOriginatingFrom(wildcardBound);
                  if (match != null) {
                    if (match.isProvablyDistinct(wildcardBound)) {
                      return BoundCheckStatus.MISMATCH;
                    }
                  } else {
                    if (denotesRelevantSuperClass(wildcardBound)
                        && denotesRelevantSuperClass(substitutedSuperType)) {
                      // non-object real superclass should have produced a valid 'match' above
                      return BoundCheckStatus.MISMATCH;
                    }
                  }
                }
              }
            }
          }
          boolean mustImplement = isArrayBound || ((ReferenceBinding) wildcardBound).isFinal();
          for (int i = 0, length = this.superInterfaces.length; i < length; i++) {
            TypeBinding substitutedSuperType =
                hasSubstitution
                    ? Scope.substitute(substitution, this.superInterfaces[i])
                    : this.superInterfaces[i];
            if (isArrayBound) {
              if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope))
                return BoundCheckStatus.MISMATCH;
            } else {
              TypeBinding match = wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType);
              if (match != null) {
                if (substitutedSuperType.isProvablyDistinct(match)) {
                  return BoundCheckStatus.MISMATCH;
                }
              } else if (mustImplement) {
                return BoundCheckStatus
                    .MISMATCH; // cannot be extended further to satisfy missing bounds
              }
            }
          }
          break;

        case Wildcard.SUPER:
          // if the wildcard is lower-bounded by a type variable that has no relevant upper bound
          // there's nothing to check here (bug 282152):
          if (wildcard.bound.isTypeVariable()
              && ((TypeVariableBinding) wildcard.bound).superclass.id == TypeIds.T_JavaLangObject)
            break;
          return boundCheck(substitution, wildcard.bound, scope, location);

        case Wildcard.UNBOUND:
          break;
      }
      return BoundCheckStatus.OK;
    }
    boolean unchecked = false;
    boolean checkNullAnnotations = scope.environment().usesNullTypeAnnotations();
    boolean haveReportedNullProblem = false;
    if (this.superclass.id != TypeIds.T_JavaLangObject) {
      TypeBinding substitutedSuperType =
          hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass;
      if (TypeBinding.notEquals(substitutedSuperType, argumentType)) {
        if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) {
          return BoundCheckStatus.MISMATCH;
        }
        TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType);
        if (match != null) {
          // Enum#RAW is not a substitute for <E extends Enum<E>> (86838)
          if (match.isRawType() && substitutedSuperType.isBoundParameterizedType())
            unchecked = true;
        }
      }
      if (location != null && checkNullAnnotations) {
        if (NullAnnotationMatching.analyse(
                this, argumentType, substitutedSuperType, substitution, -1, CheckMode.BOUND_CHECK)
            .isAnyMismatch()) {
          scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location);
          haveReportedNullProblem = true;
        }
      }
    }
    for (int i = 0, length = this.superInterfaces.length; i < length; i++) {
      TypeBinding substitutedSuperType =
          hasSubstitution
              ? Scope.substitute(substitution, this.superInterfaces[i])
              : this.superInterfaces[i];
      if (TypeBinding.notEquals(substitutedSuperType, argumentType)) {
        if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) {
          return BoundCheckStatus.MISMATCH;
        }
        TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType);
        if (match != null) {
          // Enum#RAW is not a substitute for <E extends Enum<E>> (86838)
          if (match.isRawType() && substitutedSuperType.isBoundParameterizedType())
            unchecked = true;
        }
      }
      if (location != null && checkNullAnnotations) {
        if (NullAnnotationMatching.analyse(
                this, argumentType, substitutedSuperType, substitution, -1, CheckMode.BOUND_CHECK)
            .isAnyMismatch()) {
          scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location);
          haveReportedNullProblem = true;
        }
      }
    }
    if (location != null && checkNullAnnotations && !haveReportedNullProblem) {
      long nullBits = this.tagBits & TagBits.AnnotationNullMASK;
      if (nullBits != 0 && nullBits != (argumentType.tagBits & TagBits.AnnotationNullMASK)) {
        scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location);
        haveReportedNullProblem = true;
      }
    }
    return unchecked
        ? BoundCheckStatus.UNCHECKED
        : haveReportedNullProblem ? BoundCheckStatus.NULL_PROBLEM : BoundCheckStatus.OK;
  }