Code Search for Developers
 
 
  

ExpressionTypeChecker.java from DrJava at Krugle


Show ExpressionTypeChecker.java syntax highlighted

/*BEGIN_COPYRIGHT_BLOCK
 *
 * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software is Open Source Initiative approved Open Source Software.
 * Open Source Initative Approved is a trademark of the Open Source Initiative.
 * 
 * This file is part of DrJava.  Download the current version of this project
 * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 * 
 * END_COPYRIGHT_BLOCK*/

package edu.rice.cs.javalanglevels;

import edu.rice.cs.javalanglevels.tree.*;
import edu.rice.cs.javalanglevels.parser.JExprParser;
import java.util.*;
import java.io.File;
import edu.rice.cs.plt.reflect.JavaVersion;

import junit.framework.TestCase;

/**
 * This is a TypeChecker for all Expressions used in the students files.  It is
 * used with every LanguageLevel.
 */
public class ExpressionTypeChecker extends Bob {
  
  /**
   * Simply pass the necessary information on to superclass constructor.
   * @param data  The data that represents the context.
   * @param file  The file that corresponds to the source file
   * @param packageName  The string representing the package name
   * @param importedFiles  The list of file names that have been specifically imported
   * @param importedPackages  The list of package names that have been specifically imported
   * @param vars  The list of fields that have been assigned up to the point where Bob is called.
   * @param thrown  The list of exceptions that the context is declared to throw
   */
  public ExpressionTypeChecker(Data data, File file, String packageName, LinkedList<String> importedFiles, LinkedList<String> importedPackages, LinkedList<VariableData> vars, LinkedList<Pair<SymbolData, JExpression>> thrown) {
    super(data, file, packageName, importedFiles, importedPackages, vars, thrown);
  }


  /**
   * Visit the lhs of this assignment with the LValueTypeChecker, which does extra checking for the assignment.
   * Visit the rhs of this assignment with the regular expression type checker, since anything normal expression can appear on the right.
   * @param that  The SimpleAssignmentExpression to type check
   * @return  The result of the assignment.
   */
  public TypeData forSimpleAssignmentExpression(SimpleAssignmentExpression that) {
    TypeData value_result = that.getValue().visit(this);
    TypeData name_result = that.getName().visit(new LValueTypeChecker(this));
    return forSimpleAssignmentExpressionOnly(that, name_result, value_result);
  }
  
  /**
   * A SimpleAssignmentExpression is okay if both the lhs and the rhs are instances, and the rhs is assignable to the lhs.
   * Give an error if these constraints are not met.
   * Return an instance of the lhs or null if the left or right could not be resolved.
   * @param that  The SimpleAssignmentExpression being typechecked
   * @param name_result  The TypeData representing the lhs of the assignment
   * @param value_result  The TypeData representing the rhs of the assignment
   */
  public TypeData forSimpleAssignmentExpressionOnly(SimpleAssignmentExpression that, TypeData name_result, TypeData value_result) {
    if (name_result == null || value_result == null) {return null;}
  
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(name_result, that) || !assertFound(value_result, that)) {
      return null;
    }
    
