/**
   * Casting an enclosing instance will considered as useful if removing it would actually bind to a
   * different type
   */
  public static void checkNeedForEnclosingInstanceCast(
      BlockScope scope,
      Expression enclosingInstance,
      TypeBinding enclosingInstanceType,
      TypeBinding memberType) {
    if (scope.compilerOptions().getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    TypeBinding castedExpressionType = ((CastExpression) enclosingInstance).expression.resolvedType;
    if (castedExpressionType == null) return; // cannot do better
    // obvious identity cast
    if (TypeBinding.equalsEquals(castedExpressionType, enclosingInstanceType)) {
      scope.problemReporter().unnecessaryCast((CastExpression) enclosingInstance);
    } else if (castedExpressionType == TypeBinding.NULL) {
      return; // tolerate null enclosing instance cast
    } else {
      TypeBinding alternateEnclosingInstanceType = castedExpressionType;
      if (castedExpressionType.isBaseType() || castedExpressionType.isArrayType())
        return; // error case
      if (TypeBinding.equalsEquals(
          memberType,
          scope.getMemberType(
              memberType.sourceName(), (ReferenceBinding) alternateEnclosingInstanceType))) {
        scope.problemReporter().unnecessaryCast((CastExpression) enclosingInstance);
      }
    }
  }
  /**
   * Cast expressions will considered as useful if removing them all would actually bind to a
   * different method (no fine grain analysis on per casted argument basis, simply separate widening
   * cast from narrowing ones)
   */
  public static void checkNeedForArgumentCasts(
      BlockScope scope,
      Expression receiver,
      TypeBinding receiverType,
      MethodBinding binding,
      Expression[] arguments,
      TypeBinding[] argumentTypes,
      final InvocationSite invocationSite) {
    if (scope.compilerOptions().getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    int length = argumentTypes.length;

    // iterate over arguments, and retrieve original argument types (before cast)
    TypeBinding[] rawArgumentTypes = argumentTypes;
    for (int i = 0; i < length; i++) {
      Expression argument = arguments[i];
      if (argument instanceof CastExpression) {
        // narrowing conversion on base type may change value, thus necessary
        if ((argument.bits & ASTNode.UnnecessaryCast) == 0 && argument.resolvedType.isBaseType()) {
          continue;
        }
        TypeBinding castedExpressionType = ((CastExpression) argument).expression.resolvedType;
        if (castedExpressionType == null) return; // cannot do better
        // obvious identity cast
        if (TypeBinding.equalsEquals(castedExpressionType, argumentTypes[i])) {
          scope.problemReporter().unnecessaryCast((CastExpression) argument);
        } else if (castedExpressionType == TypeBinding.NULL) {
          continue; // tolerate null argument cast
        } else if ((argument.implicitConversion & TypeIds.BOXING) != 0) {
          continue; // boxing has a side effect: (int) char   is not boxed as simple char
        } else {
          if (rawArgumentTypes == argumentTypes) {
            System.arraycopy(
                rawArgumentTypes, 0, rawArgumentTypes = new TypeBinding[length], 0, length);
          }
          // retain original argument type
          rawArgumentTypes[i] = castedExpressionType;
        }
      }
    }
    // perform alternate lookup with original types
    if (rawArgumentTypes != argumentTypes) {
      checkAlternateBinding(
          scope,
          receiver,
          receiverType,
          binding,
          arguments,
          argumentTypes,
          rawArgumentTypes,
          invocationSite);
    }
  }
  /**
   * Complain if cast expression is cast, but not actually needed, int i = (int)(Integer) 12; Note
   * that this (int) cast is however needed: Integer i = 0; char c = (char)((int) i);
   */
  public static void checkNeedForCastCast(BlockScope scope, CastExpression enclosingCast) {
    if (scope.compilerOptions().getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    CastExpression nestedCast = (CastExpression) enclosingCast.expression;
    if ((nestedCast.bits & ASTNode.UnnecessaryCast) == 0) return;
    // check if could cast directly to enclosing cast type, without intermediate type cast
    CastExpression alternateCast = new CastExpression(null, enclosingCast.type);
    alternateCast.resolvedType = enclosingCast.resolvedType;
    if (!alternateCast.checkCastTypesCompatibility(
        scope,
        enclosingCast.resolvedType,
        nestedCast.expression.resolvedType,
        null /* no expr to avoid side-effects*/)) return;
    scope.problemReporter().unnecessaryCast(nestedCast);
  }
  /**
   * Complain if assigned expression is cast, but not actually used as such, e.g. Object o = (List)
   * object;
   */
  public static void checkNeedForAssignedCast(
      BlockScope scope, TypeBinding expectedType, CastExpression rhs) {
    CompilerOptions compilerOptions = scope.compilerOptions();
    if (compilerOptions.getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    TypeBinding castedExpressionType = rhs.expression.resolvedType;
    //	int i = (byte) n; // cast still had side effect
    // double d = (float) n; // cast to float is unnecessary
    if (castedExpressionType == null || rhs.resolvedType.isBaseType()) return;
    // if (castedExpressionType.id == T_null) return; // tolerate null expression cast
    if (castedExpressionType.isCompatibleWith(expectedType, scope)) {
      if (scope.environment().usesNullTypeAnnotations()) {
        // are null annotations compatible, too?
        if (NullAnnotationMatching.analyse(expectedType, castedExpressionType, -1).isAnyMismatch())
          return; // already reported unchecked cast (nullness), say no more.
      }
      scope.problemReporter().unnecessaryCast(rhs);
    }
  }
  public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {

    if (this.constantExpression != null) {
      if (this.constantExpression.constant == Constant.NotAConstant
          && !this.constantExpression.resolvedType.isEnum()) {
        currentScope.problemReporter().caseExpressionMustBeConstant(this.constantExpression);
      }
      this.constantExpression.analyseCode(currentScope, flowContext, flowInfo);
    }
    return flowInfo;
  }
  /**
   * Only complain for identity cast, since other type of casts may be useful: e.g. ~((~(long) 0) <<
   * 32) is different from: ~((~0) << 32)
   */
  public static void checkNeedForArgumentCast(
      BlockScope scope,
      int operator,
      int operatorSignature,
      Expression expression,
      int expressionTypeId) {
    if (scope.compilerOptions().getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    // check need for left operand cast
    if ((expression.bits & ASTNode.UnnecessaryCast) == 0 && expression.resolvedType.isBaseType()) {
      // narrowing conversion on base type may change value, thus necessary
      return;
    } else {
      TypeBinding alternateLeftType = ((CastExpression) expression).expression.resolvedType;
      if (alternateLeftType == null) return; // cannot do better
      if (alternateLeftType.id == expressionTypeId) { // obvious identity cast
        scope.problemReporter().unnecessaryCast((CastExpression) expression);
        return;
      }
    }
  }
  public TypeBinding resolveType(BlockScope scope) {
    this.constant = Constant.NotAConstant;
    if (this.arguments != null) {
      int argsLength = this.arguments.length;
      for (int a = argsLength; --a >= 0; ) this.arguments[a].resolveType(scope);
    }

    if (this.receiver.isImplicitThis()) throw new CompletionNodeFound(this, null, scope);

    this.actualReceiverType = this.receiver.resolveType(scope);
    if (this.actualReceiverType == null || this.actualReceiverType.isBaseType())
      throw new CompletionNodeFound();

    if (this.actualReceiverType.isArrayType()) this.actualReceiverType = scope.getJavaLangObject();
    throw new CompletionNodeFound(this, this.actualReceiverType, scope);
  }
  public TypeBinding resolveType(BlockScope scope) {
    // compute a new constant if the cast is effective

    this.constant = Constant.NotAConstant;
    this.implicitConversion = TypeIds.T_undefined;

    boolean exprContainCast = false;

    TypeBinding castType = this.resolvedType = this.type.resolveType(scope);
    if (scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8) {
      this.expression.setExpressionContext(CASTING_CONTEXT);
      if (this.expression instanceof FunctionalExpression) {
        this.expression.setExpectedType(this.resolvedType);
        this.bits |= ASTNode.DisableUnnecessaryCastCheck;
      }
    }
    if (this.expression instanceof CastExpression) {
      this.expression.bits |= ASTNode.DisableUnnecessaryCastCheck;
      exprContainCast = true;
    }
    TypeBinding expressionType = this.expression.resolveType(scope);
    if (this.expression instanceof MessageSend) {
      MessageSend messageSend = (MessageSend) this.expression;
      MethodBinding methodBinding = messageSend.binding;
      if (methodBinding != null && methodBinding.isPolymorphic()) {
        messageSend.binding =
            scope
                .environment()
                .updatePolymorphicMethodReturnType(
                    (PolymorphicMethodBinding) methodBinding, castType);
        if (TypeBinding.notEquals(expressionType, castType)) {
          expressionType = castType;
          this.bits |= ASTNode.DisableUnnecessaryCastCheck;
        }
      }
    }
    if (castType != null) {
      if (expressionType != null) {

        boolean nullAnnotationMismatch =
            scope.compilerOptions().isAnnotationBasedNullAnalysisEnabled
                && NullAnnotationMatching.analyse(castType, expressionType, -1).isAnyMismatch();

        boolean isLegal =
            checkCastTypesCompatibility(scope, castType, expressionType, this.expression);
        if (isLegal) {
          this.expression.computeConversion(scope, castType, expressionType);
          if ((this.bits & ASTNode.UnsafeCast) != 0) { // unsafe cast
            if (scope.compilerOptions().reportUnavoidableGenericTypeProblems
                || !(expressionType.isRawType()
                    && this.expression.forcedToBeRaw(scope.referenceContext()))) {
              scope.problemReporter().unsafeCast(this, scope);
            }
          } else if (nullAnnotationMismatch) {
            // report null annotation issue at medium priority
            scope.problemReporter().unsafeNullnessCast(this, scope);
          } else {
            if (castType.isRawType()
                && scope.compilerOptions().getSeverity(CompilerOptions.RawTypeReference)
                    != ProblemSeverities.Ignore) {
              scope.problemReporter().rawTypeReference(this.type, castType);
            }
            if ((this.bits & (ASTNode.UnnecessaryCast | ASTNode.DisableUnnecessaryCastCheck))
                == ASTNode.UnnecessaryCast) { // unnecessary cast
              if (!isIndirectlyUsed()) // used for generic type inference or boxing ?
              scope.problemReporter().unnecessaryCast(this);
            }
          }
        } else { // illegal cast
          if ((castType.tagBits & TagBits.HasMissingType) == 0) { // no complaint if secondary error
            scope.problemReporter().typeCastError(this, castType, expressionType);
          }
          this.bits |= ASTNode.DisableUnnecessaryCastCheck; // disable further secondary diagnosis
        }
      }
      this.resolvedType =
          castType.capture(
              scope,
              this.type.sourceStart,
              this.type.sourceEnd); // make it unique, a cast expression shares source end with the
      // expression.
      if (exprContainCast) {
        checkNeedForCastCast(scope, this);
      }
    }
    return this.resolvedType;
  }
  private static void checkAlternateBinding(
      BlockScope scope,
      Expression receiver,
      TypeBinding receiverType,
      MethodBinding binding,
      Expression[] arguments,
      TypeBinding[] originalArgumentTypes,
      TypeBinding[] alternateArgumentTypes,
      final InvocationSite invocationSite) {
    InvocationSite fakeInvocationSite =
        new InvocationSite() {
          public TypeBinding[] genericTypeArguments() {
            return null;
          }

          public boolean isSuperAccess() {
            return invocationSite.isSuperAccess();
          }

          public boolean isTypeAccess() {
            return invocationSite.isTypeAccess();
          }

          public void setActualReceiverType(ReferenceBinding actualReceiverType) {
            /* ignore */
          }

          public void setDepth(int depth) {
            /* ignore */
          }

          public void setFieldIndex(int depth) {
            /* ignore */
          }

          public int sourceStart() {
            return 0;
          }

          public int sourceEnd() {
            return 0;
          }

          public TypeBinding invocationTargetType() {
            return invocationSite.invocationTargetType();
          }

          public boolean receiverIsImplicitThis() {
            return invocationSite.receiverIsImplicitThis();
          }

          public InferenceContext18 freshInferenceContext(Scope someScope) {
            return invocationSite.freshInferenceContext(someScope);
          }

          public ExpressionContext getExpressionContext() {
            return invocationSite.getExpressionContext();
          }

          public boolean isQualifiedSuper() {
            return invocationSite.isQualifiedSuper();
          }

          public boolean checkingPotentialCompatibility() {
            return false;
          }

          public void acceptPotentiallyCompatibleMethods(MethodBinding[] methods) {
            /* ignore */
          }
        };
    MethodBinding bindingIfNoCast;
    if (binding.isConstructor()) {
      bindingIfNoCast =
          scope.getConstructor(
              (ReferenceBinding) receiverType, alternateArgumentTypes, fakeInvocationSite);
    } else {
      bindingIfNoCast =
          receiver.isImplicitThis()
              ? scope.getImplicitMethod(
                  binding.selector, alternateArgumentTypes, fakeInvocationSite)
              : scope.getMethod(
                  receiverType, binding.selector, alternateArgumentTypes, fakeInvocationSite);
    }
    if (bindingIfNoCast == binding) {
      int argumentLength = originalArgumentTypes.length;
      if (binding.isVarargs()) {
        int paramLength = binding.parameters.length;
        if (paramLength == argumentLength) {
          int varargsIndex = paramLength - 1;
          ArrayBinding varargsType = (ArrayBinding) binding.parameters[varargsIndex];
          TypeBinding lastArgType = alternateArgumentTypes[varargsIndex];
          // originalType may be compatible already, but cast mandated
          // to clarify between varargs/non-varargs call
          if (varargsType.dimensions != lastArgType.dimensions()) {
            return;
          }
          if (lastArgType.isCompatibleWith(varargsType.elementsType())
              && lastArgType.isCompatibleWith(varargsType)) {
            return;
          }
        }
      }
      for (int i = 0; i < argumentLength; i++) {
        if (TypeBinding.notEquals(originalArgumentTypes[i], alternateArgumentTypes[i])
        /*&& !originalArgumentTypes[i].needsUncheckedConversion(alternateArgumentTypes[i])*/ ) {
          scope.problemReporter().unnecessaryCast((CastExpression) arguments[i]);
        }
      }
    }
  }
  /** Check binary operator casted arguments */
  public static void checkNeedForArgumentCasts(
      BlockScope scope,
      int operator,
      int operatorSignature,
      Expression left,
      int leftTypeId,
      boolean leftIsCast,
      Expression right,
      int rightTypeId,
      boolean rightIsCast) {
    if (scope.compilerOptions().getSeverity(CompilerOptions.UnnecessaryTypeCheck)
        == ProblemSeverities.Ignore) return;

    // check need for left operand cast
    int alternateLeftTypeId = leftTypeId;
    if (leftIsCast) {
      if ((left.bits & ASTNode.UnnecessaryCast) == 0 && left.resolvedType.isBaseType()) {
        // narrowing conversion on base type may change value, thus necessary
        leftIsCast = false;
      } else {
        TypeBinding alternateLeftType = ((CastExpression) left).expression.resolvedType;
        if (alternateLeftType == null) return; // cannot do better
        if ((alternateLeftTypeId = alternateLeftType.id) == leftTypeId
            || scope.environment().computeBoxingType(alternateLeftType).id
                == leftTypeId) { // obvious identity cast
          scope.problemReporter().unnecessaryCast((CastExpression) left);
          leftIsCast = false;
        } else if (alternateLeftTypeId == TypeIds.T_null) {
          alternateLeftTypeId = leftTypeId; // tolerate null argument cast
          leftIsCast = false;
        }
      }
    }
    // check need for right operand cast
    int alternateRightTypeId = rightTypeId;
    if (rightIsCast) {
      if ((right.bits & ASTNode.UnnecessaryCast) == 0 && right.resolvedType.isBaseType()) {
        // narrowing conversion on base type may change value, thus necessary
        rightIsCast = false;
      } else {
        TypeBinding alternateRightType = ((CastExpression) right).expression.resolvedType;
        if (alternateRightType == null) return; // cannot do better
        if ((alternateRightTypeId = alternateRightType.id) == rightTypeId
            || scope.environment().computeBoxingType(alternateRightType).id
                == rightTypeId) { // obvious identity cast
          scope.problemReporter().unnecessaryCast((CastExpression) right);
          rightIsCast = false;
        } else if (alternateRightTypeId == TypeIds.T_null) {
          alternateRightTypeId = rightTypeId; // tolerate null argument cast
          rightIsCast = false;
        }
      }
    }
    if (leftIsCast || rightIsCast) {
      if (alternateLeftTypeId > 15
          || alternateRightTypeId > 15) { // must convert String + Object || Object + String
        if (alternateLeftTypeId == TypeIds.T_JavaLangString) {
          alternateRightTypeId = TypeIds.T_JavaLangObject;
        } else if (alternateRightTypeId == TypeIds.T_JavaLangString) {
          alternateLeftTypeId = TypeIds.T_JavaLangObject;
        } else {
          return; // invalid operator
        }
      }
      int alternateOperatorSignature =
          OperatorExpression.OperatorSignatures[operator][
              (alternateLeftTypeId << 4) + alternateRightTypeId];
      // (cast)  left   Op (cast)  right --> result
      //  1111   0000       1111   0000     1111
      //  <<16   <<12       <<8    <<4       <<0
      final int CompareMASK = (0xF << 16) + (0xF << 8) + 0xF; // mask hiding compile-time types
      if ((operatorSignature & CompareMASK)
          == (alternateOperatorSignature & CompareMASK)) { // same promotions and result
        if (leftIsCast) scope.problemReporter().unnecessaryCast((CastExpression) left);
        if (rightIsCast) scope.problemReporter().unnecessaryCast((CastExpression) right);
      }
    }
  }
  /**
   * Returns the constant intValue or ordinal for enum constants. If constant is NotAConstant, then
   * answers Float.MIN_VALUE
   *
   * @see
   *     org.aspectj.org.eclipse.jdt.internal.compiler.ast.Statement#resolveCase(org.aspectj.org.eclipse.jdt.internal.compiler.lookup.BlockScope,
   *     org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeBinding,
   *     org.aspectj.org.eclipse.jdt.internal.compiler.ast.SwitchStatement)
   */
  public Constant resolveCase(
      BlockScope scope, TypeBinding switchExpressionType, SwitchStatement switchStatement) {
    // switchExpressionType maybe null in error case
    scope.enclosingCase = this; // record entering in a switch case block

    if (this.constantExpression == null) {
      // remember the default case into the associated switch statement
      if (switchStatement.defaultCase != null) scope.problemReporter().duplicateDefaultCase(this);

      // on error the last default will be the selected one ...
      switchStatement.defaultCase = this;
      return Constant.NotAConstant;
    }
    // add into the collection of cases of the associated switch statement
    switchStatement.cases[switchStatement.caseCount++] = this;
    // tag constant name with enum type for privileged access to its members
    if (switchExpressionType != null
        && switchExpressionType.isEnum()
        && (this.constantExpression instanceof SingleNameReference)) {
      ((SingleNameReference) this.constantExpression)
          .setActualReceiverType((ReferenceBinding) switchExpressionType);
    }
    TypeBinding caseType = this.constantExpression.resolveType(scope);
    if (caseType == null || switchExpressionType == null) return Constant.NotAConstant;
    if (this.constantExpression.isConstantValueOfTypeAssignableToType(
            caseType, switchExpressionType)
        || caseType.isCompatibleWith(switchExpressionType)) {
      if (caseType.isEnum()) {
        if (((this.constantExpression.bits & ASTNode.ParenthesizedMASK)
                >> ASTNode.ParenthesizedSHIFT)
            != 0) {
          scope
              .problemReporter()
              .enumConstantsCannotBeSurroundedByParenthesis(this.constantExpression);
        }

        if (this.constantExpression instanceof NameReference
            && (this.constantExpression.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) {
          NameReference reference = (NameReference) this.constantExpression;
          FieldBinding field = reference.fieldBinding();
          if ((field.modifiers & ClassFileConstants.AccEnum) == 0) {
            scope.problemReporter().enumSwitchCannotTargetField(reference, field);
          } else if (reference instanceof QualifiedNameReference) {
            scope.problemReporter().cannotUseQualifiedEnumConstantInCaseLabel(reference, field);
          }
          return IntConstant.fromValue(
              field.original().id
                  + 1); // (ordinal value + 1) zero should not be returned see bug 141810
        }
      } else {
        return this.constantExpression.constant;
      }
    } else if (isBoxingCompatible(caseType, switchExpressionType, this.constantExpression, scope)) {
      // constantExpression.computeConversion(scope, caseType, switchExpressionType); - do not
      // report boxing/unboxing conversion
      return this.constantExpression.constant;
    }
    scope
        .problemReporter()
        .typeMismatchError(
            caseType, switchExpressionType, this.constantExpression, switchStatement.expression);
    return Constant.NotAConstant;
  }