/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.compiler.ast;
import org.eclipse.wst.jsdt.core.ast.IASTNode;
import org.eclipse.wst.jsdt.core.ast.IExpression;
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowContext;
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowInfo;
import org.eclipse.wst.jsdt.internal.compiler.impl.Constant;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.Binding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ClassScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.Scope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeIds;
public abstract class Expression extends Statement implements IExpression {
public Constant constant;
public int statementEnd = -1;
//Some expression may not be used - from a java semantic point
//of view only - as statements. Other may. In order to avoid the creation
//of wrappers around expression in order to tune them as expression
//Expression is a subclass of Statement. See the message isValidJavaStatement()
public int implicitConversion;
public TypeBinding resolvedType = TypeBinding.UNKNOWN;
public static final boolean isConstantValueRepresentable(Constant constant, int constantTypeID, int targetTypeID) {
//true if there is no loss of precision while casting.
// constantTypeID == constant.typeID
if (targetTypeID == constantTypeID || constantTypeID==T_any)
return true;
switch (targetTypeID) {
case T_char :
switch (constantTypeID) {
case T_char :
return true;
case T_double :
return constant.doubleValue() == constant.charValue();
case T_float :
return constant.floatValue() == constant.charValue();
case T_int :
return constant.intValue() == constant.charValue();
case T_short :
return constant.shortValue() == constant.charValue();
case T_long :
return constant.longValue() == constant.charValue();
default :
return false;//boolean
}
case T_float :
switch (constantTypeID) {
case T_char :
return constant.charValue() == constant.floatValue();
case T_double :
return constant.doubleValue() == constant.floatValue();
case T_float :
return true;
case T_int :
return constant.intValue() == constant.floatValue();
case T_short :
return constant.shortValue() == constant.floatValue();
case T_long :
return constant.longValue() == constant.floatValue();
default :
return false;//boolean
}
case T_double :
switch (constantTypeID) {
case T_char :
return constant.charValue() == constant.doubleValue();
case T_double :
return true;
case T_float :
return constant.floatValue() == constant.doubleValue();
case T_int :
return constant.intValue() == constant.doubleValue();
case T_short :
return constant.shortValue() == constant.doubleValue();
case T_long :
return constant.longValue() == constant.doubleValue();
default :
return false; //boolean
}
case T_short :
switch (constantTypeID) {
case T_char :
return constant.charValue() == constant.shortValue();
case T_double :
return constant.doubleValue() == constant.shortValue();
case T_float :
return constant.floatValue() == constant.shortValue();
case T_int :
return constant.intValue() == constant.shortValue();
case T_short :
return true;
case T_long :
return constant.longValue() == constant.shortValue();
default :
return false; //boolean
}
case T_int :
switch (constantTypeID) {
case T_char :
return constant.charValue() == constant.intValue();
case T_double :
return constant.doubleValue() == constant.intValue();
case T_float :
return constant.floatValue() == constant.intValue();
case T_int :
return true;
case T_short :
return constant.shortValue() == constant.intValue();
case T_long :
return constant.longValue() == constant.intValue();
default :
return false; //boolean
}
case T_long :
switch (constantTypeID) {
case T_char :
return constant.charValue() == constant.longValue();
case T_double :
return constant.doubleValue() == constant.longValue();
case T_float :
return constant.floatValue() == constant.longValue();
case T_int :
return constant.intValue() == constant.longValue();
case T_short :
return constant.shortValue() == constant.longValue();
case T_long :
return true;
default :
return false; //boolean
}
default :
return false; //boolean
}
}
public Expression() {
super();
}
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
return flowInfo;
}
/**
* More sophisticated for of the flow analysis used for analyzing expressions, and be able to optimize out
* portions of expressions where no actual value is required.
*
* @param currentScope
* @param flowContext
* @param flowInfo
* @param valueRequired
* @return The state of initialization after the analysis of the current expression
*/
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) {
return analyseCode(currentScope, flowContext, flowInfo);
}
/**
* Returns false if cast is not legal.
*/
public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castType, TypeBinding expressionType, Expression expression) {
// see specifications 5.5
// handle errors and process constant when needed
// if either one of the type is null ==>
// some error has been already reported some where ==>
// we then do not report an obvious-cascade-error.
if (castType == null || expressionType == null) return true;
if (castType==expressionType || castType.id==expressionType.id)
return true;
// identity conversion cannot be performed upfront, due to side-effects
// like constant propagation
boolean use15specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
if (castType.isBaseType()) {
if (expressionType.isBaseType()) {
if (expressionType == castType) {
if (expression != null) {
this.constant = expression.constant; //use the same constant
}
tagAsUnnecessaryCast(scope, castType);
return true;
}
boolean necessary = false;
if (expressionType.isCompatibleWith(castType)
|| (necessary = BaseTypeBinding.isNarrowing(castType.id, expressionType.id))) {
if (expression != null) {
expression.implicitConversion = (castType.id << 4) + expressionType.id;
if (expression.constant != Constant.NotAConstant) {
this.constant = expression.constant.castTo(expression.implicitConversion);
}
}
if (!necessary) tagAsUnnecessaryCast(scope, castType);
return true;
}
} else if (use15specifics
&& scope.environment().computeBoxingType(expressionType).isCompatibleWith(castType)) { // unboxing - only widening match is allowed
tagAsUnnecessaryCast(scope, castType);
return true;
}
return false;
} else if (use15specifics
&& expressionType.isBaseType()
&& scope.environment().computeBoxingType(expressionType).isCompatibleWith(castType)) { // boxing - only widening match is allowed
tagAsUnnecessaryCast(scope, castType);
return true;
}
switch(expressionType.kind()) {
case Binding.BASE_TYPE :
//-----------cast to something which is NOT a base type--------------------------
if (expressionType == TypeBinding.NULL) {
tagAsUnnecessaryCast(scope, castType);
return true; //null is compatible with every thing
}
return false;
case Binding.ARRAY_TYPE :
if (castType == expressionType) {
tagAsUnnecessaryCast(scope, castType);
return true; // identity conversion
}
switch (castType.kind()) {
case Binding.ARRAY_TYPE :
// ( ARRAY ) ARRAY
TypeBinding castElementType = ((ArrayBinding) castType).elementsType();
TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType();
if (exprElementType.isBaseType() || castElementType.isBaseType()) {
if (castElementType == exprElementType) {
tagAsNeedCheckCast();
return true;
}
return false;
}
// recurse on array type elements
return checkCastTypesCompatibility(scope, castElementType, exprElementType, expression);
default:
// ( CLASS/INTERFACE ) ARRAY
switch (castType.id) {
case T_JavaLangObject :
tagAsUnnecessaryCast(scope, castType);
return true;
default :
return false;
}
}
default:
switch (castType.kind()) {
case Binding.ARRAY_TYPE :
// ( ARRAY ) CLASS
if (expressionType.id == TypeIds.T_JavaLangObject) { // potential runtime error
if (use15specifics) checkUnsafeCast(scope, castType, expressionType, expressionType, true);
tagAsNeedCheckCast();
return true;
}
return false;
default :
// ( CLASS ) CLASS
TypeBinding match = expressionType.findSuperTypeWithSameErasure(castType);
if (match != null) {
if (expression != null && castType.id == TypeIds.T_JavaLangString) this.constant = expression.constant; // (String) cst is still a constant
return checkUnsafeCast(scope, castType, expressionType, match, false);
}
match = castType.findSuperTypeWithSameErasure(expressionType);
if (match != null) {
tagAsNeedCheckCast();
return checkUnsafeCast(scope, castType, expressionType, match, true);
}
return false;
}
}
}
/**
* Check the local variable of this expression, if any, against potential NPEs
* given a flow context and an upstream flow info. If so, report the risk to
* the context. Marks the local as checked, which affects the flow info.
* @param scope the scope of the analysis
* @param flowContext the current flow context
* @param flowInfo the upstream flow info; caveat: may get modified
*/
public void checkNPE(BlockScope scope, FlowContext flowContext,
FlowInfo flowInfo) {
LocalVariableBinding local = this.localVariableBinding();
if (local != null /*&&
(local.type.tagBits & TagBits.IsBaseType) == 0*/) {
if ((this.bits & ASTNode.IsNonNull) == 0) {
flowContext.recordUsingNullReference(scope, local, this,
FlowContext.MAY_NULL, flowInfo);
}
flowInfo.markAsComparedEqualToNonNull(local);
// from thereon it is set
if (flowContext.initsOnFinally != null) {
flowContext.initsOnFinally.markAsComparedEqualToNonNull(local);
}
}
}
public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) {
if (match == castType) {
if (!isNarrowing) tagAsUnnecessaryCast(scope, castType);
return true;
}
if (!isNarrowing) tagAsUnnecessaryCast(scope, castType);
return true;
}
public boolean isCompactableOperation() {
return false;
}
//Return true if the conversion is done AUTOMATICALLY by the vm
//while the javaVM is an int based-machine, thus for example pushing
//a byte onto the stack , will automatically create an int on the stack
//(this request some work d be done by the VM on signed numbers)
public boolean isConstantValueOfTypeAssignableToType(TypeBinding constantType, TypeBinding targetType) {
if (this.constant == Constant.NotAConstant)
return false;
if (constantType == targetType)
return true;
if (constantType.id==targetType.id)
return true;
if (constantType.isBaseType() && targetType.isBaseType()) {
//No free assignment conversion from anything but to integral ones.
if ((constantType == TypeBinding.INT
|| BaseTypeBinding.isWidening(TypeIds.T_int, constantType.id))
&& (BaseTypeBinding.isNarrowing(targetType.id, TypeIds.T_int))) {
//use current explicit conversion in order to get some new value to compare with current one
return isConstantValueRepresentable(this.constant, constantType.id, targetType.id);
}
}
return false;
}
public boolean isTypeReference() {
return false;
}
/**
* Returns the local variable referenced by this node. Can be a direct reference (SingleNameReference)
* or thru a cast expression etc...
*/
public LocalVariableBinding localVariableBinding() {
return null;
}
/**
* Mark this expression as being non null, per a specific tag in the
* source code.
*/
// this is no more called for now, waiting for inter procedural null reference analysis
public void markAsNonNull() {
this.bits |= ASTNode.IsNonNull;
}
public int nullStatus(FlowInfo flowInfo) {
if (/* (this.bits & IsNonNull) != 0 || */
this.constant != null && this.constant != Constant.NotAConstant)
return FlowInfo.NON_NULL; // constant expression cannot be null
LocalVariableBinding local = localVariableBinding();
if (local != null) {
if (flowInfo.isDefinitelyNull(local))
return FlowInfo.NULL;
if (flowInfo.isDefinitelyNonNull(local))
return FlowInfo.NON_NULL;
return FlowInfo.UNKNOWN;
}
return FlowInfo.NON_NULL;
}
/**
* Constant usable for bytecode pattern optimizations, but cannot be inlined
* since it is not strictly equivalent to the definition of constant expressions.
* In particular, some side-effects may be required to occur (only the end value
* is known).
* @return Constant known to be of boolean type
*/
public Constant optimizedBooleanConstant() {
if(this.constant != null)
return this.constant;
return Constant.NotAConstant;
}
/**
* Returns the type of the expression after required implicit conversions. When expression type gets promoted
* or inserted a generic cast, the converted type will differ from the resolved type (surface side-effects from
* #computeConversion(...)).
* @return the type after implicit conversion
*/
public TypeBinding postConversionType(Scope scope) {
TypeBinding convertedType = this.resolvedType;
int runtimeType = (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
switch (runtimeType) {
case T_boolean :
convertedType = TypeBinding.BOOLEAN;
break;
case T_short :
convertedType = TypeBinding.SHORT;
break;
case T_char :
convertedType = TypeBinding.CHAR;
break;
case T_int :
convertedType = TypeBinding.INT;
break;
case T_float :
convertedType = TypeBinding.FLOAT;
break;
case T_long :
convertedType = TypeBinding.LONG;
break;
case T_double :
convertedType = TypeBinding.DOUBLE;
break;
default :
}
if ((this.implicitConversion & TypeIds.BOXING) != 0) {
convertedType = scope.environment().computeBoxingType(convertedType);
}
return convertedType;
}
public StringBuffer print(int indent, StringBuffer output) {
printIndent(indent, output);
return printExpression(indent, output);
}
public abstract StringBuffer printExpression(int indent, StringBuffer output);
public StringBuffer printStatement(int indent, StringBuffer output) {
return print(indent, output).append(";"); //$NON-NLS-1$
}
public void resolve(BlockScope scope) {
// drops the returning expression's type whatever the type is.
this.resolveType(scope);
return;
}
/**
* Resolve the type of this expression in the context of a blockScope
*
* @param scope
* @return
* Return the actual type of this expression after resolution
*/
public TypeBinding resolveType(BlockScope scope) {
// by default... subclasses should implement a better TB if required.
return null;
}
public TypeBinding resolveType(BlockScope scope, boolean define, TypeBinding useType) {
return resolveType(scope);
}
/**
* Resolve the type of this expression in the context of a classScope
*
* @param scope
* @return
* Return the actual type of this expression after resolution
*/
public TypeBinding resolveType(ClassScope scope) {
// by default... subclasses should implement a better TB if required.
return null;
}
public TypeBinding resolveTypeExpecting(
BlockScope scope,
TypeBinding [] expectedTypes) {
this.setExpectedType(expectedTypes[0]); // needed in case of generic method invocation
TypeBinding expressionType = this.resolveType(scope);
if (expressionType == null) return null;
for (int i = 0; i < expectedTypes.length; i++) {
if (expressionType == expectedTypes[i]) return expressionType;
if (expressionType.isCompatibleWith(expectedTypes[i])) {
// if (scope.isBoxingCompatibleWith(expressionType, expectedType)) {
// this.computeConversion(scope, expectedType, expressionType);
// } else {
// }
return expressionType;
}
}
scope.problemReporter().typeMismatchError(expressionType, expectedTypes[0], this);
return null;
}
public TypeBinding resolveTypeExpecting(
BlockScope scope,
TypeBinding expectedType) {
this.setExpectedType(expectedType); // needed in case of generic method invocation
TypeBinding expressionType = this.resolveType(scope);
if (expressionType == null) return null;
if (expressionType == expectedType) return expressionType;
if (!expressionType.isCompatibleWith(expectedType)) {
if (scope.isBoxingCompatibleWith(expressionType, expectedType)) {
} else {
if (expectedType!=TypeBinding.BOOLEAN || expressionType==TypeBinding.VOID)
{
scope.problemReporter().typeMismatchError(expressionType, expectedType, this);
return null;
}
}
}
return expressionType;
}
/**
* Returns an object which can be used to identify identical JSR sequence targets
* (see TryStatement subroutine codegen)
* or <code>null</null> if not reusable
*/
public Object reusableJSRTarget() {
if (this.constant != Constant.NotAConstant)
return this.constant;
return null;
}
/**
* Record the type expectation before this expression is typechecked.
* e.g. String s = foo();, foo() will be tagged as being expected of type String
* Used to trigger proper inference of generic method invocations.
*
* @param expectedType
* The type denoting an expectation in the context of an assignment conversion
*/
public void setExpectedType(TypeBinding expectedType) {
// do nothing by default
}
public void tagAsNeedCheckCast() {
// do nothing by default
}
/**
* Record the fact a cast expression got detected as being unnecessary.
*
* @param scope
* @param castType
*/
public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) {
// do nothing by default
}
public Expression toTypeReference() {
//by default undefined
//this method is meanly used by the parser in order to transform
//an expression that is used as a type reference in a cast ....
//--appreciate the fact that castExpression and ExpressionWithParenthesis
//--starts with the same pattern.....
return this;
}
/**
* Traverse an expression in the context of a blockScope
* @param visitor
* @param scope
*/
public void traverse(ASTVisitor visitor, BlockScope scope) {
// nothing to do
}
/**
* Traverse an expression in the context of a classScope
* @param visitor
* @param scope
*/
public void traverse(ASTVisitor visitor, ClassScope scope) {
// nothing to do
}
public void traverse(ASTVisitor visitior, Scope scope)
{
if (scope instanceof BlockScope)
traverse(visitior,(BlockScope)scope);
else if (scope instanceof ClassScope)
traverse(visitior,(ClassScope)scope);
else if (scope instanceof CompilationUnitScope)
traverse(visitior,(CompilationUnitScope)scope);
}
public boolean isPrototype()
{
return false;
}
// is completion or selection node
public boolean isSpecialNode()
{
return false;
}
public Binding alternateBinding()
{ return null;}
public TypeBinding resolveForAllocation(BlockScope scope, ASTNode location)
{
switch (getASTType()) {
case IASTNode.STRING_LITERAL:
case IASTNode.CHAR_LITERAL:
case IASTNode.ARRAY_REFERENCE:
case IASTNode.FUNCTION_CALL:
break;
default:
System.out.println("IMPLEMENT resolveForAllocation for "+this.getClass());
break;
}
return this.resolveType(scope);
}
public int getASTType() {
return IASTNode.EXPRESSION;
}
}