    //make sure both are instance datas
    if (assertInstanceType(name_result, "You cannot assign a value to the type " + name_result.getName(), that) &&
        assertInstanceType(value_result, "You cannot use the type name " + value_result.getName() + " on the right hand side of an assignment", that)) {
      
      //make sure the rhs can be assigned to the lhs
      if (!value_result.getSymbolData().isAssignableTo(name_result.getSymbolData(), _targetVersion)) {
        _addError("You cannot assign something of type " + value_result.getName() + " to something of type " + name_result.getName(), that);
      }
    }   
    return name_result.getInstanceData();
  }

  /**
   * Visit the lhs of this assignment with the LValueWithValueTypeChecker, which does extra checking for the lhs.
   * Visit the rhs of this assignment with the regular expression type checker, since anything regular expression can appear on the right.
   * @param that  The PlusAssignmentExpression to type check
   * @return  The result of the assignment.
   */
  public TypeData forPlusAssignmentExpression(PlusAssignmentExpression that) {
    TypeData value_result = that.getValue().visit(this);
    TypeData name_result = that.getName().visit(new LValueWithValueTypeChecker(this));

    return forPlusAssignmentExpressionOnly(that, name_result, value_result);
  }
  
  /**
   * A PlusAssignmentExpression is okay if the lhs and rhs are both instances.  If the lhs is a string, the rhs can be anything.  If the rhs is a string,
   * the lhs had better be a string too.  If neither of them is a string, they should both be numbers, and the rhs should be assignable to the lhs.
   * @param that  The PlusAssignmentExpression we are typechecking
   * @param name_result  The TypeData representing the lhs
   * @param value_result  The TypeData representing the rhs
   * @return  An instance of the result of the lhs, or null if either the right or the left could not be resolved.
   */
  public TypeData forPlusAssignmentExpressionOnly(PlusAssignmentExpression that, TypeData name_result, TypeData value_result) {
    if (name_result == null || value_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(name_result, that) || !assertFound(value_result, that)) {
      return null;
    }
    
    //need to see if rhs is a String.
    SymbolData string = getSymbolData("java.lang.String", that, false, false);

    if (name_result.getSymbolData().isAssignableTo(string, _targetVersion)) {
      //the rhs is a String, so just make sure they are both instance types.
      assertInstanceType(name_result, "The arguments to a Plus Assignment Operator (+=) must both be instances, but you have specified a type name", that);
      assertInstanceType(value_result, "The arguments to a Plus Assignment Operator (+=) must both be instances, but you have specified a type name", that);
      return string.getInstanceData();
    }
    
    else { //neither is a string, so they must both be numbers
      if (!name_result.getSymbolData().isNumberType(_targetVersion) || !value_result.getSymbolData().isNumberType(_targetVersion)) {
        _addError("The arguments to the Plus Assignment Operator (+=) must either include an instance of a String or both be numbers.  You have specified arguments of type " + name_result.getName() + " and " + value_result.getName(), that);
        return string.getInstanceData(); //return String by default
      }
      
      else if (!value_result.getSymbolData().isAssignableTo(name_result.getSymbolData(), _targetVersion)) {
        _addError("You cannot increment something of type " + name_result.getName() + " with something of type " + value_result.getName(), that);
      }
      
      else {
        assertInstanceType(name_result, "The arguments to the Plus Assignment Operator (+=) must both be instances, but you have specified a type name", that);
        assertInstanceType(value_result, "The arguments to the Plus Assignment Operator (+=) must both be instances, but you have specified a type name", that);
      }
    
      return name_result.getInstanceData();
    }
  }

  
  /**
   * Visit the lhs of this assignment with the LValueWithValueTypeChecker, which does extra checking for the lhs, because it needs to be able
   * to be assigned to and already have a value.
   * Visit the rhs of this assignment with the regular expression type checker, since any regular expression can appear on the right.
   * @param that  The NumericAssignmentExpression to type check
   * @return  The result of the assignment.
   */
  public TypeData forNumericAssignmentExpression(NumericAssignmentExpression that) {
    TypeData value_result = that.getValue().visit(this);
    TypeData name_result = that.getName().visit(new LValueWithValueTypeChecker(this));

    return forNumericAssignmentExpressionOnly(that, name_result, value_result);
  }
  

  /**
   * Delegate to method for super class.
   */
  public TypeData forMinusAssignmentExpression(MinusAssignmentExpression that) {
    return forNumericAssignmentExpression(that);
  }

  /**
   * Delegate to method for super class.
   */
  public TypeData forMultiplyAssignmentExpression(MultiplyAssignmentExpression that) {
    return forNumericAssignmentExpression(that);
  }
  
  /**
   * Delegate to method for super class.
   */
  public TypeData forDivideAssignmentExpression(DivideAssignmentExpression that) {
    return forNumericAssignmentExpression(that);
  }

  /**
   * Delegate to method for super class.
   */ 
  public TypeData forModAssignmentExpression(ModAssignmentExpression that) {
    return forNumericAssignmentExpression(that);
  }

    
  /**
   * A NumericAssignmentExpression is okay if both the lhs and the rhs are instances, both are numbers, and the rhs is assignable to the lhs.
   * Return the lhs, or null
   * @param that  The SimpleAssignmentExpression being typechecked
   * @param name_result  The TypeData representing the lhs of the assignment
   * @param value_result  The TypeData representing the rhs of the assignment
   * @return  An instance of the lhs, or null if the lhs or rhs could not be resolved.
   */
  public TypeData forNumericAssignmentExpressionOnly(NumericAssignmentExpression that, TypeData name_result, TypeData value_result) {
    if (name_result == null || value_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(name_result, that) || !assertFound(value_result, that)) {
      return null;
    }
    
    //make sure both are instance datas
    if (assertInstanceType(name_result, "You cannot use a numeric assignment (-=, %=, *=, /=) on the type " + name_result.getName(), that) &&
        assertInstanceType(value_result, "You cannot use the type name " + value_result.getName() + " on the left hand side of a numeric assignment (-=, %=, *=, /=)", that)) {
      
      boolean error = false;
      //make sure that both lhs and rhs are number types:
      if (!name_result.getSymbolData().isNumberType(_targetVersion)) {
        _addError("The left side of this expression is not a number.  Therefore, you cannot apply a numeric assignment (-=, %=, *=, /=) to it", that);
        error=true;
      }
      if (!value_result.getSymbolData().isNumberType(_targetVersion)) {
        _addError("The right side of this expression is not a number.  Therefore, you cannot apply a numeric assignment (-=, %=, *=, /=) to it", that);
        error = true;
      }
            
      //make sure the lhs is parent type of rhs  NOTE: technically, this is allowable in full java (try int i = 0; i+= 4.2), but it is inconsistent
      //with the fact that you cannot say int i = 0; i = i + 4.2;  To avoid student confusion, we will not allow it.
      if (!error && !value_result.getSymbolData().isAssignableTo(name_result.getSymbolData(), _targetVersion)) {
        _addError("You cannot use a numeric assignment (-=, %=, *=, /=) on something of type " + name_result.getName() + " with something of type " + value_result.getName(), that);
      }
    }  
    return name_result.getInstanceData();  
  }

  /**
   * Not currently supported.
   */
  public TypeData forShiftAssignmentExpressionOnly(ShiftAssignmentExpression that, TypeData name_result, TypeData value_result) {
    throw new RuntimeException ("Internal Program Error: Shift assignment operators are not supported.  This should have been caught before the TypeChecker.  Please report this bug.");
  }

  /**
   * Not currently supported.
   */
  public TypeData forBitwiseAssignmentExpressionOnly(BitwiseAssignmentExpression that, TypeData name_result, TypeData value_result) {
    throw new RuntimeException ("Internal Program Error: Bitwise assignment operators are not supported.  This should have been caught before the TypeChecker.  Please report this bug.");
  }
  
  /**
   * Check if this BooleanExpression was okay.  It is not okay if either the left or the right result are not boolean types
   * or if they are not instance datas.  Throw an appropriate error if any of these is the case.  Always return the boolean instance type.
   * @param that  The BooleanExpression being checked
   * @param left_result  The result from visiting the left side of the BooleanExpression
   * @param right_result  The result from visiting the right side of the BooleanExpression
   * @return  The boolean instance type
   */
  public TypeData forBooleanExpressionOnly(BooleanExpression that, TypeData left_result, TypeData right_result) {
   if (left_result == null || right_result == null) {return null;}
    
   //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(left_result, that) || !assertFound(right_result, that)) {
      return null;
    }
   
    if (assertInstanceType(left_result, "The left side of this expression is a type, not an instance", that) &&
        !left_result.getSymbolData().isAssignableTo(SymbolData.BOOLEAN_TYPE, _targetVersion)) {
      
      _addError("The left side of this expression is not a boolean value.  Therefore, you cannot apply a Boolean Operator (&&, ||) to it", that);
    }
    
    if (assertInstanceType(right_result, "The right side of this expression is a type, not an instance", that) &&
        !right_result.getSymbolData().isAssignableTo(SymbolData.BOOLEAN_TYPE, _targetVersion)) {
      
      _addError("The right side of this expression is not a boolean value.  Therefore, you cannot apply a Boolean Operator (&&, ||) to it", that);
    }

    
    return SymbolData.BOOLEAN_TYPE.getInstanceData();
  }

  /**
   * Not currently supported.
   */
  public TypeData forBitwiseBinaryExpressionOnly(BitwiseBinaryExpression that, TypeData left_result, TypeData right_result) {
    throw new RuntimeException ("Internal Program Error: Bitwise operators are not supported.  This should have been caught before the TypeChecker.  Please report this bug.");
  }

  /**
   * This EqualityExpression is badly formed if left_result and right_result are not both reference types or not both number types
   * or not both boolean types.  Also, both the left and the right should be instance datas.  Throw an error if this is not correct.
   * Return the InstanceData corresponding to boolean, the return type from an equality check.
   * @param that  The EqualityExpression being checked
   * @param left_result  The result of visiting the left side of the expression
   * @param right_result  The result of visiting the right side of the expression
   * @return  SymbolData.BOOLEAN_TYPE.getInstanceData()
   */
  public TypeData forEqualityExpressionOnly(EqualityExpression that, TypeData left_result, TypeData right_result) {
    if (left_result == null || right_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(left_result, that) || !assertFound(right_result, that)) {
      return null;
    }

    
    //if either left or right are primitive, the must either be both numeric or both boolean
    if (left_result.getSymbolData().isPrimitiveType() || right_result.getSymbolData().isPrimitiveType()) {
      if (!((left_result.getSymbolData().isNumberType(_targetVersion) && right_result.getSymbolData().isNumberType(_targetVersion)) ||
            (left_result.getSymbolData().isAssignableTo(SymbolData.BOOLEAN_TYPE, _targetVersion) && right_result.getSymbolData().isAssignableTo(SymbolData.BOOLEAN_TYPE, _targetVersion)))) {
        _addError("At least one of the arguments to this Equality Operator (==, !=) is primitive.  Therefore, they must either both be number types or both be boolean types.  You have specified expressions with type " + left_result.getName() + " and " + right_result.getName(), that);
      }
    }
    
    //otherwise, anything goes...just check for instance types
    
    assertInstanceType(left_result, "The arguments to this Equality Operator(==, !=) must both be instances.  Instead, you have referenced a type name on the left side", that);
    assertInstanceType(right_result, "The arguments to this Equality Operator(==, !=) must both be instances.  Instead, you have referenced a type name on the right side", that);
    
    return SymbolData.BOOLEAN_TYPE.getInstanceData();
  }

  /**
   * Verify that both the left and right of this comparison expression are number types and InstanceDatas.  Give an
   * error if this is not the case.  Return the InstanceData for boolean, since that is the result of a comparison expression.
   * @param that  The Comparison expression being type-checked
   * @param left_result  The result of visiting the left side of the expression
   * @param right_result  The result of visiting the right side of the expression
   * @return  SymbolData.BOOLEAN_TYPE.getInstanceData()
   */
  public TypeData forComparisonExpressionOnly(ComparisonExpression that, TypeData left_result, TypeData right_result) {
    if (left_result == null || right_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(left_result, that) || !assertFound(right_result, that)) {
      return null;
    }

    
    if (!left_result.getSymbolData().isNumberType(_targetVersion)) {
      _addError("The left side of this expression is not a number.  Therefore, you cannot apply a Comparison Operator (<, >; <=, >=) to it", that);
    }
    else {
      assertInstanceType(left_result, "The left side of this expression is a type, not an instance", that);
    }

    if (!right_result.getSymbolData().isNumberType(_targetVersion)) {
      _addError("The right side of this expression is not a number.  Therefore, you cannot apply a Comparison Operator (<, >; <=, >=) to it", that);
    }
    else {
      assertInstanceType(right_result, "The right side of this expression is a type, not an instance", that);
    }    
   
    return SymbolData.BOOLEAN_TYPE.getInstanceData();
  }

  /**
   * Not currently supported
   */
  public TypeData forShiftBinaryExpressionOnly(ShiftBinaryExpression that, TypeData left_result, TypeData right_result) {
    throw new RuntimeException ("Internal Program Error: BinaryShifts are not supported.  This should have been caught before the TypeChecker.  Please report this bug.");
  }

  
  /**
   * A plus operator can either be used on a string and any other type of object or on two numbers.  If one of the arguments
   * is of String type, check to make sure that both types are InstanceDatas and then return an InstanceData for String.
   * If neither of the arguments are a String type, verify that they are both number types and both InstanceDatas, and return
   * the Instance Data corresponding to their least restrictive type.
   * @param that  The PlusExpression being type-checked.
   * @param left_result  The result of visiting the left side of this plus expression
   * @param right_result  The result of visiting the right side of this plus expression
   */
  public TypeData forPlusExpressionOnly(PlusExpression that, TypeData left_result, TypeData right_result) {
    if (left_result == null || right_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(left_result, that) || !assertFound(right_result, that)) {
      return null;
    }
    
    SymbolData string = getSymbolData("java.lang.String", that, false, false);

    if (left_result.getSymbolData().isAssignableTo(string, _targetVersion) || right_result.getSymbolData().isAssignableTo(string, _targetVersion)) {
      //one of these is a String, so just make sure they are both instance types.
      assertInstanceType(left_result, "The arguments to the Plus Operator (+) must both be instances, but you have specified a type name", that);
      assertInstanceType(right_result, "The arguments to the Plus Operator (+) must both be instances, but you have specified a type name", that);
      return string.getInstanceData();
    }
    
    else { //neither is a string, so they must both be numbers
      if (!left_result.getSymbolData().isNumberType(_targetVersion) || !right_result.getSymbolData().isNumberType(_targetVersion)) {
        _addError("The arguments to the Plus Operator (+) must either include an instance of a String or both be numbers.  You have specified arguments of type " + left_result.getName() + " and " + right_result.getName(), that);
        return string.getInstanceData(); //return String by default
      }
      else {
        assertInstanceType(left_result, "The arguments to the Plus Operator (+) must both be instances, but you have specified a type name", that);
        assertInstanceType(right_result, "The arguments to the Plus Operator (+) must both be instances, but you have specified a type name", that);
      }
      
      return _getLeastRestrictiveType(left_result.getSymbolData(), right_result.getSymbolData()).getInstanceData();

    }
  }

  /**
   * Check if this NumericBinaryExpression was okay.  It is not okay if either the left or the right result are not number types
   * or if they are not instance datas.  Throw an appropriate error if any of these is the case.  Always return the least
   * restrictive subtype of the left and the right.
   * @param that  The NumericBinaryExpression being checked
   * @param left_result  The result from visiting the left side of the NumericBinaryExpression
   * @param right_result  The result from visiting the right side of the NumericBinaryExpression
   * @return  An InstanceData of the least restrictive type of the left and right sides.
   */
  public TypeData forNumericBinaryExpressionOnly(NumericBinaryExpression that, TypeData left_result, TypeData right_result) {
    if (left_result == null || right_result == null) {return null;}
    
    //make sure that both lhs and rhs could be resolved (not PackageDatas)
    if (!assertFound(left_result, that) || !assertFound(right_result, that)) {
      return null;
    }
    
    if (assertInstanceType(left_result, "The left side of this expression is a type, not an instance", that) &&
        !left_result.getSymbolData().isNumberType(_targetVersion)) {
      
      _addError("The left side of this expression is not a number.  Therefore, you cannot apply a Numeric Binary Operator (*, /, -, %) to it", that);
      return right_result.getInstanceData();
    }
    
    if (assertInstanceType(right_result, "The right side of this expression is a type, not an instance", that) &&
        !right_result.getSymbolData().isNumberType(_targetVersion)) {
      
      _addError("The right side of this expression is not a number.  Therefore, you cannot apply a Numeric Binary Operator (*, /, -, %) to it", that);
      return left_result.getInstanceData();
    }

    
    return _getLeastRestrictiveType(left_result.getSymbolData(), right_result.getSymbolData()).getInstanceData();
  }

  /**
   * This should have been caught in the first pass.  Throw a RuntimeException.
   */
  public TypeData forNoOpExpressionOnly(NoOpExpression that, TypeData left_result, TypeData right_result) {
    throw new RuntimeException("Internal Program Error: The student is missing an operator.  This should have been caught before the TypeChecker.  Please report this bug.");
  }

  
  /**
   * Visit the value of this increment expression with the LValueWithValueTypeChecker, since
   * whatever it represents should already have a value before we try to increment it.
   */
  public TypeData forIncrementExpression(IncrementExpression that) {
    TypeData value_result = that.getValue().visit(new LValueWithValueTypeChecker(this));
    return forIncrementExpressionOnly(that, value_result);
  }
  
  
  /**
   * For these concrete instantiations of IncrementExpression, delegate to abstract method
   */
  public TypeData forPositivePrefixIncrementExpression(PositivePrefixIncrementExpression that) {
    return forIncrementExpression(that);
  }
  
  public TypeData forNegativePrefixIncrementExpression(NegativePrefixIncrementExpression that) {
    return forIncrementExpression(that);
  }
  
  public TypeData forPositivePostfixIncrementExpression(PositivePostfixIncrementExpression that) {
    return forIncrementExpression(that);
  }
  
  public TypeData forNegativePostfixIncrementExpression(NegativePostfixIncrementExpression that) {
    return forIncrementExpression(that);
  }
  
  
  /**
   * An IncrementExpression is badly formatted if the thing being incremented is a type (value_result is not an InstanceData)
   * or if the value being incremented cannot be assigned to.  Throw an error in either of these cases.
   * @param that  The IncrementExpression that is being type checked.
   * @param value_result  The result of evaluating the argument to the increment expression.
   * @return  The type of what is being incremented.
   */
  public TypeData forIncrementExpressionOnly(IncrementExpression that, TypeData value_result) {
    if (value_result == null) {return null;}
 
    //make sure that lhs could be resolved (not PackageData)
    if (!assertFound(value_result, that)) {
      return null;
    }
    
    if (assertInstanceType(value_result, "You cannot increment or decrement " + value_result.getName() + ", because it is a class name not an instance", that)) {
      if (!value_result.getSymbolData().isNumberType(_targetVersion)) {
        _addError("You cannot increment or decrement something that is not a number type.  You have specified something of type " + value_result.getName(), that);
      }
    }
    return value_result.getInstanceData();
  }
    

  /**
   * A NumericUnaryExpression was well-formed if its value_result is an instance type and if its value_result's symbol data
   * is a number type (to which a double can be assigned).  If this numeric unary expression was not well formed, throw an error.
   * @param that  The NumericUnaryExpression being evaluated
   * @param value_result  The result of evaluating the argument to this expression.
   * @return  The new result of this expression.
   */
  public TypeData forNumericUnaryExpressionOnly(NumericUnaryExpression that, TypeData value_result) {
    if (value_result==null) {return null;}
    
    //make sure that lhs could be resolved (not PackageData)
    if (!assertFound(value_result, that)) {
      return null;
    }
    
    if (assertInstanceType(value_result, "You cannot use a numeric unary operator (+, -) with " + value_result.getName() + ", because it is a class name, not an instance", that) &&
        !value_result.getSymbolData().isNumberType(_targetVersion)) {
      
      _addError("You cannot apply this unary operator to something of type " + value_result.getName() + ".  You can only apply it to a numeric type such as double, int, or char", that);
      return value_result;
    }
    
    //call this so that chars and bytes are widened to an int.
    return _getLeastRestrictiveType(value_result.getSymbolData(), SymbolData.INT_TYPE).getInstanceData();
  }
  
    /**
     * Not Currently Supported.
     */
  public TypeData forBitwiseNotExpressionOnly(BitwiseNotExpression that, TypeData value_result) {
    throw new RuntimeException("Internal Program Error: BitwiseNot is not supported.  It should have been caught before getting to the TypeChecker.  Please report this bug.");
  }

  
  /**
   * A NotExpression is illformed if its argument is not an instance type or its argument is not of type boolean.  Give an error if this
   * is the case.  Always return SymbolData.BOOLEAN_TYPE.getInstanceData() since this is the correct type for this expression.
   * @param that  The NotExpression being type-checked
   * @param value_result  The type of the argument to the NotExpression
   * @return  SymbolData.BOOLEAN_TYPE.getInstanceData()
   */
  public TypeData forNotExpressionOnly(NotExpression that, TypeData value_result) {
    if (value_result == null) {return null;}
    
    //make sure that lhs could be resolved (not PackageData)
    if (!assertFound(value_result, that)) {
      return null;
    }
    
    if (assertInstanceType(value_result, "You cannot use the not (!) operator with " + value_result.getName() + ", because it is a class name, not an instance", that) &&
        !value_result.getSymbolData().isAssignableTo(SymbolData.BOOLEAN_TYPE, _targetVersion)) {
      
      _addError("You cannot use the not (!) operator with something of type " + value_result.getName() + ". Instead, it should be used with an expression of boolean type", that);
    }
    
    return SymbolData.BOOLEAN_TYPE.getInstanceData(); //it should always be a boolean type.
    
  }

  /**
   * Not currently supported
   */
  public TypeData forConditionalExpressionOnly(ConditionalExpression that, TypeData condition_result, TypeData forTrue_result, TypeData forFalse_result) {
    throw new RuntimeException ("Internal Program Error: Conditional expressions are not supported.  This should have been caught before the TypeChecker.  Please report this bug.");
  }
  
  
  /**
   * Not currently supported
   */
  public TypeData forInstanceofExpressionOnly(InstanceofExpression that, TypeData value_result, TypeData type_result) {
    throw new RuntimeException("Internal Program Error: instanceof is not currently supported.  This should have been caught before the Type Checker.  Please report this bug.");
  }
  
  
   
  /**
   * Check to see if this CastExpression is okay.  It is not okay if type_result is not a SymbolData,
   * value_result is not an InstanceData, or if value_result cannot be cast to type_result.  If any of these are the case, give an
   * appropriate error message.  Return an instance data corresponding to type_result.
   * @param that  The CastExpression being examined.
   * @param type_result  The type of the cast expression
   * @param value_result  The instance type of what is being cast
   * @return  type_result's instance data.
   */
  public TypeData forCastExpressionOnly(CastExpression that, TypeData type_result, TypeData value_result) {
    if (type_result == null || value_result == null) {return null;}
    
    //make sure that lhs could be resolved (not PackageData)
    if (!assertFound(value_result, that) || !assertFound(type_result, that)) {
      return null;
    }
    
    if (type_result.isInstanceType()) {
      _addError("You are trying to cast to an instance of a type, which is not allowed.  Perhaps you meant to cast to the type itself, " + type_result.getName(), that);
    }
    
    else if (assertInstanceType(value_result, "You are trying to cast " + value_result.getName() + ", which is a class or interface type, not an instance", that) &&
             !value_result.getSymbolData().isCastableTo(type_result.getSymbolData(), _targetVersion)) {
      
      _addError("You cannot cast an expression of type " + value_result.getName() + " to type " + type_result.getName() + " because they are not related", that);
    }
    
    return type_result.getInstanceData();
  }


  /**
   * Give a Runtime Exception, because the fact that there is an EmptyExpression here should have been
   * caught before the TypeChecker pass.
   */
  public TypeData forEmptyExpressionOnly(EmptyExpression that) {
    throw new RuntimeException("Internal Program Error: EmptyExpression encountered.  Student is missing something.  Should have been caught before TypeChecker.  Please report this bug.");
  }
  
  
  /** 
   * Visit the ClassInstantiation's arguments.  Lookup the required constructor matching the ClassInstantiation's argument types.
   * Check accessibility of the constructor.  In all cases, returns classToInstantiate.getInstanceData.
   */
  public InstanceData classInstantiationHelper(ClassInstantiation that, SymbolData classToInstantiate) {
    if (classToInstantiate == null) {return null;}
    Expression[] expr = that.getArguments().getExpressions();
    InstanceData[] args = new InstanceData[expr.length];
    for (int i = 0; i<expr.length; i++) {
      Expression e = expr[i];
      TypeData type = e.visit(this);
      if (type == null || !assertFound(type, expr[i]) || ! assertInstanceType(type, "Cannot pass a class or interface name as a constructor argument", e)) {
        // by default, return an instance type of context
        return classToInstantiate.getInstanceData();
      }
      args[i] = type.getInstanceData();
    }

    MethodData md = _lookupMethod(LanguageLevelVisitor.getUnqualifiedClassName(that.getType().getName()), classToInstantiate, args, that, 
                           "No constructor found in class " + Data.dollarSignsToDots(classToInstantiate.getName()) + " with signature: ", 
                           true, _getData().getSymbolData());


    if (md == null) {return classToInstantiate.getInstanceData();}
    
    //if MethodData is declared to throw exceptions, add them to thrown list:
    String[] thrown = md.getThrown();
    for (int i = 0; i<thrown.length; i++) {
      _thrown.addLast(new Pair<SymbolData, JExpression>(getSymbolData(thrown[i], _getData(), that), that));
    }
    
    return classToInstantiate.getInstanceData();
  }
  
  
  
  
  /**
   * Handle a simple class instantiation--
   * Here, if the type of the instantiation cannot be resolved, return null because an error will already been thrown.
   * Also check to see if the class being instantiated is non-static, is not a top level class, and the name used has a dot in it.
   * If so, then this is a non-static inner class being referenced like a static inner class, and an error should be thrown.
   * Once you've checked all of that, just delegate to the class Instantion helper, which will 
   * resolve the type of the class.
   * @return  The InstanceData corresponding to the instantiation
   */
  public TypeData forSimpleNamedClassInstantiation(SimpleNamedClassInstantiation that) {
    SymbolData type = getSymbolData(that.getType().getName(), _getData(), that);
    if (type == null) {return null;}
    //It is an error to instantiate a non-static inner class from a static context (i.e. new A.B() where B is not a static inner class).
    //Here, we make sure that if B is non-static, it is not an inner class of anything.
    String name = that.getType().getName();
    int lastIndexOfDot = name.lastIndexOf(".");
    if (!type.hasModifier("static") && (type.getOuterData() != null) && lastIndexOfDot != -1) {
      String firstPart = name.substring(0, lastIndexOfDot);
      String secondPart = name.substring(lastIndexOfDot + 1, name.length()); //skip the dot itself
      _addError(Data.dollarSignsToDots(type.getName()) + " is not a static inner class, and thus cannot be instantiated from this context.  Perhaps you meant to use an instantiation of the form new " + firstPart + "().new " + secondPart + "()", that);
    }
    InstanceData result = classInstantiationHelper(that, type);
    if (result != null && result.getSymbolData().hasModifier("abstract")) {
      _addError(Data.dollarSignsToDots(type.getName()) + " is abstract and thus cannot be instantiated", that);
    }
    return result;
  }
  
  /**
   * Handle this complex named class instantiation.  First, visit the lhs and get the enclosing type.  If the enclosing type is null, or
   * a PackageData, return null, because an error has already been thrown.  Otherwise, call the classInstantiationHelper to get a new
   * instance of the rhs, from the context of the lhs.  It is an error if the class being instantiated is non-static, but it is called from
   * a static context.  It is an error if the class being instantiated is static but it is being called as a.new B();
   * @param that  The ComplexNamedClassInstantiation being created
   * @return  An InstanceData corresponding to the instantiation
   */
  public TypeData forComplexNamedClassInstantiation(ComplexNamedClassInstantiation that) {
    TypeData enclosingType = that.getEnclosing().visit(this);
    if ((enclosingType == null) || ! assertFound(enclosingType, that.getEnclosing())) { return null; }
    
    else {
      //make sure we can see enclosingType
      checkAccessibility(that, enclosingType.getSymbolData().getMav(), enclosingType.getSymbolData().getName(), enclosingType.getSymbolData(), _data.getSymbolData(), "class or interface", true);

      // TODO: will getSymbolData correctly handle all cases here?
      //TODO: We still do not handle static fields on the lhs correctly.  I think.
      //this call to getSymbolData will throw ambiguous reference error, if appropriate
      SymbolData innerClass = getSymbolData(that.getType().getName(), enclosingType.getSymbolData(), that.getType());
      if (innerClass == null) {return null;}
      
      //make sure we can see inner class
      checkAccessibility(that, innerClass.getMav(), innerClass.getName(), innerClass, _data.getSymbolData(), "class or interface", true);
      InstanceData result = classInstantiationHelper(that, innerClass);
      if (result == null) {return null;}
      boolean resultIsStatic = result.getSymbolData().hasModifier("static");
    
      if (!enclosingType.isInstanceType() && !resultIsStatic) {
               _addError ("The constructor of a non-static inner class can only be called on an instance of its containing class (e.g. new " + 
                   Data.dollarSignsToDots(enclosingType.getName()) + "().new " + that.getType().getName() + "())", that);
      }
      else if (resultIsStatic) {
        _addError("You cannot instantiate a static inner class or interface with this syntax.  Instead, try new " + 
                  Data.dollarSignsToDots(result.getName()) + "()", that);
      }
      
      if (result.getSymbolData().hasModifier("abstract")) {
        _addError(Data.dollarSignsToDots(result.getName()) + " is abstract and thus cannot be instantiated", that);
      }
      return result;
    }
  }
  
  
  /**
   * Do the work that is shared between a SimpleAnonymousClassInstantiation and a ComplexAnonymousClassInstantiation.
   * Basically, update the anonymous inner class corresponding to the enclosing data and the superC with
   * superC and accessors, if necessary.
   * @param that  The AnonymousClassInstantiation being processed.
   * @param superC  The data corresponding to the super class of this instantiation (the type being created)
   */
  public SymbolData handleAnonymousClassInstantiation(AnonymousClassInstantiation that, SymbolData superC) {
    SymbolData sd = _data.getNextAnonymousInnerClass();
    if (sd == null) {
      throw new RuntimeException("Internal Program Error: Couldn't find the SymbolData for the anonymous inner class.  Please report this bug.");
    }
    if (sd.getSuperClass() == null) {
      if (superC.isInterface()) {sd.setSuperClass(symbolTable.get("java.lang.Object")); sd.addInterface(superC);}
      else { sd.setSuperClass(superC);}
    }
    LanguageLevelVisitor.createAccessors(sd, _file);

    return sd;
  }
  
  
 
  /**
   * Resolve the type of this anonymous class.  Look it up in the enclosing data, check that
   * it is using a valid constructor through the classInstantiationHelper and visit the body.
   * Make sure that all abstract methods are overwritten.
   * @param that  The SimpleAnonymousClassInstantiation being type-checked
   * @return  The result of type checking the class instantiation.
   */
  public TypeData forSimpleAnonymousClassInstantiation(SimpleAnonymousClassInstantiation that) {
    final SymbolData superclass_result = getSymbolData(that.getType().getName(), _data, that);//resolve super class

    // Get this anonymous inner class's SymbolData, and finish resolving it.
    SymbolData myData = handleAnonymousClassInstantiation(that, superclass_result);
      
    //It is an error to instantiate a non-static inner class from a static context (i.e. new A.B() where B is not a static inner class).
    //Here, we make sure that if B is non-static, it is not an inner class of anything.
    String name = that.getType().getName();
    int lastIndexOfDot = name.lastIndexOf(".");
    if (!superclass_result.hasModifier("static") && !superclass_result.isInterface() && (superclass_result.getOuterData() != null) && lastIndexOfDot != -1) {
      String firstPart = name.substring(0, lastIndexOfDot);
      String secondPart = name.substring(lastIndexOfDot + 1, name.length());
      _addError(Data.dollarSignsToDots(superclass_result.getName()) + " is not a static inner class, and thus cannot be instantiated from this context.  Perhaps you meant to use an instantiation of the form new " + Data.dollarSignsToDots(firstPart) + "().new " + Data.dollarSignsToDots(secondPart) + "()", that);
    }
    
    
    //if superclass_result is an interface, then the constructor that should be used is Object--i.e. no arguments
    if (superclass_result.isInterface()) {
      Expression[] expr = that.getArguments().getExpressions();
      if (expr.length > 0) { _addError("You are creating an anonymous inner class that directly implements an interface, thus you should use the Object constructor which takes in no arguments.  However, you have specified " + expr.length + " arguments", that);}
    }
    
    else {classInstantiationHelper(that, superclass_result);} //use super class here, since it has constructors in it
    
    
    //clone the variables and visit the body.
    LinkedList<VariableData> vars = cloneVariableDataList(_vars);
    vars.addAll(myData.getVars());
    final TypeData body_result = that.getBody().visit(new ClassBodyTypeChecker(myData, _file, _package, _importedFiles, _importedPackages, vars, _thrown));

    
    _checkAbstractMethods(myData, that);
    return myData.getInstanceData();  //but actually return an instance of the anonymous inner class
  }
  
  /**
   * Resolve the type of this anonymous class.  Look it up in the enclosing data, check that
   * it is using a valid constructor through the classInstantiationHelper and visit the body.
   * Make sure that all abstract methods are overwritten.  The enclosing data is found by first resolving the
   * enclosing data.  Make sure that if this is an inner class it is being called from the appropriate static/non-static context (see
   * ComplexNamedClassInstantiation for more details)
   * @param that  The SimpleAnonymousClassInstantiation being type-checked
   * @return  The result of type checking the class instantiation.
   */
  public TypeData forComplexAnonymousClassInstantiation(ComplexAnonymousClassInstantiation that) {
    TypeData enclosingType = that.getEnclosing().visit(this);
    if ((enclosingType == null) || !assertFound(enclosingType, that.getEnclosing())) {return null; }
    
    //make sure we can see enclosingType
    checkAccessibility(that, enclosingType.getSymbolData().getMav(), enclosingType.getSymbolData().getName(), enclosingType.getSymbolData(), _data.getSymbolData(), "class or interface", true);
    
    final SymbolData superclass_result = getSymbolData(that.getType().getName(), enclosingType.getSymbolData(), that.getType());

    
    
    // Get this anonymous inner class's SymbolData
    SymbolData myData = handleAnonymousClassInstantiation(that, superclass_result);
      

    // TODO: will getSymbolData correctly handle all cases here?
    //TODO: We still do not handle static fields on the lhs correctly.  I think.
    
    boolean resultIsStatic;
    
    if (superclass_result.isInterface()) {
      Expression[] expr = that.getArguments().getExpressions();
      if (expr.length > 0) { _addError("You are creating an anonymous inner class that directly implements an interface, thus you should use the Object constructor which takes in no arguments.  However, you have specified " + expr.length + " arguments", that);}
      resultIsStatic = true;
    }
    
    
    else { //superclass_result is an interface...need to do some extra checking for static types.
      InstanceData result = classInstantiationHelper(that, superclass_result); //use super class here, since it has constructors in it
      if (result == null) {return null;}

      resultIsStatic = result.getSymbolData().hasModifier("static");
    }
    
    if (!enclosingType.isInstanceType() && !resultIsStatic) {
      _addError ("The constructor of a non-static inner class can only be called on an instance of its containing class (e.g. new " + 
                 Data.dollarSignsToDots(enclosingType.getName()) + "().new " + that.getType().getName() + "())", that);
    }
    
    else if (enclosingType.isInstanceType() && resultIsStatic) {
      _addError("You cannot instantiate a static inner class or interface with this syntax.  Instead, try new " + 
                Data.dollarSignsToDots(superclass_result.getName()) + "()", that);
    }

    //clone the variables and visit the body.
    LinkedList<VariableData> vars = cloneVariableDataList(_vars);
    vars.addAll(myData.getVars());
    
    final TypeData body_result = that.getBody().visit(new ClassBodyTypeChecker(myData, _file, _package, _importedFiles, _importedPackages, vars, _thrown));

    //make sure all abstract super class methods are overwritten
    _checkAbstractMethods(myData, that);
   
    return myData.getInstanceData(); //actually return an intance of the anonymous inner class
  }
  

  /**
   * SimpleThisConstructorInvocations are not allowed outside of the first line of a constructor.
   */
  public TypeData forSimpleThisConstructorInvocation(SimpleThisConstructorInvocation that) {
    _addError("This constructor invocations are only allowed as the first statement of a constructor body", that);
    return null;
  }
  
  /**
   * ComplexThisConstructorInvocations are not ever allowed.
   */
  public TypeData forComplexThisConstructorInvocation(ComplexThisConstructorInvocation that) {
    _addError("Constructor invocations of this form are never allowed", that);
    return null;
  }
  
  /**
   * Try to resolve this SimpleNameReference.  It is either:
   *    1. a field or variable reference (return the instance type of the field/variable)
   *    2. a class or interface name reference (return the type of the class or interface)
   *    3. part of a package reference or an error (return a new package data corresponding to the reference.
   * No need to call forSimpleNameReference only, since all the checking is done here.
   */
  public TypeData forSimpleNameReference(SimpleNameReference that) {
    Word myWord = that.getName();
    myWord.visit(this);
    
    //first, try to resolve this name as a field or variable reference
    
    VariableData reference = getFieldOrVariable(myWord.getText(), _data, _data.getSymbolData(), that, _vars, true, true);
    if (reference != null) {
      if (!reference.hasValue()) {
        _addError("You cannot use " + reference.getName() + " because it may not have been given a value", that.getName());
      }
      
      //if reference is non-static, but context is static, give error
      if (!reference.hasModifier("static") && inStaticMethod()) {
        _addError("Non static field or variable " + reference.getName() + " cannot be referenced from a static context", that);
      }
      
      return reference.getType().getInstanceData();
        
    }
      
    //next, try to resolve this name as a class or interface reference
    SymbolData classR = findClassReference(null, myWord.getText(), that);
    if (classR != null && classR != SymbolData.AMBIGUOUS_REFERENCE) {
      //Only return the symbolData if it is accessible--otherwise, return PackageData
      if (checkAccessibility(that, classR.getMav(), classR.getName(), classR, _data.getSymbolData(), "class or interface", false)) {
        return classR;
      }
    }
    if (classR == SymbolData.AMBIGUOUS_REFERENCE) {return null;}
    
    PackageData packageD = new PackageData(myWord.getText());
    return packageD;
  }
    
    
  /**
   * To resolve this ComplexNameReference, first visit the lhs with an instance
   * of this visitor in order to get its type.  Then, try to figure out how the name reference
   * on the right fits with the type on the left.
   *   1. If the lhs is a package data, then either the rhs is a class reference, or the whole thing is another PackageData
   *   2. If the rhs is a variable or field visible from the context of the lhs, it must be static if the lhs is a SymbolData, and regardless, it
   *       must have a value to be referenced here.
   *   3. If the rhs references an inner class of the lhs, the lhs must be a SymbolData if the rhs is a static inner class, and the rhs must
   *        be static if the lhs is a SymbolData.
   *   4.  Otherwise, give an error because we couldn't resolve the symbol.
   */
  public TypeData forComplexNameReference(ComplexNameReference that) {
    TypeData lhs = that.getEnclosing().visit(this);
    Word myWord = that.getName();
    
    //if lhs is a package data, either we found a class reference or this piece is still part of the package
    if (lhs instanceof PackageData) {
      SymbolData classRef =  findClassReference(lhs, myWord.getText(), that);
      if (classRef != null) {return classRef;}
      return new PackageData((PackageData) lhs, myWord.getText());
    }
    
    checkAccessibility(that, lhs.getSymbolData().getMav(), lhs.getSymbolData().getName(), lhs.getSymbolData(), _data.getSymbolData(), "class or interface", true);
     
    //if the word is a variable reference, make sure it can be seen from this context
    VariableData reference = getFieldOrVariable(myWord.getText(), lhs.getSymbolData(), _data.getSymbolData(), that);
    if (reference != null) {
      if (lhs instanceof SymbolData) {
        //does this reference a field? if so, it must be static
        if (!reference.hasModifier("static")) {
          _addError("Non-static variable " + reference.getName() + " cannot be accessed from the static context " + Data.dollarSignsToDots(lhs.getName()) + ".  Perhaps you meant to instantiate an instance of " + Data.dollarSignsToDots(lhs.getName()), that);
          return reference.getType().getInstanceData();
        }
      }
        
      //make sure it already had a value
      if (!reference.hasValue()) {
        _addError("You cannot use " + reference.getName() + " here, because it may not have been given a value", that.getName());
      }
        
      return reference.getType().getInstanceData();
    }
      
    //does this reference an inner class? if so, it must be static
    SymbolData sd = getSymbolData(true, myWord.getText(), lhs.getSymbolData(), that, false); //give error below instead, if it cannot be found
    if (sd != null && sd != SymbolData.AMBIGUOUS_REFERENCE) {
      if (!checkAccessibility(that, sd.getMav(), sd.getName(), sd, _data.getSymbolData(), "class or interface")) {return null;}
      if (!sd.hasModifier("static")) {
        _addError("Non-static inner class " + Data.dollarSignsToDots(sd.getName()) + " cannot be accessed from this context.  Perhaps you meant to instantiate it", that);
      }
      
      //you cannot reference static inner classes from the context of an instantiation of their outer class
      else if (lhs instanceof InstanceData) {
        _addError("You cannot reference the static inner class " + Data.dollarSignsToDots(sd.getName()) + " from an instance of " + Data.dollarSignsToDots(lhs.getName()) + ".  Perhaps you meant to say " + Data.dollarSignsToDots(sd.getName()), that);
      }
      return sd;
    }
 
    if (sd != SymbolData.AMBIGUOUS_REFERENCE) { _addError("Could not resolve " + myWord.getText() + " from the context of " + Data.dollarSignsToDots(lhs.getName()), that);}
    return null;
  }


  /**
   * Make sure we are in a non-static context.
   * @return an instance data corresponding to the enclosing class of this context.
   */
  public TypeData forSimpleThisReference(SimpleThisReference that) {
    if (inStaticMethod()) {
      _addError("'this' cannot be referenced from within a static method", that);
    }
    return _getData().getSymbolData().getInstanceData();
  }

  /**
   * Check to make sure that the enclosing result could be resolved and that it a type name.
   * Insure that an enclosing instance of that name exists in the current (non-static) context.
   * Return the instance data corresponding to its "this" field.
   * @param that  The ComplexThisReference we are type-checking
   * @param enclosing_result  The TypeData whose this field is being referenced
   * @return  An InstanceData corresponding to the enclosing_result.
   */
  public TypeData forComplexThisReferenceOnly(ComplexThisReference that, TypeData enclosing_result) {
    //make sure that enclosingResult is not null and not a PackageData.  If it is, return null
    if ((enclosing_result == null) || ! assertFound(enclosing_result, that.getEnclosing())) { return null; }
    
    if (inStaticMethod()) {
      _addError("'this' cannot be referenced from within a static method", that);
    }
    
    if (enclosing_result.isInstanceType()) {
      _addError("'this' can only be referenced from a type name, but you have specified an instance of that type.", that);
    }
    
    SymbolData myData = _getData().getSymbolData();
    if (!myData.isInnerClassOf(enclosing_result.getSymbolData(), true)) {
      // Test whether myData is an inner class of enclosing_result at all.  Somewhat inefficient, but only happens when errors occur.
      if (myData.isInnerClassOf(enclosing_result.getSymbolData(), false)) {
        _addError("You cannot reference " + enclosing_result.getName() + ".this from here, because " + myData.getName() + " or one of its enclosing classes " +
                  "is static.  Thus, an enclosing instance of " + enclosing_result.getName() + " does not exist", that);
      }
      else {
        _addError("You cannot reference " + enclosing_result.getName() + ".this from here, because " + enclosing_result.getName() + 
                  " is not an outer class of " + myData.getName(), that);
      }
    }

    return enclosing_result.getInstanceData();
  }
    
  /**
   * All classes should have a super class, which is java.lang.Object by default.  Lookup this class's super class.
   * If it is null, give an error (but keep in mind this should never happen).  Otherwise, return the instance data corresponding to 
   * the super class.
   * @param that  The SimpleSuperReference we are resolving.
   * @return  InstanceData corresponding to the super class
   */
  public TypeData forSimpleSuperReference(SimpleSuperReference that) {
    if (inStaticMethod()) {
      _addError("'super' cannot be referenced from within a static method", that);
    }
    SymbolData superClass = _getData().getSymbolData().getSuperClass();
    if (superClass == null) {  //this should never happen, because all classes should have a super class
      _addError("The class " + _getData().getSymbolData().getName() + " does not have a super class", that);
      return null;
    }
    return superClass.getInstanceData();
  }
  
  /**
   * Make sure that the enclosing result is not null--if it is, return null.  Insure that an 
   * enclosing instance of that name exists in the current (non-static) context.  Give an error if the enclosing_result
   * is not an instance type.  Get its super class, and return an instance data corresponding to it.
   * @param that  The ComplexSuperReference being typechecked
   * @param enclosing_result  The type of the left hand side of this reference.
   * @return  An InstanceData corresponding to the super class of enclosing_result.
   */
  public TypeData forComplexSuperReferenceOnly(ComplexSuperReference that, TypeData enclosing_result) {
    //make sure that enclosing_result is not null and not a PackageData.  If it is, return null
    if ((enclosing_result == null) || ! assertFound(enclosing_result, that.getEnclosing())) { return null; }
    
    if (inStaticMethod()) {
      _addError("'super' cannot be referenced from within a static method", that);
    }
    if (enclosing_result.isInstanceType()) {
      _addError("'super' can only be referenced from a type name, but you have specified an instance of that type.", that);
    }
    
    SymbolData myData = _getData().getSymbolData();
    if (!myData.isInnerClassOf(enclosing_result.getSymbolData(), true)) {
      // Test whether myData is an inner class of enclosing_result at all.  Somewhat inefficient, but only happens when errors occur.
      if (myData.isInnerClassOf(enclosing_result.getSymbolData(), false)) {
        _addError("You cannot reference " + enclosing_result.getName() + ".super from here, because " + myData.getName() + " or one of its enclosing classes " +
                  "is static.  Thus, an enclosing instance of " + enclosing_result.getName() + " does not exist", that);
      }
      else {
        _addError("You cannot reference " + enclosing_result.getName() + ".super from here, because " + enclosing_result.getName() + 
                  " is not an outer class of " + myData.getName(), that);
      }
    }

    SymbolData superClass = enclosing_result.getSymbolData().getSuperClass();
    if (superClass == null) {  //this should never happen, because all classes should have a super class
      _addError("The class " + enclosing_result.getName() + " does not have a super class", that);
      return null;
    }
    
    return superClass.getInstanceData();
  }
    

  
  
