示例#1
0
 /** During deferred checking re-visit a previously recording unboxing situation. */
 protected void checkUnboxing(Scope scope, Expression expression, FlowInfo flowInfo) {
   int status = expression.nullStatus(flowInfo, this);
   if ((status & FlowInfo.NULL) != 0) {
     scope.problemReporter().nullUnboxing(expression, expression.resolvedType);
     return;
   } else if ((status & FlowInfo.POTENTIALLY_NULL) != 0) {
     scope.problemReporter().potentialNullUnboxing(expression, expression.resolvedType);
     return;
   } else if ((status & FlowInfo.NON_NULL) != 0) {
     return;
   }
   // not handled, perhaps our parent will eventually have something to say?
   if (this.parent != null) {
     this.parent.recordUnboxing(scope, expression, FlowInfo.UNKNOWN, flowInfo);
   }
 }
示例#2
0
 /**
  * Record that a nullity mismatch was detected against an annotated type reference.
  *
  * @param currentScope scope for error reporting
  * @param expression the expression violating the specification
  * @param providedType the type of the provided value, i.e., either expression or an element
  *     thereof (in ForeachStatements)
  * @param expectedType the declared type of the spec'ed variable, for error reporting.
  * @param flowInfo the flowInfo observed when visiting expression
  * @param nullStatus the null status of expression at the current location
  * @param annotationStatus status from type annotation analysis, or null
  */
 public void recordNullityMismatch(
     BlockScope currentScope,
     Expression expression,
     TypeBinding providedType,
     TypeBinding expectedType,
     FlowInfo flowInfo,
     int nullStatus,
     NullAnnotationMatching annotationStatus) {
   if (providedType == null) {
     return; // assume type error was already reported
   }
   if (expression.localVariableBinding()
       != null) { // flowContext cannot yet handle non-localvar expressions (e.g., fields)
     // find the inner-most flowContext that might need deferred handling:
     FlowContext currentContext = this;
     while (currentContext != null) {
       // some flow contexts implement deferred checking, should we participate in that?
       int isInsideAssert = 0x0;
       if ((this.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) {
         isInsideAssert = FlowContext.HIDE_NULL_COMPARISON_WARNING;
       }
       if (currentContext.internalRecordNullityMismatch(
           expression,
           providedType,
           flowInfo,
           nullStatus,
           expectedType,
           ASSIGN_TO_NONNULL | isInsideAssert)) return;
       currentContext = currentContext.parent;
     }
   }
   // no reason to defer, so report now:
   if (annotationStatus != null)
     currentScope
         .problemReporter()
         .nullityMismatchingTypeAnnotation(
             expression, providedType, expectedType, annotationStatus);
   else
     currentScope
         .problemReporter()
         .nullityMismatch(
             expression,
             providedType,
             expectedType,
             nullStatus,
             currentScope.environment().getNonNullAnnotationName());
 }
示例#3
0
  /**
   * @param isExceptionOnAutoClose This is for checking exception handlers for exceptions raised
   *     during the auto close of resources inside a try with resources statement. (Relevant for
   *     source levels 1.7 and above only)
   */
  public void checkExceptionHandlers(
      TypeBinding raisedException,
      ASTNode location,
      FlowInfo flowInfo,
      BlockScope scope,
      boolean isExceptionOnAutoClose) {
    // LIGHT-VERSION OF THE EQUIVALENT WITH AN ARRAY OF EXCEPTIONS
    // check that all the argument exception types are handled
    // JDK Compatible implementation - when an exception type is thrown,
    // all related catch blocks are marked as reachable... instead of those only
    // until the point where it is safely handled (Smarter - see comment at the end)
    FlowContext traversedContext = this;
    ArrayList abruptlyExitedLoops = null;
    if (scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_7
        && location instanceof ThrowStatement) {
      Expression throwExpression = ((ThrowStatement) location).exception;
      LocalVariableBinding throwArgBinding = throwExpression.localVariableBinding();
      if (throwExpression
              instanceof SingleNameReference // https://bugs.eclipse.org/bugs/show_bug.cgi?id=350361
          && throwArgBinding instanceof CatchParameterBinding
          && throwArgBinding.isEffectivelyFinal()) {
        CatchParameterBinding parameter = (CatchParameterBinding) throwArgBinding;
        checkExceptionHandlers(parameter.getPreciseTypes(), location, flowInfo, scope);
        return;
      }
    }
    while (traversedContext != null) {
      SubRoutineStatement sub;
      if (((sub = traversedContext.subroutine()) != null) && sub.isSubRoutineEscaping()) {
        // traversing a non-returning subroutine means that all unhandled
        // exceptions will actually never get sent...
        return;
      }

      // filter exceptions that are locally caught from the innermost enclosing
      // try statement to the outermost ones.
      if (traversedContext instanceof ExceptionHandlingFlowContext) {
        ExceptionHandlingFlowContext exceptionContext =
            (ExceptionHandlingFlowContext) traversedContext;
        ReferenceBinding[] caughtExceptions;
        if ((caughtExceptions = exceptionContext.handledExceptions) != Binding.NO_EXCEPTIONS) {
          boolean definitelyCaught = false;
          for (int caughtIndex = 0, caughtCount = caughtExceptions.length;
              caughtIndex < caughtCount;
              caughtIndex++) {
            ReferenceBinding caughtException = caughtExceptions[caughtIndex];
            FlowInfo exceptionFlow = flowInfo;
            int state =
                caughtException == null
                    ? Scope.EQUAL_OR_MORE_SPECIFIC /* any exception */
                    : Scope.compareTypes(raisedException, caughtException);
            if (abruptlyExitedLoops != null
                && caughtException != null
                && state != Scope.NOT_RELATED) {
              for (int i = 0, abruptlyExitedLoopsCount = abruptlyExitedLoops.size();
                  i < abruptlyExitedLoopsCount;
                  i++) {
                LoopingFlowContext loop = (LoopingFlowContext) abruptlyExitedLoops.get(i);
                loop.recordCatchContextOfEscapingException(
                    exceptionContext, caughtException, flowInfo);
              }
              exceptionFlow =
                  FlowInfo
                      .DEAD_END; // don't use flow info on first round, flow info will be evaluated
              // during loopback simulation
            }
            switch (state) {
              case Scope.EQUAL_OR_MORE_SPECIFIC:
                exceptionContext.recordHandlingException(
                    caughtException,
                    exceptionFlow.unconditionalInits(),
                    raisedException,
                    raisedException, // precise exception that will be caught
                    location,
                    definitelyCaught);
                // was it already definitely caught ?
                definitelyCaught = true;
                break;
              case Scope.MORE_GENERIC:
                exceptionContext.recordHandlingException(
                    caughtException,
                    exceptionFlow.unconditionalInits(),
                    raisedException,
                    caughtException,
                    location,
                    false);
                // was not caught already per construction
            }
          }
          if (definitelyCaught) return;
        }
        // method treatment for unchecked exceptions
        if (exceptionContext.isMethodContext) {
          if (raisedException.isUncheckedException(false)) return;
          boolean shouldMergeUnhandledExceptions =
              exceptionContext instanceof ExceptionInferenceFlowContext;

          // anonymous constructors are allowed to throw any exceptions (their thrown exceptions
          // clause will be fixed up later as per JLS 8.6).
          if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) {
            AbstractMethodDeclaration method =
                (AbstractMethodDeclaration) exceptionContext.associatedNode;
            if (method.isConstructor() && method.binding.declaringClass.isAnonymousType())
              shouldMergeUnhandledExceptions = true;
          }
          if (shouldMergeUnhandledExceptions) {
            exceptionContext.mergeUnhandledException(raisedException);
            return; // no need to complain, will fix up constructor/lambda exceptions
          }
          break; // not handled anywhere, thus jump to error handling
        }
      } else if (traversedContext instanceof LoopingFlowContext) {
        if (abruptlyExitedLoops == null) {
          abruptlyExitedLoops = new ArrayList(5);
        }
        abruptlyExitedLoops.add(traversedContext);
      }

      traversedContext.recordReturnFrom(flowInfo.unconditionalInits());

      if (!isExceptionOnAutoClose) {
        if (traversedContext instanceof InsideSubRoutineFlowContext) {
          ASTNode node = traversedContext.associatedNode;
          if (node instanceof TryStatement) {
            TryStatement tryStatement = (TryStatement) node;
            flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); // collect inits
          }
        }
      }
      traversedContext = traversedContext.getLocalParent();
    }
    // if reaches this point, then there are some remaining unhandled exception types.
    if (isExceptionOnAutoClose) {
      scope.problemReporter().unhandledExceptionFromAutoClose(raisedException, location);
    } else {
      scope.problemReporter().unhandledException(raisedException, location);
    }
  }
