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,