/******************************************************************************* * Copyright (c) 2015, 2016 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 org.eclipse.core.runtime.CoreException; import org.eclipse.dltk.ast.references.TypeReference; import org.eclipse.dltk.ast.references.VariableReference; import org.eclipse.dltk.core.*; import org.eclipse.dltk.evaluation.types.MultiTypeType; import org.eclipse.dltk.ti.GoalState; 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.core.compiler.ast.nodes.FormalParameter; import org.eclipse.php.core.compiler.ast.nodes.PHPDocBlock; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag; import org.eclipse.php.core.compiler.ast.nodes.PHPMethodDeclaration; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag.TagKind; import org.eclipse.php.internal.core.Logger; import org.eclipse.php.internal.core.typeinference.GeneratorClassType; import org.eclipse.php.internal.core.typeinference.IModelAccessCache; import org.eclipse.php.internal.core.typeinference.PHPClassType; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; 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.IteratorTypeGoal; import org.eclipse.php.internal.core.typeinference.goals.MethodElementReturnTypeGoal; import org.eclipse.php.internal.core.typeinference.goals.phpdoc.PHPDocMethodReturnTypeGoal; public class IteratorTypeGoalEvaluator extends GoalEvaluator { private IEvaluatedType result; public IteratorTypeGoalEvaluator(IGoal goal) { super(goal); } public IGoal[] init() { IteratorTypeGoal typedGoal = (IteratorTypeGoal) goal; return new IGoal[] { new ExpressionTypeGoal(goal.getContext(), typedGoal.getExpression()) }; } public Object produceResult() { return result; } public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState state) { IModelAccessCache cache = null; if (goal.getContext() instanceof IModelCacheContext) { cache = (IModelAccessCache) ((IModelCacheContext) goal.getContext()).getCache(); } String variableName = null; IteratorTypeGoal iteratorTypeGoal = (IteratorTypeGoal) goal; if (iteratorTypeGoal.getExpression() instanceof VariableReference) { variableName = ((VariableReference) iteratorTypeGoal.getExpression()).getName(); } if (state != GoalState.RECURSIVE) { if (result instanceof GeneratorClassType) { MultiTypeType type = new MultiTypeType(); type.getTypes().addAll(((GeneratorClassType) result).getTypes()); this.result = type; return IGoal.NO_GOALS; } else if (result instanceof PHPClassType) { if (subgoal instanceof ExpressionTypeGoal) { ISourceModule sourceModule = ((ISourceModuleContext) subgoal.getContext()).getSourceModule(); PHPClassType classType = (PHPClassType) result; List<IGoal> subGoals = new LinkedList<IGoal>(); try { // XXX: offset is 0 here but it should still work, // because classType already contains the namespace part IType[] types = PHPModelUtils.getTypes(classType.getTypeName(), sourceModule, 0, cache, null); for (IType type : types) { IType[] superTypes = PHPModelUtils.getSuperClasses(type, cache == null ? null : cache.getSuperTypeHierarchy(type, null)); if (subgoal.getContext() instanceof MethodContext) { MethodContext methodContext = (MethodContext) subgoal.getContext(); if (isArrayType(methodContext, variableName, type)) { MultiTypeType mType = new MultiTypeType(); mType.addType((IEvaluatedType) result); this.result = mType; return IGoal.NO_GOALS; } } if (isImplementedIterator(superTypes)) { subGoals.add(new MethodElementReturnTypeGoal(subgoal.getContext(), new IType[] { type }, "current", new String[0], type.getSourceRange().getOffset())); //$NON-NLS-1$ subGoals.add(new PHPDocMethodReturnTypeGoal(subgoal.getContext(), new IType[] { type }, "current", new String[0], type.getSourceRange().getOffset())); //$NON-NLS-1$ } } if (subGoals.size() == 0) { MultiTypeType mType = new MultiTypeType(); mType.addType((IEvaluatedType) result); this.result = mType; return IGoal.NO_GOALS; } return subGoals.toArray(new IGoal[subGoals.size()]); } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } MultiTypeType type = new MultiTypeType(); type.addType((IEvaluatedType) result); this.result = type; return IGoal.NO_GOALS; } this.result = (IEvaluatedType) result; } return IGoal.NO_GOALS; } /** * Check if IType is a typed array in the methodContext: * * <pre> * /* * * @param SomeClass[] $elements * *\/ * public function foo(array $elements); * </pre> * * * @param methodContext * @param variableName * @param type * @return boolean */ private boolean isArrayType(MethodContext methodContext, String variableName, IType type) { PHPMethodDeclaration methodDeclaration = (PHPMethodDeclaration) methodContext.getMethodNode(); PHPDocBlock[] docBlocks = new PHPDocBlock[0]; for (Object object : methodDeclaration.getArguments()) { if (object instanceof FormalParameter) { FormalParameter formalParameter = (FormalParameter) object; if (formalParameter.getName().equals(variableName) && formalParameter.isVariadic()) { return true; } } } 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) { Logger.logException(e); } if (docBlocks.length > 0) { for (int i = 0; i < docBlocks.length; i++) { if (docBlocks[i] == null) { continue; } PHPDocTag[] tags = docBlocks[i].getTags(TagKind.PARAM); for (int j = 0; j < tags.length; j++) { PHPDocTag tag = tags[j]; if (tag.isValidParamTag() && tag.getVariableReference().getName().equals(variableName)) { for (TypeReference reference : tag.getTypeReferences()) { if (PHPEvaluationUtils.isArrayType(reference.getName())) { return true; } } break; } } } } return false; } private boolean isImplementedIterator(IType[] superClasses) { if (superClasses == null) return false; for (IType superClass : superClasses) { if (superClass.getFullyQualifiedName().equalsIgnoreCase("Iterator")) { //$NON-NLS-1$ return true; } } return false; } }