| /******************************************************************************* |
| * Copyright (c) 2005, 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.ruby.typeinference.evaluators; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| 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.CallArgumentsList; |
| import org.eclipse.dltk.core.IMethod; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.evaluation.types.AmbiguousType; |
| import org.eclipse.dltk.ruby.ast.RubyReturnStatement; |
| import org.eclipse.dltk.ruby.core.RubyPlugin; |
| import org.eclipse.dltk.ruby.core.model.FakeMethod; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.IRubyMixinElement; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixin; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinClass; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinMethod; |
| import org.eclipse.dltk.ruby.typeinference.MethodContext; |
| import org.eclipse.dltk.ruby.typeinference.RubyClassType; |
| import org.eclipse.dltk.ruby.typeinference.RubyModelUtils; |
| import org.eclipse.dltk.ruby.typeinference.RubyTypeInferencingUtils; |
| import org.eclipse.dltk.ti.GoalState; |
| import org.eclipse.dltk.ti.InstanceContext; |
| import org.eclipse.dltk.ti.goals.ExpressionTypeGoal; |
| import org.eclipse.dltk.ti.goals.IGoal; |
| import org.eclipse.dltk.ti.goals.MethodReturnTypeGoal; |
| import org.eclipse.dltk.ti.types.ClassType; |
| import org.eclipse.dltk.ti.types.IEvaluatedType; |
| |
| public class MethodReturnTypeEvaluator extends RubyMixinGoalEvaluator { |
| |
| private final List<ASTNode> possibilities = new ArrayList<ASTNode>(); |
| private final List evaluated = new ArrayList(); |
| |
| private IEvaluatedType rdocResult = null; |
| |
| private MethodContext innerContext; |
| |
| public MethodReturnTypeEvaluator(IGoal goal) { |
| super(goal); |
| } |
| |
| private MethodReturnTypeGoal getTypedGoal() { |
| return (MethodReturnTypeGoal) this.getGoal(); |
| } |
| |
| private InstanceContext getTypedContext() { |
| return (InstanceContext) this.getGoal().getContext(); |
| } |
| |
| @Override |
| public Object produceResult() { |
| if (rdocResult != null) |
| return rdocResult; |
| if (!evaluated.isEmpty()) { |
| return RubyTypeInferencingUtils.combineTypes(evaluated); |
| } |
| return null; |
| } |
| |
| @Override |
| public IGoal[] init() { |
| MethodReturnTypeGoal typedGoal = getTypedGoal(); |
| InstanceContext typedContext = getTypedContext(); |
| IEvaluatedType instanceType = typedContext.getInstanceType(); |
| if (instanceType instanceof AmbiguousType) |
| instanceType = ((AmbiguousType) instanceType).getPossibleTypes()[0]; |
| String methodName = typedGoal.getMethodName(); |
| |
| if (!(instanceType instanceof RubyClassType)) |
| return null; |
| |
| IEvaluatedType intrinsicMethodReturnType = checkSpecialMethodReturnType( |
| (ClassType) instanceType, methodName, typedGoal.getArguments()); |
| if (intrinsicMethodReturnType != null) { |
| evaluated.add(intrinsicMethodReturnType); |
| return IGoal.NO_GOALS; |
| } |
| |
| MethodDeclaration decl = null; |
| List<IMethod> methods = new ArrayList<IMethod>(); |
| if (instanceType == null) { |
| instanceType = new RubyClassType("Object"); //$NON-NLS-1$ |
| } |
| if (instanceType instanceof RubyClassType) { |
| RubyClassType rubyClassType = (RubyClassType) instanceType; |
| RubyMixinClass class1 = mixinModel.createRubyClass(rubyClassType); |
| if (class1 != null) { |
| RubyMixinMethod mixinMethods = class1.getMethod(methodName); |
| // System.out.println(); |
| if (mixinMethods != null) |
| methods.addAll(Arrays.asList(mixinMethods |
| .getSourceMethods())); |
| } |
| if (rubyClassType.getModelKey().equals("Object")) { //$NON-NLS-1$ |
| IRubyMixinElement element = mixinModel |
| .createRubyElement(methodName); |
| if (element instanceof RubyMixinMethod) { |
| RubyMixinMethod rubyMixinMethod = (RubyMixinMethod) element; |
| methods.addAll(Arrays.asList(rubyMixinMethod |
| .getSourceMethods())); |
| } |
| } |
| } |
| |
| IMethod resultMethod = null; |
| // in case of ambiguity, prefer methods from the same module |
| IMethod resultMethodFromSameModule = null; |
| for (Iterator<IMethod> iterator = methods.iterator(); iterator.hasNext();) { |
| IMethod method = iterator.next(); |
| if (method instanceof FakeMethod || method == null) |
| continue; |
| String elementName = method.getElementName(); |
| if (elementName.equals(methodName)) { |
| if (method.getSourceModule().equals( |
| typedContext.getSourceModule())) |
| resultMethodFromSameModule = method; |
| resultMethod = method; |
| } |
| } |
| if (resultMethodFromSameModule != null) |
| resultMethod = resultMethodFromSameModule; |
| |
| if (resultMethod == null) |
| return IGoal.NO_GOALS; |
| |
| ISourceModule sourceModule = resultMethod.getSourceModule(); |
| ModuleDeclaration module = RubyTypeInferencingUtils |
| .parseSource(sourceModule); |
| try { |
| decl = RubyModelUtils.getNodeByMethod(module, resultMethod); |
| } catch (ModelException e) { |
| e.printStackTrace(); |
| } |
| String[] parameters; |
| try { |
| parameters = resultMethod.getParameterNames(); |
| } catch (ModelException e1) { |
| RubyPlugin.log(e1); |
| parameters = new String[0]; |
| } |
| innerContext = new MethodContext(goal.getContext(), sourceModule, |
| module, parameters, typedGoal.getArguments()); |
| |
| ASTVisitor visitor = new ASTVisitor() { |
| |
| @Override |
| public boolean visitGeneral(ASTNode node) throws Exception { |
| if (node instanceof RubyReturnStatement) { |
| RubyReturnStatement statement = (RubyReturnStatement) node; |
| CallArgumentsList list = statement.getValue(); |
| if (list.getChilds().size() == 0) { |
| MethodReturnTypeEvaluator.this.evaluated |
| .add(new RubyClassType("NilClass")); //$NON-NLS-1$ |
| } else if (list.getChilds().size() > 1) { |
| MethodReturnTypeEvaluator.this.evaluated |
| .add(new RubyClassType("Array")); //$NON-NLS-1$ |
| } else { |
| possibilities.add(list.getChilds().get(0)); |
| } |
| } |
| return super.visitGeneral(node); |
| } |
| |
| }; |
| if (decl != null) { |
| try { |
| decl.traverse(visitor); |
| } catch (Exception e) { |
| RubyPlugin.log(e); |
| } |
| if (decl.getBody() != null) |
| possibilities.add(decl.getBody()); |
| } |
| |
| IGoal[] newGoals = new IGoal[possibilities.size()]; |
| int i = 0; |
| for (Iterator<ASTNode> iterator = possibilities.iterator(); iterator.hasNext();) { |
| ASTNode st = iterator.next(); |
| ExpressionTypeGoal subgoal = new ExpressionTypeGoal(innerContext, st); |
| newGoals[i++] = subgoal; |
| } |
| return newGoals; |
| } |
| |
| private IEvaluatedType checkSpecialMethodReturnType(ClassType instanceType, |
| String methodName, IEvaluatedType[] arguments) { |
| if (methodName.equals("clone") && instanceType.getModelKey().endsWith(RubyMixin.INSTANCE_SUFFIX)) //$NON-NLS-1$ |
| return instanceType; |
| if (instanceType == null |
| || instanceType.getModelKey().endsWith( |
| RubyMixin.INSTANCE_SUFFIX) |
| || instanceType.getModelKey() |
| .endsWith(RubyMixin.VIRTUAL_SUFFIX)) |
| return null; |
| if (methodName.equals("new")) //$NON-NLS-1$ |
| return new RubyClassType(instanceType.getModelKey() |
| + RubyMixin.INSTANCE_SUFFIX); |
| return null; |
| } |
| |
| @Override |
| public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState state) { |
| if (result != null) |
| evaluated.add(result); |
| return IGoal.NO_GOALS; |
| } |
| |
| } |