/**
 * Make sure the lhs is actually an array type and that the index is an int.
 */
  public TypeData forArrayAccessOnly(ArrayAccess that, TypeData lhs, TypeData index) {
    //if either lhs or index is null then an error has already been caught--return null
    if (lhs == null || index == null) {return null;}
    
    //if either lhs or index cannot be resolved, give error
    if (!assertFound(lhs, that) || !assertFound(index, that)) {
      return null;
    }
    
    if (assertInstanceType(lhs, "You cannot access an array element of a type name", that) &&
        ! (lhs.getSymbolData() instanceof ArrayData)) {
      _addError("The variable referred to by this array access is a " + lhs.getSymbolData().getName() + ", not an array", that);
      return lhs.getInstanceData();
    }
    
    if (assertInstanceType(index, "You have used a type name in place of an array index", that) &&
        !index.getSymbolData().isAssignableTo(SymbolData.INT_TYPE, _targetVersion)) {
      _addError("You cannot reference an array element with an index of type " + index.getSymbolData().getName() + ".  Instead, you must use an int", that);
      
    }
    
    return ((ArrayData)lhs.getSymbolData()).getElementType().getInstanceData();
  
  
}
    

  //*** Primitives and Literals *******//
  public TypeData forStringLiteralOnly(StringLiteral that) {
    return symbolTable.get("java.lang.String").getInstanceData();
  }

  public TypeData forIntegerLiteralOnly(IntegerLiteral that) {
    return SymbolData.INT_TYPE.getInstanceData();//forLiteralOnly(that);
  }

  public TypeData forLongLiteralOnly(LongLiteral that) {
    return SymbolData.LONG_TYPE.getInstanceData();
  }

  public TypeData forFloatLiteralOnly(FloatLiteral that) {
    return SymbolData.FLOAT_TYPE.getInstanceData();
  }

  public TypeData forDoubleLiteralOnly(DoubleLiteral that) {
    return SymbolData.DOUBLE_TYPE.getInstanceData();
  }

  public TypeData forCharLiteralOnly(CharLiteral that) {
    return SymbolData.CHAR_TYPE.getInstanceData();
  }

  public TypeData forBooleanLiteralOnly(BooleanLiteral that) {
    return SymbolData.BOOLEAN_TYPE.getInstanceData();
  }

  public TypeData forNullLiteralOnly(NullLiteral that) {
    return SymbolData.NULL_TYPE.getInstanceData();
  }

  public TypeData forClassLiteralOnly(ClassLiteral that) {
    return symbolTable.get("java.lang.Class").getInstanceData();
  }

  
  /**
   * Check a few constraints on this Parenthesized
   */
  public TypeData forParenthesizedOnly(Parenthesized that, TypeData value_result) {
    if (value_result == null) {return null;}
    
    if (!assertFound(value_result, that.getValue())) {
      return null;
    }
        
    assertInstanceType(value_result, "This class or interface name cannot appear in parentheses", that);
    return value_result.getInstanceData();
  }
  
  
  
  /**
   * Look up the method called in the method invocation within the context of the context TypeData.
   * Resolve all arguments to the method, and make sure they are instance datas.
   * If an argument is a type, the method cannot be found, or the method is called from a static context but is
   * not static, give appropriate error.
   * If the method is declared to throw any exceptions, add them to the thrown list.
   * @param that  The MethodInvocation we are type checking
   * @param context  The TypeData that should contain the method being invoked.
   */
  public TypeData methodInvocationHelper(MethodInvocation that, TypeData context) {
    Expression[] exprs = that.getArguments().getExpressions();
    TypeData[] args = new TypeData[exprs.length];
    InstanceData[] newArgs = new InstanceData[exprs.length];
    for (int i = 0; i < exprs.length; i++) {
      args[i] = exprs[i].visit(this);
      if (args[i] == null) {
        return null;
      }
      
      if (!assertFound(args[i], that)) {return null;}
      if (!args[i].isInstanceType()) {
        _addError("Cannot pass a class or interface name as an argument to a method.  Perhaps you meant to create an instance or use " + args[i].getName() + ".class", exprs[i]);
      }
      newArgs[i]=args[i].getInstanceData();

    }

    // Pass in both sd and the current SymbolData so that lookupMethod can check
    // if we have access to the method from here.
    MethodData md = _lookupMethod(that.getName().getText(), context.getSymbolData(), newArgs, that, 
                           "No method found in class " + context.getName() + " with signature: ", 
                           false, _getData().getSymbolData());
        
        
    if (md == null) {
      return null;
    }

    if (!context.isInstanceType() && !md.hasModifier("static")) {
      _addError("Cannot access the non-static method " + md.getName() + " from a static context", that);
    }

    //if MethodData is declared to throw exceptions, add them to thrown list:
    String[] thrown = md.getThrown();
    for (int i = 0; i<thrown.length; i++) {
      _thrown.addLast(new Pair<SymbolData, JExpression>(getSymbolData(thrown[i], _getData(), that), that));
    }

    return md.getReturnType().getInstanceData();
  }
  
  /**
   * Try to match this method invocation to a method in the context.  Here, the context is the 
   * enclosing data of where this is being invoked.
   * @param that  ComplexMethodInvocation we are typechecking
   * @return  The return type of the method, or null if method cannot be seen or found.
   */
  //TODO: We should handle static fields too!
  public TypeData forSimpleMethodInvocation(SimpleMethodInvocation that) {
    TypeData context = _getData().getSymbolData().getInstanceData();
    if (inStaticMethod()) { context = context.getSymbolData();} //Because it is static, want the SymbolData corresponding to the context, not the instance data.
    return methodInvocationHelper(that, context);
  }

  /**
   * Try to match this method invocation to a method in the context.  Here, the context is the 
   * enclosing field of the method invocation.
   * @param that  ComplexMethodInvocation we are typechecking
   * @return  The return type of the method, or null if method cannot be seen or found.
   */
  //TODO: We should handle static fields too!
  public TypeData forComplexMethodInvocation(ComplexMethodInvocation that) {
    TypeData context = that.getEnclosing().visit(this);
    if (!assertFound(context, that.getEnclosing()) || context==null) {return null;}
  
    //make sure we can see enclosingType
    checkAccessibility(that, context.getSymbolData().getMav(), context.getSymbolData().getName(), context.getSymbolData(), _data.getSymbolData(), "class or interface", true);

    
    //this next check insures that only static methods can be called from a static context, by forcing the rhs to be a static context.
    if (inStaticMethod()) { context = context.getSymbolData();}
    return methodInvocationHelper(that, context);
  }
  
  
  /**
   * A variable data can be assigned to if it is not final or it does not have a value.
   * (in other words, only final variables that have already been assigned are the only type that cannot be given a value.
   * @param vd  The VariableData to check.
   */
  protected boolean canBeAssigned(VariableData vd) {
    return !vd.isFinal() || !vd.hasValue();
  }
  

  
  /**
   * Returns the least restrictive numerical type.  According to the JLS: "If an integer 
   * operator other than a shift operator has at least one operand of type long, then the 
   * operation is carried out using 64-bit precision, and the result of the numerical operator 
   * is of type long. If the other operand is not long, it is first widened (§5.1.4) to type 
   * long by numeric promotion (§5.6). Otherwise, the operation is carried out using 32-bit 
   * precision, and the result of the numerical operator is of type int. If either operand 
   * is not an int, it is first widened to type int by numeric promotion."
   * So, check to see if one fo the SymboLDatas is a type less restrictive than int.  If so, return that type,
   * otherwise return INT_TYPE.
   */
  protected SymbolData _getLeastRestrictiveType(SymbolData sd1, SymbolData sd2) {
    if ((sd1.isDoubleType(_targetVersion) && sd2.isNumberType(_targetVersion)) ||
        (sd2.isDoubleType(_targetVersion) && sd1.isNumberType(_targetVersion))) {
      return SymbolData.DOUBLE_TYPE;
    }
    else if ((sd1.isFloatType(_targetVersion) && sd2.isNumberType(_targetVersion)) ||
             (sd2.isFloatType(_targetVersion) && sd1.isNumberType(_targetVersion))) {
      return SymbolData.FLOAT_TYPE;
    }
    else if ((sd1.isLongType(_targetVersion) && sd2.isNumberType(_targetVersion)) ||
             (sd2.isLongType(_targetVersion) && sd1.isNumberType(_targetVersion))) {
      return SymbolData.LONG_TYPE;
    }
    else if (sd1.isBooleanType(_targetVersion) && sd2.isBooleanType(_targetVersion)) {
      return SymbolData.BOOLEAN_TYPE;
    }
    else return SymbolData.INT_TYPE; // NOTE: It seems like any binary operation on number types with only ints, shorts, chars, or bytes will return an int
  }




  /**
   * Throw runtime exception, since conditional expressions are not allowed, and this should have been caught
   * before the TypeChecker.
   */
  public TypeData forConditionalExpression(ConditionalExpression that) {
    throw new RuntimeException("Internal Program Error: Conditional expressions are not supported.  This should have been caught before the Type Checker.  Please report this bug.");
  }
  
  /**
   * Throw runtime exception, since instanceof expressions are not allowed, and this should have been caught before the TypeChecker
   */
  public TypeData forInstanceofExpression(InstanceofExpression that) {
    throw new RuntimeException("Internal Program Error: Instance of expressions are not supported.  This should have been caught before the Type Checker.  Please report this bug.");
  }
  
  /*
   * Try to look up the type of the cast, and visit the expression that is being cast.
   * If the type being cast to is null, add an error, and return null.
   * If what is being cast cannot be resolved, just return the expected result of the cast, to allow type checking.
   * If everything is okay, call forCastExpressionOnly to do other checks.
   * @param that  The CastExpression being typeChecked
   * @return  The TypeData result of the cast, or null
   */
  public TypeData forCastExpression(CastExpression that) {
    //this call to getSymbolData will not throw any errors, but may return null.  If null is returned, an error needs to be added.
    final SymbolData type_result = getSymbolData(that.getType().getName(), _data.getSymbolData(), that.getType(), false);
    final TypeData value_result = that.getValue().visit(this);
    
    if (type_result == null) {
      _addError(that.getType().getName() + " cannot appear as the type of a cast expression because it is not a valid type", that.getType());
      return null;
    }

    if (value_result == null|| !assertFound(value_result, that.getValue())) {
      // An error occurred type-checking the value; return the expected type to
      // allow type-checking to continue.
      return type_result.getInstanceData();
    }
    
    //Neither type_result nor value_result are null.
    return forCastExpressionOnly(that, type_result, value_result);
  }
  
  
  /**
   * Make sure the dimensions of the array instantiation are all instances and subtypes of int, and then return
   * an instance of the array.
   * @param that  The UninitializedArrayInstantiation being type checked
   * @param type_result  The type of the array
   * @param dimensions_result  The array of the result of type-checking all the dimensions of this array.
   * @return an instance of the array.
   */
  public TypeData forUninitializedArrayInstantiationOnly(UninitializedArrayInstantiation that, TypeData type_result, TypeData[] dimensions_result) {
    //make sure all of the dimensions_result dimensions are instance datas
    Expression[] dims = that.getDimensionSizes().getExpressions();
    for (int i = 0; i<dimensions_result.length; i++) {
      if (dimensions_result[i] != null && assertFound(dimensions_result[i], dims[i])) {
        if (!dimensions_result[i].getSymbolData().isAssignableTo(SymbolData.INT_TYPE, _targetVersion)) {
          _addError("The dimensions of an array instantiation must all be ints.  You have specified something of type " + dimensions_result[i].getName(), dims[i]);
        }
        else {
          assertInstanceType(dimensions_result[i], "All dimensions of an array instantiation must be instances.  You have specified the type " + dimensions_result[i].getName(), dims[i]);
        }               
      }
    }
    
    if (type_result instanceof ArrayData) {
      int dim = ((ArrayData) type_result).getDimensions();
      if (dimensions_result.length > dim) {
       //uh oh!  Dimensions list is too long!
        _addError("You are trying to initialize an array of type " + type_result.getName() + " which requires " + dim + " dimensions, but you have specified " + dimensions_result.length + " dimensions--the wrong number", that);
      }
    }

    //return an instance of the new type
    if (type_result == null || !assertFound(type_result, that)) {return null;}
    return type_result.getInstanceData();
  }
  
  /**
   * Resolve the type of the array and visit its dimensions.  Call Only method to check instances in dimensions.
   * @param that  The SimpleUninitializedArrayInstantiation being type-checked.
   * @return  The type of the array, or null if there was an error.
   */
  public TypeData forSimpleUninitializedArrayInstantiation(SimpleUninitializedArrayInstantiation that) {
    final SymbolData type_result = getSymbolData(that.getType().getName(), _data.getSymbolData(), that.getType());
    final TypeData[] dimensions_result = makeArrayOfRetType(that.getDimensionSizes().getExpressions().length);
    
    for (int i = 0; i<that.getDimensionSizes().getExpressions().length; i++) {
      dimensions_result[i] = that.getDimensionSizes().getExpressions()[i].visit(this);
    }
    return forUninitializedArrayInstantiationOnly(that, type_result, dimensions_result);
  }
  

  /**
   * This is not legal java--should have been caught before the TypeChecker.  Give a runtime exception
   */
  public TypeData forComplexUninitializedArrayInstantiation(ComplexUninitializedArrayInstantiation that) {
    throw new RuntimeException("Internal Program Error: Complex Uninitialized Array Instantiations are not legal Java.  This should have been caught before the Type Checker.  Please report this bug.");
  }
  
  
  /**
   * The array initializer needs the type of the array to ensure it is properly handled.  Because of this, we use a
   * helper instead of calling this method directly.
   */
  public TypeData forArrayInitializer(ArrayInitializer that) {
    throw new RuntimeException("Internal Program Error: forArrayInitializer should never be called, but it was.  Please report this bug.");
  }
  
  /**
   * Lookup the type of the array instantiation, and if there are any errors with it, give them.
   * Then, check the array initializer.
   * @param that  The SimpleInitializedArrayAllocationInstantiation that is being type-checked
   * @return  An instance of the array
   */
  public TypeData forSimpleInitializedArrayInstantiation(SimpleInitializedArrayInstantiation that) {
    SymbolData type_result = getSymbolData(that.getType().getName(), _data, that.getType());
    TypeData elementResult = forArrayInitializerHelper(that.getInitializer(), type_result);
    if (type_result == null) {return null;}
    return type_result.getInstanceData();
  }

  /**
   * This is not legal java--should have been caught before the TypeChecker.  Give a runtime exception
   */
  public TypeData forComplexInitializedArrayInstantiation(ComplexInitializedArrayInstantiation that) {
    throw new RuntimeException("Internal Program Error: Complex Initialized Array Instantiations are not legal Java.  This should have been caught before the Type Checker.  Please report this bug.");
  }
  
 
  //moved from TypeChecker
  public TypeData forInnerClassDef(InnerClassDef that) {
    String className = that.getName().getText();
    SymbolData sd = _data.getInnerClassOrInterface(className); // This works because className will never be a qualified name
    // Check for cyclic inheritance
    if (checkForCyclicInheritance(sd, new LinkedList<SymbolData>(), that)) {
      return null;
    }
    final TypeData mav_result = that.getMav().visit(this);
    final TypeData name_result = that.getName().visit(this);
    final TypeData[] typeParameters_result = makeArrayOfRetType(that.getTypeParameters().length);
    for (int i = 0; i < that.getTypeParameters().length; i++) {
      typeParameters_result[i] = that.getTypeParameters()[i].visit(this);
    }
    final TypeData superclass_result = that.getSuperclass().visit(this);
    final TypeData[] interfaces_result = makeArrayOfRetType(that.getInterfaces().length);
    for (int i = 0; i < that.getInterfaces().length; i++) {
      interfaces_result[i] = that.getInterfaces()[i].visit(this);
    }
    final TypeData body_result = that.getBody().visit(new ClassBodyTypeChecker(sd, _file, _package, _importedFiles, _importedPackages, _vars, _thrown));
    //return forInnerClassDefOnly(that, mav_result, name_result, typeParameters_result, superclass_result, interfaces_result, body_result);
    return null;
  }
    
  
  /**
   * Compare the two lists of variable datas, and if a data is in both lists, mark it as
   * having been assigned.
   * @param l1  One of the lists of variable datas
   * @param l2  The other list of variable datas.
   */
  void reassignVariableDatas(LinkedList<VariableData> l1, LinkedList<VariableData> l2) {
    for (int i = 0; i<l1.size(); i++) { 
      if (l2.contains(l1.get(i))) {
        l1.get(i).gotValue();
      }
    }
  }
  
  /**
   * Compare a list of variable datas and a list of list of variable datas.
   * If a variable data is in the list and in each list of the lists of lists, mark it as having been
   * assigned.
   * @param tryBlock  The list of variable datas.
   * @param catchBlocks  The list of list of variable datas.
   */
  void reassignLotsaVariableDatas(LinkedList<VariableData> tryBlock, LinkedList<LinkedList<VariableData>> catchBlocks) {
    for (int i = 0; i<tryBlock.size(); i++) {
      boolean seenIt = true;
      for (int j = 0; j<catchBlocks.size(); i++) {
        if (!catchBlocks.get(j).contains(tryBlock.get(i))) {seenIt = false;}
      }
    
      if (seenIt) {        //find the variable data in vars and give it a value!
        tryBlock.get(i).gotValue();
      }
    }
  }
  


  /**
   * Throw the appropriate error, based on the type of the JExpression where the exception was unchecked
   * @param sd  The SymbolData corresponding to the exception that is thrown
   * @param j  The JExpression corresponding to the context of where the exception is thrown from.
   */
  public void handleUncheckedException(SymbolData sd, JExpression j) {
    if (j instanceof MethodInvocation) {
      _addError("The method " + ((MethodInvocation)j).getName().getText() + " is declared to throw the exception " + sd.getName() + " which needs to be caught or declared to be thrown", j);
      }
      else if (j instanceof ThrowStatement) {
        _addError("This statement throws the exception " + sd.getName() + " which needs to be caught or declared to be thrown", j);
      }
      else if (j instanceof ClassInstantiation) {
        _addError("The constructor for the class " + ((ClassInstantiation)j).getType().getName() + " is declared to throw the exception " + sd.getName() + " which needs to be caught or declared to be thrown.", j);
      }
      else {
        throw new RuntimeException("Internal Program Error: Something besides a method invocation or throw statement threw an exception.  Please report this bug.");
      }
  }
  
  
  /**
   * Returns whether the sd is a checked exception, i.e. one that needs to be caught or declared to be thrown.
   * This is defined as all subclasses of java.lang.Throwable except for subclasses of java.lang.RuntimeException
   */
  public boolean isCheckedException(SymbolData sd, JExpression that) {
    return sd.isSubClassOf(getSymbolData("java.lang.Throwable", _data, that, false)) &&
      ! sd.isSubClassOf(getSymbolData("java.lang.RuntimeException", _data, that, false)) &&
      ! sd.isSubClassOf(getSymbolData("java.lang.Error", _data, that, false));
  }
  
  /**
   * Return true if the Exception is a checked exception yet is not caught or declared to be thrown, and false otherwise.
   * An exception is a checked if it does not extend either java.lang.RuntimeException or java.lang.Error,
   * and is not declared to be thrown by the enclosing method.
   * @param sd  The SymbolData of the Exception we are checking.
   * @param that  The JExpression passed to getSymbolData for error purposes.
   */
  public boolean isUncaughtCheckedException(SymbolData sd, JExpression that) {
    return isCheckedException(sd, that);
  }
  
  //TODO: To optimize this, should 2nd for loop be moved outside of first for loop?
  public TypeData forBracedBody(BracedBody that) {
    final TypeData[] items_result = makeArrayOfRetType(that.getStatements().length);
    for (int i = 0; i < that.getStatements().length; i++) {
      items_result[i] = that.getStatements()[i].visit(this);
      //walk over what has been thrown and throw an error if it contains an unchecked exception
      for (int j = 0; j<this._thrown.size(); j++) {
        if (isUncaughtCheckedException(this._thrown.get(j).getFirst(), that)) {
          handleUncheckedException(this._thrown.get(j).getFirst(), this._thrown.get(j).getSecond());
        }
      }
    }

    return forBracedBodyOnly(that, items_result);
  }
  
  /*@return true type by default*/
  public TypeData forEmptyForCondition(EmptyForCondition that) {
    return SymbolData.BOOLEAN_TYPE.getInstanceData();
  }
      
  
  /*Test the methods defined in the above (enclosing) class*/
  public static class ExpressionTypeCheckerTest extends TestCase {
    
    private ExpressionTypeChecker _etc;
    
    private SymbolData _sd1;
    private SymbolData _sd2;
    private SymbolData _sd3;
    private SymbolData _sd4;
    private SymbolData _sd5;
    private SymbolData _sd6;
    private ModifiersAndVisibility _publicMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"public"});
    private ModifiersAndVisibility _protectedMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"protected"});
    private ModifiersAndVisibility _privateMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"private"});
    private ModifiersAndVisibility _packageMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[0]);
    private ModifiersAndVisibility _abstractMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"abstract"});
    private ModifiersAndVisibility _finalMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"final"});
    private ModifiersAndVisibility _finalPublicMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"final", "public"});
    private ModifiersAndVisibility _publicAbstractMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"public", "abstract"});
    private ModifiersAndVisibility _publicStaticMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"public", "static"});
    
    
    public ExpressionTypeCheckerTest() {
      this("");
    }
    public ExpressionTypeCheckerTest(String name) {
      super(name);
    }
    
    public void setUp() {
      errors = new LinkedList<Pair<String, JExpressionIF>>();
      symbolTable = new Symboltable();
      _etc = new ExpressionTypeChecker(null, new File(""), "", new LinkedList<String>(), new LinkedList<String>(), new LinkedList<VariableData>(), new LinkedList<Pair<SymbolData, JExpression>>());
      _etc._targetVersion = JavaVersion.JAVA_5;
      _etc._importedPackages.addFirst("java.lang");
      _sd1 = new SymbolData("i.like.monkey");
      _sd2 = new SymbolData("i.like.giraffe");
      _sd3 = new SymbolData("zebra");
      _sd4 = new SymbolData("u.like.emu");
      _sd5 = new SymbolData("");
      _sd6 = new SymbolData("cebu");
      _etc._data = _sd1;
    }
    

    public void testForCastExpression() {
      CastExpression ce = new CastExpression(JExprParser.NO_SOURCE_INFO, 
                                             new PrimitiveType(JExprParser.NO_SOURCE_INFO, "dan"), 
                                             new NullLiteral(JExprParser.NO_SOURCE_INFO));
      
      // if cast type is not a valid type, casting should not be allowed
      assertEquals("Should return null", null, ce.visit(_etc));
      assertEquals("There should be one error", 1, errors.size());
      assertEquals("Error message should be correct", "dan cannot appear as the type of a cast expression because it is not a valid type", errors.getLast().getFirst());
      
      //if cast expression cannot be resolved, return cast type instance to allow type checking to continue
      CastExpression ce2 = new CastExpression(JExprParser.NO_SOURCE_INFO,
                                             new PrimitiveType(JExprParser.NO_SOURCE_INFO, "int"),
                                             new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "notReal")));
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), ce2.visit(_etc));
      assertEquals("There should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol notReal", errors.getLast().getFirst());

      //now, try one that should work
      CastExpression ce3 = new CastExpression(JExprParser.NO_SOURCE_INFO,
                                             new PrimitiveType(JExprParser.NO_SOURCE_INFO, "int"),
                                             new DoubleLiteral(JExprParser.NO_SOURCE_INFO, 5));
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), ce3.visit(_etc));
      assertEquals("There should still be 2 errors", 2, errors.size());
      
      
    }
    
    
    public void testForCastExpressionOnly() {
      SymbolData sd1 = SymbolData.DOUBLE_TYPE;
      SymbolData sd2 = SymbolData.BOOLEAN_TYPE;
      SymbolData sd3 = SymbolData.INT_TYPE;
      
      CastExpression cd = new CastExpression(JExprParser.NO_SOURCE_INFO, 
                                             JExprParser.NO_TYPE, 
                                             new NullLiteral(JExprParser.NO_SOURCE_INFO));

      assertEquals("When value_result is subtype of type_result, return type_result.", sd1.getInstanceData(), _etc.forCastExpressionOnly(cd, sd1, sd3.getInstanceData()));
      assertEquals("Should not throw an error.", 0, errors.size());
      assertEquals("When type_result is subtype of value_result, return type_result.", sd3.getInstanceData(), _etc.forCastExpressionOnly(cd, sd3, sd1.getInstanceData()));
      assertEquals("Should not throw an error.", 0, errors.size());
      assertEquals("When type_result and value_result are not subtypes of each other, return type_result", sd2.getInstanceData(), _etc.forCastExpressionOnly(cd, sd2, sd1.getInstanceData()));
      assertEquals("Should now be one error.", 1, errors.size());
      assertEquals("Error message should be correct.", "You cannot cast an expression of type " + sd1.getName() + " to type " + sd2.getName() + " because they are not related", errors.getLast().getFirst());     
      SymbolData foo = new SymbolData("Foo");
      SymbolData fooMama = new SymbolData("FooMama");
      foo.setSuperClass(fooMama);
      assertEquals("When value_result is a SymbolData, return type_result", fooMama.getInstanceData(), _etc.forCastExpressionOnly(cd, fooMama, foo));
      assertEquals("There should be 2 errors.", 2, errors.size());
      assertEquals("Error message should be correct.", "You are trying to cast Foo, which is a class or interface type, not an instance.  Perhaps you meant to create a new instance of Foo", errors.getLast().getFirst());
    }
 
    public void testForEmptyExpressionOnly() {
     EmptyExpression ee = new EmptyExpression(JExprParser.NO_SOURCE_INFO);
     try {
       _etc.forEmptyExpressionOnly(ee);
       fail("Should have thrown exception");
     }
     catch (RuntimeException e) {
       assertEquals("Error message should be correct", "Internal Program Error: EmptyExpression encountered.  Student is missing something.  Should have been caught before TypeChecker.  Please report this bug.", e.getMessage());
     }
    }
    
    
    
   public void test_getLeastRestrictiveType() {
      // Assumes both number types
      assertEquals("Should return double.", SymbolData.FLOAT_TYPE, _etc._getLeastRestrictiveType(SymbolData.INT_TYPE, SymbolData.FLOAT_TYPE));
      assertEquals("Should return double.", SymbolData.FLOAT_TYPE, _etc._getLeastRestrictiveType(SymbolData.FLOAT_TYPE, SymbolData.FLOAT_TYPE));
      assertEquals("Should return int.", SymbolData.INT_TYPE, _etc._getLeastRestrictiveType(SymbolData.INT_TYPE, SymbolData.CHAR_TYPE));
      assertEquals("Should return char.", SymbolData.INT_TYPE, _etc._getLeastRestrictiveType(SymbolData.CHAR_TYPE, SymbolData.CHAR_TYPE));
    }
    
    public void test_isAssignableFrom() {
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.DOUBLE_TYPE, SymbolData.DOUBLE_TYPE));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.DOUBLE_TYPE, SymbolData.INT_TYPE));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.DOUBLE_TYPE, SymbolData.CHAR_TYPE));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.INT_TYPE, SymbolData.INT_TYPE));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.INT_TYPE, SymbolData.CHAR_TYPE));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(SymbolData.CHAR_TYPE, SymbolData.CHAR_TYPE));
      
      _sd2.setSuperClass(_sd1);
      assertTrue("Should be assignable.", _etc._isAssignableFrom(_sd1, _sd1));
      assertTrue("Should be assignable.", _etc._isAssignableFrom(_sd1, _sd2));
    }


    //for expressions we want to check, but don't fit neatly into a category
    public void testRandomExpressions() {
      //a string of + and - before a number
      PositiveExpression pe = new PositiveExpression(JExprParser.NO_SOURCE_INFO, new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 5));
      PositiveExpression pe2 = new PositiveExpression(JExprParser.NO_SOURCE_INFO, pe);
      NegativeExpression pe3 = new NegativeExpression(JExprParser.NO_SOURCE_INFO, pe2);
      PositiveExpression pe4 = new PositiveExpression(JExprParser.NO_SOURCE_INFO, pe3);
      PositiveExpression pe5 = new PositiveExpression(JExprParser.NO_SOURCE_INFO, pe4);
      NegativeExpression pe6 = new NegativeExpression(JExprParser.NO_SOURCE_INFO, pe5);
      
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), pe6.visit(_etc));
      assertEquals("Should be no errors", 0, errors.size());
    }
   
    public void testForSimpleUninitializedArrayInstantiation() {
      LanguageLevelVisitor llv = new LanguageLevelVisitor(_etc._file, _etc._package, _etc._importedFiles, 
                                                          _etc._importedPackages, new LinkedList<String>(), new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(), 
                                                          new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
      llv.symbolTable = _etc.symbolTable;
      
      SourceInfo si = JExprParser.NO_SOURCE_INFO;
      
      ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
      intArray.setIsContinuation(false);
      symbolTable.remove("int[]");
      symbolTable.put("int[]", intArray);
      
      ArrayData intArrayArray = new ArrayData(intArray, llv, si);
      intArrayArray.setIsContinuation(false);
      symbolTable.put("int[][]", intArrayArray);

      ArrayData intArray3 = new ArrayData(intArrayArray, llv, si);
      intArray3.setIsContinuation(false);
      symbolTable.put("int[][][]", intArray3);

      Expression i1 = new IntegerLiteral(si, 5);
      Expression i2 = new PlusExpression(si, new IntegerLiteral(si, 5), new IntegerLiteral(si, 7));
      Expression i3 = new CharLiteral(si, 'c');
      Expression badIndexD = new DoubleLiteral(si, 4.2);
      Expression badIndexL = new LongLiteral(si, 4l);

      //Test one that works
      SimpleUninitializedArrayInstantiation sa1 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "int[][][]", new ArrayType(si, "int[][]", new ArrayType(si, "int[]", new PrimitiveType(si, "int")))), 
                                                                                            new DimensionExpressionList(si, new Expression[] {i1, i2, i3}));
      assertEquals("Should return instance of int[][][]", intArray3.getInstanceData(), sa1.visit(_etc));
      assertEquals("There should be no errors", 0, errors.size());
      
      //Test one with a bad index
      SimpleUninitializedArrayInstantiation sa2 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "int[][][]", new ArrayType(si, "int[][]", new ArrayType(si, "int[]", new PrimitiveType(si, "int")))), 
                                                                                            new DimensionExpressionList(si, new Expression[] {i1, i2, badIndexD}));
      assertEquals("Should return instance of int[][][]", intArray3.getInstanceData(), sa2.visit(_etc));
      assertEquals("There should be one error", 1, errors.size());
      assertEquals("The error message should be correct", "The dimensions of an array instantiation must all be ints.  You have specified something of type double", errors.getLast().getFirst());
      
      //Test one with a bad type
      SimpleUninitializedArrayInstantiation sa3 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "Jonathan[]", new ClassOrInterfaceType(si, "Jonathan", new Type[0])), 
                                                                                            new DimensionExpressionList(si, new Expression[]{i1}));
      assertEquals("Should return null", null, sa3.visit(_etc));
      assertEquals("There should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "Class or variable Jonathan[] not found.", errors.getLast().getFirst());
      
      //Test one with wrong dimensions--too many
      SimpleUninitializedArrayInstantiation sa4 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "int[][]", new ArrayType(si, "int[]", new PrimitiveType(si, "int"))), 
                                                                                            new DimensionExpressionList(si, new Expression[] {i1, i2, i3}));
      assertEquals("Should return instance of int[][]", intArrayArray.getInstanceData(), sa4.visit(_etc));
      assertEquals("There should be 3 errors", 3, errors.size());
      assertEquals("Error message should be correct", "You are trying to initialize an array of type int[][] which requires 2 dimensions, but you have specified 3 dimensions--the wrong number", errors.getLast().getFirst());

      
      //Test one with wrong dimensions--too few--should be no additional errors
      SimpleUninitializedArrayInstantiation sa5 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "int[][][]", new ArrayType(si, "int[][]", new ArrayType(si, "int[]", new PrimitiveType(si, "int")))), 
                                                                                            new DimensionExpressionList(si, new Expression[] {i1, i2}));
      assertEquals("Should return instance of int[][][]", intArray3.getInstanceData(), sa5.visit(_etc));
      assertEquals("There should still be 3 errors", 3, errors.size());
      
      //Test one where type is not accessible
      intArray3.setMav(_privateMav);
      assertEquals("Should return instance of int[][][]", intArray3.getInstanceData(), sa1.visit(_etc));
      assertEquals("There should be no errors", 4, errors.size());
      assertEquals("Error message should be correct", "The class or interface int[][][] is private and cannot be accessed from i.like.monkey", errors.getLast().getFirst());
      intArray3.setMav(_publicMav);
    }
    

    
    public void testForComplexUninitializedArrayInstantiation() {
      ComplexUninitializedArrayInstantiation ca1 = new ComplexUninitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "my")),
                                                                                              new ArrayType(JExprParser.NO_SOURCE_INFO, "type[][][]", new ArrayType(JExprParser.NO_SOURCE_INFO, "type[][]", new ArrayType(JExprParser.NO_SOURCE_INFO, "type[]", new ClassOrInterfaceType(JExprParser.NO_SOURCE_INFO, "type", new Type[0])))), 
                                                                                            new DimensionExpressionList(JExprParser.NO_SOURCE_INFO, new Expression[0]));
      //This should always give a runtime exception
      try {
        ca1.visit(_etc);
        fail("Should have throw runtime exception");
      }
      catch (RuntimeException e) {
        assertEquals("Correct exception should have been thrown","Internal Program Error: Complex Uninitialized Array Instantiations are not legal Java.  This should have been caught before the Type Checker.  Please report this bug." , e.getMessage());
      }
    }    
    
    public void testForUninitializedArrayInstantiationOnly() {
      LanguageLevelVisitor llv = new LanguageLevelVisitor(_etc._file, _etc._package, _etc._importedFiles, 
                                                          _etc._importedPackages, new LinkedList<String>(), new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(), 
                                                          new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
      llv.symbolTable = _etc.symbolTable;
      
      SourceInfo si = JExprParser.NO_SOURCE_INFO;
      
      ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, si);
      intArray.setIsContinuation(false);
      symbolTable.remove("int[]");
      symbolTable.put("int[]", intArray);
      
      ArrayData intArrayArray = new ArrayData(intArray, llv, si);
      intArrayArray.setIsContinuation(false);
      symbolTable.put("int[][]", intArrayArray);

      ArrayData intArray3 = new ArrayData(intArrayArray, llv, si);
      intArray3.setIsContinuation(false);
      symbolTable.put("int[][][]", intArray3);

      //one that works--int instance index
      SimpleUninitializedArrayInstantiation sa1 = new SimpleUninitializedArrayInstantiation(si, new ArrayType(si, "int[][][]", new ArrayType(si, "int[][]", new ArrayType(si, "int[]", new PrimitiveType(si, "int")))), 
                                                                                            new DimensionExpressionList(si, new Expression[] {new NullLiteral(si), new NullLiteral(si), new NullLiteral(si)}));

      assertEquals("Should return int[][][] instance", intArray3.getInstanceData(), _etc.forUninitializedArrayInstantiationOnly(sa1, intArray3, new TypeData[] {SymbolData.INT_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()}));
      assertEquals("Should be no errors", 0, errors.size());
            
      //one that works--char instance index
      assertEquals("Should return int[][][] instance", intArray3.getInstanceData(), _etc.forUninitializedArrayInstantiationOnly(sa1, intArray3, new TypeData[] {SymbolData.INT_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData(), SymbolData.CHAR_TYPE.getInstanceData()}));
      assertEquals("Should be no errors", 0, errors.size());
      
      //one with bad index: not instance type
      assertEquals("Should return int[][][] instance", intArray3.getInstanceData(), _etc.forUninitializedArrayInstantiationOnly(sa1, intArray3, new TypeData[] {SymbolData.INT_TYPE.getInstanceData(), SymbolData.INT_TYPE, SymbolData.CHAR_TYPE.getInstanceData()}));
      assertEquals("Should be one error", 1, errors.size());
      assertEquals("Error message should be correct", "All dimensions of an array instantiation must be instances.  You have specified the type int.  Perhaps you meant to create a new instance of int", errors.getLast().getFirst());
      
      //one with bad index: not int type
      assertEquals("Should return int[][][] instance", intArray3.getInstanceData(), _etc.forUninitializedArrayInstantiationOnly(sa1, intArray3, new TypeData[] {SymbolData.INT_TYPE.getInstanceData(), SymbolData.BOOLEAN_TYPE, SymbolData.CHAR_TYPE.getInstanceData()}));
      assertEquals("Should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "The dimensions of an array instantiation must all be ints.  You have specified something of type boolean" , errors.getLast().getFirst());
      

    }
    
    public void testForArrayInitializer() {
      ArrayInitializer ai = new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 2)});
      try {
        ai.visit(_etc);
        fail("Should have throw runtime exception");
      }
      catch(RuntimeException e) {
        assertEquals("Exception message should be correct", "Internal Program Error: forArrayInitializer should never be called, but it was.  Please report this bug.", e.getMessage());
      }

    }
    
    public void testForSimpleInitializedArrayInstantiation() {
      IntegerLiteral e1 = new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 5);
      IntegerLiteral e2 = new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 7);
      SimpleNameReference e3 = new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "int"));
      BooleanLiteral e4 = new BooleanLiteral(JExprParser.NO_SOURCE_INFO, true);
      DoubleLiteral e5 = new DoubleLiteral(JExprParser.NO_SOURCE_INFO, 4.2);
      CharLiteral e6 = new CharLiteral(JExprParser.NO_SOURCE_INFO, 'e');
      SimpleNameReference e7 = new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "int"));
      
      ArrayType intArrayType = new ArrayType(JExprParser.NO_SOURCE_INFO, "int[]", new PrimitiveType(JExprParser.NO_SOURCE_INFO, "int"));

      LanguageLevelVisitor llv = new LanguageLevelVisitor(_etc._file, _etc._package, _etc._importedFiles, 
                                                          _etc._importedPackages, new LinkedList<String>(), new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>(), 
                                                          new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
      
      
      ArrayData intArray = new ArrayData(SymbolData.INT_TYPE, llv, JExprParser.NO_SOURCE_INFO);
      intArray.setIsContinuation(false);
      symbolTable.remove("int[]");
      symbolTable.put("int[]", intArray);

      //try one that should work:
      InitializedArrayInstantiation good = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, intArrayType, new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e2}));
      assertEquals("Should return int array instance", intArray.getInstanceData(), good.visit(_etc));
      assertEquals("Should be no errors", 0, errors.size());
      
      //char is a subtype of int, so it can be used here
      good = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, intArrayType, new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e2, e6}));
      assertEquals("Should return int array instance", intArray.getInstanceData(), good.visit(_etc));
      assertEquals("Should be no errors", 0, errors.size());
      
      //lhs is not an array type
      InitializedArrayInstantiation bad = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, new PrimitiveType(JExprParser.NO_SOURCE_INFO, "int"), new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e2}));
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), bad.visit(_etc));
      assertEquals("Should be 1 error", 1, errors.size());
      assertEquals("Error message should be correct", "You cannot initialize the non-array type int with an array initializer", errors.getLast().getFirst());
      
      //one of the elements is the wrong type
      //boolean
      bad = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, intArrayType, new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e4, e2, e6}));
      assertEquals("Should return int array instance", intArray.getInstanceData(), bad.visit(_etc));
      assertEquals("Should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "The elements of this initializer should have type int but element 1 has type boolean", errors.getLast().getFirst());
      
      //double
      bad = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, intArrayType, new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e5, e2, e6}));
      assertEquals("Should return int array instance", intArray.getInstanceData(), bad.visit(_etc));
      assertEquals("Should be 3 errors", 3, errors.size());
      assertEquals("Error message should be correct", "The elements of this initializer should have type int but element 1 has type double", errors.getLast().getFirst());

      //cannot resolve lhs
      bad = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, new PrimitiveType(JExprParser.NO_SOURCE_INFO, "ej"), new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e2}));
      assertEquals("Should return null", null, bad.visit(_etc));
      assertEquals("Should be 4 error", 4, errors.size());
      assertEquals("Error message should be correct", "Class or variable ej not found.", errors.getLast().getFirst());
      
      //one of the things in the initializer is a type name!
      bad = new SimpleInitializedArrayInstantiation(JExprParser.NO_SOURCE_INFO, intArrayType, new ArrayInitializer(JExprParser.NO_SOURCE_INFO, new VariableInitializerI[] {e1, e7}));
      assertEquals("Should return instance of int[]", intArray.getInstanceData(), bad.visit(_etc));
      assertEquals("Should now be 5 error messages", 5, errors.size());
      assertEquals("Error message should be correct", "The elements of this initializer should all be instances, but you have specified the type name int.  Perhaps you meant to create a new instance of int", errors.getLast().getFirst());

      
    }
    

    
    
    public void testForSimpleAssignmentExpressionOnly() {
      SimpleAssignmentExpression sae = new SimpleAssignmentExpression(JExprParser.NO_SOURCE_INFO, new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "i")), new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 5));

      //if lhs is assignable to rhs, and both instances, do not give any errors
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.DOUBLE_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if either input is null, return null
      assertEquals("Should return null", null, _etc.forSimpleAssignmentExpressionOnly(sae, null, SymbolData.INT_TYPE));
      assertEquals("Should return null", null, _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.INT_TYPE, null));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if lhs is a PackageData, give error and return null
      PackageData pd = new PackageData("bad_reference");
      assertEquals("Should return null", null, _etc.forSimpleAssignmentExpressionOnly(sae, pd, SymbolData.INT_TYPE));
      assertEquals("Should be 1 error", 1, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());
      
      
      //if rhs is a PackageData, give an error and return null
      assertEquals("Should return null", null, _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.INT_TYPE, pd));
      assertEquals("Should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());

      //if rhs or lhs are not instance datas, give appropriate errors
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.DOUBLE_TYPE, SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be 3 errors", 3, errors.size());
      assertEquals("Error message should be correct", "You cannot assign a value to the type double.  Perhaps you meant to create a new instance of double", errors.get(2).getFirst());
      
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.DOUBLE_TYPE.getInstanceData(), SymbolData.INT_TYPE));
      assertEquals("Should be 4 errors", 4, errors.size());
      assertEquals("Error message should be correct", "You cannot use the type name int on the right hand side of an assignment.  Perhaps you meant to create a new instance of int", errors.getLast().getFirst());

      //if rhs cannot be assigned to lhs, give error
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), _etc.forSimpleAssignmentExpressionOnly(sae, SymbolData.INT_TYPE.getInstanceData(), SymbolData.DOUBLE_TYPE.getInstanceData()));
      assertEquals("Should now be 5 errors", 5, errors.size());
      assertEquals("Error message should be correct", "You cannot assign something of type double to something of type int", errors.getLast().getFirst());
      
    }

    
    public void testForPlusAssignmentExpressionOnly() {
      PlusAssignmentExpression pae = new PlusAssignmentExpression(JExprParser.NO_SOURCE_INFO, new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 5), new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 6));
      
      //if lhs is a string, and lhs and rhs both instances, no errors
      SymbolData string = new SymbolData("java.lang.String");
      string.setIsContinuation(false);
      string.setPackage("java.lang");
      string.setMav(_publicMav);
      symbolTable.put("java.lang.String", string);
      
      assertEquals("Should return string instance", string.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, string.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if both number instances, no errors
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.DOUBLE_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if either input is null, return null
      assertEquals("Should return null", null, _etc.forPlusAssignmentExpressionOnly(pae, null, SymbolData.INT_TYPE));
      assertEquals("Should return null", null, _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.INT_TYPE, null));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if lhs is a PackageData, give error and return null
      PackageData pd = new PackageData("bad_reference");
      assertEquals("Should return null", null, _etc.forPlusAssignmentExpressionOnly(pae, pd, SymbolData.INT_TYPE));
      assertEquals("Should be 1 error", 1, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());
      
      
      //if rhs is a PackageData, give an error and return null
      assertEquals("Should return null", null, _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.INT_TYPE, pd));
      assertEquals("Should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());
      
      //if lhs is a string, but not an instance data, give error
      assertEquals("Should return string instance", string.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, string, SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be 3 errors", 3, errors.size());
      assertEquals("Error message should be correct","The arguments to a Plus Assignment Operator (+=) must both be instances, but you have specified a type name.  Perhaps you meant to create a new instance of java.lang.String" , errors.getLast().getFirst());
      
      //if lhs is a string, but rhs is not an instance, give error
      assertEquals("Should return string instance", string.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, string.getInstanceData(), SymbolData.INT_TYPE));
      assertEquals("Should be 4 errors", 4, errors.size());
      assertEquals("Error message should be correct","The arguments to a Plus Assignment Operator (+=) must both be instances, but you have specified a type name.  Perhaps you meant to create a new instance of int" , errors.getLast().getFirst());

      //if rhs is not a number or string, give error
      assertEquals("Should return string, by default", string.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, _sd2.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be 5 errors", 5, errors.size());
      assertEquals("Error message should be correct", "The arguments to the Plus Assignment Operator (+=) must either include an instance of a String or both be numbers.  You have specified arguments of type " + _sd2.getName() + " and int", errors.getLast().getFirst());
      
      //if rhs is number but lhs is not, give error
      assertEquals("should return string, by default", string.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.INT_TYPE.getInstanceData(), _sd2.getInstanceData()));
      assertEquals("Should be 6 errors", 6, errors.size());
      assertEquals("Error message should be correct", "The arguments to the Plus Assignment Operator (+=) must either include an instance of a String or both be numbers.  You have specified arguments of type int and " + _sd2.getName(), errors.getLast().getFirst());
      

      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.INT_TYPE.getInstanceData(), SymbolData.DOUBLE_TYPE.getInstanceData()));
      assertEquals("Should be 7 errors", 7, errors.size());
      assertEquals("Error message should be correct", "You cannot increment something of type int with something of type double", errors.getLast().getFirst());

      //if both numbers, but not instances, give errors
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forPlusAssignmentExpressionOnly(pae, SymbolData.DOUBLE_TYPE, SymbolData.INT_TYPE));
      assertEquals("Should be 9 errors", 9, errors.size());
      assertEquals("Second error message should be correct", "The arguments to the Plus Assignment Operator (+=) must both be instances, but you have specified a type name.  Perhaps you meant to create a new instance of double", errors.get(7).getFirst());
      assertEquals("First error message should be correct", "The arguments to the Plus Assignment Operator (+=) must both be instances, but you have specified a type name.  Perhaps you meant to create a new instance of int", errors.getLast().getFirst());
    }
    
    

    public void testForNumericAssignmentExpressionOnly() {
      NumericAssignmentExpression nae = new MinusAssignmentExpression(JExprParser.NO_SOURCE_INFO, new SimpleNameReference(JExprParser.NO_SOURCE_INFO, new Word(JExprParser.NO_SOURCE_INFO, "i")), new IntegerLiteral(JExprParser.NO_SOURCE_INFO, 5));

      //if both lhs and rhs are instances of numbers, and lhs is assignable to rhs, should be no errors
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), _etc.forNumericAssignmentExpressionOnly(nae, SymbolData.INT_TYPE.getInstanceData(), SymbolData.CHAR_TYPE.getInstanceData()));
      assertEquals("Should return double instance", SymbolData.DOUBLE_TYPE.getInstanceData(), _etc.forNumericAssignmentExpressionOnly(nae, SymbolData.DOUBLE_TYPE.getInstanceData(), SymbolData.INT_TYPE.getInstanceData()));
      assertEquals("Should be no errors", 0, errors.size());

      
      //if either input is null, return null
      assertEquals("Should return null", null, _etc.forNumericAssignmentExpressionOnly(nae, null, SymbolData.INT_TYPE));
      assertEquals("Should return null", null, _etc.forNumericAssignmentExpressionOnly(nae, SymbolData.INT_TYPE, null));
      assertEquals("Should be no errors", 0, errors.size());
      
      //if lhs is a PackageData, give error and return null
      PackageData pd = new PackageData("bad_reference");
      assertEquals("Should return null", null, _etc.forNumericAssignmentExpressionOnly(nae, pd, SymbolData.INT_TYPE));
      assertEquals("Should be 1 error", 1, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());
      
      
      //if rhs is a PackageData, give an error and return null
      assertEquals("Should return null", null, _etc.forNumericAssignmentExpressionOnly(nae, SymbolData.INT_TYPE, pd));
      assertEquals("Should be 2 errors", 2, errors.size());
      assertEquals("Error message should be correct", "Could not resolve symbol bad_reference", errors.getLast().getFirst());

      //if lhs not an instance data, give error
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), _etc.forNumericAssignmentExpressionOnly(nae, SymbolData.INT_TYPE, SymbolData.CHAR_TYPE.getInstanceData()));
      assertEquals("Should be 3 errors", 3, errors.size());
      assertEquals("Error message should be correct", "You cannot use a numeric assignment (-=, %=, *=, /=) on the type int.  Perhaps you meant to create a new instance of int", errors.getLast().getFirst());
      
      //if rhs not instance data, give error
      assertEquals("Should return int instance", SymbolData.INT_TYPE.getInstanceData(), _etc.forNumericAssignmentExpressionOnly(nae,