blob: ea7b1f8e66a12970eefc4dd9b2f6c64b1e9c3ba7 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}