blob: 06e0bb96f606e35671bbcb5fafe35425fea08106 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 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 java.util.ArrayList;
import org.eclipse.wst.jsdt.core.JavaScriptCore;
import org.eclipse.wst.jsdt.core.ast.IASTNode;
import org.eclipse.wst.jsdt.core.ast.IFieldReference;
import org.eclipse.wst.jsdt.core.compiler.CharOperation;
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.Binding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.InvocationSite;
import org.eclipse.wst.jsdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.Scope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TagBits;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeIds;
import org.eclipse.wst.jsdt.internal.compiler.util.Util;
public class FieldReference extends Reference implements InvocationSite, IFieldReference {
public static final int READ = 0;
public static final int WRITE = 1;
public Expression receiver;
public char[] token;
public FieldBinding binding; // exact binding resulting from lookup
public TypeBinding typeBinding; // exact binding resulting from lookup
// protected FieldBinding codegenBinding; // actual binding used for code generation (if no synthetic accessor)
// public FunctionBinding[] syntheticAccessors; // [0]=read accessor [1]=write accessor
public long nameSourcePosition; //(start<<32)+end
public TypeBinding receiverType;
// public TypeBinding genericCast;
public FieldReference(char[] source, long pos) {
token = source;
nameSourcePosition = pos;
//by default the position are the one of the field (not true for super access)
sourceStart = (int) (pos >>> 32);
sourceEnd = (int) (pos & 0x00000000FFFFFFFFL);
bits |= Binding.FIELD;
}
public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, Assignment assignment, boolean isCompound) {
// compound assignment extra work
// if (isCompound) { // check the variable part is initialized if blank final
// if (binding.isBlankFinal()
// && receiver.isThis()
// && currentScope.allowBlankFinalFieldAssignment(binding)
// && (!flowInfo.isDefinitelyAssigned(binding))) {
// currentScope.problemReporter().uninitializedBlankFinalField(binding, this);
// // we could improve error msg here telling "cannot use compound assignment on final blank field"
// }
// manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
// }
if (receiver instanceof SingleNameReference && ((SingleNameReference)receiver).binding instanceof LocalVariableBinding)
{
flowInfo.markAsDefinitelyNonNull((LocalVariableBinding)((SingleNameReference)receiver).binding);
flowInfo.markAsDefinitelyAssigned((LocalVariableBinding)((SingleNameReference)receiver).binding);
}
flowInfo =
receiver
.analyseCode(currentScope, flowContext, flowInfo, binding==null || !binding.isStatic())
.unconditionalInits();
if (assignment.expression != null) {
flowInfo =
assignment
.expression
.analyseCode(currentScope, flowContext, flowInfo)
.unconditionalInits();
}
manageSyntheticAccessIfNecessary(currentScope, flowInfo, false /*write-access*/);
// check if assigning a final field
if (binding!=null && binding.isFinal()) {
// in a context where it can be assigned?
if (binding.isBlankFinal()
&& !isCompound
&& receiver.isThis()
&& !(receiver instanceof QualifiedThisReference)
&& ((receiver.bits & ParenthesizedMASK) == 0) // (this).x is forbidden
&& currentScope.allowBlankFinalFieldAssignment(binding)) {
if (flowInfo.isPotentiallyAssigned(binding)) {
currentScope.problemReporter().duplicateInitializationOfBlankFinalField(
binding,
this);
} else {
flowContext.recordSettingFinal(binding, this, flowInfo);
}
flowInfo.markAsDefinitelyAssigned(binding);
} else {
// assigning a final field outside an initializer or constructor or wrong reference
currentScope.problemReporter().cannotAssignToFinalField(binding, this);
}
}
return flowInfo;
}
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
return analyseCode(currentScope, flowContext, flowInfo, true);
}
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) {
boolean nonStatic = binding==null || !binding.isStatic();
receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic);
if (nonStatic) {
receiver.checkNPE(currentScope, flowContext, flowInfo);
}
if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) {
manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
}
return flowInfo;
}
/**
* @see org.eclipse.wst.jsdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.wst.jsdt.internal.compiler.lookup.Scope, org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding, org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding)
*/
public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) {
if (runtimeTimeType == null || compileTimeType == null)
return;
// set the generic cast after the fact, once the type expectation is fully known (no need for strict cast)
if (this.binding != null && this.binding.isValidBinding()) {
FieldBinding originalBinding = this.binding.original();
TypeBinding originalType = originalBinding.type;
// extra cast needed if method return type is type variable
if (originalBinding != this.binding
&& originalType != this.binding.type
&& runtimeTimeType.id != T_JavaLangObject
&& (originalType.tagBits & TagBits.HasTypeVariable) != 0) {
// TypeBinding targetType = (!compileTimeType.isBaseType() && runtimeTimeType.isBaseType())
// ? compileTimeType // unboxing: checkcast before conversion
// : runtimeTimeType;
// this.genericCast = originalBinding.type.genericCast(targetType);
}
}
super.computeConversion(scope, runtimeTimeType, compileTimeType);
}
public FieldBinding fieldBinding() {
return binding;
}
/**
* @see org.eclipse.wst.jsdt.internal.compiler.lookup.InvocationSite#genericTypeArguments()
*/
public TypeBinding[] genericTypeArguments() {
return null;
}
public boolean isSuperAccess() {
return receiver.isSuper();
}
public boolean isTypeAccess() {
return receiver != null && receiver.isTypeReference();
}
/*
* No need to emulate access to protected fields since not implicitly accessed
*/
public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo, boolean isReadAccess) {
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) != 0) return;
// if field from parameterized type got found, use the original field at codegen time
// this.codegenBinding = this.binding.original();
//
// if (binding.isPrivate()) {
// if ((currentScope.enclosingSourceType() != this.codegenBinding.declaringClass)
// && binding.constant() == Constant.NotAConstant) {
// if (syntheticAccessors == null)
// syntheticAccessors = new FunctionBinding[2];
// syntheticAccessors[isReadAccess ? READ : WRITE] =
// ((SourceTypeBinding) this.codegenBinding.declaringClass).addSyntheticMethod(this.codegenBinding, isReadAccess);
// currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
// return;
// }
//
// } else if (receiver instanceof QualifiedSuperReference) { // qualified super
//
// // qualified super need emulation always
// SourceTypeBinding destinationType =
// (SourceTypeBinding) (((QualifiedSuperReference) receiver)
// .currentCompatibleType);
// if (syntheticAccessors == null)
// syntheticAccessors = new FunctionBinding[2];
// syntheticAccessors[isReadAccess ? READ : WRITE] = destinationType.addSyntheticMethod(this.codegenBinding, isReadAccess);
// currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
// return;
//
// } else if (binding.isProtected()) {
//
// SourceTypeBinding enclosingSourceType;
// if (((bits & DepthMASK) != 0)
// && binding.declaringClass.getPackage()
// != (enclosingSourceType = currentScope.enclosingSourceType()).getPackage()) {
//
// SourceTypeBinding currentCompatibleType =
// (SourceTypeBinding) enclosingSourceType.enclosingTypeAt(
// (bits & DepthMASK) >> DepthSHIFT);
// if (syntheticAccessors == null)
// syntheticAccessors = new FunctionBinding[2];
// syntheticAccessors[isReadAccess ? READ : WRITE] = currentCompatibleType.addSyntheticMethod(this.codegenBinding, isReadAccess);
// currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
// return;
// }
// }
// if the binding declaring class is not visible, need special action
// for runtime compatibility on 1.2 VMs : change the declaring class of the binding
// NOTE: from target 1.2 on, field's declaring class is touched if any different from receiver type
// and not from Object or implicit static field access.
// if (this.binding.declaringClass != this.receiverType
// && !this.receiverType.isArrayType()
// && this.binding.declaringClass != null // array.length
// && this.binding.constant() == Constant.NotAConstant) {
// CompilerOptions options = currentScope.compilerOptions();
// if ((options.targetJDK >= ClassFileConstants.JDK1_2
// && (options.complianceLevel >= ClassFileConstants.JDK1_4 || !(receiver.isImplicitThis() && this.codegenBinding.isStatic()))
// && this.binding.declaringClass.id != T_JavaLangObject) // no change for Object fields
// || !this.binding.declaringClass.canBeSeenBy(currentScope)) {
//
// this.codegenBinding =
// currentScope.enclosingSourceType().getUpdatedFieldBinding(
// this.codegenBinding,
// (ReferenceBinding) this.receiverType.erasure());
// }
// }
}
public int nullStatus(FlowInfo flowInfo) {
return FlowInfo.UNKNOWN;
}
public Constant optimizedBooleanConstant() {
switch (this.resolvedType.id) {
case T_boolean :
case T_JavaLangBoolean :
return this.constant != Constant.NotAConstant ? this.constant : this.binding.constant();
default :
return Constant.NotAConstant;
}
}
/**
* @see org.eclipse.wst.jsdt.internal.compiler.ast.Expression#postConversionType(Scope)
*/
public TypeBinding postConversionType(Scope scope) {
TypeBinding convertedType = this.resolvedType;
// if (this.genericCast != null)
// convertedType = this.genericCast;
int runtimeType = (this.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4;
switch (runtimeType) {
case T_boolean :
convertedType = TypeBinding.BOOLEAN;
break;
case T_byte :
convertedType = TypeBinding.BYTE;
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 & BOXING) != 0) {
convertedType = scope.environment().computeBoxingType(convertedType);
}
return convertedType;
}
public StringBuffer printExpression(int indent, StringBuffer output) {
return receiver.printExpression(0, output).append('.').append(token);
}
public TypeBinding resolveType(BlockScope scope) {
return resolveType(scope, false, null);
}
public TypeBinding resolveType(BlockScope scope, boolean define, TypeBinding useType) {
// Answer the signature type of the field.
// constants are propaged when the field is final
// and initialized with a (compile time) constant
//always ignore receiver cast, since may affect constant pool reference
// boolean receiverCast = false;
// if (this.receiver instanceof CastExpression) {
// this.receiver.bits |= DisableUnnecessaryCastCheck; // will check later on
// receiverCast = true;
// }
/*
* Handle if this is a reference to the prototype of a type
*
* By default, the prototype is of type Object, but if there is an InferredType
* for the receiver, it should yeild the receiver type.
*/
if( this.isPrototype() ){
//construc the name of the type based on the receiver
char [] possibleTypeName = Util.getTypeName( receiver );
TypeBinding typeBinding = scope.getJavaLangObject();
if( possibleTypeName != null ){
Binding possibleTypeBinding = scope.getBinding( possibleTypeName, Binding.TYPE & RestrictiveFlagMASK, this, true /*resolve*/);
if( possibleTypeBinding.isValidBinding() ){
//get the super class
// TypeBinding superTypeBinding = ((ReferenceBinding)possibleTypeBinding).superclass();
// if( superTypeBinding != null )
// typeBinding = superTypeBinding;
typeBinding = (TypeBinding)possibleTypeBinding;
}
char[] fieldname=new char[]{'p','r','o','t','o','t','y','p','e'};
this.binding=scope.getJavaLangObject().getField(fieldname, true);
constant = Constant.NotAConstant;
return this.resolvedType = typeBinding;
}
}
this.receiverType = receiver.resolveType(scope);
if (this.receiverType == null) {
char [] possibleTypeName = Util.getTypeName( this );
Binding possibleTypeBinding =null;
if (possibleTypeName!=null)
possibleTypeBinding = scope.getBinding( possibleTypeName, Binding.TYPE & RestrictiveFlagMASK, this, true /*resolve*/);
if (possibleTypeBinding!=null && possibleTypeBinding.isValidBinding())
{
this.typeBinding=(TypeBinding)possibleTypeBinding;
this.bits|=Binding.TYPE;
return this.typeBinding;
}
else
{
this.binding=new ProblemFieldBinding(null,this.token,ProblemReasons.NotFound);
constant = Constant.NotAConstant;
this.resolvedType=TypeBinding.ANY;
}
return null;
}
// if (receiverCast) {
// // due to change of declaring class with receiver type, only identity cast should be notified
// if (((CastExpression)this.receiver).expression.resolvedType == this.receiverType) {
// scope.problemReporter().unnecessaryCast((CastExpression)this.receiver);
// }
// }
// the case receiverType.isArrayType and token = 'length' is handled by the scope API
/*
* Need to look in the fields and method for a match... In JS there is no distinction between member functions
* or field. We are trying to mimic that property below (Java does have a distinction)
*/
if (this.receiverType.id==TypeIds.T_any)
{
constant = Constant.NotAConstant;
this.binding=new ProblemFieldBinding( null, token, ProblemReasons.NotFound) ;
return this.resolvedType=TypeBinding.ANY;
}
Binding memberBinding = scope.getFieldOrMethod(this.receiverType, token, this);
boolean receiverIsType = (receiver instanceof NameReference || receiver instanceof FieldReference)
&& ( receiver.bits & Binding.TYPE) != 0;
if (!memberBinding.isValidBinding() && (this.receiverType!=null && this.receiverType.isFunctionType()))
{
Binding alternateBinding = receiver.alternateBinding();
if (alternateBinding instanceof TypeBinding)
{
this.receiverType=(TypeBinding)alternateBinding;
memberBinding = scope.getFieldOrMethod(this.receiverType, token, this);
receiverIsType=true;
}
}
//FieldBinding fieldBinding = this.codegenBinding = this.binding = scope.getField(this.receiverType, token, this);
constant = Constant.NotAConstant;
if( memberBinding instanceof FieldBinding ){
FieldBinding fieldBinding =/* this.codegenBinding =*/ this.binding = (FieldBinding)memberBinding;
if (!fieldBinding.isValidBinding()) {
this.binding=fieldBinding;
this.resolvedType=TypeBinding.ANY;
if (!define)
{
constant = Constant.NotAConstant;
scope.problemReporter().invalidField(this, this.receiverType);
return null;
}
else // should add binding here
{
}
// return this.resolvedType=TypeBinding.UNKNOWN;
}
if (JavaScriptCore.IS_ECMASCRIPT4)
{
TypeBinding receiverErasure = this.receiverType.erasure();
if (receiverErasure instanceof ReferenceBinding) {
if (receiverErasure.findSuperTypeWithSameErasure(fieldBinding.declaringClass) == null) {
this.receiverType = fieldBinding.declaringClass; // handle indirect inheritance thru variable secondary bound
}
}
}
this.receiver.computeConversion(scope, this.receiverType, this.receiverType);
if (isFieldUseDeprecated(fieldBinding, scope, (this.bits & IsStrictlyAssigned) !=0)) {
scope.problemReporter().deprecatedField(fieldBinding, this);
}
boolean isImplicitThisRcv = receiver.isImplicitThis();
constant = isImplicitThisRcv ? fieldBinding.constant() : Constant.NotAConstant;
if (fieldBinding.isStatic()) {
// static field accessed through receiver? legal but unoptimal (optional warning)
if (!(isImplicitThisRcv
|| (receiver instanceof NameReference
&& receiverIsType
))) {
scope.problemReporter().nonStaticAccessToStaticField(this, fieldBinding);
}
if (!isImplicitThisRcv
&& fieldBinding.declaringClass != receiverType
&& fieldBinding.declaringClass.canBeSeenBy(scope)) {
scope.problemReporter().indirectAccessToStaticField(this, fieldBinding);
}
}
// perform capture conversion if read access
return this.resolvedType =
(((this.bits & IsStrictlyAssigned) == 0)
? fieldBinding.type.capture(scope, this.sourceEnd)
: fieldBinding.type);
}
else if( memberBinding instanceof MethodBinding ){
this.resolvedType= scope.getJavaLangFunction();
this.binding=new ProblemFieldBinding(null,this.token,ProblemReasons.NotFound);
if( memberBinding.isValidBinding() )
return this.resolvedType;
return null;
}
return null;
}
public void setActualReceiverType(ReferenceBinding receiverType) {
// ignored
}
public void setDepth(int depth) {
bits &= ~DepthMASK; // flush previous depth if any
if (depth > 0) {
bits |= (depth & 0xFF) << DepthSHIFT; // encoded on 8 bits
}
}
public void setFieldIndex(int index) {
// ignored
}
public void traverse(ASTVisitor visitor, BlockScope scope) {
if (visitor.visit(this, scope)) {
receiver.traverse(visitor, scope);
}
visitor.endVisit(this, scope);
}
public boolean isPrototype()
{
return (CharOperation.equals(TypeConstants.PROTOTYPE,this.token));
}
public TypeBinding resolveForAllocation(BlockScope scope, ASTNode location)
{
char [][]qualifiedName=asQualifiedName();
TypeBinding typeBinding=null;
if (qualifiedName!=null)
{
typeBinding=scope.getType(CharOperation.concatWith(qualifiedName, '.'));
}
if (typeBinding==null || !typeBinding.isValidBinding())
{
this.receiverType = receiver.resolveType(scope);
if (this.receiverType == null) {
this.binding=new ProblemFieldBinding(null,this.token,ProblemReasons.NotFound);
constant = Constant.NotAConstant;
this.resolvedType=TypeBinding.ANY;
return null;
}
Binding memberBinding = scope.getFieldOrMethod(this.receiverType, token, this);
if( memberBinding instanceof MethodBinding && memberBinding.isValidBinding()){
this.resolvedType= ((MethodBinding)memberBinding).allocationType;
this.binding=new ProblemFieldBinding(null,this.token,ProblemReasons.NotFound);
if( memberBinding.isValidBinding() )
return this.resolvedType;
}
}
if (typeBinding==null)
{
if (qualifiedName==null)
qualifiedName=new char[][]{token};
typeBinding=new ProblemReferenceBinding(qualifiedName,null,ProblemReasons.NotFound);
}
return typeBinding;
}
public int getASTType() {
return IASTNode.FIELD_REFERENCE;
}
public char [][] asQualifiedName()
{
ArrayList list=new ArrayList();
list.add(token);
FieldReference fieldReference=this;
while (fieldReference!=null)
{
if ( fieldReference.receiver instanceof SingleNameReference)
{
list.add(0,((SingleNameReference)fieldReference.receiver).token);
fieldReference=null;
}
else if (fieldReference.receiver instanceof FieldReference)
{
fieldReference=(FieldReference)fieldReference.receiver;
list.add(0,fieldReference.token);
}
else
return null;
}
return (char [][])list.toArray(new char[list.size()][]);
}
}