/******************************************************************************* | |
* Copyright (c) 2005, 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.core.infer; | |
import java.util.ArrayList; | |
import java.util.LinkedList; | |
import java.util.List; | |
import org.eclipse.wst.jsdt.core.ast.ASTVisitor; | |
import org.eclipse.wst.jsdt.core.ast.IASTNode; | |
import org.eclipse.wst.jsdt.core.ast.IAbstractFunctionDeclaration; | |
import org.eclipse.wst.jsdt.core.ast.IAbstractVariableDeclaration; | |
import org.eclipse.wst.jsdt.core.ast.IAllocationExpression; | |
import org.eclipse.wst.jsdt.core.ast.IArgument; | |
import org.eclipse.wst.jsdt.core.ast.IAssignment; | |
import org.eclipse.wst.jsdt.core.ast.IBinaryExpression; | |
import org.eclipse.wst.jsdt.core.ast.IExpression; | |
import org.eclipse.wst.jsdt.core.ast.IFalseLiteral; | |
import org.eclipse.wst.jsdt.core.ast.IFieldReference; | |
import org.eclipse.wst.jsdt.core.ast.IFunctionCall; | |
import org.eclipse.wst.jsdt.core.ast.IFunctionDeclaration; | |
import org.eclipse.wst.jsdt.core.ast.IFunctionExpression; | |
import org.eclipse.wst.jsdt.core.ast.IJsDoc; | |
import org.eclipse.wst.jsdt.core.ast.ILocalDeclaration; | |
import org.eclipse.wst.jsdt.core.ast.INumberLiteral; | |
import org.eclipse.wst.jsdt.core.ast.IObjectLiteral; | |
import org.eclipse.wst.jsdt.core.ast.IObjectLiteralField; | |
import org.eclipse.wst.jsdt.core.ast.IProgramElement; | |
import org.eclipse.wst.jsdt.core.ast.IReference; | |
import org.eclipse.wst.jsdt.core.ast.IReturnStatement; | |
import org.eclipse.wst.jsdt.core.ast.IScriptFileDeclaration; | |
import org.eclipse.wst.jsdt.core.ast.ISingleNameReference; | |
import org.eclipse.wst.jsdt.core.ast.IStringLiteral; | |
import org.eclipse.wst.jsdt.core.ast.IThisReference; | |
import org.eclipse.wst.jsdt.core.ast.ITrueLiteral; | |
import org.eclipse.wst.jsdt.core.compiler.CharOperation; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractMethodDeclaration; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractVariableDeclaration; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.AllocationExpression; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.ArrayInitializer; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.ArrayReference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.Assignment; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.BinaryExpression; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.CompilationUnitDeclaration; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.Expression; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldReference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.FunctionExpression; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.Javadoc; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocSingleNameReference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.LocalDeclaration; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.MessageSend; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.MethodDeclaration; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.ObjectLiteral; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.OperatorIds; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.Reference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.SingleNameReference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.StringLiteral; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.ThisReference; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.UnaryExpression; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeConstants; | |
import org.eclipse.wst.jsdt.internal.compiler.util.HashtableOfObject; | |
import org.eclipse.wst.jsdt.internal.compiler.util.Util; | |
import org.eclipse.wst.jsdt.internal.core.search.indexing.IIndexConstants; | |
/** | |
* The default inference engine. | |
* | |
* <p> | |
* Clients may subclass this class but should expect some breakage by future releases. | |
* </p> | |
* | |
* Provisional API: This class/interface is part of an interim API that is still under development | |
* and expected to | |
* change significantly before reaching stability. It is being made available at this early stage to | |
* solicit feedback | |
* from pioneering adopters on the understanding that any code that uses this API will almost | |
* certainly be broken | |
* (repeatedly) as the API evolves. | |
*/ | |
public class InferEngine extends ASTVisitor implements IInferEngine { | |
/** | |
* <p> | |
* String type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fStringType; | |
/** | |
* <p> | |
* Number type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fNumberType; | |
/** | |
* <p> | |
* Boolean type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fBooleanType; | |
/** | |
* <p> | |
* Function type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fFunctionType; | |
/** | |
* <p> | |
* Array type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fArrayType; | |
/** | |
* <p> | |
* Void type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fVoidType; | |
/** | |
* <p> | |
* Object type that is initialized on first use and added to the compilation unit. | |
* </p> | |
*/ | |
private InferredType fObjectType; | |
InferOptions inferOptions; | |
CompilationUnitDeclaration compUnit; | |
Context[] contexts = new Context[100]; | |
int contextPtr = -1; | |
Context currentContext = new Context(); | |
protected int passNumber = 1; | |
boolean isTopLevelAnonymousFunction; | |
int anonymousCount = 0; | |
public static boolean DEBUG = false; | |
public InferrenceProvider inferenceProvider; | |
/** | |
* @deprecated use {@link #getStringType()} | |
*/ | |
public InferredType StringType = new InferredType(TypeConstants.JAVA_LANG_STRING[0]); | |
/** | |
* @deprecated use {@link #getNumberType()} | |
*/ | |
public InferredType NumberType = new InferredType(TypeConstants.NUMBER[0]); | |
/** | |
* @deprecated use {@link #getBooleanType()} | |
*/ | |
public InferredType BooleanType = new InferredType(TypeConstants.BOOLEAN_OBJECT[0]); | |
/** | |
* @deprecated use {@link #getFunctionType()} | |
*/ | |
public InferredType FunctionType = new InferredType(TypeConstants.FUNCTION[0]); | |
/** | |
* @deprecated use {@link #getArrayType()} | |
*/ | |
public InferredType ArrayType = new InferredType(TypeConstants.ARRAY[0]); | |
/** | |
* @deprecated use {@link #getVoidType()} | |
*/ | |
public InferredType VoidType = new InferredType(TypeConstants.VOID); | |
/** | |
* @deprecated use {@link #getObjectType()} | |
*/ | |
public InferredType ObjectType = new InferredType(TypeConstants.OBJECT); | |
/** | |
* @deprecated - no longer used | |
*/ | |
public InferredType GlobalType = new InferredType(InferredType.GLOBAL_NAME); | |
public static HashtableOfObject WellKnownTypes = new HashtableOfObject(); | |
{ | |
WellKnownTypes.put(TypeConstants.OBJECT, null); | |
WellKnownTypes.put(TypeConstants.ARRAY[0], null); | |
WellKnownTypes.put(TypeConstants.JAVA_LANG_STRING[0], null); | |
WellKnownTypes.put(TypeConstants.NUMBER[0], null); | |
WellKnownTypes.put(TypeConstants.BOOLEAN_OBJECT[0], null); | |
WellKnownTypes.put(TypeConstants.FUNCTION[0], null); | |
WellKnownTypes.put(new char[] { 'D', 'a', 't', 'e' }, null); | |
WellKnownTypes.put(new char[] { 'M', 'a', 't', 'h' }, null); | |
WellKnownTypes.put(new char[] { 'R', 'e', 'g', 'E', 'x', 'p' }, null); | |
WellKnownTypes.put(new char[] { 'E', 'r', 'r', 'o', 'r' }, null); | |
} | |
protected InferredType inferredGlobal = null; | |
/** | |
* @deprecated Will be removed | |
*/ | |
static final char[] CONSTRUCTOR_ID = { 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', 'o', 'r' }; | |
/** | |
* <p> | |
* Use to keep track of the current context of the infer engine. | |
* </p> | |
*/ | |
static class Context { | |
InferredType currentType; | |
IFunctionDeclaration currentMethod; | |
/** The current assignment. */ | |
IAssignment currentAssignment; | |
/** the current declaration */ | |
ILocalDeclaration currentLocalDeclaration; | |
/** The current return */ | |
IReturnStatement currentReturn; | |
boolean isJsDocClass; | |
private HashtableOfObject definedMembers; | |
/* Parent context to provide chaining when searching | |
* for members in scope. */ | |
private Context parent = null; | |
/* Root context */ | |
Context() { | |
} | |
/* Nested context */ | |
Context(Context parent) { | |
this.parent = parent; | |
currentType = parent.currentType; | |
currentMethod = parent.currentMethod; | |
this.currentAssignment = parent.currentAssignment; | |
this.currentLocalDeclaration = parent.currentLocalDeclaration; | |
this.currentReturn = parent.currentReturn; | |
this.isJsDocClass = parent.isJsDocClass; | |
} | |
public Object getMember(char[] key) { | |
Object value = null; | |
if(definedMembers != null) { | |
value = definedMembers.get(key); | |
} | |
// chain lookup | |
if(value == null && parent != null) { | |
value = parent.getMember(key); | |
} | |
return value; | |
} | |
public void addMember(char[] key, Object member) { | |
if(key == null) | |
return; | |
if(definedMembers == null) { | |
definedMembers = new HashtableOfObject(); | |
} | |
definedMembers.put(key, member); | |
} | |
public void setCurrentType(InferredType type) { | |
this.currentType = type; | |
Context parentContext = this.parent; | |
while(parentContext != null && parentContext.currentMethod == this.currentMethod) { | |
parentContext.currentType = type; | |
parentContext = parentContext.parent; | |
} | |
} | |
} | |
private static boolean REPORT_INFER_TIME = false; | |
/** | |
* <p> | |
* Constructor that uses default {@link InferOptions}. | |
* </p> | |
*/ | |
public InferEngine() { | |
this(new InferOptions()); | |
} | |
/** | |
* <p> | |
* Constructor using given {@link InferOptions}. | |
* </p> | |
* | |
* @param inferOptions | |
* to create this infer engine with | |
*/ | |
public InferEngine(InferOptions inferOptions) { | |
this.inferOptions = inferOptions; | |
} | |
public void initialize() { | |
this.contextPtr = -1; | |
this.currentContext = new Context(); | |
this.passNumber = 1; | |
this.isTopLevelAnonymousFunction = false; | |
this.anonymousCount = 0; | |
this.inferredGlobal = null; | |
} | |
public void setCompilationUnit(CompilationUnitDeclaration scriptFileDeclaration) { | |
this.compUnit = scriptFileDeclaration; | |
buildDefinedMembers(scriptFileDeclaration.getStatements(), null); | |
} | |
public boolean visit(IFunctionCall functionCall) { | |
boolean visitChildren = handleFunctionCall(functionCall); | |
if(visitChildren) { | |
if(functionCall.getReceiver() instanceof FunctionExpression) { | |
if (this.contextPtr == -1) { | |
this.isTopLevelAnonymousFunction = true; | |
} | |
if (functionCall instanceof MessageSend && ((MessageSend) functionCall).getArguments() != null) { | |
MethodDeclaration methodDeclaration = ((FunctionExpression) functionCall.getReceiver()).getMethodDeclaration(); | |
if (methodDeclaration != null && methodDeclaration.getArguments() != null) { | |
IArgument[] declaredArguments = methodDeclaration.getArguments(); | |
IExpression[] sentArguments = ((MessageSend) functionCall).getArguments(); | |
for (int i = 0; i < declaredArguments.length; i++) { | |
if (i >= sentArguments.length) { | |
continue; | |
} | |
handleFunctionDeclarationArgument(declaredArguments[i], sentArguments[i]); | |
} | |
} | |
} | |
} | |
} | |
return visitChildren; | |
} | |
protected void handleFunctionDeclarationArgument(IArgument declaredArgument, IExpression sentArgument) { | |
// set the declared argument's type to be the matching parameter type | |
if (!declaredArgument.isType() && declaredArgument.getInferredType() == null) { | |
if (sentArgument instanceof SingleNameReference) { | |
InferredType inferredType = getInferredType(sentArgument); | |
if (inferredType == null) { | |
char[] parameterName = getName(sentArgument); | |
if (parameterName!= null && isGlobal(parameterName)) { | |
inferredType = createAnonymousGlobalType(parameterName); | |
} | |
} | |
declaredArgument.setInferredType(inferredType); | |
} | |
else if (sentArgument instanceof ThisReference) { | |
//check if "this" refers to the global object | |
if (this.isTopLevelAnonymousFunction) { | |
char[] parameterName = declaredArgument.getName(); | |
if (parameterName != null) { | |
InferredType inferredType = createAnonymousGlobalType(parameterName); | |
declaredArgument.setInferredType(inferredType); | |
} | |
} | |
} | |
} | |
} | |
public boolean visit(ILocalDeclaration localDeclaration) { | |
// add as a member of the current context | |
currentContext.addMember(localDeclaration.getName(), localDeclaration); | |
// create a new context for the local declaration | |
pushContext(); | |
this.currentContext.currentLocalDeclaration = localDeclaration; | |
if(this.passNumber == 1 && localDeclaration instanceof LocalDeclaration && this.currentContext.currentMethod == null) { | |
((LocalDeclaration)localDeclaration).setIsLocal(false); | |
} | |
if(localDeclaration.getJsDoc() != null) { | |
Javadoc javadoc = (Javadoc) localDeclaration.getJsDoc(); | |
createTypeIfNecessary(javadoc); | |
InferredAttribute attribute = null; | |
if(javadoc.memberOf != null) { | |
InferredType type = this.addType(javadoc.memberOf.getFullTypeName(), true); | |
int nameStart = localDeclaration.sourceStart(); | |
attribute = type.addAttribute(localDeclaration.getName(), localDeclaration, nameStart); | |
handleAttributeDeclaration(attribute, localDeclaration.getInitialization()); | |
if(localDeclaration.getInitialization() != null) { | |
attribute.initializationStart = localDeclaration.getInitialization().sourceStart(); | |
attribute.type = getTypeOf(localDeclaration.getInitialization()); | |
} | |
attribute.inType = type; | |
} | |
if(javadoc.returnType != null) { | |
InferredType type = this.addType(changePrimitiveToObject(javadoc.returnType.getFullTypeName())); | |
localDeclaration.setInferredType(type); | |
if(attribute != null) | |
attribute.type = type; | |
} | |
} | |
// visit the function in case it defines a type | |
if(localDeclaration.getInitialization() instanceof IFunctionExpression) { | |
boolean keepVisiting = handleFunctionExpressionLocalDeclaration(localDeclaration); | |
if(!keepVisiting) { | |
return false; | |
} | |
} | |
//if initialization set, attempt to set the inferred type | |
if(localDeclaration.getInitialization() != null) { | |
if(localDeclaration.getInitialization() instanceof MessageSend) { | |
handleFunctionCall((IFunctionCall) localDeclaration.getInitialization(), | |
(LocalDeclaration) localDeclaration); | |
if(((MessageSend)localDeclaration.getInitialization()).receiver instanceof IFunctionExpression && this.passNumber == 2) { | |
if(((FunctionExpression)((MessageSend)localDeclaration.getInitialization()).receiver).methodDeclaration != null) { | |
localDeclaration.setInferredType(((FunctionExpression)((MessageSend)localDeclaration.getInitialization()).receiver).methodDeclaration.inferredType); | |
} | |
} | |
} else { | |
if (this.isExpressionAType(localDeclaration.getInitialization())) { | |
localDeclaration.setIsType(true); | |
handleLocalDeclarationExpressionType(localDeclaration); | |
} | |
InferredType type = this.getTypeForVariableInitialization(localDeclaration.getName(), localDeclaration.getInitialization()); | |
if (localDeclaration.getInferredType() == null || (type != null && type.isAnonymous)) | |
localDeclaration.setInferredType(type); | |
} | |
} | |
return true; | |
} | |
/** | |
* @see org.eclipse.wst.jsdt.core.ast.ASTVisitor#endVisit(org.eclipse.wst.jsdt.core.ast.ILocalDeclaration) | |
*/ | |
public void endVisit(ILocalDeclaration localDeclaration) { | |
popContext(); | |
} | |
private void createTypeIfNecessary(Javadoc javadoc) { | |
if(javadoc.memberOf != null) { | |
char[][] namespace = {}; | |
char[][] typeName = javadoc.memberOf.getTypeName(); | |
if(javadoc.namespace != null) { | |
namespace = javadoc.namespace.getTypeName(); | |
} | |
char[] name = | |
CharOperation.concat(CharOperation.concatWith(namespace, '.'), CharOperation.concatWith(typeName, | |
'.'), '.'); | |
this.currentContext.currentType = addType(name); | |
if(javadoc.extendsType != null) { | |
char[] superName = CharOperation.concatWith(javadoc.extendsType.getTypeName(), '.'); | |
this.currentContext.currentType.setSuperType(addType(superName)); | |
} | |
this.currentContext.isJsDocClass = true; | |
} | |
} | |
public boolean visit(IAssignment assignment) { | |
//if assigning to single name add assignment to context if there is not an existing var declaration | |
IAbstractVariableDeclaration existingVarDecl = null; | |
IAssignment existingAssignmentDecl = null; | |
if(assignment.getLeftHandSide() instanceof ISingleNameReference) { | |
existingVarDecl = this.getVariable(assignment.getLeftHandSide()); | |
if(existingVarDecl == null) { | |
existingAssignmentDecl = this.getAssignment(assignment.getLeftHandSide()); | |
if(existingAssignmentDecl == null) { | |
currentContext.addMember(this.getName(assignment.getLeftHandSide()), assignment); | |
} | |
} | |
} | |
pushContext(); | |
this.currentContext.currentAssignment = assignment; | |
//set the function that contains this assignment | |
if(this.passNumber == 1 && assignment instanceof Assignment && this.currentContext.currentMethod != null) { | |
((Assignment)assignment).setContainingFunction(this.currentContext.currentMethod); | |
} | |
IExpression assignmentExpression = assignment.getExpression(); | |
if(handlePotentialType(assignment)) { | |
} else if(assignmentExpression instanceof FunctionExpression) { | |
boolean keepVisiting = handleFunctionExpressionAssignment(assignment); | |
//set the type on the existing var declaration if it does not already have one set | |
if(assignment.getInferredType() != null && existingVarDecl != null && existingVarDecl.getInferredType() == null) { | |
existingVarDecl.setInferredType(assignment.getInferredType()); | |
} | |
if(!keepVisiting) { | |
return false; | |
} | |
} else if(assignmentExpression instanceof SingleNameReference && this.currentContext.currentType != null | |
&& isThis(assignment.getLeftHandSide())) { | |
ISingleNameReference snr = (ISingleNameReference) assignmentExpression; | |
Object object = this.currentContext.getMember(snr.getToken()); | |
IFieldReference fieldReference = (IFieldReference) assignment.getLeftHandSide(); | |
char[] memberName = fieldReference.getToken(); | |
InferredMember member = null; | |
int nameStart = fieldReference.sourceEnd() - memberName.length + 1; | |
/* this.foo = bar //bar is a function */ | |
if(object instanceof MethodDeclaration) { | |
MethodDeclaration method = (MethodDeclaration) object; | |
member = this.currentContext.currentType.addMethod(memberName, method, nameStart); | |
} | |
/* this.foo = bar //assume that bar is not a function and create a new attribute in the | |
* current type */ | |
else { | |
member = this.currentContext.currentType.addAttribute(memberName, assignment, nameStart); | |
handleAttributeDeclaration((InferredAttribute) member, assignment.getExpression()); | |
if(((InferredAttribute) member).type == null) | |
((InferredAttribute) member).type = getTypeOf(assignmentExpression); | |
} | |
// setting location | |
if(member != null) { | |
// this is a not static member because it is being set on the this | |
member.isStatic = false; | |
} | |
} | |
// foo = ??; | |
else if(assignment.getLeftHandSide() instanceof ISingleNameReference) { | |
char[] variableName = this.getName(assignment); | |
/* if there is an existing variable declaration | |
* else there is not */ | |
InferredType existingType = null; | |
if(existingVarDecl != null) { | |
existingType = existingVarDecl.getInferredType(); | |
} else if(existingAssignmentDecl != null) { | |
existingType = existingAssignmentDecl.getInferredType(); | |
} | |
/* if existing variable declaration does not already have an inferred type | |
* | |
* else it does and the new assignment type is different then the currently | |
* assigned type */ | |
InferredType type = null; | |
if(existingType == null) { | |
type = this.getTypeForVariableInitialization(variableName, assignmentExpression); | |
if(this.isExpressionAType(assignmentExpression)) { | |
assignment.setIsType(true); | |
} | |
} else { | |
InferredType newAssignmentType = this.getTypeOf(assignmentExpression); | |
if(newAssignmentType != null && existingType != newAssignmentType) { | |
/* if the existing type is not anonymous, not global and | |
* existing var is not an argument then create a new | |
* global anonymous type to mix everything together in | |
* | |
* else just use the existing type */ | |
InferredType newCombinedType = null; | |
if(!existingType.isAnonymous && !existingType.isGlobal() && !(existingVarDecl instanceof IArgument)) { | |
if(existingVarDecl instanceof LocalDeclaration && ((LocalDeclaration)existingVarDecl).isLocal()) { | |
newCombinedType = this.createAnonymousType(existingVarDecl, null); | |
} else { | |
newCombinedType = this.createAnonymousGlobalType(variableName); | |
} | |
newCombinedType.setIsDefinition(true); | |
/* add the original type of the variable declaration to the new combined type | |
* | |
* if the existing type is indexed then add it later | |
* else it is not must add it now */ | |
if(existingType.isIndexed()) { | |
newCombinedType.addMixin(existingType.getName()); | |
} else { | |
newCombinedType.mixin(existingType); | |
} | |
} else { | |
newCombinedType = existingType; | |
} | |
/* add the type of the new assignment to the combined type | |
* | |
* if the existing type is indexed then add it later | |
* else it is not must add it now */ | |
if(newAssignmentType.isIndexed()) { | |
newCombinedType.addMixin(newAssignmentType.getName()); | |
} else { | |
newCombinedType.mixin(newAssignmentType); | |
} | |
type = newCombinedType; | |
} | |
} | |
/* use the new type to set the inferred type on this assignment and | |
* any existing variable declaration or assignment */ | |
if(type != null) { | |
assignment.setInferredType(type); | |
if(existingVarDecl != null) { | |
existingVarDecl.setInferredType(type); | |
} | |
if(existingAssignmentDecl != null) { | |
existingAssignmentDecl.setInferredType(type); | |
} | |
} | |
return true; | |
} | |
else if(assignmentExpression instanceof AllocationExpression | |
&& ((AllocationExpression) assignmentExpression).member instanceof FunctionExpression) { | |
handleFunctionExpressionAssignment(assignment); | |
} else if(assignmentExpression instanceof Assignment | |
&& ((Assignment) assignmentExpression).expression instanceof FunctionExpression) { | |
handleFunctionExpressionAssignment(assignment); | |
} | |
else if(this.inferOptions.useAssignments) { | |
IExpression lhs = assignment.getLeftHandSide(); | |
// if foo.bar = ? where ? is not {} and not a function | |
if(lhs instanceof FieldReference || lhs instanceof ArrayReference) { | |
Reference lhsRef = (Reference) lhs; | |
Expression receiver = null; | |
char[] attName = null; | |
int nameStart = 0; | |
if(lhsRef instanceof FieldReference) { | |
receiver = ((FieldReference) lhsRef).receiver; | |
attName = ((FieldReference) lhsRef).token; | |
nameStart = (int) (((FieldReference) lhsRef).nameSourcePosition >>> 32); | |
} else if(lhsRef instanceof ArrayReference) { | |
if(((ArrayReference) lhsRef).position instanceof StringLiteral) { | |
receiver = ((ArrayReference) lhsRef).receiver; | |
attName = ((StringLiteral) ((ArrayReference) lhsRef).position).source(); | |
nameStart = ((StringLiteral) ((ArrayReference) lhsRef).position).sourceStart + 1; | |
} | |
} | |
//attempt to get receiver type | |
InferredType receiverType = this.getInferredType(receiver); | |
//if not found type yet check if receiver is function | |
if(receiverType == null) { | |
IFunctionDeclaration function = getDefinedFunction(receiver); | |
if(function != null) { | |
char[] typeName = constructTypeName(receiver); | |
if(typeName != null) { | |
receiverType = addType(typeName); | |
} | |
} | |
} | |
//all else fails on pass two will possibly create a receiver type | |
if(receiverType == null && this.passNumber == 2) { | |
receiverType = this.getReceiverType(receiver, true); | |
} | |
if(receiver != null && receiverType != null && attName != null && attName.length > 0) { | |
/* if the receiver type is not anonymous and is the expression is not a type then | |
* create a new anonymous sub type to do the assignment to */ | |
if(!receiverType.isAnonymous && !this.isExpressionAType(receiver)) { | |
receiverType = this.createTypeToAssignTo(receiver, receiverType); | |
this.setTypeOf(receiver, receiverType); | |
} | |
// in the case where the supertype of the reciever is a function, make sure we store the actual function | |
// for later use | |
if(receiverType != null && receiverType.getSuperType() != null && receiverType.getSuperType().isFunction()) { | |
IAbstractVariableDeclaration varDecl = this.getVariable(receiver); | |
if(varDecl != null) { | |
IExpression expression = varDecl.getInitialization(); | |
if(expression != null && expression instanceof IFunctionExpression) { | |
receiverType.setCorrespondingFunction(((IFunctionExpression)expression).getMethodDeclaration()); | |
} | |
} | |
} | |
/* if receiver is instance of type so create new type to assign to | |
* | |
* else if receiver is a type then will just assign directly to it, | |
* and if not anonymous then its a static assignment that type statically */ | |
boolean isStatic = false; | |
if(!this.isExpressionAType(receiver)) { | |
receiverType = this.createTypeToAssignTo(receiver, receiverType); | |
} else if(!receiverType.isAnonymous && !receiver.isThis()) { | |
isStatic = true; | |
} | |
// check if there is an attribute or function already created | |
InferredMethod method = null; | |
InferredAttribute attr = receiverType.findAttribute(attName); | |
if(attr == null) { | |
method = receiverType.findMethod(attName, null); | |
} else if (this.passNumber == 2) { | |
handleAttributeDeclaration(attr); | |
} | |
// ignore if the attribute exists and has a type | |
if((method == null && attr == null) || (method == null && attr != null && attr.type == null)) { | |
//if type already set on assignment, use that | |
InferredType rhsType = assignment.getInferredType(); | |
// If the RHS is a type then create a type on the LHS | |
boolean isType = false; | |
if(rhsType != null && assignment.getInferredType() != null && assignment.isType() && | |
this.isExpressionAType(receiver) && !receiverType.isAnonymous) { | |
isType = true; | |
//create new type name | |
char[] newTypeName = receiverType.getName(); | |
newTypeName = CharOperation.concat(newTypeName, attName, '.'); | |
/* if the RHS type is anonymous just rename it to use the LHS name | |
* else create new type on the LHS and add it as a synonym of the RHS type */ | |
if(rhsType.isAnonymous && !rhsType.isGlobal()) { | |
this.convertAnonymousTypeToNamed(rhsType, newTypeName); | |
rhsType.setIsDefinition(true); | |
rhsType.setNameStart(assignment.sourceStart()); | |
} | |
else if(!CharOperation.equals(newTypeName, rhsType.getName())){ | |
InferredType newType = this.addType(newTypeName, true); | |
rhsType.addSynonym(newType); | |
} | |
} | |
//if type not found type yet, check if RHS refers to a function definition | |
IFunctionDeclaration definedFunction = null; | |
if(rhsType == null) { | |
definedFunction = this.getDefinedFunction(assignmentExpression); | |
} | |
/* if RHS is a function, add a function to the receiver type | |
* else add new attribute to receiver type */ | |
if(definedFunction != null) { | |
method = receiverType.addMethod(attName, definedFunction, nameStart); | |
receiverType.setIsDefinition(true); | |
method.isStatic = isStatic; | |
} else { | |
//create the attribute | |
int nameStart_ = nameStart; | |
attr = receiverType.addAttribute(attName, assignment, nameStart_); | |
receiverType.setIsDefinition(true); | |
this.handleAttributeDeclaration(attr, assignmentExpression); | |
//if still not RHS type then get one | |
if(rhsType == null) { | |
/* if LHS is global "this" | |
* else just an attribute on a type */ | |
if(receiver instanceof IThisReference && | |
this.currentContext.currentType == this.getInferredGlobal(false)) { | |
rhsType = this.getTypeForVariableInitialization(attName, assignmentExpression); | |
} else { | |
rhsType = this.getTypeOf(assignmentExpression); | |
if(receiverType != null && rhsType != null && receiverType.isGlobal() && rhsType.isAnonymous) { | |
char[] globalAttName = createAnonymousGlobalTypeName(attName); | |
convertAnonymousTypeToNamed(rhsType, globalAttName); | |
} | |
// if the attribute is also a function, add the function using the corresponding function stored earlier | |
if(rhsType != null && rhsType.getSuperType() != null && rhsType.getSuperType().isFunction() && rhsType.getCorrespondingFunction() != null) { | |
method = receiverType.addMethod(attName, rhsType.getCorrespondingFunction(), nameStart); | |
method.isStatic = isStatic; | |
} | |
} | |
} | |
//determine if static | |
char[] possibleTypeName = constructTypeName(receiver); | |
attr.isStatic = isStatic || (possibleTypeName != null && compUnit.findInferredType(possibleTypeName) != null); | |
//assign the type to the attribute | |
if(attr.type == null || rhsType != null) { | |
attr.type = rhsType; | |
attr.setIsType(isType); | |
} | |
//if determined RHS type set it as the inferred type for the assignment | |
if(rhsType != null) { | |
assignment.setInferredType(rhsType); | |
} | |
} | |
} else if(method == null && attr != null && attr.type != null && attr.type.getSuperType() != null && attr.type.getSuperType().isFunction() && attr.type.getCorrespondingFunction() != null) { | |
// if the attribute is also a function, add the function using the cooresponding function stored earlier | |
method = receiverType.addMethod(attName, attr.type.getCorrespondingFunction(), nameStart); | |
receiverType.setIsDefinition(true); | |
method.isStatic = isStatic; | |
} | |
} | |
} | |
// if foo = ? where ? is not {} and not a function | |
else { | |
// no inferred type already set, use the type of the RHS expression | |
if(assignment.getInferredType() == null) { | |
InferredType rhsType = this.getTypeOf(assignment.getExpression()); | |
assignment.setInferredType(rhsType); | |
} | |
} | |
//only create global type for LHS if LHS's root is global | |
if(this.isRootGlobal(lhs)) { | |
// construct the LHS and RHS type names | |
char[] lhsName = constructTypeName(assignment.getLeftHandSide()); | |
char[] rhsName = constructTypeName(assignment.getExpression()); | |
//if RHS type exists then create LHS type and add it as synonym of the RHS type | |
if(lhsName != null && lhsName.length > 0 && rhsName != null && rhsName.length > 0) { | |
InferredType rhsType = this.findDefinedType(rhsName); | |
if(rhsType != null) { | |
InferredType lhsType = this.addType(lhsName, true); | |
lhsType.setNameStart(lhs.sourceStart()); | |
lhsType.addSynonym(rhsType); | |
} | |
} | |
} | |
} | |
return true; // do nothing by default, keep traversing | |
} | |
protected InferredType createAnonymousType(char[] possibleTypeName, InferredType currentType) { | |
char[] name; | |
if(this.isKnownType(possibleTypeName)) { | |
name = possibleTypeName; | |
} else { | |
char[] cs = String.valueOf(this.anonymousCount++).toCharArray(); | |
name = CharOperation.concat(ANONYMOUS_PREFIX, possibleTypeName, cs); | |
} | |
InferredType type = addType(name, true); | |
type.isAnonymous = true; | |
type.setIsGlobal(false); | |
if(currentType != null) { | |
type.setSuperType(currentType); | |
} | |
return type; | |
} | |
/** | |
* <p> | |
* Creates an anonymous type for a given node with an optional parent type. | |
* </p> | |
* | |
* @param forNode | |
* the node to create the anonymous type for, the text range of | |
* this node will be used to create the anonymous type name | |
* @param parrentType | |
* optional parent type of the new anonymous type | |
* | |
* @return a new anonymous type | |
*/ | |
protected InferredType createAnonymousType(IASTNode forNode, InferredType parrentType) { | |
char[] name = createAnonymousTypeName(forNode); | |
InferredType type = addType(name, true); | |
type.isAnonymous = true; | |
type.setIsGlobal(false); | |
if(parrentType != null) { | |
type.setSuperType(parrentType); | |
} | |
return type; | |
} | |
/** | |
* @deprecated - here for compatibility | |
*/ | |
private InferredType createAnonymousType(IAbstractVariableDeclaration var) { | |
InferredType currentType = var.getInferredType(); | |
if (currentType==null || !currentType.isAnonymous) | |
{ | |
InferredType type=createAnonymousType(var, currentType); | |
var.setInferredType(type); | |
} | |
return var.getInferredType(); | |
} | |
/* Creates an anonymous type based in the location in the document. This information is used | |
* to avoid creating duplicates because of the 2-pass nature of this engine. */ | |
private InferredType createAnonymousType(IObjectLiteral objLit) { | |
InferredType anonType = objLit.getInferredType(); | |
if(anonType == null) { | |
char[] name = createAnonymousTypeName(objLit); | |
anonType = addType(name, true); | |
anonType.isAnonymous = true; | |
anonType.isObjectLiteral = true; | |
anonType.setSuperType(this.getObjectType()); | |
anonType.setIsGlobal(false); | |
anonType.sourceStart = objLit.sourceStart(); | |
anonType.sourceEnd = objLit.sourceEnd(); | |
} | |
populateType(anonType, objLit, false); | |
return anonType; | |
} | |
/** | |
* <p> | |
* Creates a global anonymous type. | |
* </p> | |
* | |
* @param varName | |
* name of the global variable to create the global anonymous type for | |
* | |
* @return a global anonymous type created from for the given global variable name | |
*/ | |
protected InferredType createAnonymousGlobalType(char[] varName) { | |
char[] name = createAnonymousGlobalTypeName(varName); | |
InferredType globalType = this.compUnit.findInferredType(name); | |
if(globalType == null) { | |
globalType = this.addType(name, false); | |
globalType.isAnonymous = true; | |
globalType.isObjectLiteral = true; | |
globalType.setSuperType(this.getObjectType()); | |
globalType.setIsGlobal(true); | |
} | |
return globalType; | |
} | |
/** | |
* <p> | |
* Creates an anonymous type name for the given {@link IASTNode} | |
* </p> | |
* | |
* @param node | |
* create the anonymous type name off the location of this node | |
* @return an anonymous type name based off the given nodes location | |
*/ | |
protected static char[] createAnonymousTypeName(IASTNode node) { | |
char[] loc = (String.valueOf(node.sourceStart()) + '_' + String.valueOf(node.sourceEnd())).toCharArray(); | |
return CharOperation.concat(ANONYMOUS_PREFIX, ANONYMOUS_CLASS_ID, loc); | |
} | |
/** | |
* <p> | |
* Creates an anonymous type name from the given variable name. | |
* </p> | |
* | |
* @param varName | |
* to use when creating the anonymous type name | |
* @return | |
*/ | |
public static char[] createAnonymousGlobalTypeName(char[] varName) { | |
return CharOperation.concat(CharOperation.concat(ANONYMOUS_PREFIX, ANONYMOUS_CLASS_ID), varName, '_'); | |
} | |
/** | |
* handle the inferencing for an assignment whose right hand side is a function expression | |
* | |
* @param the | |
* assignment AST node | |
* @return true if handled | |
*/ | |
protected boolean handleFunctionExpressionAssignment(IAssignment assignment) { | |
IFunctionExpression functionExpression = null; | |
if(assignment.getExpression() instanceof IFunctionExpression) { | |
functionExpression = (IFunctionExpression) assignment.getExpression(); | |
} else if(assignment.getExpression() instanceof IAllocationExpression) { | |
functionExpression = (IFunctionExpression) ((IAllocationExpression) assignment.getExpression()).getMember(); | |
} else if(assignment.getExpression() instanceof IAssignment) { | |
functionExpression = (FunctionExpression) ((IAssignment) assignment.getExpression()).getExpression(); | |
} | |
if (functionExpression == null) { | |
return false; | |
} | |
MethodDeclaration methodDeclaration = functionExpression.getMethodDeclaration(); | |
char[] possibleTypeName = constructTypeName(assignment.getLeftHandSide()); | |
InferredType type = null; | |
if(possibleTypeName != null) { | |
type = compUnit.findInferredType(possibleTypeName); | |
if(type == null && isPossibleClassName(possibleTypeName)) { | |
type = addType(possibleTypeName, true); | |
} | |
if(type == null && methodDeclaration.getJsDoc() != null | |
&& ((Javadoc) methodDeclaration.getJsDoc()).isConstructor) { | |
type = addType(possibleTypeName, true); | |
handleJSDocConstructor(type, methodDeclaration, assignment.sourceStart()); | |
} | |
} | |
// isConstructor | |
if(type != null) { | |
if(this.inferOptions.useInitMethod) { | |
this.currentContext.currentType = type; | |
int nameStart = assignment.getLeftHandSide().sourceStart(); | |
int expressionEnd = assignment.getExpression().sourceEnd(); | |
handleConstructor(type, methodDeclaration, nameStart, expressionEnd); | |
// want to be sure to set the type of the assignment and local declaration if there is one | |
assignment.setInferredType(type); | |
if(this.currentContext.currentLocalDeclaration != null && CharOperation.equals(this.currentContext.currentLocalDeclaration.getName(), getName(assignment))) { | |
this.currentContext.currentLocalDeclaration.setInferredType(type); | |
} | |
// constructor is actually an anonymous function assigned to a single name | |
methodDeclaration.setIsAnonymous(true); | |
} | |
} else {// could be method | |
if(assignment.getLeftHandSide() instanceof FieldReference | |
|| assignment.getLeftHandSide() instanceof ArrayReference) { | |
Reference ref = (Reference) assignment.getLeftHandSide(); | |
Expression receiver = null; | |
char[] methodName = null; | |
int nameStart = 0; | |
if(ref instanceof FieldReference) { | |
receiver = ((FieldReference) ref).receiver; | |
methodName = ((FieldReference) ref).token; | |
nameStart = (int) (((FieldReference) ref).nameSourcePosition >>> 32); | |
} else if(ref instanceof ArrayReference) { | |
if(((ArrayReference) ref).position instanceof StringLiteral) { | |
receiver = ((ArrayReference) ref).receiver; | |
methodName = ((StringLiteral) ((ArrayReference) ref).position).source(); | |
nameStart = ((StringLiteral) ((ArrayReference) ref).position).sourceStart + 1; | |
} | |
} | |
//if no receiver then done | |
if(receiver == null) { | |
return false; | |
} | |
InferredType receiverType = getInferredType(receiver); | |
if(receiverType == null && passNumber == 2) { | |
receiverType = this.getReceiverType(receiver, true); | |
} | |
if(receiverType != null && methodName != null) { | |
if(!receiver.isThis()) { | |
receiverType = this.createTypeToAssignTo(receiver, receiverType); | |
} | |
// check if there is a member method already created | |
InferredMethod method = receiverType.findMethod(methodName, methodDeclaration); | |
if(method == null) { | |
// create member method if it does not exist | |
method = receiverType.addMethod(methodName, methodDeclaration, nameStart); | |
receiverType.setIsDefinition(true); | |
/* determine if static | |
* check if the receiver is a type */ | |
char[] possibleInTypeName = constructTypeName(receiver); | |
method.isStatic = (possibleInTypeName != null && compUnit.findInferredType(possibleInTypeName) != null); | |
return true; // keep visiting to get return type | |
} else if(this.passNumber == 2) { | |
return false; // no need to visit again | |
} | |
} else if(this.passNumber == 2 && methodName != null) { // create anonymous class | |
receiverType = this.getReceiverType(receiver, false); | |
if(receiverType != null) { | |
InferredMethod method = receiverType.addMethod(methodName, methodDeclaration, nameStart); | |
method.isStatic = !receiverType.isObjectLiteral; | |
receiverType.updatePositions(assignment.sourceStart(), assignment.sourceEnd()); | |
} | |
} | |
} else if(assignment.getLeftHandSide() instanceof SingleNameReference && this.passNumber == 2) { | |
// set the inferred type | |
assignment.setInferredType(getTypeOf(assignment.getExpression())); | |
methodDeclaration.setIsAnonymous(true); | |
methodDeclaration.setSelector(((SingleNameReference) assignment.getLeftHandSide()).token); | |
methodDeclaration.sourceStart = (((SingleNameReference) assignment.getLeftHandSide()).sourceStart()); | |
} | |
} | |
return true; | |
} | |
/** | |
* <p> | |
* Handle a local declaration who's right hand side is a function. | |
* </p> | |
* <p> | |
* Use case: | |
* </p> | |
* | |
* <pre> | |
* foo.bar.Test = function() { this.num = 42; } | |
* </pre> | |
* | |
* @param localDeclaration | |
* {@link ILocalDeclaration} to attempt to infer a type from | |
* @return <code>true</code> if keep visiting, <code>false</code> otherwise. | |
*/ | |
private boolean handleFunctionExpressionLocalDeclaration(ILocalDeclaration localDeclaration) { | |
boolean keepVisiting = true; | |
IFunctionExpression functionExpression = null; | |
IExpression expression = localDeclaration.getInitialization(); | |
if(expression instanceof IFunctionExpression) { | |
functionExpression = (IFunctionExpression) expression; | |
} else if(expression instanceof IAllocationExpression) { | |
functionExpression = (IFunctionExpression) ((IAllocationExpression) expression).getMember(); | |
} else if(expression instanceof IAssignment) { | |
functionExpression = (FunctionExpression) ((IAssignment) expression).getExpression(); | |
} | |
if (functionExpression == null) { | |
return false; | |
} | |
MethodDeclaration methodDeclaration = functionExpression.getMethodDeclaration(); | |
char[] possibleTypeName = localDeclaration.getName(); | |
InferredType type = null; | |
if(possibleTypeName != null) { | |
type = compUnit.findInferredType(possibleTypeName); | |
if(type == null && isPossibleClassName(possibleTypeName)) { | |
type = addType(possibleTypeName, true); | |
} | |
if(type == null && methodDeclaration.getJsDoc() != null | |
&& ((Javadoc) methodDeclaration.getJsDoc()).isConstructor) { | |
type = addType(possibleTypeName, true); | |
handleJSDocConstructor(type, methodDeclaration, localDeclaration.sourceStart()); | |
} | |
} | |
if(type != null) { // isConstructor | |
if(this.inferOptions.useInitMethod) { | |
this.currentContext.currentType = type; | |
type.setIsDefinition(true); | |
int nameStart = localDeclaration.sourceStart(); | |
type.addConstructorMethod(type.name, methodDeclaration, nameStart); | |
type.updatePositions(nameStart, localDeclaration.getInitialization().sourceEnd()); | |
//set the type for the local declaration to be the new type | |
localDeclaration.setInferredType(type); | |
localDeclaration.setIsType(true); | |
} | |
keepVisiting = false; | |
} | |
return keepVisiting; | |
} | |
/** | |
* @param assignment | |
* @return whether a type was not created for this assignment | |
* @since 1.3 | |
*/ | |
protected boolean handleLocalDeclarationExpressionType(ILocalDeclaration localDeclaration) { | |
IExpression expr = localDeclaration.getInitialization(); | |
InferredType type = null; | |
if(expr instanceof IAssignment) { | |
type = ((IAssignment) expr).getInferredType(); | |
} else if(expr instanceof IAbstractVariableDeclaration) { | |
type = ((IAbstractVariableDeclaration) expr).getInferredType(); | |
} else if(expr instanceof IFieldReference) { | |
IExpression receiver = ((IFieldReference) expr).getReceiver(); | |
InferredType receiverType = this.getTypeOf(receiver); | |
if(receiverType != null) { | |
InferredAttribute attr = receiverType.findAttribute(((IFieldReference) expr).getToken()); | |
if (attr != null) | |
type = attr.inType; | |
} | |
} else if(expr instanceof ISingleNameReference) { | |
IAbstractVariableDeclaration varDecl = this.getVariable(expr); | |
if(varDecl != null) { | |
type = varDecl.getInferredType(); | |
} | |
if(type == null) { | |
IAssignment assign = this.getAssignment(expr); | |
if(assign != null) { | |
type = assign.getInferredType(); | |
} | |
} | |
if(type == null) { | |
IAbstractFunctionDeclaration funcDecl = this.getFunction(expr); | |
if(funcDecl != null && funcDecl.getName() != null) { | |
type = this.findDefinedType(funcDecl.getName()); | |
} | |
} | |
if(type == null) { | |
type =this.compUnit.findInferredType(((ISingleNameReference) expr).getToken()); | |
} | |
} else if(expr instanceof IThisReference) { | |
InferredType newType = null; | |
IFunctionDeclaration parentMethod = this.currentContext.currentMethod; | |
IAssignment parentAssignment = this.currentContext.parent.currentAssignment; | |
ILocalDeclaration parentLocalDeclaration = this.currentContext.parent.currentLocalDeclaration; | |
char[] newTypeName = null; | |
int typeStart = 0; | |
int typeEnd = 0; | |
/* if there is a current assignment and LHS is a function and that function | |
* is the current method then use the RHS as the type name | |
* | |
* else if there is a current local declaration and the LHS is a function and | |
* that function is the current method then use the RHS as the type name | |
* | |
* else if the parent method is not in a type and has a name use that as the type | |
* name */ | |
if(this.currentContext.parent != null | |
&& parentAssignment != null | |
&& parentAssignment.getExpression() instanceof IFunctionExpression | |
&& ((IFunctionExpression) parentAssignment.getExpression()).getMethodDeclaration() == parentMethod) { | |
// see if we're adding a field through the prototype | |
if (parentAssignment.getLeftHandSide().getASTType() == IASTNode.FIELD_REFERENCE | |
&& ((Expression) ((IFieldReference) parentAssignment.getLeftHandSide()).getReceiver()).isPrototype()) { | |
newTypeName = Util.getTypeName(((IFieldReference) ((IFieldReference) parentAssignment.getLeftHandSide()).getReceiver()).getReceiver()); | |
} | |
else { | |
newTypeName = Util.getTypeName(parentAssignment.getLeftHandSide()); | |
} | |
typeStart = parentAssignment.sourceStart(); | |
typeEnd = parentAssignment.sourceEnd(); | |
} else if(this.currentContext.parent != null | |
&& parentLocalDeclaration != null | |
&& parentLocalDeclaration.getInitialization() instanceof IFunctionExpression | |
&& ((IFunctionExpression) parentLocalDeclaration.getInitialization()).getMethodDeclaration() == parentMethod) { | |
newTypeName = parentLocalDeclaration.getName(); | |
typeStart = parentLocalDeclaration.sourceStart(); | |
typeEnd = parentLocalDeclaration.sourceEnd(); | |
} else if(parentMethod != null | |
&& parentMethod.getName() != null | |
&& (parentMethod.getInferredMethod() == null || parentMethod.getInferredMethod().inType == null)) { | |
newTypeName = parentMethod.getName(); | |
typeStart = parentMethod.sourceStart(); | |
typeEnd = parentMethod.sourceEnd(); | |
} | |
// if calculated new type name, use it to create a new type | |
if(newTypeName != null) { | |
newType = compUnit.findInferredType(newTypeName); | |
// create the new type if not found | |
if(newType == null) { | |
newType = addType(newTypeName); | |
} | |
} else { | |
return false; // no type to create | |
} | |
newType.setIsDefinition(true); | |
newType.updatePositions(typeStart, typeEnd); | |
type = newType; | |
} | |
// prevent Object literal based anonymous types from being created more than once | |
if(passNumber == 1 && expr instanceof IObjectLiteral) { | |
return false; | |
} | |
if (type != null) { | |
this.setTypeOf(expr, type); | |
if (localDeclaration instanceof AbstractVariableDeclaration) { | |
((AbstractVariableDeclaration)localDeclaration).setInferredType(type); | |
} | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @param assignment | |
* @return whether a type was not created for this assignment | |
*/ | |
protected boolean handlePotentialType(IAssignment assignment) { | |
IExpression lhs = assignment.getLeftHandSide(); | |
if(lhs instanceof FieldReference) { | |
FieldReference fieldReference = (FieldReference) lhs; | |
/* foo.prototype = ? */ | |
if(fieldReference.isPrototype()) { | |
/* When encountering a prototype, we are going to assume that the | |
* receiver is a type. | |
* | |
* If the type had not been inferred, it will be added at this point */ | |
InferredType newType = null; | |
char[] possibleTypeName = constructTypeName(fieldReference.getReceiver()); | |
if(possibleTypeName != null) | |
newType = compUnit.findInferredType(possibleTypeName); | |
else | |
return true; // no type created | |
// create the new type if not found | |
if(newType == null) { | |
// if we have the function, check if it has a return type, if so, don't consider it a new type | |
IAbstractFunctionDeclaration theFunction = this.getFunction(fieldReference.getReceiver()); | |
IAbstractVariableDeclaration theVariable = null; | |
if(theFunction == null) | |
theVariable = this.getVariable(fieldReference.getReceiver()); | |
if(theFunction != null) { | |
if(theFunction.getInferredType() != null && !theFunction.getInferredType().isVoid()) { | |
return false; | |
} | |
} else if (theVariable != null) { | |
if(theVariable.getInitialization() != null && theVariable.getInitialization() instanceof IFunctionExpression) { | |
if(((IFunctionExpression)theVariable.getInitialization()).getMethodDeclaration().getInferredType() != null && | |
!((IFunctionExpression)theVariable.getInitialization()).getMethodDeclaration().getInferredType().isVoid()) { | |
return false; | |
} | |
} | |
} | |
newType = addType(possibleTypeName, true); | |
} | |
newType.setIsDefinition(true); | |
newType.updatePositions(assignment.sourceStart(), assignment.sourceEnd()); | |
/* foo.prototype = new ... */ | |
if(assignment.getExpression() instanceof IAllocationExpression) { | |
// setting the super type | |
IAllocationExpression allocationExpression = (IAllocationExpression) assignment.getExpression(); | |
InferredType superType = null; | |
char[] possibleSuperTypeName = constructTypeName(allocationExpression.getMember()); | |
if(possibleSuperTypeName != null) { | |
superType = compUnit.findInferredType(possibleSuperTypeName); | |
if(superType == null) | |
superType = addType(possibleSuperTypeName); | |
// check if it is set already because it might be set by jsdocs | |
if(newType.getSuperType() == null) | |
newType.setSuperType(superType); | |
} | |
return true; | |
} | |
/* foo.prototype = {...} */ | |
else if(assignment.getExpression() instanceof IObjectLiteral) { | |
// rather than creating an anonymous type, is better just to set the members | |
// directly | |
// on newType | |
populateType(newType, (IObjectLiteral) assignment.getExpression(), false); | |
// check if it is set already because it might be set by jsdocs | |
if(newType.getSuperType() == null) | |
newType.setSuperType(this.getObjectType()); | |
return true; | |
} | |
/* foo.prototype = foo.someField; */ | |
else if(assignment.getExpression() instanceof FieldReference) { | |
InferredType superType = getTypeOf(assignment.getExpression()); | |
if(newType.getSuperType() == null && superType != null) { | |
newType.setSuperType(superType); | |
} | |
return true; | |
} | |
/* foo.prototype = somevar; */ | |
else if(assignment.getExpression() instanceof SingleNameReference) { | |
InferredType superType = getTypeOf(assignment.getExpression()); | |
if (newType.getSuperType() == null && superType != null) { | |
newType.setSuperType(superType); | |
} | |
return true; | |
} | |
} | |
/* foo.prototype.bar = ? */ | |
else if(fieldReference.receiver.isPrototype() ) { | |
FieldReference prototype = (FieldReference) fieldReference.receiver; | |
//if prototype receiver is a type, get its type | |
InferredType assignedToType = null; | |
if(this.isExpressionAType(prototype.receiver)) { | |
assignedToType = this.getTypeOf(prototype.receiver); | |
} | |
//if not found assigned to type and can create possible name, then create a type | |
if(assignedToType == null) { | |
char[] possibleTypeName = constructTypeName(prototype.receiver); | |
if(possibleTypeName != null) { | |
assignedToType = addType(possibleTypeName); | |
assignedToType.updatePositions(assignment.sourceStart(), assignment.sourceEnd()); | |
assignedToType.setIsDefinition(true); | |
} else { | |
return true; // can not create type, keep visiting | |
} | |
} | |
// prevent Object literal based anonymous types from being created more than once | |
if(passNumber == 1 && assignment.getExpression() instanceof IObjectLiteral) { | |
return false; | |
} | |
char[] memberName = fieldReference.token; | |
int nameStart = (int) (fieldReference.nameSourcePosition >>> 32); | |
InferredType typeOf = | |
(assignment.getJsDoc() != null && assignment.getJsDoc() instanceof Javadoc && ((Javadoc) assignment.getJsDoc()).returnType != null) ? this.addType(changePrimitiveToObject(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName())) | |
: getTypeOf(assignment.getExpression()); | |
IFunctionDeclaration methodDecl = null; | |
if(typeOf == null || typeOf == this.getFunctionType()) | |
methodDecl = getDefinedFunction(assignment.getExpression()); | |
if(methodDecl != null) { | |
assignedToType.addMethod(memberName, methodDecl, nameStart); | |
} else { | |
InferredAttribute attribute = assignedToType.addAttribute(memberName, assignment, nameStart); | |
handleAttributeDeclaration(attribute, assignment.getExpression()); | |
attribute.initializationStart = assignment.getExpression().sourceStart(); | |
if(attribute.type == null) | |
attribute.type = typeOf; | |
} | |
return true; | |
} | |
/* this.foo = ? */ | |
else if(fieldReference.receiver instanceof IThisReference) { | |
InferredType newType = null; | |
IFunctionDeclaration parentMethod = this.currentContext.currentMethod; | |
IAssignment parentAssignment = this.currentContext.parent.currentAssignment; | |
ILocalDeclaration parentLocalDeclaration = this.currentContext.parent.currentLocalDeclaration; | |
char[] newTypeName = null; | |
int typeStart = 0; | |
int typeEnd = 0; | |
/* if there is a current assignment and LHS is a function and that function | |
* is the current method then use the RHS as the type name | |
* | |
* else if there is a current local declaration and the LHS is a function and | |
* that function is the current method then use the RHS as the type name | |
* | |
* else if the parent method is not in a type and has a name use that as the type | |
* name */ | |
if(this.currentContext.parent != null | |
&& parentAssignment != null | |
&& parentAssignment.getExpression() instanceof IFunctionExpression | |
&& ((IFunctionExpression) parentAssignment.getExpression()).getMethodDeclaration() == parentMethod) { | |
// see if we're adding a field through the prototype | |
if (parentAssignment.getLeftHandSide().getASTType() == IASTNode.FIELD_REFERENCE | |
&& ((Expression) ((IFieldReference) parentAssignment.getLeftHandSide()).getReceiver()).isPrototype()) { | |
newTypeName = Util.getTypeName(((IFieldReference) ((IFieldReference) parentAssignment.getLeftHandSide()).getReceiver()).getReceiver()); | |
} | |
else { | |
newTypeName = Util.getTypeName(parentAssignment.getLeftHandSide()); | |
} | |
typeStart = parentAssignment.sourceStart(); | |
typeEnd = parentAssignment.sourceEnd(); | |
} else if(this.currentContext.parent != null | |
&& parentLocalDeclaration != null | |
&& parentLocalDeclaration.getInitialization() instanceof IFunctionExpression | |
&& ((IFunctionExpression) parentLocalDeclaration.getInitialization()).getMethodDeclaration() == parentMethod) { | |
newTypeName = parentLocalDeclaration.getName(); | |
typeStart = parentLocalDeclaration.sourceStart(); | |
typeEnd = parentLocalDeclaration.sourceEnd(); | |
} else if(parentMethod != null | |
&& parentMethod.getName() != null | |
&& (parentMethod.getInferredMethod() == null || parentMethod.getInferredMethod().inType == null)) { | |
newTypeName = parentMethod.getName(); | |
typeStart = parentMethod.sourceStart(); | |
typeEnd = parentMethod.sourceEnd(); | |
} | |
// if calculated new type name, use it to create a new type | |
if(newTypeName != null) { | |
newType = compUnit.findInferredType(newTypeName); | |
// create the new type if not found | |
if(newType == null) { | |
newType = addType(newTypeName); | |
} | |
} else { | |
return false; // no type to create | |
} | |
newType.setIsDefinition(true); | |
newType.updatePositions(typeStart, typeEnd); | |
//if there is a parent assignment then set the inferred type and that it is a type | |
if(parentAssignment != null) { | |
parentAssignment.setInferredType(newType); | |
parentAssignment.setIsType(true); | |
} | |
//if there is a parent declaration then set the inferred type and that it is a type | |
if(parentLocalDeclaration != null) { | |
parentLocalDeclaration.setInferredType(newType); | |
parentLocalDeclaration.setIsType(true); | |
} | |
// prevent Object literal based anonymous types from being created more than once | |
if(passNumber == 1 && assignment.getExpression() instanceof IObjectLiteral) { | |
return false; | |
} | |
char[] memberName = fieldReference.token; | |
int nameStart = (int) (fieldReference.nameSourcePosition >>> 32); | |
InferredType typeOf = getTypeOf(assignment.getExpression()); | |
IFunctionDeclaration methodDecl = null; | |
if(typeOf == null || typeOf == this.getFunctionType()) { | |
methodDecl = getDefinedFunction(assignment.getExpression()); | |
} | |
if(methodDecl != null) { | |
newType.addMethod(memberName, methodDecl, nameStart); | |
if(methodDecl.getInferredType() == null && assignment.getJsDoc() != null | |
&& ((Javadoc) assignment.getJsDoc()).returnType != null) { | |
if(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName() != null) | |
methodDecl.setInferredType(addType(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName())); | |
} | |
} else { | |
InferredAttribute attribute = newType.addAttribute(memberName, assignment, nameStart); | |
if(attribute.type == null && assignment.getJsDoc() != null | |
&& ((Javadoc) assignment.getJsDoc()).returnType != null) { | |
if(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName() != null) | |
attribute.type = addType(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName()); | |
} | |
handleAttributeDeclaration(attribute, assignment.getExpression()); | |
attribute.initializationStart = assignment.getExpression().sourceStart(); | |
if(attribute.type == null) | |
attribute.type = typeOf; | |
} | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Get the function referenced by the expression | |
* | |
* @param expression | |
* AST node | |
* @return the function or null | |
*/ | |
protected IFunctionDeclaration getDefinedFunction(IExpression expression) { | |
if(expression instanceof SingleNameReference) { | |
Object object = this.currentContext.getMember(((SingleNameReference) expression).token); | |
if(object instanceof AbstractMethodDeclaration) { | |
return (MethodDeclaration) object; | |
} | |
} else if(expression instanceof FunctionExpression) { | |
return ((FunctionExpression) expression).methodDeclaration; | |
} else if(expression instanceof FieldReference) { | |
FieldReference fieldReference = (FieldReference) expression; | |
InferredType receiverType = getInferredType(fieldReference.receiver); | |
if(receiverType == null && passNumber == 2) { | |
receiverType = this.getReceiverType(fieldReference.receiver, false); | |
} | |
if(receiverType != null) { | |
InferredMethod method = receiverType.findMethod(fieldReference.token, null); | |
if(method != null) { | |
return method.getFunctionDeclaration(); | |
} | |
} | |
} | |
return null; | |
} | |
/** | |
* <p> | |
* Sets the inferred type of the given expression to the given type. Any existing inferred type | |
* is overridden. If the given expression is not supported by this method then it is a no op. | |
* </p> | |
* | |
* <p> | |
* Currently supports: | |
* <ul> | |
* <li>{@link ISingleNameReference}</li> | |
* <li>{@link FieldReference} - if it can resolve everything it needs to</li> | |
* <li>{@link IObjectLiteral}</li> | |
* <li>{@link IThisReference}</li> | |
* </ul> | |
* </p> | |
* | |
* @param expression | |
* to set the inferred type for | |
* @param type | |
* inferred type to set on the given expression | |
*/ | |
protected void setTypeOf(IExpression expression, InferredType type) { | |
if(expression instanceof ISingleNameReference) { | |
//set on variable declaration if there is one | |
IAbstractVariableDeclaration varDecl = getVariable(expression); | |
if(varDecl != null) { | |
varDecl.setInferredType(type); | |
} | |
//set on global field if there is one | |
if(this.inferredGlobal != null) { | |
InferredAttribute attribute = | |
this.inferredGlobal.findAttribute(((ISingleNameReference) expression).getToken()); | |
if(attribute != null) { | |
attribute.type = type; | |
} | |
} | |
//set on assignment if there is one | |
IAssignment varAssignment = this.getAssignment(expression); | |
if(varAssignment != null) { | |
varAssignment.setInferredType(type); | |
} | |
} else if(expression instanceof FieldReference) { | |
FieldReference fieldReference = (FieldReference) expression; | |
InferredType parentType = this.getTypeOf(fieldReference.getReceiver()); | |
if(parentType != null) { | |
InferredAttribute attr = parentType.findAttribute(fieldReference.token); | |
if(attr != null) { | |
attr.type = type; | |
} | |
} | |
} else if(expression instanceof IObjectLiteral) { | |
((IObjectLiteral) expression).setInferredType(type); | |
} else if(expression instanceof IThisReference) { | |
this.currentContext.currentType = type; | |
} | |
} | |
protected InferredType getTypeOf(IExpression expression) { | |
if(expression instanceof IStringLiteral) { | |
return this.getStringType(); | |
} else if(expression instanceof INumberLiteral) { | |
return this.getNumberType(); | |
} else if(expression instanceof IAllocationExpression) { | |
IAllocationExpression allocationExpression = (IAllocationExpression) expression; | |
IExpression member = allocationExpression.getMember(); | |
InferredType type = null; | |
//check for type with the same name as the allocation member | |
char[] possibleTypeName = constructTypeName(member); | |
if(possibleTypeName != null) { | |
type = compUnit.findInferredType(possibleTypeName); | |
} | |
/* if no existing type with the same name as the allocation member | |
* attempt to get the type of the member */ | |
if(type == null) { | |
type = this.getTypeOf(member); | |
} | |
/* if no type with same name as member, and member does not have a type, | |
* create a new type with the same name as the member */ | |
if((type == null || type == getFunctionType()) && possibleTypeName != null && possibleTypeName.length > 0) { | |
type = this.addType(possibleTypeName, false); | |
} | |
return type; | |
} else if(expression instanceof ISingleNameReference) { | |
//check for variable declaration to get type from | |
IAbstractVariableDeclaration varDecl = this.getVariable(expression); | |
if(varDecl != null) { | |
return varDecl.getInferredType(); | |
} | |
//check global type to get type from | |
if(this.inferredGlobal != null) { | |
InferredAttribute attribute = | |
this.inferredGlobal.findAttribute(((ISingleNameReference) expression).getToken()); | |
if(attribute != null) { | |
return attribute.type; | |
} | |
} | |
//check for an assignment to get the type from | |
IAssignment assign = this.getAssignment(expression); | |
if(assign != null) { | |
InferredType type = assign.getInferredType(); | |
if(type != null) { | |
return type; | |
} | |
} | |
/* check if expression is a function and if there is a type with the same name, | |
* if so then the given expression is a reference to that type */ | |
IAbstractFunctionDeclaration funcDecl = this.getFunction(expression); | |
if(funcDecl != null) { | |
InferredType type = this.findDefinedType(funcDecl.getName()); | |
if(type != null) { | |
return type; | |
} | |
} | |
//search for type with that name | |
char[] possibleTypeName = this.constructTypeName(expression); | |
if(possibleTypeName != null) { | |
InferredType type = compUnit.findInferredType(possibleTypeName); | |
if(type != null) { | |
return type; | |
} | |
} | |
//search for anonymous global type based of SNR | |
char[] possibleGlobalTypeName = createAnonymousGlobalTypeName(((SingleNameReference)expression).token); | |
if(possibleGlobalTypeName != null) { | |
InferredType type = compUnit.findInferredType(possibleGlobalTypeName); | |
if(type != null) { | |
return type; | |
} | |
} | |
if(funcDecl != null) { | |
return getFunctionType(); | |
} | |
} else if(expression instanceof FieldReference) { | |
FieldReference fieldReference = (FieldReference) expression; | |
/* if this reference and in current type | |
* else not */ | |
if(fieldReference.receiver.isThis() && currentContext.currentType != null) { | |
InferredAttribute attribute = currentContext.currentType.findAttribute(fieldReference.getToken()); | |
if(attribute != null) { | |
return attribute.type; | |
} | |
} else { | |
//get the receiver type then get the type of an existing field or function | |
InferredType recieverType = this.getReceiverType(((FieldReference) expression).getReceiver(), false); | |
if(recieverType != null) { | |
char[] fieldName = ((FieldReference) expression).getToken(); | |
InferredAttribute attr = recieverType.findAttribute(fieldName); | |
if(attr != null) { | |
return attr.type; | |
} else if(recieverType.findMethod(fieldName, null) != null){ | |
return this.getFunctionType(); | |
} | |
} | |
} | |
char[] typeName = constructTypeName(expression); | |
if(typeName != null && typeName.length > 0) { | |
InferredType type = this.findDefinedType(typeName); | |
if(type != null) { | |
return type; | |
} | |
} | |
} else if(expression instanceof IReturnStatement) { | |
return ((IReturnStatement) expression).getInferredType(); | |
} else if(expression instanceof ArrayInitializer) { | |
ArrayInitializer arrayInitializer = (ArrayInitializer) expression; | |
boolean typeSet = false; | |
InferredType memberType = null; | |
if(arrayInitializer.expressions != null) | |
for(int i = 0; i < arrayInitializer.expressions.length; i++) { | |
InferredType thisType = getTypeOf(arrayInitializer.expressions[i]); | |
if(thisType != null) { | |
if(!thisType.equals(memberType)) | |
if(!typeSet) { | |
memberType = thisType; | |
} else { | |
memberType = null; | |
} | |
typeSet = true; | |
} | |
} | |
if(memberType != null) { | |
InferredType type = new InferredType(InferredType.ARRAY_NAME); | |
type.referenceClass = memberType; | |
return type; | |
} else { | |
return this.getArrayType(); | |
} | |
} else if(expression instanceof ITrueLiteral || expression instanceof IFalseLiteral) { | |
return this.getBooleanType(); | |
} else if(expression instanceof IObjectLiteral) { | |
// create an anonymous type based on the ObjectLiteral | |
InferredType type = createAnonymousType((IObjectLiteral) expression); | |
// set the start and end | |
type.sourceStart = expression.sourceStart(); | |
type.sourceEnd = expression.sourceEnd(); | |
return type; | |
} else if(expression instanceof IThisReference) { | |
return this.currentContext.currentType != null ? this.currentContext.currentType : getInferredGlobal(true); | |
} else if(expression instanceof Assignment) | |
return getTypeOf(((Assignment) expression).getExpression()); | |
else if(expression instanceof FunctionExpression) | |
return this.getFunctionType(); | |
else if(expression instanceof UnaryExpression) { | |
return getTypeOf(((UnaryExpression) expression).expression); | |
} else if(expression instanceof BinaryExpression) { | |
BinaryExpression bExpression = (BinaryExpression) expression; | |
int operator = (bExpression.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT; | |
switch(operator) { | |
case OperatorIds.MULTIPLY: | |
case OperatorIds.DIVIDE: | |
case OperatorIds.REMAINDER: | |
case OperatorIds.MINUS: | |
case OperatorIds.LEFT_SHIFT: | |
case OperatorIds.RIGHT_SHIFT: | |
return this.getNumberType(); | |
case OperatorIds.PLUS: { | |
InferredType leftType = getTypeOf(bExpression.left); | |
InferredType rightType = getTypeOf(bExpression.right); | |
if(leftType != null && leftType.equals(this.getStringType())) { | |
return this.getStringType(); | |
} | |
if(rightType != null && rightType.equals(this.getStringType())) { | |
return this.getStringType(); | |
} | |
if(leftType == null || rightType == null) { | |
return null; | |
} | |
if(leftType.equals(this.getStringType()) || rightType.equals(this.getStringType())) { | |
return this.getStringType(); | |
} else if(leftType.equals(this.getNumberType()) && rightType.equals(this.getNumberType())) { | |
return this.getNumberType(); | |
} | |
return null; | |
} | |
case OperatorIds.AND_AND: | |
case OperatorIds.OR_OR: { | |
/* if the case: | |
* foo = foo || "" | |
* return foo || "" | |
* foo = foo && "" | |
* return foo || "" | |
* else a normal || binary case */ | |
if((this.currentContext.currentAssignment != null && expressionContains(this.currentContext.currentAssignment.getExpression(), bExpression)) || | |
(this.currentContext.currentLocalDeclaration != null && expressionContains(this.currentContext.currentLocalDeclaration.getInitialization(), bExpression)) || | |
(this.currentContext.currentReturn != null && expressionContains(this.currentContext.currentReturn.getExpression(), bExpression))) { | |
InferredType leftType = this.getTypeOf(bExpression.left); | |
InferredType rightType = this.getTypeOf(bExpression.right); | |
/* if both the left and right type are the same then just return the left one | |
* else merge the two types into one type | |
*/ | |
if(leftType == rightType) { | |
return leftType; | |
} else if(leftType != null && rightType != null) { | |
InferredType newCombinedType = this.createAnonymousType(expression, null); | |
/* add the left type to the combined type | |
* | |
* if the left type is indexed then add it later | |
* else it is not must add it now */ | |
if(leftType.isIndexed()) { | |
newCombinedType.addMixin(leftType.getName()); | |
} else { | |
newCombinedType.mixin(leftType); | |
} | |
/* add the right type to the combined type | |
* | |
* if the right type is indexed then add it later | |
* else it is not must add it now */ | |
if(rightType.isIndexed()) { | |
newCombinedType.addMixin(rightType.getName()); | |
} else { | |
newCombinedType.mixin(rightType); | |
} | |
return newCombinedType; | |
} else if(leftType != null) { | |
return leftType; | |
} else if(rightType != null) { | |
return rightType; | |
} | |
} | |
return this.getBooleanType(); | |
} | |
case OperatorIds.EQUAL_EQUAL: | |
case OperatorIds.EQUAL_EQUAL_EQUAL: | |
case OperatorIds.NOT_EQUAL: | |
case OperatorIds.NOT_EQUAL_EQUAL: | |
case OperatorIds.GREATER: | |
case OperatorIds.GREATER_EQUAL: | |
case OperatorIds.LESS: | |
case OperatorIds.LESS_EQUAL: | |
case OperatorIds.INSTANCEOF: | |
case OperatorIds.IN: | |
return this.getBooleanType(); | |
default: | |
return null; | |
} | |
} else if(expression instanceof MessageSend) { | |
if(((MessageSend)expression).receiver instanceof IFunctionExpression) { | |
if(((FunctionExpression)((MessageSend)expression).receiver).methodDeclaration != null) { | |
return ((FunctionExpression)((MessageSend)expression).receiver).methodDeclaration.inferredType; | |
} | |
} else if(((MessageSend)expression).selector != null) { | |
// if the method call has no receiver, see if we can look it up | |
// in the current context. If found, the inferredType(return type) of | |
// that function is the value we are looking for. | |
Object method = this.currentContext.getMember(((MessageSend)expression).selector); | |
if(method instanceof MethodDeclaration) { | |
return ((MethodDeclaration) method).inferredType; | |
} | |
} | |
} | |
return null; | |
} | |
protected void populateType(InferredType type, IObjectLiteral objLit, boolean isStatic) { | |
if(objLit.getInferredType() == null) { | |
objLit.setInferredType(type); | |
if(objLit.getFields() != null) { | |
for(int i = 0; i < objLit.getFields().length; i++) { | |
IObjectLiteralField field = objLit.getFields()[i]; | |
char[] name = null; | |
int nameStart = -1; | |
if(field.getFieldName() instanceof SingleNameReference) { | |
SingleNameReference singleNameReference = (SingleNameReference) field.getFieldName(); | |
name = singleNameReference.token; | |
nameStart = singleNameReference.sourceStart; | |
} else if(field.getFieldName() instanceof IStringLiteral) { | |
IStringLiteral stringLiteral = (IStringLiteral) field.getFieldName(); | |
name = stringLiteral.source(); | |
nameStart = stringLiteral.sourceStart(); | |
} else | |
continue; // not supporting this case right now | |
Javadoc javaDoc = (Javadoc) field.getJsDoc(); | |
InferredType returnType = null; | |
if(javaDoc != null) { | |
if(javaDoc.memberOf != null) { | |
char[] typeName = javaDoc.memberOf.getFullTypeName(); | |
convertAnonymousTypeToNamed(type, typeName); | |
type.setIsDefinition(true); | |
} else if(this.currentContext.isJsDocClass && javaDoc.property != null) { | |
if(type.isAnonymous) { | |
InferredType previousType = this.currentContext.currentType; | |
if(previousType != null) { | |
copyAnonymousTypeToNamed(type, previousType); | |
objLit.setInferredType(type = this.currentContext.currentType = previousType); | |
} | |
} | |
} | |
if(javaDoc.returnType != null) { | |
returnType = this.addType(changePrimitiveToObject(javaDoc.returnType.getFullTypeName())); | |
} | |
} | |
// need to build the members of the anonymous inferred type | |
if(field.getInitializer() instanceof IFunctionExpression) { | |
IFunctionExpression functionExpression = (IFunctionExpression) field.getInitializer(); | |
InferredMember method = | |
type.addMethod(name, functionExpression.getMethodDeclaration(), nameStart); | |
method.isStatic = isStatic; | |
if(javaDoc != null) { | |
functionExpression.getMethodDeclaration().modifiers = javaDoc.modifiers; | |
} | |
handleFunctionDeclarationArguments(functionExpression.getMethodDeclaration(), javaDoc); | |
if(returnType != null && functionExpression.getMethodDeclaration().getInferredType() == null) { | |
functionExpression.getMethodDeclaration().setInferredType(returnType); | |
} | |
} | |
// attribute | |
else if (field.getInitializer() != null) { | |
InferredAttribute attribute = type.findAttribute(name); | |
if(attribute == null) { | |
attribute = type.addAttribute(name, field.getInitializer(), nameStart); | |
handleAttributeDeclaration(attribute, field.getInitializer()); | |
attribute.isStatic = isStatic; | |
// @GINO: recursion might not be the best idea | |
if(returnType != null) { | |
attribute.type = returnType; | |
// apply (force) type onto OL initializer | |
if(field.getInitializer() instanceof ObjectLiteral) { | |
((ObjectLiteral) field.getInitializer()).setInferredType(returnType); | |
} | |
} else { | |
attribute.type = getTypeOf(field.getInitializer()); | |
} | |
} | |
//if attribute is function then create a function as well | |
if(attribute.type != null && attribute.type.isFunction()) { | |
if( type.findMethod(attribute.name, null) == null) { | |
IAbstractFunctionDeclaration func = this.getFunction(field.getInitializer()); | |
if(func instanceof IFunctionDeclaration) { | |
type.addMethod(name, (IFunctionDeclaration)func, nameStart); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
public void endVisit(IAssignment assignment) { | |
popContext(); | |
} | |
protected boolean handleAttributeDeclaration(InferredAttribute attribute) { | |
return true; | |
} | |
protected boolean handleAttributeDeclaration(InferredAttribute attribute, IExpression initializer) { | |
return true; | |
} | |
protected boolean handleFunctionCall(IFunctionCall messageSend) { | |
return handleFunctionCall(messageSend, null); | |
} | |
protected boolean handleFunctionCall(IFunctionCall messageSend, LocalDeclaration assignmentExpression) { | |
return true; | |
} | |
public boolean visit(IReturnStatement returnStatement) { | |
this.pushContext(); | |
this.currentContext.currentReturn = returnStatement; | |
if(currentContext.currentMethod != null) { | |
if(returnStatement.getExpression() != null) { | |
InferredType type = null; | |
IExpression expression = returnStatement.getExpression(); | |
if(expression instanceof IObjectLiteral) { | |
type = createAnonymousType((ObjectLiteral) expression); | |
// set the start and end | |
type.sourceStart = expression.sourceStart(); | |
type.sourceEnd = expression.sourceEnd(); | |
} else { | |
type = getTypeOf(expression); | |
} | |
if(currentContext.currentMethod.getInferredType() == this.getVoidType() && type != null) { | |
currentContext.currentMethod.setInferredType(type); | |
} else if(currentContext.currentMethod.getInferredType() == this.getArrayType() | |
&& currentContext.currentMethod.getInferredType().referenceClass == null | |
&& type != null && CharOperation.equals(type.name, TypeConstants.ARRAY[0])){ | |
currentContext.currentMethod.setInferredType(type); | |
} else { | |
/* If the return statement inferred type is null or | |
* the existing inferred return type and the statement return type are not | |
* equal and the return type is either not well known or is well | |
* known and the return type names are the same | |
* | |
* This logic is to cover the scenario where the return type is a known type but | |
* is from a different instance of the InferEngine */ | |
boolean shouldSetToAny = !((MethodDeclaration) currentContext.currentMethod).isInferredJsDocType(); | |
if(type != null && shouldSetToAny) { | |
// get the name of the current methods inferred return type | |
String currentMethodInferredType = null; | |
if(this.currentContext.currentMethod.getInferredType() != null | |
&& this.currentContext.currentMethod.getInferredType().name != null) { | |
currentMethodInferredType = | |
new String(this.currentContext.currentMethod.getInferredType().name); | |
} | |
boolean returnTypesEqual = type.equals(currentContext.currentMethod.getInferredType()); | |
boolean returnTypeNamesEqual = (new String(type.name)).equals(currentMethodInferredType); | |
boolean returnTypeIsWellKnown = WellKnownTypes.containsKey(type.name); | |
shouldSetToAny = | |
!returnTypesEqual | |
&& (!returnTypeIsWellKnown || !(returnTypeIsWellKnown && returnTypeNamesEqual)); | |
} | |
if(shouldSetToAny) { | |
currentContext.currentMethod.setInferredType(null); | |
} | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* @see org.eclipse.wst.jsdt.core.ast.ASTVisitor#endVisit(org.eclipse.wst.jsdt.core.ast.IReturnStatement) | |
*/ | |
public void endVisit(IReturnStatement returnStatement) { | |
this.popContext(); | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.wst.jsdt.core.ast.ASTVisitor#endVisit(org.eclipse.wst.jsdt.core.ast.IFunctionCall) | |
*/ | |
public void endVisit(IFunctionCall functionCall) { | |
super.endVisit(functionCall); | |
if(functionCall.getReceiver() instanceof FunctionExpression) { | |
if (this.contextPtr == -1) { | |
this.isTopLevelAnonymousFunction = true; | |
} | |
if (functionCall instanceof MessageSend && ((MessageSend) functionCall).getArguments() != null) { | |
MethodDeclaration methodDeclaration = ((FunctionExpression) functionCall.getReceiver()).getMethodDeclaration(); | |
if (methodDeclaration != null && methodDeclaration.getArguments() != null) { | |
IArgument[] declaredArguments = methodDeclaration.getArguments(); | |
IExpression[] sentArguments = ((MessageSend) functionCall).getArguments(); | |
for (int i = 0; i < declaredArguments.length; i++) { | |
if (i >= sentArguments.length) { | |
continue; | |
} | |
// if a sent argument now is a subtype of the declared argument, update the sent argument to be the type of the declared argument | |
char[] parameterName = null; | |
if ((parameterName = getName(sentArguments[i])) != null && passNumber == 2 && declaredArguments[i].getInferredType()!=null) { | |
IASTNode node = (IASTNode) currentContext.getMember(parameterName); | |
if (node instanceof IAbstractVariableDeclaration && ((IAbstractVariableDeclaration) node).getInferredType() != null) { | |
if (((IAbstractVariableDeclaration) node).getInferredType().isNamed() && ((LocalDeclaration) node).getInferredType().equals(declaredArguments[i].getInferredType().getSuperType())) { | |
((IAbstractVariableDeclaration) node).setInferredType(declaredArguments[i].getInferredType()); | |
} | |
} | |
else if (node instanceof IAssignment && ((IAssignment) node).getInferredType() != null) { | |
if (((IAssignment) node).getInferredType().isNamed() && ((IAssignment) node).getInferredType().equals(declaredArguments[i].getInferredType().getSuperType())) { | |
((IAssignment) node).setInferredType(declaredArguments[i].getInferredType()); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
public void endVisit(IFunctionDeclaration methodDeclaration) { | |
popContext(); | |
} | |
public boolean visit(IFunctionDeclaration methodDeclaration) { | |
pushContext(); | |
if(this.isTopLevelAnonymousFunction && this.currentContext.currentType == null) { | |
this.currentContext.currentType = this.getInferredGlobal(true); | |
} else if(!this.isTopLevelAnonymousFunction && this.currentContext.currentType != null && CharOperation.equals(this.currentContext.currentType.getName(), IIndexConstants.GLOBAL_SYMBOL)) { | |
// if we are not a top level function and the current type is global, reset it to null. | |
// this prevents this.anything style attributes from leaking to the global scope. | |
this.currentContext.currentType = null; | |
} | |
this.isTopLevelAnonymousFunction = false; | |
char[] methodName = methodDeclaration.getName(); | |
// if declaration didn't have name get name from inferred method if there is one | |
if(methodName == null && methodDeclaration.getInferredMethod() != null) { | |
methodName = methodDeclaration.getInferredMethod().name; | |
} | |
//set the function that contains this assignment | |
if(this.passNumber == 1 && methodDeclaration instanceof AbstractMethodDeclaration && this.currentContext.currentMethod != null) { | |
((AbstractMethodDeclaration)methodDeclaration).setContainingFunction(this.currentContext.currentMethod); | |
} | |
//always need to build arguments | |
buildDefinedMembers(methodDeclaration.getStatements(), methodDeclaration.getArguments()); | |
//only on the first pass should JS doc be looked at | |
if(passNumber == 1) { | |
if(methodDeclaration.getJsDoc() != null) { | |
InferredMethod method = null; | |
Javadoc javadoc = (Javadoc) methodDeclaration.getJsDoc(); | |
createTypeIfNecessary(javadoc); | |
if(javadoc.isConstructor) { | |
InferredType type; | |
if(!this.currentContext.isJsDocClass && methodName != null) { | |
type = this.addType(methodName); | |
} else { | |
type = this.currentContext.currentType; | |
} | |
if(type != null) { | |
handleJSDocConstructor(type, methodDeclaration, methodDeclaration.sourceStart()); | |
} | |
} else if(javadoc.memberOf != null) { | |
InferredType type = this.addType(javadoc.memberOf.getFullTypeName(), true); | |
char[] name = methodName; | |
int nameStart = methodDeclaration.sourceStart(); | |
if(name != null) { | |
method = type.addMethod(methodName, methodDeclaration, nameStart); | |
} | |
} else if(javadoc.methodDef != null && this.currentContext.isJsDocClass) { | |
InferredType type = this.currentContext.currentType; | |
char[][] methName = javadoc.methodDef.getTypeName(); | |
int nameStart = ((MethodDeclaration) methodDeclaration).sourceStart; | |
if(methName.length == 1) { | |
method = type.addMethod(methName[0], methodDeclaration, nameStart); | |
} else { | |
method = type.addMethod(methName[methName.length - 1], methodDeclaration, nameStart); | |
method.isStatic = true; | |
} | |
} | |
if(javadoc.returnType != null) { | |
InferredType type = this.addType(changePrimitiveToObject(javadoc.returnType.getFullTypeName())); | |
methodDeclaration.setInferredType(type); | |
((MethodDeclaration) methodDeclaration).bits |= ASTNode.IsInferredJsDocType; | |
} | |
} | |
handleFunctionDeclarationArguments(methodDeclaration, methodDeclaration.getJsDoc()); | |
} | |
// check if this is a constructor | |
if(passNumber == 2) { | |
if(methodName != null) { | |
InferredType type = compUnit.findInferredType(methodName); | |
InferredMethod inferMeth = methodDeclaration.getInferredMethod(); | |
//only a constructor if the method is in the found type, or is in a type with the same name | |
if(type != null && | |
(inferMeth == null || | |
inferMeth.inType == null || | |
inferMeth.inType == type || | |
CharOperation.equals(inferMeth.inType.getName(), type.getName())) ) { | |
this.currentContext.currentType = type; | |
type.setIsDefinition(true); | |
if(inferMeth == null || !inferMeth.isConstructor) { | |
int nameStart = methodDeclaration.sourceStart(); | |
type.addConstructorMethod(methodName, methodDeclaration, nameStart); | |
} | |
} | |
} | |
} | |
this.currentContext.currentMethod = methodDeclaration; | |
if(methodDeclaration.getInferredMethod() != null && methodDeclaration.getInferredMethod().inType != null) { | |
this.currentContext.currentType = methodDeclaration.getInferredMethod().inType; | |
} | |
if(methodDeclaration.getInferredType() == null) { | |
methodDeclaration.setInferredType(this.getVoidType()); | |
} | |
return true; | |
} | |
protected void handleConstructor(InferredType type, IFunctionDeclaration methodDeclaration, int start, int end) { | |
type.setIsDefinition(true); | |
type.addConstructorMethod(type.name, methodDeclaration, start); | |
type.updatePositions(start, end); | |
} | |
protected void handleJSDocConstructor(InferredType type, IFunctionDeclaration methodDeclaration, int nameStart) { | |
Javadoc javadoc = (Javadoc) methodDeclaration.getJsDoc(); | |
type.setIsDefinition(true); | |
type.addConstructorMethod(type.name, methodDeclaration, nameStart); | |
if(javadoc.extendsType != null) { | |
InferredType superType = this.addType(javadoc.extendsType.getFullTypeName()); | |
type.setSuperType(superType); | |
} | |
} | |
protected void handleFunctionDeclarationArguments(IFunctionDeclaration methodDeclaration, IJsDoc jsdoc) { | |
if(jsdoc == null || !(jsdoc instanceof Javadoc)) | |
return; | |
Javadoc javadoc = (Javadoc) jsdoc; | |
IArgument[] arguments = methodDeclaration.getArguments(); | |
if(arguments != null) { | |
for(int i = 0; i < arguments.length; i++) { | |
//skip if argument already has a named type | |
InferredType currType = arguments[i].getInferredType(); | |
if(currType != null && !currType.isAnonymous) { | |
//TODO: this could possibly be smarter | |
continue; | |
} | |
InferredType paramType = null; | |
JavadocSingleNameReference param = javadoc.findParam(arguments[i].getName()); | |
if(param != null) { | |
if(param.types != null) { | |
char[] name = {}; | |
for(int j = 0; j < param.types.length; j++) { | |
// char []typeName=param.types[j].getFullTypeName(); | |
// make sure we are using the type version of Boolean, even if the user | |
// entered boolean as the JSdoc type. | |
char[] typeName = changePrimitiveToObject(param.types[j].getFullTypeName()); | |
if(j == 0) { | |
name = typeName; | |
} else { | |
name = CharOperation.append(name, '|'); | |
name = CharOperation.concat(name, typeName); | |
} | |
} | |
paramType = this.addType(name); | |
} | |
} | |
/** | |
* http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs | |
**/ | |
else if(arguments[i].getJsDoc() != null) { | |
if(((Javadoc) arguments[i].getJsDoc()).returnType != null) { | |
paramType = this.addType(((Javadoc) arguments[i].getJsDoc()).returnType.getFullTypeName()); | |
} | |
} else if(arguments[i].getComment() != null) { | |
char[] comment = CharOperation.trim(arguments[i].getComment()); | |
boolean validForName = true; | |
for(int j = 0; j < comment.length && validForName; j++) { | |
validForName &= | |
!CharOperation.isWhitespace(comment[j]) | |
&& (Character.isJavaIdentifierPart(comment[j]) || comment[j] == '.'); | |
} | |
if(validForName) { | |
paramType = this.addType(comment); | |
} | |
} | |
if(paramType != null) { | |
/* if the current type is not null (then it it is also anonymous) | |
* set the doced type as its parent */ | |
if(currType != null) { | |
InferredType currSuperType = currType.getSuperType(); | |
currType.setSuperType(paramType); | |
paramType.setSuperType(currSuperType); | |
paramType = currType; | |
} | |
arguments[i].setInferredType(paramType); | |
} | |
} | |
} | |
} | |
private void copyAnonymousTypeToNamed(InferredType inClass, InferredType toType) { | |
if(toType == null) | |
return; | |
compUnit.inferredTypesHash.removeKey(inClass.name); | |
if(inClass.methods != null) { | |
toType.methods.addAll(inClass.methods); | |
} | |
if(inClass.attributes != null) { | |
for(int i = 0; i < inClass.numberAttributes; i++) { | |
toType.addAttribute(inClass.attributes[i]); | |
} | |
} | |
} | |
/** | |
* <p> | |
* Renames the given type to the given name. If there is a constructor on the type | |
* that is also renamed. | |
* </p> | |
* | |
* @param type | |
* {@link InferredType} to rename | |
* @param newTypeName | |
* new type name for the given {@link InferredType} | |
*/ | |
protected void renameType(InferredType type, char[] newTypeName) { | |
//rename constructor on type if there is one | |
InferredMethod constructor = type.findMethod(TypeConstants.INIT, null); | |
if(constructor != null) { | |
constructor.name = newTypeName; | |
} | |
//rename the type | |
compUnit.inferredTypesHash.removeKey(type.name); | |
type.name = newTypeName; | |
compUnit.inferredTypesHash.put(newTypeName, type); | |
} | |
/** | |
* <p> | |
* Converts the given anonymous type to a named global type. If the given type is not anonymous | |
* then this is a no-op. | |
* </p> | |
* | |
* @param type | |
* anonymous {@link InferredType} to name | |
* @param newTypeName | |
* new type name for the given anonymous {@link InferredType} | |
* | |
* @see InferredType#isAnonymous | |
*/ | |
protected void convertAnonymousTypeToNamed(InferredType type, char[] newTypeName) { | |
if(type.isAnonymous) { | |
this.renameType(type, newTypeName); | |
type.isAnonymous = false; | |
type.setIsGlobal(true); | |
} | |
} | |
protected boolean isMatch(IExpression expr, char[][] names, int index) { | |
char[] matchName = names[index]; | |
if(expr instanceof SingleNameReference) { | |
SingleNameReference snr = (SingleNameReference) expr; | |
return CharOperation.equals(snr.token, matchName); | |
} else if(expr instanceof FieldReference && names.length > 1 && index > 0) { | |
FieldReference fieldReference = (FieldReference) expr; | |
if(CharOperation.equals(fieldReference.token, matchName)) { | |
return isMatch(fieldReference.receiver, names, index - 1); | |
} | |
} | |
return false; | |
} | |
/** | |
* @deprecated not used | |
*/ | |
protected boolean isFunction(IFunctionCall messageSend, String string) { | |
String[] names = string.split("\\."); //$NON-NLS-1$ | |
char[] functionName = names[names.length - 1].toCharArray(); | |
if(!CharOperation.equals(functionName, messageSend.getSelector())) { | |
return false; | |
} | |
char[][] namesChars = new char[names.length][]; | |
for(int i = 0; i < namesChars.length; i++) { | |
namesChars[i] = names[i].toCharArray(); | |
} | |
if(names.length > 1) { | |
return isMatch(messageSend.getReceiver(), namesChars, namesChars.length - 2); | |
} | |
return true; | |
} | |
protected boolean isFunction(IFunctionCall messageSend, char[][] names) { | |
if(messageSend == null) { | |
return false; | |
} | |
char[] functionName = names[names.length - 1]; | |
if(!CharOperation.equals(functionName, messageSend.getSelector())) { | |
return false; | |
} | |
if(names.length > 1) { | |
return isMatch(messageSend.getReceiver(), names, names.length - 2); | |
} | |
return true; | |
} | |
public void doInfer() { | |
try { | |
long time0 = 0; | |
if(REPORT_INFER_TIME) { | |
time0 = System.currentTimeMillis(); | |
} | |
clearBuiltInTypes(); | |
ASTVisitor visitor = getVisitor(compUnit); | |
if (visitor != null) | |
compUnit.traverse(visitor); | |
passNumber = 2; | |
visitor = getVisitor(compUnit); | |
if (visitor != null) | |
compUnit.traverse(visitor); | |
for(int i = 0; i < compUnit.numberInferredTypes; i++) { | |
if(compUnit.inferredTypes[i].sourceStart < 0) | |
compUnit.inferredTypes[i].sourceStart = 0; | |
} | |
if(REPORT_INFER_TIME) { | |
long time = System.currentTimeMillis() - time0; | |
System.err.println(getClass().getName() + " inferred " + new String(compUnit.getFileName()) + " in " //$NON-NLS-1$ //$NON-NLS-2$ | |
+ time + "ms"); //$NON-NLS-1$ | |
} | |
this.compUnit = null; | |
} catch(RuntimeException e) { | |
org.eclipse.wst.jsdt.internal.core.util.Util.log(e, "error during type inferencing"); //$NON-NLS-1$ | |
} | |
} | |
protected InferredType addType(char[] className) { | |
return addType(className, false); | |
} | |
/** | |
* Create a new inferred type with the given name | |
* | |
* @param className | |
* the name of the inferred type | |
* @param isDefinition | |
* true if this unit defines the type | |
* @return new Inferred type | |
*/ | |
protected InferredType addType(char[] className, boolean isDefinition) { | |
InferredType type = compUnit.addType(className, isDefinition, this.inferenceProvider.getID()); | |
return type; | |
} | |
protected final void pushContext() { | |
Context newContext = new Context(currentContext); | |
contexts[++contextPtr] = currentContext; | |
currentContext = newContext; | |
} | |
protected final void popContext() { | |
currentContext = contexts[contextPtr]; | |
contexts[contextPtr--] = null; | |
} | |
/** | |
* @deprecated not used internally, will be removed | |
*/ | |
protected final boolean isInNamedMethod() { | |
return this.currentContext.currentMethod != null && this.currentContext.currentMethod.getName() != null; | |
} | |
/** | |
* Finds a Var Declaration on the context from the name represented with the expression | |
*/ | |
protected IAbstractVariableDeclaration getVariable(IExpression expression) { | |
char[] name = null; | |
if(expression instanceof ISingleNameReference) { | |
name = ((ISingleNameReference) expression).getToken(); | |
} else if(expression instanceof IFieldReference) { | |
IReference ref = getRoot((IFieldReference)expression); | |
if(ref != null && ref instanceof SingleNameReference) { | |
name = ((SingleNameReference)ref).getToken(); | |
} | |
} | |
if(name != null) { | |
Object var = this.currentContext.getMember(name); | |
if(var instanceof IAbstractVariableDeclaration) { | |
return (IAbstractVariableDeclaration) var; | |
} | |
} | |
return null; | |
} | |
/** | |
* Finds a assignment on the context from the name represented with the expression | |
*/ | |
protected IAssignment getAssignment(IExpression expression) { | |
char[] name = null; | |
if(expression instanceof ISingleNameReference) { | |
name = ((ISingleNameReference) expression).getToken(); | |
} else if(expression instanceof IFieldReference) { | |
IReference ref = getRoot((IFieldReference)expression); | |
if(ref != null && ref instanceof SingleNameReference) { | |
name = ((SingleNameReference)ref).getToken(); | |
} | |
} | |
if(name != null) { | |
Object assignment = this.currentContext.getMember(name); | |
if(assignment instanceof IAssignment) { | |
return (IAssignment) assignment; | |
} | |
} | |
return null; | |
} | |
/** | |
* <p> | |
* Finds a Function Declaration on the context from the name represented | |
* with the expression | |
* </p> | |
* | |
* <p> | |
* Supported: | |
* <ul> | |
* <li>{@link ISingleNameReference}</li> | |
* <li>{@link ILocalDeclaration}</li> | |
* </ul> | |
* </p> | |
*/ | |
protected IAbstractFunctionDeclaration getFunction(IExpression expression) { | |
IAbstractFunctionDeclaration function = null; | |
char[] name = null; | |
if(expression instanceof ISingleNameReference) { | |
name = ((ISingleNameReference) expression).getToken(); | |
Object method = this.currentContext.getMember(name); | |
if(method instanceof IAbstractFunctionDeclaration) { | |
function = (IAbstractFunctionDeclaration) method; | |
} else if(method instanceof ILocalDeclaration) { | |
IExpression init = ((ILocalDeclaration) method).getInitialization(); | |
if(init instanceof IFunctionExpression) { | |
function = ((IFunctionExpression)init).getMethodDeclaration(); | |
} | |
} | |
} else if(expression instanceof IFieldReference) { | |
InferredType receiverType = this.getReceiverType(((IFieldReference) expression).getReceiver(), false); | |
if(receiverType != null) { | |
InferredMethod inferredMethod = receiverType.findMethod(((IFieldReference) expression).getToken(), null); | |
if(inferredMethod != null) { | |
function = inferredMethod.getFunctionDeclaration(); | |
} | |
} | |
name = ((IFieldReference) expression).getToken(); | |
} else if(expression instanceof MessageSend) { | |
name = ((MessageSend)expression).selector; | |
if(name != null) { | |
// look up the function in the current context, we want to get | |
// to its return statement eventually. | |
Object method = this.currentContext.getMember(name); | |
if(method instanceof IAbstractFunctionDeclaration) { | |
IAbstractFunctionDeclaration calledFunction = (IAbstractFunctionDeclaration) method; | |
if(calledFunction.getStatements() != null) { | |
IProgramElement[] statements = calledFunction.getStatements(); | |
// this is a bit of a guess, but look for a return statement | |
// if found and it is returning a function, then use that | |
// if not we just return nothing, no harm done. | |
for(int i = 0; i < statements.length; i++) { | |
if(statements[i] instanceof IReturnStatement) { | |
if(((IReturnStatement)statements[i]).getExpression() instanceof IFunctionExpression) { | |
function = ((IFunctionExpression) ((IReturnStatement)statements[i]).getExpression()).getMethodDeclaration(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
return function; | |
} | |
private void buildDefinedMembers(IProgramElement[] statements, IArgument[] arguments) { | |
if(arguments != null) { | |
for(int i = 0; i < arguments.length; i++) { | |
this.currentContext.addMember(arguments[i].getName(), arguments[i]); | |
} | |
} | |
if(statements != null) { | |
for(int i = 0; i < statements.length; i++) { | |
if(statements[i] instanceof ILocalDeclaration) { | |
ILocalDeclaration local = (ILocalDeclaration) statements[i]; | |
this.currentContext.addMember(local.getName(), local); | |
} else if(statements[i] instanceof IAbstractFunctionDeclaration) { | |
IAbstractFunctionDeclaration method = (IAbstractFunctionDeclaration) statements[i]; | |
if(method.getName() != null) | |
this.currentContext.addMember(method.getName(), method); | |
} | |
} | |
} | |
} | |
private static boolean isThis(IExpression expression) { | |
return expression instanceof ASTNode && ((ASTNode)expression).isThis(); | |
} | |
/* This method is used to determined the inferred type of a LHS Expression. | |
* | |
* It could return null. | |
* | |
* a.b.c */ | |
private InferredType getInferredType(IExpression expression) { | |
InferredType type = null; | |
/* this */ | |
if(expression instanceof IThisReference) { | |
type = this.currentContext.currentType; | |
} | |
/* foo (could be a Type name or a reference to a variable) */ | |
else if(expression instanceof SingleNameReference) { | |
//check for local variable first | |
IAbstractVariableDeclaration varDecl = this.getVariable(expression); | |
if(varDecl != null) { | |
type = varDecl.getInferredType(); | |
} | |
//if not found type yet check for assignment | |
IAssignment assignment = null; | |
if(type == null) { | |
assignment = getAssignment(expression); | |
if(assignment != null) { | |
type = assignment.getInferredType(); | |
} | |
} | |
/* if not found type yet and there was no declaration or assignment, | |
* or the found type is Function then check if there is a known type | |
* with the same name as the SNR. */ | |
if((type == null && varDecl == null && assignment == null) || | |
(type != null && type.isFunction())) { | |
char[] possibleTypeName = constructTypeName(expression); | |
if(possibleTypeName != null) { | |
/* if pre-existing type, use that | |
* else check if known type name */ | |
InferredType existingType = compUnit.findInferredType(possibleTypeName); | |
if(existingType != null) { | |
type = existingType; | |
} else if(WellKnownTypes.containsKey(possibleTypeName) || | |
this.isKnownType(possibleTypeName)) { | |
type = addType(possibleTypeName, false); | |
} | |
} | |
} | |
} | |
/* foo.bar.xxx... */ | |
else if(expression instanceof FieldReference) { | |
char[] possibleTypeName = constructTypeName(expression); | |
if(possibleTypeName != null) { | |
// search the defined types in the context | |
type = compUnit.findInferredType(possibleTypeName); | |
} | |
if(type == null && isPossibleClassName(possibleTypeName)) { | |
type = addType(possibleTypeName, true); | |
} | |
/* Continue the search by trying to resolve further down the name | |
* because this token of the field reference could be a member of a | |
* type or instance of a type */ | |
if(type == null) { | |
FieldReference fRef = (FieldReference) expression; | |
// this | |
InferredType parentType = getInferredType(fRef.receiver); | |
if(parentType != null) { | |
// check the members and return type | |
InferredAttribute typeAttribute = parentType.findAttribute(fRef.token); | |
if(typeAttribute != null) { | |
type = typeAttribute.type; | |
} | |
} | |
} | |
} | |
return type; | |
} | |
/** | |
* @deprecated - here for compatibility | |
*/ | |
protected InferredType getInferredType2(IExpression fieldReceiver) { | |
InferredType receiverType = null; | |
IAbstractVariableDeclaration var = getVariable(fieldReceiver); | |
if(var != null) { | |
receiverType = createAnonymousType(var); | |
} else { | |
if(this.inferredGlobal != null && fieldReceiver instanceof ISingleNameReference) { | |
char[] name = ((ISingleNameReference) fieldReceiver).getToken(); | |
InferredAttribute attr = this.inferredGlobal.findAttribute(name); | |
if(attr != null) { | |
receiverType = attr.type; | |
} | |
} | |
} | |
return receiverType; | |
} | |
protected boolean isKnownType(char[] possibleTypeName) { | |
return false; | |
} | |
/* For SNR it returns the name | |
* For FR it construct a Qualified name separated by '.' | |
* | |
* If at any point it hits a portion of the Field reference that is | |
* not supported (such as a function call, a prototype, or this ) */ | |
protected final char[] constructTypeName(IExpression expression) { | |
return Util.getTypeName(expression); | |
} | |
public boolean visit(IObjectLiteral literal) { | |
if(this.passNumber == 1 && literal.getInferredType() == null) { | |
createAnonymousType(literal); | |
} | |
pushContext(); | |
this.currentContext.currentType = literal.getInferredType(); | |
return true; | |
} | |
public void endVisit(IObjectLiteral literal) { | |
popContext(); | |
} | |
/** | |
* Overridden by client who wish to update the infer options | |
* | |
* @param options | |
*/ | |
public void initializeOptions(InferOptions options) { | |
} | |
protected boolean isPossibleClassName(char[] name) { | |
return false; | |
} | |
/** | |
* Get the Script file this inference is being done on | |
* | |
* @return | |
*/ | |
public IScriptFileDeclaration getScriptFileDeclaration() { | |
return this.compUnit; | |
} | |
public InferredType findDefinedType(char[] className) { | |
return compUnit.findInferredType(className); | |
} | |
protected Object findDefinedMember(char[] memberName) { | |
return currentContext.getMember(memberName); | |
} | |
protected char[] changePrimitiveToObject(char[] name) { | |
/* | |
* Changes the first character of the name of the primitive types to | |
* uppercase. This will allow future reference to the object wrapper | |
* instead of the primitive type. | |
*/ | |
if(CharOperation.equals(name, TypeConstants.BOOLEAN, false)) { | |
return this.getBooleanType().getName(); | |
} | |
return name; | |
} | |
/** | |
* @return {@link InferredType} for the String type | |
*/ | |
protected InferredType getStringType() { | |
if(fStringType == null) { | |
fStringType = this.addType(TypeConstants.JAVA_LANG_STRING[0]); | |
} | |
return fStringType; | |
} | |
/** | |
* @return {@link InferredType} for the Number type | |
*/ | |
protected InferredType getNumberType() { | |
if(fNumberType == null) { | |
fNumberType = this.addType(TypeConstants.NUMBER[0]); | |
} | |
return fNumberType; | |
} | |
/** | |
* @return {@link InferredType} for the Boolean type | |
*/ | |
protected InferredType getBooleanType() { | |
if (fBooleanType == null) { | |
fBooleanType = this.addType(TypeConstants.BOOLEAN_OBJECT[0]); | |
} | |
return fBooleanType; | |
} | |
/** | |
* @return {@link InferredType} for the Function type | |
*/ | |
protected InferredType getFunctionType() { | |
if(fFunctionType == null) { | |
fFunctionType = this.addType(TypeConstants.FUNCTION[0]); | |
} | |
return fFunctionType; | |
} | |
/** | |
* @return {@link InferredType} for the Array type | |
*/ | |
protected InferredType getArrayType() { | |
if(fArrayType == null) { | |
fArrayType = this.addType(TypeConstants.ARRAY[0]); | |
} | |
return fArrayType; | |
} | |
/** | |
* @return {@link InferredType} for the Void type | |
*/ | |
protected InferredType getVoidType() { | |
if(fVoidType == null) { | |
fVoidType = this.addType(TypeConstants.VOID); | |
} | |
return fVoidType; | |
} | |
/** | |
* @return {@link InferredType} for the Object type | |
*/ | |
protected InferredType getObjectType() { | |
if(fObjectType == null) { | |
fObjectType = this.addType(TypeConstants.OBJECT); | |
} | |
return fObjectType; | |
} | |
/** | |
* <p> | |
* Gets the name of the given expression. | |
* </p> | |
* | |
* <p> | |
* Supported: | |
* <ul> | |
* <li>{@link ISingleNameReference}</li> | |
* <li>{@link IFieldReference}</li> | |
* <li>{@link IAssignment}</li> | |
* </ul> | |
* </p> | |
* | |
* @param expression | |
* {@link IExpression} to get the name for | |
* | |
* @return name of the given {@link IExpression} or <code>null</code> if none | |
* can be determined | |
*/ | |
protected char[] getName(IExpression expression) { | |
char[] name = null; | |
if(expression instanceof ISingleNameReference) { | |
name = ((ISingleNameReference) expression).getToken(); | |
} else if(expression instanceof IFieldReference) { | |
name = ((IFieldReference) expression).getToken(); | |
} else if(expression instanceof IAssignment) { | |
name = this.getName(((IAssignment) expression).getLeftHandSide()); | |
} | |
return name; | |
} | |
/** | |
* <p> | |
* Given a variable name and an initialization expression determines the | |
* type to initialize the variable with the given name to. | |
* </p> | |
* | |
* @param variableName | |
* name of the variable to be initialized | |
* @param initialization | |
* the initialization expression used to initialize the | |
* variable with the given name | |
* | |
* @return {@link InferredType} to initialize the variable with the given | |
* name with | |
*/ | |
private InferredType getTypeForVariableInitialization(char[] variableName, IExpression initialization) { | |
InferredType type = this.getTypeOf(initialization); | |
/* if the initialization type is not indexed and the variable declaration is in the global scope | |
* rename the type to a globally anonymous type and mark it as a global type | |
*/ | |
boolean isGlobal = this.isGlobal(variableName); | |
if(type != null && !type.isIndexed() && isGlobal) { | |
char[] globalTypeName = createAnonymousGlobalTypeName(variableName); | |
this.renameType(type, globalTypeName); | |
type.isAnonymous = true; | |
type.setIsGlobal(true); | |
} else if(type == null && isGlobal && this.passNumber == 2) { | |
type = this.createAnonymousGlobalType(variableName); | |
} | |
return type; | |
} | |
/** | |
* <p> | |
* Will create a type to assign to given the receiver that is being assigned to, the current | |
* type that is or will be assigned to the receiver, and the assignment expression that will be | |
* used for the assignment. | |
* </p> | |
* | |
* <p> | |
* <b>NOTE:</b> This does not actually deal with the assignment, it just creates the type to assign | |
* to and updates the type for the given receiver. | |
* </p> | |
* | |
* @param receiver | |
* the receiver to update the type for | |
* @param currentReceiverType | |
* current type that is or will be assigned to the receiver | |
* | |
* @return {@link InferredType} that was created to do the assignment to or the given current | |
* receiver type if no new type was created | |
*/ | |
private InferredType createTypeToAssignTo(IExpression receiver, InferredType currentReceiverType) { | |
char[] varName = this.getName(receiver); | |
InferredType newType = null; | |
/* If the current receiver type is not anonymous and is not a this statement, | |
* and the receiver is not a type (rather then instance of a type, then | |
* create a new anonymous type with current type as the parent. | |
* | |
* else use the given current receiver type */ | |
if(!currentReceiverType.isAnonymous && !isThis(receiver) | |
&& !CharOperation.equals(currentReceiverType.name, varName) && !this.isExpressionAType(receiver)) { | |
/* if the variable being assigned to is in the global scope then | |
* need to create a global type | |
* | |
* else create a local anonymous type | |
*/ | |
boolean isGlobal = this.isGlobal(varName); | |
if(isGlobal) { | |
/* if current type is a function create a new type with the var name | |
* else create new anonymous global type */ | |
if(currentReceiverType.isFunction()) { | |
newType = this.addType(varName, true); | |
} else { | |
newType = this.createAnonymousGlobalType(varName); | |
} | |
} else { | |
newType = this.createAnonymousType(receiver, currentReceiverType); | |
} | |
} | |
/* if created a new type set its super type to the original type and update the type for the receiver | |
* | |
* else just return the original given current receiver type */ | |
if(newType != null) { | |
newType.setSuperType(currentReceiverType); | |
this.setTypeOf(receiver, newType); | |
} else { | |
newType = currentReceiverType; | |
} | |
return newType; | |
} | |
/** | |
* <p> | |
* Clears out all of the built in types. | |
* </p> | |
*/ | |
private void clearBuiltInTypes() { | |
fStringType = null; | |
fNumberType = null; | |
fBooleanType = null; | |
fFunctionType = null; | |
fArrayType = null; | |
fVoidType = null; | |
fObjectType = null; | |
} | |
/** | |
* <p> | |
* This method is intended to take a chain of field references and | |
* determine the type that the last field should be or is defined on. | |
* </p> | |
* | |
* <p> | |
* EX: <code>foo.bar.awesome.crazy = 42;</code><br> | |
* <br> | |
* If that is the entirety of the file and the receiver of the | |
* <code>foo.bar.awesome.crazy</code> statement is given to this function, | |
* so the <code>foo.bar.awesome</code> part, then this function will | |
* create a <code>foo</code> field on the global inferred type and and | |
* then give it a type that has a <code>bar</code> field, and then give | |
* the <code>bar</code> field a type with an <code>awesome</code> field | |
* and then finally return a new type assigned to the <code>awesome</code> | |
* field such that some other code can deal with assigning the | |
* <code>crazy</code> field with whatever type is on the right hand side | |
* of the assignment. | |
* </p> | |
* | |
* @param receiver | |
* the receiver side of a {@link FieldReference} to get the | |
* type for | |
* | |
* @param defineRoot | |
* Has two purposes. If the root of the field reference has no | |
* type and this is <code>true</code> a type will be created. | |
* If the root of this field reference does not have a type and | |
* this is <code>false</code> no root type will be created an | |
* thus no type will be returned by this method. When there is | |
* a root type if this argument is <code>true</code> then the | |
* type on the root will be marked as a definition, else if | |
* <code>false</code> the root will not be marked as a | |
* definition. | |
* | |
* @return {@link InferredType} associated with the given receiver side of | |
* a {@link FieldReference} | |
*/ | |
protected InferredType getReceiverType(IExpression receiver, boolean defineRoot) { | |
InferredType receiverType = null; | |
IExpression current = receiver; | |
List recievers = new ArrayList(); | |
InferredType rootType = null; | |
/* Find the SingleNameReference that the reference chain starts with | |
* or the root type if the chain starts with a THIS statement */ | |
while(current != null && !(current instanceof SingleNameReference) && rootType == null) { | |
if(current instanceof FieldReference) { | |
recievers.add(current); | |
current = ((FieldReference) current).getReceiver(); | |
} else if(current instanceof ThisReference) { | |
rootType = this.currentContext.currentType; | |
((ThisReference) current).setInferredType(rootType); | |
current = null; | |
} else { | |
current = null; | |
} | |
} | |
//if the root was a single name then determine or create its type | |
IAbstractVariableDeclaration rootVarDecl = null; | |
IAssignment rootAssignment = null; | |
if(rootType == null && current instanceof SingleNameReference) { | |
//first check if there is a type with the same name as the single name reference | |
char[] name = ((SingleNameReference) current).getToken(); | |
rootType = this.findDefinedType(name); | |
// if the single name reference is a function then create a type using the function name | |
if(rootType == null) { | |
IFunctionDeclaration func = this.getDefinedFunction(current); | |
if(func != null) { | |
rootType = this.addType(name, true); | |
rootType.setNameStart(current.sourceStart()); | |
rootType.setSourceEnd(current.sourceEnd()); | |
} | |
} | |
// if single name reference is to a variable | |
if(rootType == null) { | |
rootVarDecl = this.getVariable(current); | |
if(rootVarDecl != null) { | |
rootType = rootVarDecl.getInferredType(); | |
/* if variable does not already have an inferred type create an anonymous one | |
* or if the receiver type is not anonymous and is the expression is not a type then | |
* create a new anonymous sub type to do the assignment to */ | |
if(rootType == null || (!rootType.isAnonymous && !rootVarDecl.isType())) { | |
/* if global then create anonymous global type and set super type as current root type | |
* else create anonymous type using current root type as super type */ | |
boolean isGlobal = this.isGlobal(name); | |
if(isGlobal) { | |
InferredType globalType = this.createAnonymousGlobalType(name); | |
globalType.setSuperType(rootType); | |
rootType = globalType; | |
rootType.setIsDefinition(true); | |
rootVarDecl.setInferredType(rootType); | |
} else if(defineRoot) { | |
rootType = this.createAnonymousType(current, rootType); | |
rootType.setIsDefinition(true); | |
rootVarDecl.setInferredType(rootType); | |
} | |
} | |
} | |
} | |
//if single name reference is to a field on the inferred 'global' type | |
if(rootType == null && this.inferredGlobal != null) { | |
InferredAttribute attr = this.getInferredGlobal(false).findAttribute(name); | |
if(attr != null) { | |
if(attr.type == null) { | |
attr.type = this.createAnonymousGlobalType(attr.name); | |
} | |
rootType = attr.type; | |
} else { | |
InferredMethod meth = this.getInferredGlobal(false).findMethod(name, null); | |
if(meth != null) { | |
rootType = this.addType(name, true); | |
rootType.setNameStart(current.sourceStart()); | |
rootType.setSourceEnd(current.sourceEnd()); | |
} | |
} | |
} | |
// if single name reference is to an existing assignment with no declaration | |
if(rootType == null) { | |
rootAssignment = this.getAssignment(current); | |
if(rootAssignment != null) { | |
rootType = rootAssignment.getInferredType(); | |
} | |
} | |
/* else if define root create a new anonymous global type */ | |
if(rootType == null && defineRoot) { | |
rootType = this.createAnonymousGlobalType(name); | |
} | |
} | |
/* if determined a root type, and there is no case where we should not, | |
* make sure there is fields and types built up for the rest of the reference chain */ | |
if(rootType != null) { | |
/* only define root if requested and root not part of long chain, | |
* or the root was already defined somewhere else in this file | |
* | |
* IE: foo.bar.blarg = 42, foo is not defined here | |
* foo.bar = 42, foo is defined here */ | |
if(defineRoot && (recievers.isEmpty() || rootVarDecl != null || rootAssignment != null)) { | |
rootType.setIsDefinition(true); | |
} | |
InferredType currentType = rootType; | |
for(int i = recievers.size()-1; i >= 0; --i) { | |
FieldReference ref = (FieldReference)recievers.get(i); | |
InferredAttribute attr = currentType.findAttribute(ref.getToken()); | |
if(attr != null) { | |
if(attr.type != null) { | |
currentType = attr.type; | |
} else { | |
attr.type = this.createAnonymousType(attr, null); | |
currentType = attr.type; | |
} | |
} else { | |
InferredMethod meth = currentType.findMethod(ref.getToken(), null); | |
if(meth != null) { | |
char[] typeName = CharOperation.concatWith(ref.asQualifiedName(), '.'); | |
currentType = this.addType(typeName, true); | |
} else { | |
attr = currentType.addAttribute(ref.getToken(), ref, ref.sourceStart); | |
if(currentType == this.getInferredGlobal(false)) { | |
attr.type = this.createAnonymousGlobalType(attr.name); | |
attr.type.setIsDefinition(true); | |
} else { | |
attr.type = this.createAnonymousType(attr, null); | |
} | |
currentType = attr.type; | |
} | |
} | |
} | |
//set the last type in the chain as the receiver type | |
receiverType = currentType; | |
} | |
return receiverType; | |
} | |
/** | |
* @param define | |
* <code>true</code> to define the inferred global type if one | |
* is not yet defined, <code>false</code> otherwise | |
* | |
* @return inferred global type, or <code>null</code> if none is yet | |
* defined and <code>define</code> was given as <code>false</code> | |
*/ | |
protected InferredType getInferredGlobal(boolean define) { | |
if(this.inferredGlobal == null && define) { | |
this.inferredGlobal = addType(IIndexConstants.GLOBAL_SYMBOL, true); | |
this.inferredGlobal.isAnonymous = true; | |
this.inferredGlobal.setIsGlobal(true); | |
} | |
return this.inferredGlobal; | |
} | |
/** | |
* <p> | |
* Determine if the root of the given expression is global or not. | |
* </p> | |
* | |
* @param expr | |
* Determine if the root of this expression is global or not | |
* | |
* @return <code>true</code> if the root of the given expression is | |
* global, <code>false</code> otherwise | |
*/ | |
private boolean isRootGlobal(IExpression expr) { | |
boolean isGlobal = false; | |
IReference root = null; | |
if(expr instanceof SingleNameReference) { | |
root = (SingleNameReference) expr; | |
} else if(expr instanceof FieldReference) { | |
root = getRoot((FieldReference)expr); | |
} | |
/* if root is a single name reference then determine if it is global | |
* else if no root then assume global */ | |
if(root != null && root instanceof ISingleNameReference) { | |
isGlobal = this.isGlobal(((ISingleNameReference)root).getToken()); | |
} else if(root != null && root instanceof IThisReference) { | |
isGlobal = this.currentContext.currentType == this.getInferredGlobal(false); | |
} else if(root == null){ | |
isGlobal = true; | |
} | |
return isGlobal; | |
} | |
/** | |
* <p> | |
* Determines if the given variable name is global. | |
* </p> | |
* | |
* @param name | |
* determine if there is a global variable with this name | |
* | |
* @return <code>true</code> if there is a global variable with this name, | |
* <code>false</code> otherwise | |
*/ | |
protected boolean isGlobal(char[] name) { | |
boolean isGlobal = false; | |
if (name == null) | |
return isGlobal; | |
//check the root context | |
Object globalMember; | |
Object currentContextMember = this.currentContext.getMember(name); | |
if(this.contexts[0] != null) { | |
globalMember = this.contexts[0].getMember(name); | |
} else { | |
globalMember = currentContextMember; | |
} | |
/* is global if global member with same name is not null and from the current context | |
* the first member with that name is also the global member, this is to cover the case | |
* with a shadowing local variable */ | |
isGlobal = globalMember != null && currentContextMember == globalMember; | |
/* if not determined to be global then check if the current context member | |
* is an assignment. If it is then that assignment is an assignment without | |
* a local declaration which means it is global */ | |
if(!isGlobal) { | |
isGlobal = currentContextMember instanceof Assignment; | |
} | |
//if not determined to be global yet then check inferred global type | |
if(!isGlobal) { | |
globalMember = null; | |
InferredType inferredGlobal = this.getInferredGlobal(false); | |
if(inferredGlobal != null) { | |
globalMember = inferredGlobal.findAttribute(name); | |
if(globalMember == null) { | |
globalMember = inferredGlobal.findMethod(name, null); | |
} | |
} | |
isGlobal = globalMember != null; | |
} | |
/* if not determined to be global yet and no global member or | |
* local member found assume to be global */ | |
if(!isGlobal) { | |
if(globalMember == null && currentContextMember == null) { | |
isGlobal = true; | |
} | |
} | |
return isGlobal; | |
} | |
/** | |
* <p> | |
* Given an {@link IFieldReference} finds the root | |
* {@link SingleNameReference}. | |
* </p> | |
* | |
* @param ref | |
* {@link IFieldReference} to find the root | |
* {@link SingleNameReference} for | |
* | |
* @return {@link SingleNameReference} that is the root of the given | |
* {@link IFieldReference}, or <code>null</code> if it can not be | |
* found. | |
*/ | |
private static IReference getRoot(IFieldReference ref) { | |
IReference root = null; | |
IExpression current = ref; | |
while(root == null && current != null) { | |
if(current instanceof IFieldReference) { | |
current = ((IFieldReference) current).getReceiver(); | |
} else if(current instanceof SingleNameReference) { | |
root = (SingleNameReference)current; | |
} else if(current instanceof IThisReference) { | |
root = (IThisReference)current; | |
} else { | |
current = null; | |
} | |
} | |
return root; | |
} | |
/** | |
* <p> | |
* Determines if the given parent expression contains or is the given | |
* needle expression. | |
* </p> | |
* | |
* <p> | |
* Handles: | |
* <ul> | |
* <li>{@link IBinaryExpression}</li> | |
* </ul> | |
* </p> | |
* | |
* @param parent | |
* determine if the given needle is or is contained by this | |
* parent expression | |
* @param needle | |
* determine if the this needle is or is contained by the given | |
* parent expression | |
* | |
* @return <code>true</code> if the given needle is or is contained by the | |
* given parent expression, <code>false</code> otherwise | |
*/ | |
private static boolean expressionContains(IExpression parent, IExpression needle) { | |
boolean contains = false; | |
LinkedList expressions = new LinkedList(); | |
expressions.add(parent); | |
while(expressions.size() > 0 && !contains) { | |
IExpression current = (IExpression)expressions.removeFirst(); | |
contains = needle == current; | |
if(!contains) { | |
if(current instanceof IBinaryExpression) { | |
expressions.add(((IBinaryExpression) current).getLeft()); | |
expressions.add(((IBinaryExpression) current).getRight()); | |
} else if(current instanceof Assignment) { | |
expressions.add(((Assignment) current).getLeftHandSide()); | |
expressions.add(((Assignment) current).getExpression()); | |
} | |
} | |
} | |
return contains; | |
} | |
/** | |
* <p> | |
* Determines if the given expression is a type rather then an instance of | |
* a type. | |
* </p> | |
* | |
* @param expr | |
* determine if this expression is a type rather then an | |
* instance of a type | |
* | |
* @return <code>true</code> if the given expression is a type, | |
* <code>false</code> if the given expression is the instance of a | |
* type or unknown | |
*/ | |
private boolean isExpressionAType(IExpression expr) { | |
boolean isType = false; | |
if(expr instanceof IAssignment) { | |
isType = ((IAssignment) expr).isType(); | |
} else if(expr instanceof IAbstractVariableDeclaration) { | |
isType = ((IAbstractVariableDeclaration) expr).isType(); | |
} else if(expr instanceof IFieldReference) { | |
IExpression receiver = ((IFieldReference) expr).getReceiver(); | |
InferredType receiverType = this.getTypeOf(receiver); | |
if(receiverType != null) { | |
InferredAttribute attr = receiverType.findAttribute(((IFieldReference) expr).getToken()); | |
isType = attr != null && attr.isType(); | |
} | |
} else if(expr instanceof ISingleNameReference) { | |
IAbstractVariableDeclaration varDecl = this.getVariable(expr); | |
if(varDecl != null) { | |
isType = varDecl.isType(); | |
} | |
if(!isType) { | |
IAssignment assign = this.getAssignment(expr); | |
if(assign != null) { | |
isType = assign.isType(); | |
} | |
} | |
if(!isType) { | |
IAbstractFunctionDeclaration funcDecl = this.getFunction(expr); | |
if(funcDecl != null && funcDecl.getName() != null) { | |
InferredType typeDefinedByFunc = this.findDefinedType(funcDecl.getName()); | |
isType = typeDefinedByFunc != null; | |
} | |
} | |
if(!isType) { | |
InferredType existingType =this.compUnit.findInferredType(((ISingleNameReference) expr).getToken()); | |
isType = existingType != null; | |
} | |
} else if(expr instanceof IThisReference) { | |
isType = true; | |
} | |
return isType; | |
} | |
protected InferredType createAnonymousTypeForMixin(IExpression mixInto, InferredType parentType) { | |
InferredType mixIntoType; | |
IAbstractVariableDeclaration localVar = getVariable(mixInto); | |
char[] varName = getName(mixInto); | |
if (mixInto instanceof ISingleNameReference && (localVar == null || isGlobal(varName))) { | |
mixIntoType = createAnonymousGlobalType(varName); | |
mixIntoType.setSuperType(parentType); | |
} | |
else { | |
mixIntoType = createAnonymousType(mixInto, parentType); | |
} | |
mixIntoType.isObjectLiteral = false; | |
return mixIntoType; | |
} | |
protected InferredType getAttributeType(char[] attName, IExpression receiver, boolean defineRoot) { | |
InferredType attrType = null; | |
InferredType receiverType = this.getReceiverType(receiver, defineRoot); | |
if (receiverType != null) { | |
InferredAttribute attr = receiverType.findAttribute(attName); | |
if (attr != null && attr.type != null) { | |
attrType = attr.type; | |
} | |
} | |
return attrType; | |
} | |
/** | |
* Return a visitor to traverse the given compilation unit's AST. | |
* Subclasses may override to provide a more minimal implementations while | |
* retaining use of utility methods from the base InferEngine. | |
* | |
* @return a visitor for use with the given compilation unit and current | |
* engine states, or <code>null</code> | |
*/ | |
protected ASTVisitor getVisitor(CompilationUnitDeclaration compilationUnit) { | |
return this; | |
} | |
} |