| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import java.util.ArrayList; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.impl.*; |
| import org.eclipse.jdt.internal.compiler.codegen.*; |
| import org.eclipse.jdt.internal.compiler.flow.*; |
| import org.eclipse.jdt.internal.compiler.lookup.*; |
| import org.eclipse.jdt.internal.compiler.problem.*; |
| import org.eclipse.jdt.internal.compiler.util.Util; |
| |
| public abstract class Expression extends Statement { |
| |
| 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) |
| 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_byte : |
| return constant.byteValue() == 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_byte : |
| return constant.byteValue() == 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_byte : |
| return constant.byteValue() == constant.doubleValue(); |
| case T_long : |
| return constant.longValue() == constant.doubleValue(); |
| default : |
| return false; //boolean |
| } |
| |
| case T_byte : |
| switch (constantTypeID) { |
| case T_char : |
| return constant.charValue() == constant.byteValue(); |
| case T_double : |
| return constant.doubleValue() == constant.byteValue(); |
| case T_float : |
| return constant.floatValue() == constant.byteValue(); |
| case T_int : |
| return constant.intValue() == constant.byteValue(); |
| case T_short : |
| return constant.shortValue() == constant.byteValue(); |
| case T_byte : |
| return true; |
| case T_long : |
| return constant.longValue() == constant.byteValue(); |
| 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_byte : |
| return constant.byteValue() == constant.shortValue(); |
| 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_byte : |
| return constant.byteValue() == 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_byte : |
| return constant.byteValue() == constant.longValue(); |
| case T_long : |
| return true; |
| default : |
| return false; //boolean |
| } |
| |
| default : |
| return false; //boolean |
| } |
| } |
| |
| public Constant constant; |
| |
| //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; |
| |
| public Expression() { |
| super(); |
| } |
| |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { |
| |
| return flowInfo; |
| } |
| |
| 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; |
| |
| // identity conversion cannot be performed upfront, due to side-effects |
| // like constant propagation |
| LookupEnvironment env = scope.environment(); |
| boolean use15specifics = env.options.sourceLevel >= 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) { |
| constant = expression.constant.castTo(expression.implicitConversion); |
| } |
| } |
| if (!necessary) tagAsUnnecessaryCast(scope, castType); |
| return true; |
| |
| } |
| } else if (use15specifics) { // unboxing - only exact match is allowed |
| if (env.computeBoxingType(expressionType) == castType) { |
| // TODO (philippe) could tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| } |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } else if (use15specifics && expressionType.isBaseType()) { // boxing - only exact match is allowed |
| if (env.computeBoxingType(castType) == expressionType) { |
| // TODO (philippe) could tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| } |
| |
| //-----------cast to something which is NOT a base type-------------------------- |
| if (expressionType == NullBinding) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; //null is compatible with every thing |
| } |
| if (expressionType.isBaseType()) { |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| |
| if (expressionType.isArrayType()) { |
| if (castType == expressionType) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; // identity conversion |
| } |
| |
| if (castType.isArrayType()) { |
| //------- (castType.isArray) expressionType.isArray ----------- |
| TypeBinding exprElementType = ((ArrayBinding) expressionType).elementsType(); |
| if (exprElementType.isBaseType()) { |
| // <---stop the recursion------- |
| if (((ArrayBinding) castType).elementsType() == exprElementType) { |
| tagAsNeedCheckCast(); |
| return true; |
| } else { |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| } |
| // recursively on the elements... |
| return checkCastTypesCompatibility( |
| scope, |
| ((ArrayBinding) castType).elementsType(), |
| exprElementType, |
| expression); |
| } else if (castType.isTypeVariable()) { |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| // recursively on the type variable upper bound |
| return checkCastTypesCompatibility( |
| scope, |
| castType.erasure(), |
| expressionType, |
| expression); |
| } else if (castType.isClass()) { |
| //------(castType.isClass) expressionType.isArray --------------- |
| if (castType.id == T_JavaLangObject) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| } else { //------- (castType.isInterface) expressionType.isArray ----------- |
| if (castType.id == T_JavaLangCloneable || castType.id == T_JavaIoSerializable) { |
| tagAsNeedCheckCast(); |
| return true; |
| } |
| } |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| if (expressionType.isTypeVariable() || expressionType.isWildcard()) { |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType); |
| if (match != null) { |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| // recursively on the type variable upper bound |
| return checkCastTypesCompatibility( |
| scope, |
| castType, |
| expressionType.erasure(), |
| expression); |
| } |
| |
| if (expressionType.isClass()) { |
| if (castType.isArrayType()) { |
| // ---- (castType.isArray) expressionType.isClass ------- |
| if (expressionType.id == T_JavaLangObject) { // potential runtime error |
| tagAsNeedCheckCast(); |
| return true; |
| } |
| } else if (castType.isTypeVariable()) { |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| // recursively on the type variable upper bound |
| return checkCastTypesCompatibility( |
| scope, |
| castType.erasure(), |
| expressionType, |
| expression); |
| } else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isClass ------ |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); |
| if (match != null) { |
| if (expression != null && castType.id == T_JavaLangString) this.constant = expression.constant; // (String) cst is still a constant |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); |
| if (match != null) { |
| tagAsNeedCheckCast(); |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| } else { // ----- (castType.isInterface) expressionType.isClass ------- |
| |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| // a subclass may implement the interface ==> no check at compile time |
| if (!((ReferenceBinding) expressionType).isFinal()) { |
| tagAsNeedCheckCast(); |
| match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| return true; |
| } |
| // no subclass for expressionType, thus compile-time check is valid |
| } |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| |
| // if (expressionType.isInterface()) { cannot be anything else |
| if (castType.isArrayType()) { |
| // ----- (castType.isArray) expressionType.isInterface ------ |
| if (expressionType.id == T_JavaLangCloneable |
| || expressionType.id == T_JavaIoSerializable) {// potential runtime error |
| tagAsNeedCheckCast(); |
| return true; |
| } else { |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| } else if (castType.isTypeVariable()) { |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType); |
| if (match == null) { |
| checkUnsafeCast(scope, castType, expressionType, match, true); |
| } |
| // recursively on the type variable upper bound |
| return checkCastTypesCompatibility( |
| scope, |
| castType.erasure(), |
| expressionType, |
| expression); |
| } else if (castType.isClass()) { // ----- (castType.isClass) expressionType.isInterface -------- |
| |
| if (castType.id == T_JavaLangObject) { // no runtime error |
| tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| if (((ReferenceBinding) castType).isFinal()) { |
| // no subclass for castType, thus compile-time check is valid |
| TypeBinding match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); |
| if (match == null) { |
| // potential runtime error |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| } |
| } else { // ----- (castType.isInterface) expressionType.isInterface ------- |
| |
| TypeBinding match = ((ReferenceBinding)expressionType).findSuperTypeErasingTo((ReferenceBinding)castType.erasure()); |
| if (match != null) { |
| return checkUnsafeCast(scope, castType, expressionType, match, false); |
| } |
| |
| match = ((ReferenceBinding)castType).findSuperTypeErasingTo((ReferenceBinding)expressionType.erasure()); |
| if (match != null) { |
| tagAsNeedCheckCast(); |
| return checkUnsafeCast(scope, castType, expressionType, match, true); |
| } else { |
| MethodBinding[] castTypeMethods = getAllInheritedMethods((ReferenceBinding) castType); |
| MethodBinding[] expressionTypeMethods = |
| getAllInheritedMethods((ReferenceBinding) expressionType); |
| int exprMethodsLength = expressionTypeMethods.length; |
| for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++) { |
| for (int j = 0; j < exprMethodsLength; j++) { |
| if ((castTypeMethods[i].returnType != expressionTypeMethods[j].returnType) |
| && (CharOperation.equals(castTypeMethods[i].selector, expressionTypeMethods[j].selector)) |
| && castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) { |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| |
| } |
| } |
| } |
| } |
| } |
| tagAsNeedCheckCast(); |
| return true; |
| } |
| |
| public FlowInfo checkNullStatus(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int nullStatus) { |
| |
| LocalVariableBinding local = this.localVariableBinding(); |
| if (local != null) { |
| switch(nullStatus) { |
| case FlowInfo.NULL : |
| flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NULL, flowInfo); |
| flowInfo.markAsDefinitelyNull(local); // from thereon it is set |
| break; |
| case FlowInfo.NON_NULL : |
| flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NON_NULL, flowInfo); |
| flowInfo.markAsDefinitelyNonNull(local); // from thereon it is set |
| break; |
| case FlowInfo.UNKNOWN : |
| break; |
| } |
| } |
| return flowInfo; |
| } |
| |
| private MethodBinding[] getAllInheritedMethods(ReferenceBinding binding) { |
| ArrayList collector = new ArrayList(); |
| getAllInheritedMethods0(binding, collector); |
| return (MethodBinding[]) collector.toArray(new MethodBinding[collector.size()]); |
| } |
| |
| private void getAllInheritedMethods0(ReferenceBinding binding, ArrayList collector) { |
| if (!binding.isInterface()) return; |
| MethodBinding[] methodBindings = binding.methods(); |
| for (int i = 0, max = methodBindings.length; i < max; i++) { |
| collector.add(methodBindings[i]); |
| } |
| ReferenceBinding[] superInterfaces = binding.superInterfaces(); |
| for (int i = 0, max = superInterfaces.length; i < max; i++) { |
| getAllInheritedMethods0(superInterfaces[i], collector); |
| } |
| } |
| public void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { |
| // do nothing by default - see EqualExpression |
| } |
| |
| public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) { |
| if (match == castType) { |
| if (!isNarrowing) tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| if (castType.isBoundParameterizedType() || castType.isGenericType()) { |
| if (match.isProvablyDistinctFrom(isNarrowing ? expressionType : castType, 0)) { |
| reportIllegalCast(scope, castType, expressionType); |
| return false; |
| } |
| } |
| if (!isNarrowing) tagAsUnnecessaryCast(scope, castType); |
| return true; |
| } |
| |
| /** |
| * Base types need that the widening is explicitly done by the compiler using some bytecode like i2f. |
| * Also check unsafe type operations. |
| */ |
| public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) { |
| |
| if (runtimeTimeType == null || compileTimeType == null) |
| return; |
| if (this.implicitConversion != 0) return; // already set independantly |
| |
| // it is possible for a Byte to be unboxed to a byte & then converted to an int |
| // but it is not possible for a byte to become Byte & then assigned to an Integer, |
| // or to become an int before boxed into an Integer |
| if (runtimeTimeType != NullBinding && runtimeTimeType.isBaseType()) { |
| if (!compileTimeType.isBaseType()) { |
| TypeBinding unboxedType = scope.environment().computeBoxingType(compileTimeType); |
| this.implicitConversion = UNBOXING; |
| scope.problemReporter().autoboxing(this, compileTimeType, runtimeTimeType); |
| compileTimeType = unboxedType; |
| } |
| } else { |
| if (compileTimeType != NullBinding && compileTimeType.isBaseType()) { |
| TypeBinding boxedType = scope.environment().computeBoxingType(runtimeTimeType); |
| if (boxedType == runtimeTimeType) // Object o = 12; |
| boxedType = compileTimeType; |
| this.implicitConversion = BOXING | (boxedType.id << 4) + compileTimeType.id; |
| scope.problemReporter().autoboxing(this, compileTimeType, runtimeTimeType); |
| return; |
| } |
| } |
| |
| switch (runtimeTimeType.id) { |
| case T_byte : |
| case T_short : |
| case T_char : |
| this.implicitConversion |= (T_int << 4) + compileTimeType.id; |
| break; |
| case T_JavaLangString : |
| case T_float : |
| case T_boolean : |
| case T_double : |
| case T_int : //implicitConversion may result in i2i which will result in NO code gen |
| case T_long : |
| this.implicitConversion |= (runtimeTimeType.id << 4) + compileTimeType.id; |
| break; |
| default : // regular object ref |
| // if (compileTimeType.isRawType() && runtimeTimeType.isBoundParameterizedType()) { |
| // scope.problemReporter().unsafeRawExpression(this, compileTimeType, runtimeTimeType); |
| // } |
| } |
| } |
| /** |
| * Expression statements are plain expressions, however they generate like |
| * normal expressions with no value required. |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| */ |
| public void generateCode(BlockScope currentScope, CodeStream codeStream) { |
| |
| if ((bits & IsReachableMASK) == 0) { |
| return; |
| } |
| generateCode(currentScope, codeStream, false); |
| } |
| |
| /** |
| * Every expression is responsible for generating its implicit conversion when necessary. |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| * @param valueRequired boolean |
| */ |
| public void generateCode( |
| BlockScope currentScope, |
| CodeStream codeStream, |
| boolean valueRequired) { |
| |
| if (constant != NotAConstant) { |
| // generate a constant expression |
| int pc = codeStream.position; |
| codeStream.generateConstant(constant, implicitConversion); |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } else { |
| // actual non-constant code generation |
| throw new ShouldNotImplement(Util.bind("ast.missingCode")); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Default generation of a boolean value |
| * @param currentScope |
| * @param codeStream |
| * @param trueLabel |
| * @param falseLabel |
| * @param valueRequired |
| */ |
| public void generateOptimizedBoolean( |
| BlockScope currentScope, |
| CodeStream codeStream, |
| Label trueLabel, |
| Label falseLabel, |
| boolean valueRequired) { |
| |
| // a label valued to nil means: by default we fall through the case... |
| // both nil means we leave the value on the stack |
| |
| if ((constant != Constant.NotAConstant) && (constant.typeID() == T_boolean)) { |
| int pc = codeStream.position; |
| if (constant.booleanValue() == true) { |
| // constant == true |
| if (valueRequired) { |
| if (falseLabel == null) { |
| // implicit falling through the FALSE case |
| if (trueLabel != null) { |
| codeStream.goto_(trueLabel); |
| } |
| } |
| } |
| } else { |
| if (valueRequired) { |
| if (falseLabel != null) { |
| // implicit falling through the TRUE case |
| if (trueLabel == null) { |
| codeStream.goto_(falseLabel); |
| } |
| } |
| } |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| return; |
| } |
| generateCode(currentScope, codeStream, valueRequired); |
| // branching |
| int position = codeStream.position; |
| if (valueRequired) { |
| if (falseLabel == null) { |
| if (trueLabel != null) { |
| // Implicit falling through the FALSE case |
| codeStream.ifne(trueLabel); |
| } |
| } else { |
| if (trueLabel == null) { |
| // Implicit falling through the TRUE case |
| codeStream.ifeq(falseLabel); |
| } else { |
| // No implicit fall through TRUE/FALSE --> should never occur |
| } |
| } |
| } |
| // reposition the endPC |
| codeStream.updateLastRecordedEndPC(position); |
| } |
| |
| /* Optimized (java) code generation for string concatenations that involve StringBuffer |
| * creation: going through this path means that there is no need for a new StringBuffer |
| * creation, further operands should rather be only appended to the current one. |
| * By default: no optimization. |
| */ |
| public void generateOptimizedStringConcatenation( |
| BlockScope blockScope, |
| CodeStream codeStream, |
| int typeID) { |
| |
| if (typeID == T_JavaLangString && this.constant != NotAConstant && this.constant.stringValue().length() == 0) { |
| return; // optimize str + "" |
| } |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringConcatenationAppendForType(typeID); |
| } |
| |
| /* Optimized (java) code generation for string concatenations that involve StringBuffer |
| * creation: going through this path means that there is no need for a new StringBuffer |
| * creation, further operands should rather be only appended to the current one. |
| */ |
| public void generateOptimizedStringConcatenationCreation( |
| BlockScope blockScope, |
| CodeStream codeStream, |
| int typeID) { |
| |
| codeStream.newStringContatenation(); |
| codeStream.dup(); |
| switch (typeID) { |
| case T_JavaLangObject : |
| case T_undefined : |
| // in the case the runtime value of valueOf(Object) returns null, we have to use append(Object) instead of directly valueOf(Object) |
| // append(Object) returns append(valueOf(Object)), which means that the null case is handled by the next case. |
| codeStream.invokeStringConcatenationDefaultConstructor(); |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringConcatenationAppendForType(T_JavaLangObject); |
| return; |
| case T_JavaLangString : |
| case T_null : |
| if (constant != NotAConstant) { |
| String stringValue = constant.stringValue(); |
| if (stringValue.length() == 0) { // optimize ""+<str> |
| codeStream.invokeStringConcatenationDefaultConstructor(); |
| return; |
| } |
| codeStream.ldc(stringValue); |
| } else { |
| // null case is not a constant |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringValueOf(T_JavaLangObject); |
| } |
| break; |
| default : |
| generateCode(blockScope, codeStream, true); |
| codeStream.invokeStringValueOf(typeID); |
| } |
| codeStream.invokeStringConcatenationStringConstructor(); |
| } |
| |
| 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 (constant == Constant.NotAConstant) |
| return false; |
| if (constantType == targetType) |
| return true; |
| if (constantType.isBaseType() && targetType.isBaseType()) { |
| //No free assignment conversion from anything but to integral ones. |
| if ((constantType == IntBinding |
| || BaseTypeBinding.isWidening(T_int, constantType.id)) |
| && (BaseTypeBinding.isNarrowing(targetType.id, T_int))) { |
| //use current explicit conversion in order to get some new value to compare with current one |
| return isConstantValueRepresentable(constant, constantType.id, targetType.id); |
| } |
| } |
| return false; |
| } |
| |
| public boolean isTypeReference() { |
| return false; |
| } |
| |
| public int nullStatus(FlowInfo flowInfo) { |
| |
| if (this.constant != null && this.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() { |
| return this.constant; |
| } |
| |
| 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 reportIllegalCast(Scope scope, TypeBinding castType, TypeBinding expressionType) { |
| // do nothing by default |
| } |
| |
| public void resolve(BlockScope scope) { |
| // drops the returning expression's type whatever the type is. |
| |
| this.resolveType(scope); |
| return; |
| } |
| |
| public TypeBinding resolveType(BlockScope scope) { |
| // by default... subclasses should implement a better TC if required. |
| |
| return null; |
| } |
| |
| public TypeBinding resolveType(ClassScope classScope) { |
| // by default... subclasses should implement a better TB if required. |
| 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)) { |
| this.computeConversion(scope, expectedType, expressionType); |
| } else { |
| scope.problemReporter().typeMismatchError(expressionType, expectedType, this); |
| return null; |
| } |
| } |
| return expressionType; |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setExpectedType(TypeBinding expectedType) { |
| // do nothing by default |
| } |
| |
| public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) { |
| // do nothing by default |
| } |
| |
| public void tagAsNeedCheckCast() { |
| // 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; |
| } |
| |
| public void traverse(ASTVisitor visitor, BlockScope scope) { |
| // do nothing by default |
| } |
| public void traverse(ASTVisitor visitor, ClassScope scope) { |
| // do nothing by default |
| } |
| public void traverse(ASTVisitor visitor, CompilationUnitScope scope) { |
| // do nothing by default |
| } |
| /** |
| * 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; |
| } |
| } |