示例#4
0
  /** Provides AnnotationValues with the data it needs to do its thing. */
  public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> createAnnotation(
      Class<A> type, final Node annotationNode) {
    final Annotation annotation = (Annotation) annotationNode.get();
    Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>();

    final MemberValuePair[] pairs = annotation.memberValuePairs();
    for (Method m : type.getDeclaredMethods()) {
      if (!Modifier.isPublic(m.getModifiers())) continue;
      String name = m.getName();
      List<String> raws = new ArrayList<String>();
      List<Object> guesses = new ArrayList<Object>();
      Expression fullExpression = null;
      Expression[] expressions = null;

      if (pairs != null)
        for (MemberValuePair pair : pairs) {
          char[] n = pair.name;
          String mName = n == null ? "value" : new String(pair.name);
          if (mName.equals(name)) fullExpression = pair.value;
        }

      boolean isExplicit = fullExpression != null;

      if (isExplicit) {
        if (fullExpression instanceof ArrayInitializer) {
          expressions = ((ArrayInitializer) fullExpression).expressions;
        } else expressions = new Expression[] {fullExpression};
        if (expressions != null)
          for (Expression ex : expressions) {
            StringBuffer sb = new StringBuffer();
            ex.print(0, sb);
            raws.add(sb.toString());
            guesses.add(calculateValue(ex));
          }
      }

      final Expression fullExpr = fullExpression;
      final Expression[] exprs = expressions;

      values.put(
          name,
          new AnnotationValue(annotationNode, raws, guesses, isExplicit) {
            @Override
            public void setError(String message, int valueIdx) {
              Expression ex;
              if (valueIdx == -1) ex = fullExpr;
              else ex = exprs != null ? exprs[valueIdx] : null;

              if (ex == null) ex = annotation;

              int sourceStart = ex.sourceStart;
              int sourceEnd = ex.sourceEnd;

              annotationNode.addError(message, sourceStart, sourceEnd);
            }

            @Override
            public void setWarning(String message, int valueIdx) {
              Expression ex;
              if (valueIdx == -1) ex = fullExpr;
              else ex = exprs != null ? exprs[valueIdx] : null;

              if (ex == null) ex = annotation;

              int sourceStart = ex.sourceStart;
              int sourceEnd = ex.sourceEnd;

              annotationNode.addWarning(message, sourceStart, sourceEnd);
            }
          });
    }

    return new AnnotationValues<A>(type, values, annotationNode);
  }
