[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[pdt-dev] PDT : Questions about type inference evaluators

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;
		}
	}
}