/**
   * Internal helper method to find the {@link AbstractMethodDeclaration} for a given {@link
   * MethodBinding} by searching a given set of statements.
   *
   * @param methodBinding {@link MethodBinding} to find the {@link AbstractMethodDeclaration} for
   * @param originalStatements statements to search for the {@link AbstractMethodDeclaration} in
   * @return {@link AbstractMethodDeclaration} for the given {@link MethodBinding} found in the
   *     given {@link ProgramElement}s, or <code>null</code> if it could not be found
   */
  private static AbstractMethodDeclaration declarationOf(
      MethodBinding methodBinding, ProgramElement[] originalStatements) {
    if (methodBinding != null && originalStatements != null) {
      List statements = new ArrayList(originalStatements.length);
      statements.addAll(Arrays.asList(originalStatements));

      for (int i = 0; i < statements.size(); i++) {
        IProgramElement statement = (IProgramElement) statements.get(i);
        if (statement instanceof MessageSend) {
          MessageSend msgSend = (MessageSend) statement;

          // search arguments of message send
          if (msgSend.arguments != null) {
            statements.addAll(Arrays.asList(msgSend.arguments));
          }

          /* add anonymous function message send
           *
           * function() { foo = "test" }(); */
          if (msgSend.receiver instanceof IFunctionExpression) {
            statements.add(msgSend.receiver);
          }

          continue;
        } else if (statement instanceof ObjectLiteral) {
          ObjectLiteral objLit = (ObjectLiteral) statement;
          if (objLit.fields != null) {
            statements.addAll(Arrays.asList(objLit.fields));
          }
          continue;
        } else if (statement instanceof ObjectLiteralField) {
          ObjectLiteralField objLitField = (ObjectLiteralField) statement;
          if (objLitField.initializer != null
              && (objLitField.initializer instanceof ObjectLiteral
                  || objLitField.initializer instanceof FunctionExpression)) {
            statements.add(objLitField.initializer);
            continue;
          }
        }

        AbstractMethodDeclaration methodDecl =
            AbstractMethodDeclaration.findMethodDeclaration(statement);

        // check statements inside of method declarations as well
        if (methodDecl != null && methodDecl.statements != null) {
          statements.addAll(Arrays.asList(methodDecl.statements));
        }

        // check if the found method declaration is the one that is being searched for
        if (methodDecl != null
            && (methodDecl.getBinding() == methodBinding
                || methodDecl.getBinding() == methodBinding.original())) {
          return methodDecl;
        }
      }
    }
    return null;
  }
  public void resolve(BlockScope scope) {
    MethodScope methodScope = scope.methodScope();

    if (methodScope == null) {
      /* return statement outside of a method */
      scope.problemReporter().cannotReturnOutsideFunction(this);
      return;
    }

    MethodBinding methodBinding = null;
    TypeBinding methodType =
        (methodScope.referenceContext instanceof AbstractMethodDeclaration)
            ? ((methodBinding = ((AbstractMethodDeclaration) methodScope.referenceContext).binding)
                    == null
                ? null
                : methodBinding.returnType)
            : TypeBinding.ANY;
    TypeBinding expressionType;
    if (this.expression == null) {
      if (methodType != null && !methodType.isAnyType())
        scope.problemReporter().shouldReturn(methodType, this);
      return;
    }
    this.expression.setExpectedType(methodType); // needed in case of generic method invocation
    if ((expressionType = this.expression.resolveType(scope)) == null) return;
    if (methodType == null) return;

    if (methodType
        != expressionType) // must call before computeConversion() and typeMismatchError()
    scope.compilationUnitScope().recordTypeConversion(methodType, expressionType);
    if (this.expression.isConstantValueOfTypeAssignableToType(expressionType, methodType)
        || expressionType.isCompatibleWith(methodType)) {

      return;
    }
    if (methodBinding != null && !methodBinding.isConstructor())
      scope.problemReporter().typeMismatchError(expressionType, methodType, this.expression);
  }
  public TypeBinding resolveType(BlockScope scope) {
    // Propagate the type checking to the arguments, and check if the constructor is defined.
    constant = Constant.NotAConstant;
    if (this.member != null) {
      this.resolvedType = this.member.resolveForAllocation(scope, this);
      if (this.resolvedType != null && !this.resolvedType.isValidBinding()) {
        scope.problemReporter().invalidType(this, this.resolvedType);
      }
    } else if (this.type == null) {
      // initialization of an enum constant
      this.resolvedType = scope.enclosingReceiverType();
    } else {
      this.resolvedType = this.type.resolveType(scope, true /* check bounds*/);
    }
    // will check for null after args are resolved
    // buffering the arguments' types
    boolean argsContainCast = false;
    TypeBinding[] argumentTypes = Binding.NO_PARAMETERS;
    if (arguments != null) {
      boolean argHasError = false;
      int length = arguments.length;
      argumentTypes = new TypeBinding[length];
      for (int i = 0; i < length; i++) {
        Expression argument = this.arguments[i];
        if ((argumentTypes[i] = argument.resolveType(scope)) == null) {
          argHasError = true;
          argumentTypes[i] = TypeBinding.UNKNOWN;
        }
      }
      if (argHasError) {
        //			if (this.resolvedType instanceof ReferenceBinding) {
        //				// record a best guess, for clients who need hint about possible contructor match
        //				TypeBinding[] pseudoArgs = new TypeBinding[length];
        //				for (int i = length; --i >= 0;)
        //					pseudoArgs[i] = argumentTypes[i] == null ? this.resolvedType : argumentTypes[i]; //
        // replace args with errors with receiver
        //				this.binding = scope.findMethod((ReferenceBinding) this.resolvedType,
        // TypeConstants.INIT, pseudoArgs, this);
        //			}
        //			return this.resolvedType;
      }
    }
    if (this.resolvedType == null
        || this.resolvedType.isAnyType()
        || this.resolvedType instanceof ProblemReferenceBinding) {
      this.binding =
          new ProblemMethodBinding(
              TypeConstants.INIT, Binding.NO_PARAMETERS, ProblemReasons.NotFound);
      this.resolvedType = TypeBinding.UNKNOWN;
      return this.resolvedType;
    }

    if (!this.resolvedType.isValidBinding()) return null;
    if (this.resolvedType instanceof ReferenceBinding) {
      ReferenceBinding allocationType = (ReferenceBinding) this.resolvedType;
      if (!(binding = scope.getConstructor(allocationType, argumentTypes, this)).isValidBinding()) {
        if (binding.declaringClass == null) binding.declaringClass = allocationType;
        scope.problemReporter().invalidConstructor(this, binding);
        return this.resolvedType;
      }
      if (argumentTypes.length != binding.parameters.length)
        scope.problemReporter().wrongNumberOfArguments(this, binding);
      if (isMethodUseDeprecated(binding, scope, true))
        scope.problemReporter().deprecatedMethod(binding, this);
      checkInvocationArguments(
          scope,
          null,
          allocationType,
          this.binding,
          this.arguments,
          argumentTypes,
          argsContainCast,
          this);
    }

    return this.resolvedType;
  }
  /**
   * The flowInfo corresponds to non-static field initialization infos. It may be unreachable
   * (155423), but still the explicit constructor call must be analysed as reachable, since it will
   * be generated in the end.
   */
  public void analyseCode(
      ClassScope classScope,
      InitializationFlowContext initializerFlowContext,
      FlowInfo flowInfo,
      int initialReachMode) {
    if (this.ignoreFurtherInvestigation) return;

    int nonStaticFieldInfoReachMode = flowInfo.reachMode();
    flowInfo.setReachMode(initialReachMode);

    checkUnused:
    {
      MethodBinding constructorBinding;
      if ((constructorBinding = this.binding) == null) break checkUnused;
      if (this.isDefaultConstructor) break checkUnused;
      if (constructorBinding.isUsed()) break checkUnused;
      if (constructorBinding.isPrivate()) {
        if ((this.binding.declaringClass.tagBits & TagBits.HasNonPrivateConstructor) == 0)
          break checkUnused; // tolerate as known pattern to block instantiation
      } else if ((this.binding.declaringClass.tagBits
              & (TagBits.IsAnonymousType | TagBits.IsLocalType))
          != TagBits.IsLocalType) {
        break checkUnused;
      }
      // complain unused
      this.scope.problemReporter().unusedPrivateConstructor(this);
    }

    // check constructor recursion, once all constructor got resolved
    if (isRecursive(null /*lazy initialized visited list*/)) {
      this.scope.problemReporter().recursiveConstructorInvocation(this.constructorCall);
    }

    try {
      ExceptionHandlingFlowContext constructorContext =
          new ExceptionHandlingFlowContext(
              initializerFlowContext.parent, this, null, this.scope, FlowInfo.DEAD_END);
      initializerFlowContext.checkInitializerExceptions(this.scope, constructorContext, flowInfo);

      // anonymous constructor can gain extra thrown exceptions from unhandled ones
      if (this.binding.declaringClass.isAnonymousType()) {
        ArrayList computedExceptions = constructorContext.extendedExceptions;
        if (computedExceptions != null) {
          int size;
          if ((size = computedExceptions.size()) > 0) {
            ReferenceBinding[] actuallyThrownExceptions;
            computedExceptions.toArray(actuallyThrownExceptions = new ReferenceBinding[size]);
          }
        }
      }

      // tag parameters as being set
      if (this.arguments != null) {
        for (int i = 0, count = this.arguments.length; i < count; i++) {
          flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding);
        }
      }

      // propagate to constructor call
      if (this.constructorCall != null) {
        // if calling 'this(...)', then flag all non-static fields as definitely
        // set since they are supposed to be set inside other local constructor
        if (this.constructorCall.accessMode == ExplicitConstructorCall.This) {
          FieldBinding[] fields = this.binding.declaringClass.fields();
          for (int i = 0, count = fields.length; i < count; i++) {
            FieldBinding field;
            if (!(field = fields[i]).isStatic()) {
              flowInfo.markAsDefinitelyAssigned(field);
            }
          }
        }
        flowInfo = this.constructorCall.analyseCode(this.scope, constructorContext, flowInfo);
      }

      // reuse the reachMode from non static field info
      flowInfo.setReachMode(nonStaticFieldInfoReachMode);

      // propagate to statements
      if (this.statements != null) {
        boolean didAlreadyComplain = false;
        for (int i = 0, count = this.statements.length; i < count; i++) {
          Statement stat = this.statements[i];
          if (!stat.complainIfUnreachable(flowInfo, this.scope, didAlreadyComplain)) {
            flowInfo = stat.analyseCode(this.scope, constructorContext, flowInfo);
          } else {
            didAlreadyComplain = true;
          }
        }
      }
      // check for missing returning path
      this.needFreeReturn = (flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0;

      // reuse the initial reach mode for diagnosing missing blank finals
      flowInfo.setReachMode(initialReachMode);

      // check missing blank final field initializations
      if ((this.constructorCall != null)
          && (this.constructorCall.accessMode != ExplicitConstructorCall.This)) {
        flowInfo = flowInfo.mergedWith(constructorContext.initsOnReturn);
      }
      // check unreachable catch blocks
      constructorContext.complainIfUnusedExceptionHandlers(this);
    } catch (AbortMethod e) {
      this.ignoreFurtherInvestigation = true;
    }
  }