blob: 75ea4b291ddc69502b39a808f9d2e3fd9d54c854 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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
* Genady Beriozkin - added support for reporting assignment with no effect
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
public class Assignment extends Expression {
public Expression lhs;
public Expression expression;
public Assignment(Expression lhs, Expression expression, int sourceEnd) {
//lhs is always a reference by construction ,
//but is build as an expression ==> the checkcast cannot fail
this.lhs = lhs;
lhs.bits |= IsStrictlyAssignedMASK; // tag lhs as assigned
this.expression = expression;
this.sourceStart = lhs.sourceStart;
this.sourceEnd = sourceEnd;
}
public FlowInfo analyseCode(
BlockScope currentScope,
FlowContext flowContext,
FlowInfo flowInfo) {
// record setting a variable: various scenarii are possible, setting an array reference,
// a field reference, a blank final field reference, a field of an enclosing instance or
// just a local variable.
LocalVariableBinding local = this.lhs.localVariableBinding();
int nullStatus = this.expression.nullStatus(flowInfo);
if (local != null && nullStatus == FlowInfo.NULL) {
flowContext.recordUsingNullReference(currentScope, local, this.lhs, FlowInfo.NON_NULL, flowInfo);
}
flowInfo = ((Reference) lhs)
.analyseAssignment(currentScope, flowContext, flowInfo, this, false)
.unconditionalInits();
if (local != null) {
switch(nullStatus) {
case FlowInfo.NULL :
flowInfo.markAsDefinitelyNull(local);
break;
case FlowInfo.NON_NULL :
flowInfo.markAsDefinitelyNonNull(local);
break;
}
}
return flowInfo;
}
void checkAssignmentEffect(BlockScope scope) {
Binding left = getDirectBinding(this.lhs);
if (left != null && left == getDirectBinding(this.expression)) {
scope.problemReporter().assignmentHasNoEffect(this, left.shortReadableName());
this.bits |= IsAssignmentWithNoEffectMASK; // record assignment has no effect
}
}
void checkAssignment(BlockScope scope, TypeBinding lhsType, TypeBinding rhsType) {
FieldBinding leftField = getLastField(this.lhs);
if (leftField != null && rhsType != NullBinding && lhsType.isWildcard() && ((WildcardBinding)lhsType).boundKind != Wildcard.SUPER) {
scope.problemReporter().wildcardAssignment(lhsType, rhsType, this.expression);
} else if (leftField != null && leftField.declaringClass != null /*length pseudo field*/&& leftField.declaringClass.isRawType()) {
scope.problemReporter().unsafeRawFieldAssignment(leftField, rhsType, this.lhs);
} else if (rhsType.needsUncheckedConversion(lhsType)) {
scope.problemReporter().unsafeTypeConversion(this.expression, rhsType, lhsType);
}
}
public void generateCode(
BlockScope currentScope,
CodeStream codeStream,
boolean valueRequired) {
// various scenarii are possible, setting an array reference,
// a field reference, a blank final field reference, a field of an enclosing instance or
// just a local variable.
int pc = codeStream.position;
if ((this.bits & IsAssignmentWithNoEffectMASK) != 0) {
if (valueRequired) {
this.expression.generateCode(currentScope, codeStream, true);
}
} else {
((Reference) lhs).generateAssignment(currentScope, codeStream, this, valueRequired);
// variable may have been optimized out
// the lhs is responsible to perform the implicitConversion generation for the assignment since optimized for unused local assignment.
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
Binding getDirectBinding(Expression someExpression) {
if (someExpression instanceof SingleNameReference) {
return ((SingleNameReference)someExpression).binding;
} else if (someExpression instanceof FieldReference) {
FieldReference fieldRef = (FieldReference)someExpression;
if (fieldRef.receiver.isThis() && !(fieldRef.receiver instanceof QualifiedThisReference)) {
return fieldRef.binding;
}
} else if (someExpression instanceof PostfixExpression) { // recurse for postfix: i++ --> i
return getDirectBinding(((PostfixExpression) someExpression).lhs);
}
return null;
}
FieldBinding getLastField(Expression someExpression) {
if (someExpression instanceof SingleNameReference) {
if ((someExpression.bits & RestrictiveFlagMASK) == Binding.FIELD) {
return (FieldBinding) ((SingleNameReference)someExpression).binding;
}
} else if (someExpression instanceof FieldReference) {
return ((FieldReference)someExpression).binding;
} else if (someExpression instanceof QualifiedNameReference) {
QualifiedNameReference qName = (QualifiedNameReference) someExpression;
if (qName.otherBindings == null && ((someExpression.bits & RestrictiveFlagMASK) == Binding.FIELD)) {
return (FieldBinding)qName.binding;
} else {
return qName.otherBindings[qName.otherBindings.length - 1];
}
}
return null;
}
public int nullStatus(FlowInfo flowInfo) {
return this.expression.nullStatus(flowInfo);
}
public StringBuffer print(int indent, StringBuffer output) {
//no () when used as a statement
printIndent(indent, output);
return printExpressionNoParenthesis(indent, output);
}
public StringBuffer printExpression(int indent, StringBuffer output) {
//subclass redefine printExpressionNoParenthesis()
output.append('(');
return printExpressionNoParenthesis(0, output).append(')');
}
public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) {
lhs.printExpression(indent, output).append(" = "); //$NON-NLS-1$
return expression.printExpression(0, output);
}
public StringBuffer printStatement(int indent, StringBuffer output) {
//no () when used as a statement
return print(indent, output).append(';');
}
public TypeBinding resolveType(BlockScope scope) {
// due to syntax lhs may be only a NameReference, a FieldReference or an ArrayReference
constant = NotAConstant;
if (!(this.lhs instanceof Reference) || this.lhs.isThis()) {
scope.problemReporter().expressionShouldBeAVariable(this.lhs);
return null;
}
TypeBinding lhsType = lhs.resolveType(scope);
expression.setExpectedType(lhsType); // needed in case of generic method invocation
if (lhsType != null)
this.resolvedType = lhsType.capture();
TypeBinding rhsType = expression.resolveType(scope);
if (lhsType == null || rhsType == null) {
return null;
}
checkAssignmentEffect(scope);
// Compile-time conversion of base-types : implicit narrowing integer into byte/short/character
// may require to widen the rhs expression at runtime
if (lhsType != rhsType) // must call before computeConversion() and typeMismatchError()
scope.compilationUnitScope().recordTypeConversion(lhsType, rhsType);
if ((expression.isConstantValueOfTypeAssignableToType(rhsType, lhsType)
|| (lhsType.isBaseType() && BaseTypeBinding.isWidening(lhsType.id, rhsType.id)))
|| rhsType.isCompatibleWith(lhsType)) {
expression.computeConversion(scope, lhsType, rhsType);
checkAssignment(scope, lhsType, rhsType);
return this.resolvedType;
} else if (scope.isBoxingCompatibleWith(rhsType, lhsType)) {
expression.computeConversion(scope, lhsType, rhsType);
return this.resolvedType;
}
scope.problemReporter().typeMismatchError(rhsType, lhsType, expression);
return lhsType;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.compiler.ast.Expression#resolveTypeExpecting(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding)
*/
public TypeBinding resolveTypeExpecting(
BlockScope scope,
TypeBinding expectedType) {
TypeBinding type = super.resolveTypeExpecting(scope, expectedType);
if (type == null) return null;
TypeBinding lhsType = this.resolvedType;
TypeBinding rhsType = this.expression.resolvedType;
// signal possible accidental boolean assignment (instead of using '==' operator)
if (expectedType == BooleanBinding
&& lhsType == BooleanBinding
&& (this.lhs.bits & IsStrictlyAssignedMASK) != 0) {
scope.problemReporter().possibleAccidentalBooleanAssignment(this);
}
checkAssignment(scope, lhsType, rhsType);
return type;
}
public void traverse(ASTVisitor visitor, BlockScope scope) {
if (visitor.visit(this, scope)) {
lhs.traverse(visitor, scope);
expression.traverse(visitor, scope);
}
visitor.endVisit(this, scope);
}
}