Hello Michal how are you ?
I've seen that you recently worked on some patch to correct problems with type
inference evaluators (Bug 453737 - Tag @var doesn't work well with array types).
I ran FindBugs some time ago and found some bugs related to the usage of
String#split().
Maybe they are still some pending bugs concerning type inference evaluators,
but I do not feel comfortable enough about these parts of code, so maybe you
could help ;)
What I find strange is the way that some evaluators (from packages
org.eclipse.php.internal.core.typeinference.evaluators.*) are splitting types
using String#split("\\|") and handling brackets (for array type declarations).
So my (stupid) questions are :
- why do some evaluators look after brackets (MethodReturnTypeEvaluator,
PHPDocMethodReturnTypeEvaluator, PHPDocClassVariableEvaluator) and others not
(ClassVariableDeclarationEvaluator, VariableReferenceEvaluator) ?
- why aren't splitted values (using String#split("\\|")) tested if they are
empty or not ?
- why are PHPDocClassVariableEvaluator#getArrayType(String type, IType
currentNamespace, int offset) and
PHPDocMethodReturnTypeEvaluator#getArrayType(String type, IType
currentNamespace, int offset)
different ?
- so getEvaluatedType(String typeName, IType currentNamespace) and
getArrayType(String type, IType currentNamespace, int offset) from classes
PHPDocClassVariableEvaluator and PHPDocMethodReturnTypeEvaluator can probably
be merged (into a class utility?).
I made some (untested) changes and provide them "as is" as attachments, so you
can see what I mean.
I also didn't put them on gerrit because I don't plan to make a patch ;)
Thierry.
/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.typeinference.evaluators;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.internal.core.SourceRefElement;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.IContext;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.goals.IGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.internal.core.PHPVersion;
import org.eclipse.php.internal.core.compiler.ast.nodes.*;
import org.eclipse.php.internal.core.project.ProjectOptions;
import org.eclipse.php.internal.core.typeinference.*;
import org.eclipse.php.internal.core.typeinference.context.ContextFinder;
import org.eclipse.php.internal.core.typeinference.context.IModelCacheContext;
import org.eclipse.php.internal.core.typeinference.context.MethodContext;
import org.eclipse.php.internal.core.typeinference.context.TypeContext;
import
org.eclipse.php.internal.core.typeinference.goals.ClassVariableDeclarationGoal;
/**
* This evaluator finds class field declaration either using : 1. @var hint 2.
* in method body using field access. 3. magic declaration using the @property,
*
* @property-read, @property-write
*/
public class ClassVariableDeclarationEvaluator extends AbstractPHPGoalEvaluator
{
private List<IEvaluatedType> evaluated = new
LinkedList<IEvaluatedType>();
public ClassVariableDeclarationEvaluator(IGoal goal) {
super(goal);
}
public IGoal[] init() {
ClassVariableDeclarationGoal typedGoal =
(ClassVariableDeclarationGoal) goal;
IType[] types = typedGoal.getTypes();
if (types == null) {
TypeContext context = (TypeContext)
typedGoal.getContext();
types = PHPTypeInferenceUtils.getModelElements(
context.getInstanceType(), context);
}
if (types == null) {
return null;
}
IContext context = typedGoal.getContext();
IModelAccessCache cache = null;
if (context instanceof IModelCacheContext) {
cache = ((IModelCacheContext) context).getCache();
}
String variableName = typedGoal.getVariableName();
final List<IGoal> subGoals = new LinkedList<IGoal>();
for (final IType type : types) {
try {
ITypeHierarchy hierarchy = null;
if (cache != null) {
hierarchy =
cache.getSuperTypeHierarchy(type, null);
}
IField[] fields =
PHPModelUtils.getTypeHierarchyField(type,
hierarchy, variableName, true,
null);
Map<IType, IType> fieldDeclaringTypeSet = new
HashMap<IType, IType>();
for (IField field : fields) {
IType declaringType =
field.getDeclaringType();
if (declaringType != null) {
fieldDeclaringTypeSet.put(declaringType, type);
ISourceModule sourceModule =
declaringType
.getSourceModule();
ModuleDeclaration
moduleDeclaration = SourceParserUtil
.getModuleDeclaration(sourceModule);
TypeDeclaration typeDeclaration
= PHPModelUtils
.getNodeByClass(moduleDeclaration,
declaringType);
if (typeDeclaration != null
&& field
instanceof SourceRefElement) {
SourceRefElement
sourceRefElement = (SourceRefElement) field;
ISourceRange
sourceRange = sourceRefElement
.getSourceRange();
ClassDeclarationSearcher searcher = new ClassDeclarationSearcher(
sourceModule, typeDeclaration,
sourceRange.getOffset(),
sourceRange.getLength(), null, type,
declaringType);
try {
moduleDeclaration.traverse(searcher);
if
(searcher.getResult() != null) {
subGoals.add(new ExpressionTypeGoal(
searcher.getContext(), searcher
.getResult()));
}
} catch (Exception e) {
if
(DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
}
}
if (subGoals.size() == 0) {
getGoalFromStaticDeclaration(variableName, subGoals, type,
null);
}
fieldDeclaringTypeSet.remove(type);
if (subGoals.size() == 0 &&
!fieldDeclaringTypeSet.isEmpty()) {
for (Entry<IType, IType> entry :
fieldDeclaringTypeSet
.entrySet()) {
getGoalFromStaticDeclaration(variableName, subGoals,
entry.getKey(),
entry.getValue());
}
}
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
resolveMagicClassVariableDeclaration(types, variableName,
cache);
return subGoals.toArray(new IGoal[subGoals.size()]);
}
protected void getGoalFromStaticDeclaration(String variableName,
final List<IGoal> subGoals, final IType declaringType,
IType realType) throws ModelException {
ISourceModule sourceModule = declaringType.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil
.getModuleDeclaration(sourceModule);
TypeDeclaration typeDeclaration = PHPModelUtils.getNodeByClass(
moduleDeclaration, declaringType);
// try to search declarations of type "self::$var =" or
// "$this->var ="
ClassDeclarationSearcher searcher;
if (realType != null) {
searcher = new ClassDeclarationSearcher(sourceModule,
typeDeclaration, 0, 0, variableName,
realType,
declaringType);
} else {
searcher = new ClassDeclarationSearcher(sourceModule,
typeDeclaration, 0, 0, variableName);
}
try {
moduleDeclaration.traverse(searcher);
for (Entry<ASTNode, IContext> entry : searcher
.getStaticDeclarations().entrySet()) {
final IContext context = entry.getValue();
if (context instanceof MethodContext) {
MethodContext methodContext =
(MethodContext) context;
methodContext.setCurrentType(realType);
}
if (context instanceof IModelCacheContext
&&
ClassVariableDeclarationEvaluator.this.goal
.getContext()
instanceof IModelCacheContext) {
((IModelCacheContext) context)
.setCache(((IModelCacheContext) ClassVariableDeclarationEvaluator.this.goal
.getContext()).getCache());
}
subGoals.add(new ExpressionTypeGoal(context,
entry.getKey()));
}
} catch (Exception e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
/**
* Search for magic variables using the @property tag
*
* @param types
* @param variableName
* @param cache
*/
private void resolveMagicClassVariableDeclaration(IType[] types,
String variableName, IModelAccessCache cache) {
for (IType type : types) {
resolveMagicClassVariableDeclaration(variableName,
type, cache);
try {
if (evaluated.isEmpty() &&
type.getSuperClasses() != null
&&
type.getSuperClasses().length > 0) {
ITypeHierarchy hierarchy = null;
if (cache != null) {
hierarchy =
cache.getSuperTypeHierarchy(type, null);
}
IType[] superClasses =
PHPModelUtils.getSuperClasses(type,
hierarchy);
for (int i = 0; i < superClasses.length
/* && evaluated.isEmpty() */; i++) {
IType superClass =
superClasses[i];
resolveMagicClassVariableDeclaration(variableName,
superClass,
cache);
}
}
} catch (ModelException e) {
e.printStackTrace();
}
}
}
protected void resolveMagicClassVariableDeclaration(String variableName,
IType type, IModelAccessCache cache) {
final PHPDocBlock docBlock = PHPModelUtils.getDocBlock(type);
if (docBlock == null) {
return;
}
for (PHPDocTag tag : docBlock.getTags()) {
final int tagKind = tag.getTagKind();
if (tagKind == PHPDocTag.PROPERTY
|| tagKind == PHPDocTag.PROPERTY_READ
|| tagKind == PHPDocTag.PROPERTY_WRITE)
{
final String[] typeNames =
getTypeBinding(variableName, tag);
if (typeNames != null) {
for (String typeName : typeNames) {
if (typeName.trim().length() ==
0) {
continue;
}
IEvaluatedType resolved =
PHPSimpleTypes
.fromString(typeName);
if (resolved == null) {
resolved = new
PHPClassType(typeName);
}
evaluated.add(resolved);
}
}
}
}
}
/**
* Resolves the type from the @property tag
*
* @param variableName
* @param docTag
* @return the types of the given variable
*/
private String[] getTypeBinding(String variableName, PHPDocTag docTag) {
final String[] split = docTag.getValue().trim().split("\\s+");
//$NON-NLS-1$
if (split.length < 2) {
return null;
}
if (split[1].equals(variableName)) {
return split[0].split("\\|");//$NON-NLS-1$
}
return null;
}
public Object produceResult() {
return PHPTypeInferenceUtils.combineTypes(evaluated);
}
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState
state) {
if (state != GoalState.RECURSIVE && result != null) {
evaluated.add((IEvaluatedType) result);
}
return IGoal.NO_GOALS;
}
/**
* Searches for all class variable declarations using offset and length
* which is hold by model element
*
* @author michael
*/
class ClassDeclarationSearcher extends ContextFinder {
private static final String NULL = "null"; //$NON-NLS-1$
private TypeDeclaration typeDeclaration;
private ASTNode result;
private IContext context;
private int offset;
private int length;
private String variableName;
private ISourceModule sourceModule;
private Map<ASTNode, IContext> staticDeclarations;
public ClassDeclarationSearcher(ISourceModule sourceModule,
TypeDeclaration typeDeclaration, int offset,
int length,
String variableName) {
super(sourceModule);
this.typeDeclaration = typeDeclaration;
this.offset = offset;
this.length = length;
this.sourceModule = sourceModule;
this.variableName = variableName;
this.staticDeclarations = new HashMap<ASTNode,
IContext>();
}
public ClassDeclarationSearcher(ISourceModule sourceModule,
TypeDeclaration typeDeclaration, int offset,
int length,
String variableName, IType realType, IType
declaringType) {
// this(sourceModule, typeDeclaration2, offset2,
length2,
// variableName);
super(sourceModule, realType, declaringType);
this.typeDeclaration = typeDeclaration;
this.offset = offset;
this.length = length;
this.sourceModule = sourceModule;
this.variableName = variableName;
this.staticDeclarations = new HashMap<ASTNode,
IContext>();
}
public ASTNode getResult() {
return result;
}
public Map<ASTNode, IContext> getStaticDeclarations() {
return staticDeclarations;
}
public IContext getContext() {
if (context instanceof IModelCacheContext
&&
ClassVariableDeclarationEvaluator.this.goal.getContext() instanceof
IModelCacheContext) {
((IModelCacheContext) context)
.setCache(((IModelCacheContext)
ClassVariableDeclarationEvaluator.this.goal
.getContext()).getCache());
}
return context;
}
public boolean visit(Statement e) throws Exception {
if (typeDeclaration.sourceStart() < e.sourceStart()
&& typeDeclaration.sourceEnd() >
e.sourceEnd()) {
if (e instanceof PHPFieldDeclaration) {
PHPFieldDeclaration phpFieldDecl =
(PHPFieldDeclaration) e;
if (phpFieldDecl.getDeclarationStart()
== offset
&&
phpFieldDecl.sourceEnd()
-
phpFieldDecl.getDeclarationStart() == length) {
result = ((PHPFieldDeclaration)
e).getVariableValue();
if (result instanceof Scalar) {
Scalar scalar =
(Scalar) result;
if
(scalar.getScalarType() == Scalar.TYPE_STRING
&&
scalar.getValue().toLowerCase()
.equals(NULL)) {
result = null;
}
}
context = contextStack.peek();
}
}
}
return visitGeneral(e);
}
public boolean visit(Expression e) throws Exception {
if (typeDeclaration.sourceStart() < e.sourceStart()
&& typeDeclaration.sourceEnd() >
e.sourceEnd()) {
if (e instanceof Assignment) {
if (e.sourceStart() == offset
&& e.sourceEnd() -
e.sourceStart() == length) {
result = ((Assignment)
e).getValue();
context = contextStack.peek();
} else if (variableName != null) {
Assignment assignment =
(Assignment) e;
Expression left =
assignment.getVariable();
Expression right =
assignment.getValue();
if (left instanceof
StaticFieldAccess) {
StaticFieldAccess
fieldAccess = (StaticFieldAccess) left;
Expression dispatcher =
fieldAccess.getDispatcher();
if (isSelf(dispatcher))
{
Expression
field = fieldAccess.getField();
if (field
instanceof VariableReference
&& variableName
.equals(((VariableReference) field)
.getName())) {
staticDeclarations.put(right,
contextStack.peek());
}
}
} else if (left instanceof
FieldAccess) {
FieldAccess fieldAccess
= (FieldAccess) left;
Expression dispatcher =
fieldAccess.getDispatcher();
if (dispatcher
instanceof VariableReference
&&
"$this".equals(((VariableReference) dispatcher).getName())) { //$NON-NLS-1$
Expression
field = fieldAccess.getField();
if (field
instanceof SimpleReference
&& variableName
.equals('$' + ((SimpleReference) field)
.getName())) {
staticDeclarations.put(right,
contextStack.peek());
}
}
}
}
}
}
return visitGeneral(e);
}
public boolean visitGeneral(ASTNode e) throws Exception {
return e.sourceStart() <= offset || variableName !=
null;
}
private boolean isSelf(Expression dispatcher) {
if (!(dispatcher instanceof TypeReference)) {
return false;
}
if ("self".equals(((TypeReference)
dispatcher).getName())) { //$NON-NLS-1$
return true;
} else if (PHPVersion.PHP5_4.isLessThan(ProjectOptions
.getPhpVersion(sourceModule))
&& "self".equals(((TypeReference)
dispatcher).getName() //$NON-NLS-1$
.toLowerCase())) {
return true;
}
return false;
}
}
}
/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.typeinference.evaluators;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.evaluation.types.MultiTypeType;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.IContext;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.goals.GoalEvaluator;
import org.eclipse.dltk.ti.goals.IGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.internal.core.compiler.ast.nodes.*;
import
org.eclipse.php.internal.core.compiler.ast.parser.php56.CompilerParserConstants;
import org.eclipse.php.internal.core.compiler.ast.parser.php56.PhpTokenNames;
import org.eclipse.php.internal.core.typeinference.PHPClassType;
import org.eclipse.php.internal.core.typeinference.PHPModelUtils;
import org.eclipse.php.internal.core.typeinference.PHPSimpleTypes;
import org.eclipse.php.internal.core.typeinference.context.MethodContext;
import
org.eclipse.php.internal.core.typeinference.evaluators.phpdoc.PHPDocClassVariableEvaluator;
public class FormalParameterEvaluator extends GoalEvaluator {
private static final String ELLIPSIS = PhpTokenNames
.getName(CompilerParserConstants.T_ELLIPSIS);
private IEvaluatedType result;
public FormalParameterEvaluator(IGoal goal) {
super(goal);
}
public IGoal[] init() {
ExpressionTypeGoal typedGoal = (ExpressionTypeGoal) goal;
FormalParameter parameter = (FormalParameter)
typedGoal.getExpression();
SimpleReference type = parameter.getParameterType();
if (type != null && "array".equals(type.getName()) == false) {
//$NON-NLS-1$
result = PHPClassType.fromSimpleReference(type);
} else {
IContext context = typedGoal.getContext();
if (context instanceof MethodContext) {
MethodContext methodContext = (MethodContext)
context;
PHPMethodDeclaration methodDeclaration =
(PHPMethodDeclaration) methodContext
.getMethodNode();
PHPDocBlock[] docBlocks = new PHPDocBlock[0];
try {
IModelElement element =
methodContext.getSourceModule()
.getElementAt(methodDeclaration.getNameStart());
if (element instanceof IMethod) {
IMethod method = (IMethod)
element;
if (method.getDeclaringType()
!= null) {
docBlocks =
PHPModelUtils
.getTypeHierarchyMethodDoc(
method.getDeclaringType(),
methodContext.getCache() != null ? methodContext
.getCache()
.getSuperTypeHierarchy(
method.getDeclaringType(),
null)
: null, method
.getElementName(), true,
null);
} else {
docBlocks = new
PHPDocBlock[] { methodDeclaration
.getPHPDoc() };
}
} else {
docBlocks = new PHPDocBlock[] {
methodDeclaration
.getPHPDoc() };
}
} catch (CoreException e) {
}
for (PHPDocBlock docBlock : docBlocks) {
if (result != null) {
break;
}
if (docBlock != null) {
for (PHPDocTag tag :
docBlock.getTags()) {
if (tag.getTagKind() ==
PHPDocTag.PARAM) {
SimpleReference[] references = tag
.getReferences();
if
(references.length == 2) {
String
parameterName = parameter.getName();
if
(parameter.isVariadic()) {
parameterName = ELLIPSIS
+ parameterName;
}
if
(references[0].getName().equals(
parameterName)) {
// result = PHPClassType
// .fromSimpleReference(PHPModelUtils.getFullName(references[1].getName(),
// methodContext.getSourceModule(),
// references[1].sourceStart()));
// fix unit test testDoctag7.pdtt
String typeName = references[1]
.getName();
if (typeName.indexOf('|') >= 0) {
String[] typeNames = typeName
.split("\\|"); //$NON-NLS-1$
MultiTypeType arrayType = new MultiTypeType();
for (int i = 0; i < typeNames.length; i++) {
if (typeNames[i]
.endsWith(PHPDocClassVariableEvaluator.BRACKETS)) {
typeNames[i] = typeNames[i]
.substring(
0,
typeNames[i]
.length() - 2);
}
if (typeNames[i].trim()
.length() == 0) { //$NON-NLS-1$
continue;
}
arrayType
.addType(PHPClassType
.fromTypeName(
typeNames[i],
methodContext
.getSourceModule(),
references[1]
.sourceStart()));
}
result = arrayType;
} else if (typeName
.endsWith(PHPDocClassVariableEvaluator.BRACKETS)) {
typeName = typeName.substring(0,
typeName.length() - 2);
}
if (typeName.trim().length() == 0) { //$NON-NLS-1$
continue;
}
result = PHPClassType
.fromTypeName(
typeName,
methodContext
.getSourceModule(),
references[1]
.sourceStart());
}
}
}
}
}
}
if (result == null
&&
parameter.getInitialization() instanceof Scalar) {
Scalar scalar = (Scalar)
parameter.getInitialization();
result =
PHPSimpleTypes.fromString(scalar.getType());
if (result == null) {
result = new
PHPClassType(scalar.getType());
}
}
}
}
return IGoal.NO_GOALS;
}
public Object produceResult() {
return result;
}
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState
state) {
return IGoal.NO_GOALS;
}
}
/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.typeinference.evaluators;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.IContext;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.goals.IGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.internal.core.compiler.ast.nodes.PHPDocBlock;
import org.eclipse.php.internal.core.compiler.ast.nodes.PHPDocTag;
import org.eclipse.php.internal.core.compiler.ast.nodes.ReturnStatement;
import org.eclipse.php.internal.core.compiler.ast.nodes.YieldExpression;
import org.eclipse.php.internal.core.compiler.ast.parser.ASTUtils;
import org.eclipse.php.internal.core.typeinference.*;
import org.eclipse.php.internal.core.typeinference.context.IModelCacheContext;
import org.eclipse.php.internal.core.typeinference.context.MethodContext;
import
org.eclipse.php.internal.core.typeinference.evaluators.phpdoc.PHPDocClassVariableEvaluator;
import
org.eclipse.php.internal.core.typeinference.goals.MethodElementReturnTypeGoal;
public class MethodReturnTypeEvaluator extends
AbstractMethodReturnTypeEvaluator {
private final List<IEvaluatedType> evaluated = new
LinkedList<IEvaluatedType>();
private final List<IEvaluatedType> yieldEvaluated = new
LinkedList<IEvaluatedType>();
private final List<IGoal> yieldGoals = new LinkedList<IGoal>();
public MethodReturnTypeEvaluator(IGoal goal) {
super(goal);
}
public IGoal[] init() {
MethodElementReturnTypeGoal goal =
(MethodElementReturnTypeGoal) getGoal();
String methodName = goal.getMethodName();
final List<IGoal> subGoals = new LinkedList<IGoal>();
MethodsAndTypes mat = getMethodsAndTypes();
for (int i = 0; i < mat.methods.length; i++) {
IMethod method = mat.methods[i];
ISourceModule sourceModule = method.getSourceModule();
ModuleDeclaration module = SourceParserUtil
.getModuleDeclaration(sourceModule);
MethodDeclaration decl = null;
try {
decl = PHPModelUtils.getNodeByMethod(module,
method);
} catch (ModelException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
// final boolean found[] = new boolean[1];
if (decl != null) {
final IContext innerContext =
ASTUtils.findContext(
sourceModule, module, decl);
if (innerContext instanceof MethodContext) {
MethodContext mc = (MethodContext)
innerContext;
mc.setCurrentType(mat.types[i]);
}
if (goal.getContext() instanceof
IModelCacheContext
&& innerContext instanceof
IModelCacheContext) {
((IModelCacheContext) innerContext)
.setCache(((IModelCacheContext) goal.getContext())
.getCache());
}
ASTVisitor visitor = new ASTVisitor() {
public boolean visitGeneral(ASTNode
node) throws Exception {
if (node instanceof
ReturnStatement) {
ReturnStatement
statement = (ReturnStatement) node;
Expression expr =
statement.getExpr();
if (expr == null) {
evaluated.add(PHPSimpleTypes.VOID);
} else {
subGoals.add(new ExpressionTypeGoal(
innerContext, expr));
}
} else if (node instanceof
YieldExpression) {
YieldExpression
statement = (YieldExpression) node;
Expression expr =
statement.getExpr();
if (expr == null) {
yieldEvaluated.add(PHPSimpleTypes.NULL);
} else {
final
ExpressionTypeGoal yg = new ExpressionTypeGoal(
innerContext, expr);
subGoals.add(yg);
yieldGoals.add(yg);
}
}
return super.visitGeneral(node);
}
};
try {
decl.traverse(visitor);
} catch (Exception e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
if (method != null) {
resolveMagicMethodDeclaration(method,
methodName);
}
}
return subGoals.toArray(new IGoal[subGoals.size()]);
}
/**
* Resolve magic methods defined by the @method tag
*/
private void resolveMagicMethodDeclaration(IMethod method, String
methodName) {
final IModelElement parent = method.getParent();
if (parent.getElementType() != IModelElement.TYPE) {
return;
}
IType type = (IType) parent;
final PHPDocBlock docBlock = PHPModelUtils.getDocBlock(type);
if (docBlock == null) {
return;
}
IType currentNamespace =
PHPModelUtils.getCurrentNamespace(type);
for (PHPDocTag tag : docBlock.getTags()) {
final int tagKind = tag.getTagKind();
if (tagKind == PHPDocTag.METHOD) {
final String[] typeNames =
getTypeBinding(methodName, tag);
if (typeNames != null) {
for (String typeName : typeNames) {
if (typeName.trim().length() ==
0) {
continue;
}
Matcher m =
PHPDocClassVariableEvaluator.ARRAY_TYPE_PATTERN
.matcher(typeName);
if (m.find()) {
evaluated.add(PHPDocClassVariableEvaluator
.getArrayType(m.group(), currentNamespace,
tag.sourceStart()));
} else if (typeName
.endsWith(PHPDocClassVariableEvaluator.BRACKETS)
&&
typeName.length() > 2) {
int offset =
tag.sourceStart();
evaluated.add(PHPDocClassVariableEvaluator
.getArrayType(
typeName.substring(0,
typeName.length() - 2),
currentNamespace, offset));
} else {
IEvaluatedType resolved
= PHPSimpleTypes
.fromString(typeName);
if (resolved == null) {
resolved = new
PHPClassType(typeName);
}
evaluated.add(resolved);
}
}
}
}
}
}
/**
* Resolves the type from the @property tag
*
* @param variableName
* @param docTag
* @return the types of the given variable
*/
private String[] getTypeBinding(String methodName, PHPDocTag docTag) {
final String[] split = docTag.getValue().trim().split("\\s+");
//$NON-NLS-1$
if (split.length < 2) {
return null;
}
if (split[1].equals(methodName)) {
return split[0].split("\\|");//$NON-NLS-1$
}
String substring = split[1];
int parenIndex = split[1].indexOf('('); //$NON-NLS-1$
if (parenIndex != -1) {
substring = substring.substring(0, parenIndex);
}
if (substring.equals(methodName)) {
return split[0].split("\\|");//$NON-NLS-1$
}
return null;
}
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState
state) {
if (state != GoalState.RECURSIVE && result != null) {
if (!yieldGoals.contains(subgoal)) {
evaluated.add((IEvaluatedType) result);
} else {
yieldEvaluated.add((IEvaluatedType) result);
}
}
return IGoal.NO_GOALS;
}
public Object produceResult() {
if (yieldEvaluated.size() > 0 || yieldGoals.size() > 0) {
GeneratorClassType generatorClassType = new
GeneratorClassType();
generatorClassType.getTypes().addAll(yieldEvaluated);
evaluated.add(generatorClassType);
}
return PHPTypeInferenceUtils.combineTypes(evaluated);
}
}
/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.typeinference.evaluators;
import java.util.*;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.evaluation.types.SimpleType;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.IContext;
import org.eclipse.dltk.ti.ISourceModuleContext;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.goals.GoalEvaluator;
import org.eclipse.dltk.ti.goals.IGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.internal.core.PHPVersion;
import org.eclipse.php.internal.core.compiler.ast.nodes.*;
import org.eclipse.php.internal.core.project.ProjectOptions;
import org.eclipse.php.internal.core.typeinference.ArrayDeclaration;
import org.eclipse.php.internal.core.typeinference.Declaration;
import org.eclipse.php.internal.core.typeinference.IModelAccessCache;
import org.eclipse.php.internal.core.typeinference.PHPTypeInferenceUtils;
import org.eclipse.php.internal.core.typeinference.context.ContextFinder;
import org.eclipse.php.internal.core.typeinference.context.FileContext;
import org.eclipse.php.internal.core.typeinference.context.IModelCacheContext;
import org.eclipse.php.internal.core.typeinference.context.MethodContext;
import org.eclipse.php.internal.core.typeinference.goals.ArrayDeclarationGoal;
import org.eclipse.php.internal.core.typeinference.goals.ForeachStatementGoal;
import
org.eclipse.php.internal.core.typeinference.goals.GlobalVariableReferencesGoal;
/**
* This evaluator finds all local variable declarations and produces the
* following sub-goals: {@link GlobalVariableReferencesGoal} or
* {@link VariableDeclarationGoal}
*/
public class VariableReferenceEvaluator extends GoalEvaluator {
private List<IEvaluatedType> results = new ArrayList<IEvaluatedType>();
public VariableReferenceEvaluator(IGoal goal) {
super(goal);
}
public IGoal[] init() {
final VariableReference variableReference = (VariableReference)
((ExpressionTypeGoal) goal)
.getExpression();
IContext context = goal.getContext();
IModelAccessCache cache = null;
if (context instanceof IModelCacheContext) {
cache = ((IModelCacheContext) context).getCache();
}
// Handle $this variable reference
if (variableReference.getName().equals("$this")) { //$NON-NLS-1$
if (context instanceof MethodContext) {
MethodContext methodContext = (MethodContext)
context;
final LambdaFunctionDeclaration[] lambdas = new
LambdaFunctionDeclaration[1];
ContextFinder contextFinder = new ContextFinder(
methodContext.getSourceModule()) {
@Override
public boolean visit(Expression s)
throws Exception {
if (s instanceof
LambdaFunctionDeclaration) {
LambdaFunctionDeclaration lambda = (LambdaFunctionDeclaration) s;
if
(variableReference.sourceStart() > lambda
.sourceStart()
&&
variableReference.sourceEnd() < lambda
.sourceEnd()) {
lambdas[0] =
lambda;
}
}
return super.visit(s);
}
};
try {
methodContext.getRootNode().traverse(contextFinder);
} catch (Exception e) {
}
PHPVersion phpVersion = ProjectOptions
.getPhpVersion(methodContext.getSourceModule()
.getScriptProject().getProject());
if (lambdas[0] != null
&& (lambdas[0].isStatic() ||
phpVersion
.isLessThan(PHPVersion.PHP5_4))) {
this.results.add(new
SimpleType(SimpleType.TYPE_NULL));
} else {
IEvaluatedType instanceType =
methodContext
.getInstanceType();
if (instanceType != null) {
this.results.add(instanceType);
} else {
this.results.add(new
SimpleType(SimpleType.TYPE_NULL));
}
}
return IGoal.NO_GOALS;
}
}
try {
if (context instanceof ISourceModuleContext) {
ISourceModuleContext typedContext =
(ISourceModuleContext) context;
ASTNode rootNode = typedContext.getRootNode();
ASTNode localScopeNode = rootNode;
if (context instanceof MethodContext) {
localScopeNode = ((MethodContext)
context).getMethodNode();
}
LocalReferenceDeclSearcher varDecSearcher = new
LocalReferenceDeclSearcher(
typedContext.getSourceModule(),
variableReference,
localScopeNode);
rootNode.traverse(varDecSearcher);
PHPModuleDeclaration phpModule =
(PHPModuleDeclaration) rootNode;
List<IGoal> subGoals = new LinkedList<IGoal>();
List<VarComment> varComments =
phpModule.getVarComments();
List<VarComment> newList = new
ArrayList<VarComment>(phpModule
.getVarComments().size());
newList.addAll(varComments);
Collections.sort(newList, new
Comparator<VarComment>() {
public int compare(VarComment o1,
VarComment o2) {
return o2.sourceStart() -
o1.sourceStart();
}
});
for (VarComment varComment : newList) {
if (varComment.sourceStart() >
variableReference
.sourceStart()) {
continue;
}
if
(varComment.getVariableReference().getName()
.equals(variableReference.getName())) {
List<IGoal> goals = new
LinkedList<IGoal>();
for (TypeReference ref :
varComment.getTypeReferences()) {
goals.add(new
ExpressionTypeGoal(context, ref));
}
return (IGoal[])
goals.toArray(new IGoal[goals.size()]);
}
}
List<PHPDocBlock> docBlocks = new
ArrayList<PHPDocBlock>(
phpModule.getPhpDocBlocks().size());
docBlocks.addAll(phpModule.getPhpDocBlocks());
Collections.sort(docBlocks, new
Comparator<PHPDocBlock>() {
@Override
public int compare(PHPDocBlock o1,
PHPDocBlock o2) {
return o1.sourceStart() -
o1.sourceStart();
}
});
for (PHPDocBlock block : docBlocks) {
if (block.sourceStart() >
variableReference.sourceStart()
||
localScopeNode.sourceStart() > block
.sourceStart()) {
continue;
}
for (PHPDocTag tag :
block.getTags(PHPDocTagKinds.VAR)) {
String value =
tag.getValue().trim();
if (value.charAt(0) != '$') {
continue;
}
String[] split =
value.split("\\s+"); //$NON-NLS-1$
if (split.length > 1
&&
split[0].equals(variableReference.getName())) {
List<IGoal> goals = new
LinkedList<IGoal>();
for (String name :
split[1].split("\\|")) { //$NON-NLS-1$
if
(name.trim().length() > 0) {
goals.add(new ExpressionTypeGoal(context,
new TypeReference(
tag.sourceStart(), tag
.sourceEnd(), name
.trim())));
}
}
return (IGoal[])
goals.toArray(new IGoal[goals
.size()]);
}
}
}
Declaration[] decls =
varDecSearcher.getDeclarations();
boolean mergeWithGlobalScope = false;
for (int i = 0; i < decls.length; ++i) {
Declaration decl = decls[i];
// TODO check ArrayCreation and its
element type
if (decl instanceof ArrayDeclaration) {
ArrayDeclaration
arrayDeclaration = (ArrayDeclaration) decl;
subGoals.add(new
ArrayDeclarationGoal(context,
arrayDeclaration));
} else if (decl.getNode() instanceof
GlobalStatement) {
mergeWithGlobalScope = true;
} else {
ASTNode declNode =
decl.getNode();
if (declNode instanceof
ForEachStatement) {
subGoals.add(new
ForeachStatementGoal(context,
((ForEachStatement) declNode)
.getExpression()));
} else {
subGoals.add(new
ExpressionTypeGoal(context,
declNode));
}
}
}
if (mergeWithGlobalScope
|| (decls.length == 0 &&
context.getClass() == FileContext.class)) {
// collect all global variables, and
merge results with
// existing declarations
subGoals.add(new
GlobalVariableReferencesGoal(context,
variableReference.getName()));
}
return subGoals.toArray(new
IGoal[subGoals.size()]);
}
} catch (Exception e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
return IGoal.NO_GOALS;
}
public Object produceResult() {
return PHPTypeInferenceUtils.combineTypes(results);
}
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState
state) {
if (state != GoalState.RECURSIVE && result != null) {
results.add((IEvaluatedType) result);
}
return IGoal.NO_GOALS;
}
public static class LocalReferenceDeclSearcher
extends
org.eclipse.php.internal.core.typeinference.VariableDeclarationSearcher {
private final String variableName;
private final int variableOffset;
private final ASTNode localScopeNode;
private IContext variableContext;
private int variableLevel;
public LocalReferenceDeclSearcher(ISourceModule sourceModule,
VariableReference variableReference, ASTNode
localScopeNode) {
super(sourceModule);
variableName = variableReference.getName();
variableOffset = variableReference.sourceStart();
this.localScopeNode = localScopeNode;
}
public Declaration[] getDeclarations() {
Declaration[] declarations = getScope(variableContext)
.getDeclarations(variableName);
if (variableLevel > 0 && variableLevel <
declarations.length) {
Declaration[] newDecls = new
Declaration[declarations.length
- variableLevel];
System.arraycopy(declarations, variableLevel,
newDecls, 0,
newDecls.length);
declarations = newDecls;
}
List<Declaration> filteredDecls = new
LinkedList<Declaration>();
for (Declaration decl : declarations) {
if (decl.getNode().sourceStart() >
localScopeNode.sourceStart()) {
filteredDecls.add(decl);
}
}
return (Declaration[]) filteredDecls
.toArray(new
Declaration[filteredDecls.size()]);
}
protected void postProcess(Expression node) {
if (node instanceof InstanceOfExpression) {
InstanceOfExpression expr =
(InstanceOfExpression) node;
if (expr.getExpr() instanceof
VariableReference) {
VariableReference varReference =
(VariableReference) expr
.getExpr();
if
(variableName.equals(varReference.getName())) {
getScope().addDeclaration(variableName,
expr.getClassName());
}
}
}
}
protected void postProcessGeneral(ASTNode node) {
if (node.sourceStart() <= variableOffset
&& node.sourceEnd() >= variableOffset) {
variableContext = contextStack.peek();
variableLevel =
getScope(variableContext).getInnerBlockLevel();
}
}
protected void postProcess(Statement node) {
}
protected boolean isInteresting(ASTNode node) {
return node.sourceStart() <= variableOffset;
}
}
}
_______________________________________________
pdt-dev mailing list
[email protected]
To change your delivery options, retrieve your password, or unsubscribe from
this list, visit
https://dev.eclipse.org/mailman/listinfo/pdt-dev