blob: 6948dae65a5752b5e9453a2a6dca17bd5fd891aa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 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.IExpression;
import org.eclipse.wst.jsdt.core.ast.IFieldReference;
import org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.core.infer.InferredMethod;
import org.eclipse.wst.jsdt.core.infer.InferredType;
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
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.FunctionTypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.InvocationSite;
import org.eclipse.wst.jsdt.internal.compiler.lookup.LocalFunctionBinding;
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.SourceTypeBinding;
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 Expression receiver;
public char[] token;
/**
* <p>
* exact binding resulting from lookup
* </p>
*/
public FieldBinding binding;
/**
* <p>
* exact binding resulting from lookup
* </p>
*/
public TypeBinding typeBinding;
/**
* (start<<32)+end
*/
public long nameSourcePosition;
public TypeBinding receiverType;
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) {
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();
}
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);
}
return flowInfo;
}
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();
}
public int nullStatus(FlowInfo flowInfo) {
return FlowInfo.UNKNOWN;
}
public Constant optimizedBooleanConstant() {
return Constant.NotAConstant;
}
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) {
/*
* 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()) {
// check if receiver type is defined
if ((this.receiverType = receiver.resolveType(scope)) == null) {
constant = Constant.NotAConstant;
return null;
}
// construct 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()) {
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;
}
}
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() && (TypeBinding) possibleTypeBinding != scope.getJavaLangObject()) {
this.typeBinding = (TypeBinding) possibleTypeBinding;
constant = Constant.NotAConstant;
this.bits |= Binding.TYPE;
return this.typeBinding;
}
/* if this could be a qualified type name, first check if receiver is
* defined, and if not look up as type name */
this.receiverType = this.receiver.resolveType(scope);
if (this.receiverType == null || this.receiverType == scope.getJavaLangObject()) {
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;
}
/* 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 instanceof ThisReference) && (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;
}
}
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
}
}
if (JavaScriptCore.IS_ECMASCRIPT4) {
TypeBinding receiverErasure = this.receiverType;
if (receiverErasure instanceof ReferenceBinding) {
if (receiverErasure.findSuperTypeWithSameErasure(fieldBinding.declaringClass) == null) {
// handle indirect inheritance thru variable secondary bound
this.receiverType = fieldBinding.declaringClass;
}
}
}
if (isFieldUseDeprecated(fieldBinding, scope, (this.bits & IsStrictlyAssigned) != 0)) {
scope.problemReporter().deprecatedField(fieldBinding, this);
}
boolean isImplicitThisRcv = receiver.isImplicitThis();
constant = Constant.NotAConstant;
if (fieldBinding.isStatic()) {
// static field accessed through receiver? legal but unoptimal (optional warning)
if (!(isImplicitThisRcv || receiverIsType)) {
scope.problemReporter().nonStaticAccessToStaticField(this, fieldBinding);
}
if (!isImplicitThisRcv && fieldBinding.declaringClass != receiverType && fieldBinding.declaringClass.canBeSeenBy(scope)) {
scope.problemReporter().indirectAccessToStaticField(this, fieldBinding);
}
}
else {
if (receiverIsType) {
scope.problemReporter().staticFieldAccessToNonStaticVariable(this, fieldBinding);
}
}
//if there is a given useType and the field is not valid, create a valid binding
if(useType != null && !fieldBinding.isValidBinding()) {
fieldBinding = new FieldBinding(fieldBinding, fieldBinding.declaringClass);
if(fieldBinding.declaringClass instanceof SourceTypeBinding) {
((SourceTypeBinding)fieldBinding.declaringClass).addField(fieldBinding);
}
this.binding = fieldBinding;
}
//set use type
if(useType != null) {
fieldBinding.type = useType;
//add as a function binding as well if there is not already a function binding by the same name
if(useType.isFunctionType() && fieldBinding.declaringClass instanceof SourceTypeBinding) {
SourceTypeBinding declaringBinding = (SourceTypeBinding)fieldBinding.declaringClass;
InferredMethod dupMeth = declaringBinding.getInferredType().findMethod(this.getToken(), null);
if(dupMeth == null) {
MethodBinding[] funcBindings = declaringBinding.getMethods(this.getToken());
if(funcBindings == null || funcBindings.length == 0) {
MethodBinding methBinding = new MethodBinding(
((FunctionTypeBinding)useType).functionBinding, fieldBinding.declaringClass);
methBinding.selector = fieldBinding.name;
if(methBinding.declaringClass instanceof SourceTypeBinding) {
((SourceTypeBinding)methBinding.declaringClass).addMethod(methBinding);
}
}
}
}
}
// perform capture conversion if read access
return this.resolvedType = fieldBinding.type;
}
else if (memberBinding instanceof MethodBinding) {
MethodBinding methodBinding = (MethodBinding) memberBinding;
if (!methodBinding.isStatic() || memberBinding instanceof LocalFunctionBinding) {
if (receiverIsType && methodBinding.isValidBinding() && !methodBinding.isConstructor()) {
if (this.receiverType == null || !this.receiverType.isAnonymousType())
scope.problemReporter().mustUseAStaticMethod(this, methodBinding);
}
}
else {
if (!receiverIsType && methodBinding.isValidBinding())
scope.problemReporter().nonStaticAccessToStaticMethod(this, methodBinding);
}
this.resolvedType = methodBinding.functionTypeBinding;
this.binding = new FieldBinding(((MethodBinding) memberBinding).selector, this.receiverType, ((MethodBinding) memberBinding).modifiers, methodBinding.declaringClass);
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 if (fieldReference.receiver instanceof ThisReference) {
//use the inferred type name of "this" as the next segment
InferredType type = ((ThisReference)fieldReference.receiver).getInferredType();
if(type != null) {
list.add(0, type.getName());
} else {
//if do not have a type for "this" then can't build the fully qualified name
return null;
}
fieldReference = null;
}
else {
return null;
}
}
return (char[][]) list.toArray(new char[list.size()][]);
}
public IExpression getReceiver() {
return receiver;
}
public char[] getToken() {
return token;
}
public boolean isTypeReference() {
return (this.bits & Binding.TYPE) == Binding.TYPE;
}
}