示例#5
0
  public static TypeBinding resolveType(
      TypeBinding resolvedType, MessageSend methodCall, BlockScope scope) {
    List<Extension> extensions = new ArrayList<Extension>();
    TypeDeclaration decl = scope.classScope().referenceContext;

    EclipseNode owningType = null;

    for (EclipseNode typeNode = getTypeNode(decl);
        typeNode != null;
        typeNode = upToType(typeNode)) {
      Annotation ann = getAnnotation(ExtensionMethod.class, typeNode);
      if (ann != null) {
        extensions.addAll(
            0, getApplicableExtensionMethods(typeNode, ann, methodCall.receiver.resolvedType));
        if (owningType == null) owningType = typeNode;
      }
    }

    boolean skip = false;

    if (methodCall.receiver instanceof ThisReference
        && (((ThisReference) methodCall.receiver).bits & ASTNode.IsImplicitThis) != 0) skip = true;
    if (methodCall.receiver instanceof SuperReference) skip = true;
    if (methodCall.receiver instanceof NameReference) {
      Binding binding = ((NameReference) methodCall.receiver).binding;
      if (binding instanceof TypeBinding) skip = true;
    }

    if (!skip)
      for (Extension extension : extensions) {
        if (!extension.suppressBaseMethods && !(methodCall.binding instanceof ProblemMethodBinding))
          continue;
        for (MethodBinding extensionMethod : extension.extensionMethods) {
          if (!Arrays.equals(methodCall.selector, extensionMethod.selector)) continue;
          MessageSend_postponedErrors.clear(methodCall);
          if (methodCall.receiver instanceof ThisReference) {
            methodCall.receiver.bits &= ~ASTNode.IsImplicitThis;
          }
          List<Expression> arguments = new ArrayList<Expression>();
          arguments.add(methodCall.receiver);
          if (methodCall.arguments != null) arguments.addAll(Arrays.asList(methodCall.arguments));
          List<TypeBinding> argumentTypes = new ArrayList<TypeBinding>();
          for (Expression argument : arguments) {
            if (argument.resolvedType != null) argumentTypes.add(argument.resolvedType);
            // TODO: Instead of just skipping nulls entirely, there is probably a 'unresolved type'
            // placeholder. THAT is what we ought to be adding here!
          }
          Expression[] originalArgs = methodCall.arguments;
          methodCall.arguments = arguments.toArray(new Expression[0]);
          MethodBinding fixedBinding =
              scope.getMethod(
                  extensionMethod.declaringClass,
                  methodCall.selector,
                  argumentTypes.toArray(new TypeBinding[0]),
                  methodCall);
          if (fixedBinding instanceof ProblemMethodBinding) {
            methodCall.arguments = originalArgs;
            if (fixedBinding.declaringClass != null) {
              scope.problemReporter().invalidMethod(methodCall, fixedBinding);
            }
          } else {
            for (int i = 0, iend = arguments.size(); i < iend; i++) {
              Expression arg = arguments.get(i);
              if (fixedBinding.parameters[i].isArrayType() != arg.resolvedType.isArrayType()) break;
              if (arg instanceof MessageSend) {
                ((MessageSend) arg).valueCast = arg.resolvedType;
              }
              if (!fixedBinding.parameters[i].isBaseType() && arg.resolvedType.isBaseType()) {
                int id = arg.resolvedType.id;
                arg.implicitConversion = TypeIds.BOXING | (id + (id << 4)); // magic see TypeIds
              } else if (fixedBinding.parameters[i].isBaseType()
                  && !arg.resolvedType.isBaseType()) {
                int id = fixedBinding.parameters[i].id;
                arg.implicitConversion = TypeIds.UNBOXING | (id + (id << 4)); // magic see TypeIds
              }
            }

            methodCall.receiver = createNameRef(extensionMethod.declaringClass, methodCall);
            methodCall.actualReceiverType = extensionMethod.declaringClass;
            methodCall.binding = fixedBinding;
            methodCall.resolvedType = methodCall.binding.returnType;
          }
          return methodCall.resolvedType;
        }
      }

    PostponedError error = MessageSend_postponedErrors.get(methodCall);
    if (error != null) error.fire();

    MessageSend_postponedErrors.clear(methodCall);
    return resolvedType;
  }
  public TypeBinding resolveType(BlockScope scope) {
    // Answer the signature return type
    // Base type promotion

    this.constant = Constant.NotAConstant;
    boolean receiverCast = false, argsContainCast = false;
    if (this.receiver instanceof CastExpression) {
      this.receiver.bits |= DisableUnnecessaryCastCheck; // will check later on
      receiverCast = true;
    }
    this.actualReceiverType = this.receiver.resolveType(scope);
    if (receiverCast && this.actualReceiverType != null) {
      // due to change of declaring class with receiver type, only identity cast should be notified
      if (((CastExpression) this.receiver).expression.resolvedType == this.actualReceiverType) {
        scope.problemReporter().unnecessaryCast((CastExpression) this.receiver);
      }
    }
    // resolve type arguments (for generic constructor call)
    if (this.typeArguments != null) {
      int length = this.typeArguments.length;
      boolean argHasError = false; // typeChecks all arguments
      this.genericTypeArguments = new TypeBinding[length];
      for (int i = 0; i < length; i++) {
        if ((this.genericTypeArguments[i] =
                this.typeArguments[i].resolveType(scope, true /* check bounds*/))
            == null) {
          argHasError = true;
        }
      }
      if (argHasError) {
        return null;
      }
    }
    // will check for null after args are resolved
    TypeBinding[] argumentTypes = Binding.NO_PARAMETERS;
    if (this.arguments != null) {
      boolean argHasError = false; // typeChecks all arguments
      int length = this.arguments.length;
      argumentTypes = new TypeBinding[length];
      for (int i = 0; i < length; i++) {
        Expression argument = this.arguments[i];
        if (argument instanceof CastExpression) {
          argument.bits |= DisableUnnecessaryCastCheck; // will check later on
          argsContainCast = true;
        }
        if ((argumentTypes[i] = this.arguments[i].resolveType(scope)) == null) argHasError = true;
      }
      if (argHasError) {
        if (this.actualReceiverType instanceof ReferenceBinding) {
          // record any selector match, for clients who may still need hint about possible method
          // match
          this.binding =
              scope.findMethod(
                  (ReferenceBinding) this.actualReceiverType,
                  this.selector,
                  new TypeBinding[] {},
                  this);
        }
        return null;
      }
    }
    if (this.actualReceiverType == null) {
      return null;
    }
    // base type cannot receive any message
    if (this.actualReceiverType.isBaseType()) {
      scope.problemReporter().errorNoMethodFor(this, this.actualReceiverType, argumentTypes);
      return null;
    }

    this.binding =
        this.receiver.isImplicitThis()
            ? scope.getImplicitMethod(this.selector, argumentTypes, this)
            : scope.getMethod(this.actualReceiverType, this.selector, argumentTypes, this);
    if (!this.binding.isValidBinding()) {
      if (this.binding instanceof ProblemMethodBinding
          && ((ProblemMethodBinding) this.binding).problemId() == ProblemReasons.NotVisible) {
        if (this.evaluationContext.declaringTypeName != null) {
          this.delegateThis =
              scope.getField(scope.enclosingSourceType(), EvaluationConstants.DELEGATE_THIS, this);
          if (this.delegateThis
              == null) { // if not found then internal error, field should have been found
            this.constant = Constant.NotAConstant;
            scope.problemReporter().invalidMethod(this, this.binding);
            return null;
          }
        } else {
          this.constant = Constant.NotAConstant;
          scope.problemReporter().invalidMethod(this, this.binding);
          return null;
        }
        CodeSnippetScope localScope = new CodeSnippetScope(scope);
        MethodBinding privateBinding =
            this.receiver instanceof CodeSnippetThisReference
                    && ((CodeSnippetThisReference) this.receiver).isImplicit
                ? localScope.getImplicitMethod(
                    (ReferenceBinding) this.delegateThis.type, this.selector, argumentTypes, this)
                : localScope.getMethod(this.delegateThis.type, this.selector, argumentTypes, this);
        if (!privateBinding.isValidBinding()) {
          if (this.binding.declaringClass == null) {
            if (this.actualReceiverType instanceof ReferenceBinding) {
              this.binding.declaringClass = (ReferenceBinding) this.actualReceiverType;
            } else { // really bad error ....
              scope
                  .problemReporter()
                  .errorNoMethodFor(this, this.actualReceiverType, argumentTypes);
              return null;
            }
          }
          scope.problemReporter().invalidMethod(this, this.binding);
          return null;
        } else {
          this.binding = privateBinding;
        }
      } else {
        if (this.binding.declaringClass == null) {
          if (this.actualReceiverType instanceof ReferenceBinding) {
            this.binding.declaringClass = (ReferenceBinding) this.actualReceiverType;
          } else { // really bad error ....
            scope.problemReporter().errorNoMethodFor(this, this.actualReceiverType, argumentTypes);
            return null;
          }
        }
        scope.problemReporter().invalidMethod(this, this.binding);
        return null;
      }
    }
    if (!this.binding.isStatic()) {
      // the "receiver" must not be a type, in other words, a NameReference that the TC has bound to
      // a Type
      if (this.receiver instanceof NameReference
          && (((NameReference) this.receiver).bits & Binding.TYPE) != 0) {
        scope.problemReporter().mustUseAStaticMethod(this, this.binding);
      } else {
        // handle indirect inheritance thru variable secondary bound
        // receiver may receive generic cast, as part of implicit conversion
        TypeBinding oldReceiverType = this.actualReceiverType;
        this.actualReceiverType =
            this.actualReceiverType.getErasureCompatibleType(this.binding.declaringClass);
        this.receiver.computeConversion(scope, this.actualReceiverType, this.actualReceiverType);
        if (this.actualReceiverType != oldReceiverType
            && this.receiver.postConversionType(scope)
                != this
                    .actualReceiverType) { // record need for explicit cast at codegen since
                                           // receiver could not handle it
          this.bits |= NeedReceiverGenericCast;
        }
      }
    }
    if (checkInvocationArguments(
        scope,
        this.receiver,
        this.actualReceiverType,
        this.binding,
        this.arguments,
        argumentTypes,
        argsContainCast,
        this)) {
      this.bits |= ASTNode.Unchecked;
    }

    // -------message send that are known to fail at compile time-----------
    if (this.binding.isAbstract()) {
      if (this.receiver.isSuper()) {
        scope.problemReporter().cannotDireclyInvokeAbstractMethod(this, this.binding);
      }
      // abstract private methods cannot occur nor abstract static............
    }
    if (isMethodUseDeprecated(this.binding, scope, true))
      scope.problemReporter().deprecatedMethod(this.binding, this);

    // from 1.5 compliance on, array#clone() returns the array type (but binding still shows Object)
    if (this.actualReceiverType.isArrayType()
        && this.binding.parameters == Binding.NO_PARAMETERS
        && scope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_5
        && CharOperation.equals(this.binding.selector, CLONE)) {
      this.resolvedType = this.actualReceiverType;
    } else {
      TypeBinding returnType = this.binding.returnType;

      if (returnType != null) {
        if ((this.bits & ASTNode.Unchecked) != 0 && this.genericTypeArguments == null) {
          returnType = scope.environment().convertToRawType(returnType.erasure(), true);
        }
        returnType = returnType.capture(scope, this.sourceEnd);
      }
      this.resolvedType = returnType;
    }
    return this.resolvedType;
  }
示例#7
0
  private MethodDeclaration createToString(
      EclipseNode type,
      Collection<EclipseNode> fields,
      boolean includeFieldNames,
      boolean callSuper,
      ASTNode source,
      FieldAccess fieldAccess) {
    String typeName = getTypeName(type);
    char[] suffix = ")".toCharArray();
    String infixS = ", ";
    char[] infix = infixS.toCharArray();
    int pS = source.sourceStart, pE = source.sourceEnd;
    long p = (long) pS << 32 | pE;
    final int PLUS = OperatorIds.PLUS;

    char[] prefix;

    if (callSuper) {
      prefix = (typeName + "(super=").toCharArray();
    } else if (fields.isEmpty()) {
      prefix = (typeName + "()").toCharArray();
    } else if (includeFieldNames) {
      prefix =
          (typeName
                  + "("
                  + new String(((FieldDeclaration) fields.iterator().next().get()).name)
                  + "=")
              .toCharArray();
    } else {
      prefix = (typeName + "(").toCharArray();
    }

    boolean first = true;
    Expression current = new StringLiteral(prefix, pS, pE, 0);
    Eclipse.setGeneratedBy(current, source);

    if (callSuper) {
      MessageSend callToSuper = new MessageSend();
      callToSuper.sourceStart = pS;
      callToSuper.sourceEnd = pE;
      Eclipse.setGeneratedBy(callToSuper, source);
      callToSuper.receiver = new SuperReference(pS, pE);
      Eclipse.setGeneratedBy(callToSuper, source);
      callToSuper.selector = "toString".toCharArray();
      current = new BinaryExpression(current, callToSuper, PLUS);
      Eclipse.setGeneratedBy(current, source);
      first = false;
    }

    for (EclipseNode field : fields) {
      TypeReference fType = getFieldType(field, fieldAccess);
      Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source);

      Expression ex;
      if (fType.dimensions() > 0) {
        MessageSend arrayToString = new MessageSend();
        arrayToString.sourceStart = pS;
        arrayToString.sourceEnd = pE;
        arrayToString.receiver =
            generateQualifiedNameRef(
                source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray());
        arrayToString.arguments = new Expression[] {fieldAccessor};
        Eclipse.setGeneratedBy(arrayToString.arguments[0], source);
        if (fType.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(fType.getLastToken()))) {
          arrayToString.selector = "deepToString".toCharArray();
        } else {
          arrayToString.selector = "toString".toCharArray();
        }
        ex = arrayToString;
      } else {
        ex = fieldAccessor;
      }
      Eclipse.setGeneratedBy(ex, source);

      if (first) {
        current = new BinaryExpression(current, ex, PLUS);
        current.sourceStart = pS;
        current.sourceEnd = pE;
        Eclipse.setGeneratedBy(current, source);
        first = false;
        continue;
      }

      StringLiteral fieldNameLiteral;
      if (includeFieldNames) {
        char[] namePlusEqualsSign = (infixS + field.getName() + "=").toCharArray();
        fieldNameLiteral = new StringLiteral(namePlusEqualsSign, pS, pE, 0);
      } else {
        fieldNameLiteral = new StringLiteral(infix, pS, pE, 0);
      }
      Eclipse.setGeneratedBy(fieldNameLiteral, source);
      current = new BinaryExpression(current, fieldNameLiteral, PLUS);
      Eclipse.setGeneratedBy(current, source);
      current = new BinaryExpression(current, ex, PLUS);
      Eclipse.setGeneratedBy(current, source);
    }
    if (!first) {
      StringLiteral suffixLiteral = new StringLiteral(suffix, pS, pE, 0);
      Eclipse.setGeneratedBy(suffixLiteral, source);
      current = new BinaryExpression(current, suffixLiteral, PLUS);
      Eclipse.setGeneratedBy(current, source);
    }

    ReturnStatement returnStatement = new ReturnStatement(current, pS, pE);
    Eclipse.setGeneratedBy(returnStatement, source);

    MethodDeclaration method =
        new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult);
    Eclipse.setGeneratedBy(method, source);
    method.modifiers = toEclipseModifier(AccessLevel.PUBLIC);
    method.returnType =
        new QualifiedTypeReference(TypeConstants.JAVA_LANG_STRING, new long[] {p, p, p});
    Eclipse.setGeneratedBy(method.returnType, source);
    method.annotations =
        new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)};
    method.arguments = null;
    method.selector = "toString".toCharArray();
    method.thrownExceptions = null;
    method.typeParameters = null;
    method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
    method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
    method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
    method.statements = new Statement[] {returnStatement};
    return method;
  }