blob: 07bb9bf5d280d5ee046bda9a794a5557b3aec277 [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.Iterator;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.dltk.ast.ASTNode;
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.ast.expressions.CallExpression;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.mixin.MixinModel;
import org.eclipse.dltk.evaluation.types.AmbiguousType;
import org.eclipse.dltk.evaluation.types.UnknownType;
import org.eclipse.dltk.ruby.ast.RubyCallArgument;
import org.eclipse.dltk.ruby.ast.RubyDVarExpression;
import org.eclipse.dltk.ruby.ast.RubyMethodArgument;
import org.eclipse.dltk.ruby.ast.RubySingletonMethodDeclaration;
import org.eclipse.dltk.ruby.ast.RubyVariableKind;
import org.eclipse.dltk.ruby.internal.parsers.jruby.ASTUtils;
import org.eclipse.dltk.ruby.typeinference.IArgumentsContext;
import org.eclipse.dltk.ruby.typeinference.LocalVariableInfo;
import org.eclipse.dltk.ruby.typeinference.RubyClassType;
import org.eclipse.dltk.ruby.typeinference.RubyMethodReference;
import org.eclipse.dltk.ruby.typeinference.RubyTypeInferencingUtils;
import org.eclipse.dltk.ruby.typeinference.VariableTypeGoal;
import org.eclipse.dltk.ti.BasicContext;
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.IGoal;
import org.eclipse.dltk.ti.goals.ItemReference;
import org.eclipse.dltk.ti.goals.MethodCallsGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
public class VariableReferenceEvaluator extends RubyMixinGoalEvaluator {
private LocalVariableInfo info;
private MethodCallsGoal callsGoal = null;
private List results = new ArrayList();
private MethodDeclaration methodDeclaration;
public VariableReferenceEvaluator(IGoal goal) {
super(goal);
}
@Override
public Object produceResult() {
return RubyTypeInferencingUtils.combineTypes(results);
}
private String determineEnclosingMethod(ISourceModule module,
ModuleDeclaration decl, VariableReference ref) {
RubyClassType selfClass;
ASTNode[] wayToNode = ASTUtils.restoreWayToNode(decl, ref);
for (int i = wayToNode.length - 1; i >= 0; i--) {
if (wayToNode[i] instanceof MethodDeclaration) {
methodDeclaration = (MethodDeclaration) wayToNode[i];
String name = methodDeclaration.getName();
if (wayToNode[i] instanceof ModuleDeclaration
&& !(methodDeclaration instanceof RubySingletonMethodDeclaration)) {
return name;
} else {
selfClass = RubyTypeInferencingUtils.determineSelfClass(
mixinModel, module, decl, ref.sourceStart());
if (selfClass == null)
return null;
}
return selfClass.getModelKey() + MixinModel.SEPARATOR + name;
}
}
return null;
}
@Override
public IGoal[] init() {
VariableReference ref = getGoalVariableReference();
if (ref.getVariableKind() == RubyVariableKind.LOCAL) {
IContext context = goal.getContext();
ModuleDeclaration rootNode = ((ISourceModuleContext) context)
.getRootNode();
VariableReference expression = ref;
String varName = expression.getName().trim();
if (context instanceof IArgumentsContext) {
IArgumentsContext argumentsContext = (IArgumentsContext) context;
IEvaluatedType argumentType = argumentsContext
.getArgumentType(varName);
if (argumentType != null) {
results.add(argumentType);
return IGoal.NO_GOALS;
}
}
info = RubyTypeInferencingUtils.inspectLocalVariable(rootNode,
expression.sourceStart(), varName);
List<IGoal> poss = new ArrayList<IGoal>();
if (info != null) {
if (info.getLastAssignment() != null
&& info.getLastAssignment().getRight() != null) {
IGoal subgoal = new ExpressionTypeGoal(context, info
.getLastAssignment().getRight());
poss.add(subgoal);
}
for (int i = 0; i < info.getConditionalAssignments().length; i++) {
if (info.getConditionalAssignments()[i].getRight() != null) {
IGoal subgoal = new ExpressionTypeGoal(context, info
.getConditionalAssignments()[i].getRight());
poss.add(subgoal);
}
}
}
if (poss.size() == 0) {
String key = null;
if (context instanceof ISourceModuleContext) {
ISourceModuleContext basicContext = (ISourceModuleContext) context;
key = determineEnclosingMethod(basicContext
.getSourceModule(), basicContext.getRootNode(), ref);
}
if (key != null) {
int argPos = determineArgumentPos(methodDeclaration, ref
.getName());
if (argPos != -1) {
int lastCurly = key.lastIndexOf(MixinModel.SEPARATOR);
String parent = null;
if (lastCurly != -1) {
parent = key.substring(0, lastCurly);
}
String name = key.substring(lastCurly + 1);
callsGoal = new MethodCallsGoal(context, name, parent);
return new IGoal[] { callsGoal };
}
}
}
return poss.toArray(new IGoal[poss.size()]);
} else {
IEvaluatedType selfClass = RubyTypeInferencingUtils
.determineSelfClass(mixinModel, goal.getContext(), ref
.sourceStart());
if (selfClass instanceof RubyClassType) {
String selfKey = ((RubyClassType) selfClass).getModelKey();
return new IGoal[] { new VariableTypeGoal(goal.getContext(),
ref.getName(), selfKey, ref.getVariableKind()) };
} else if (selfClass instanceof AmbiguousType) {
AmbiguousType ambiType = (AmbiguousType) selfClass;
List<IGoal> goalList = new ArrayList<IGoal>();
IEvaluatedType[] possibleTypes = ambiType.getPossibleTypes();
for (int cnt = 0, max = possibleTypes.length; cnt < max; cnt++) {
if (possibleTypes[cnt] instanceof RubyClassType) {
String selfKey = ((RubyClassType) possibleTypes[cnt])
.getModelKey();
goalList.add(new VariableTypeGoal(goal.getContext(),
ref.getName(), selfKey, ref.getVariableKind()));
}
}
return goalList.toArray(new IGoal[goalList.size()]);
}
}
return IGoal.NO_GOALS;
}
private int determineArgumentPos(MethodDeclaration decl, String varName) {
List methodArgs = methodDeclaration.getArguments();
int pos = 0;
int argPos = -1;
for (Iterator<ASTNode> iterator = methodArgs.iterator(); iterator.hasNext();) {
ASTNode marg = iterator.next();
if (marg instanceof RubyMethodArgument) {
RubyMethodArgument rubyMethodArgument = (RubyMethodArgument) marg;
if (rubyMethodArgument.getName().equals(varName)) {
argPos = pos;
break;
}
}
pos++;
}
return argPos;
}
private VariableReference getGoalVariableReference() {
ASTNode expression = ((ExpressionTypeGoal) goal).getExpression();
if (expression instanceof VariableReference)
return (VariableReference) expression;
if (expression instanceof RubyDVarExpression) {
RubyDVarExpression dvar = (RubyDVarExpression) expression;
return new VariableReference(dvar.sourceStart(), dvar.sourceEnd(),
dvar.getName(), RubyVariableKind.LOCAL);
}
return null;
}
private ASTNode getArgFromCall(CallExpression expr) {
VariableReference ref = getGoalVariableReference();
if (ref.getVariableKind() != RubyVariableKind.LOCAL)
return null;
String name = ref.getName();
int argPos = determineArgumentPos(methodDeclaration, name);
if (argPos != -1) {
CallArgumentsList args = expr.getArgs();
if (args != null) {
List<ASTNode> list = args.getChilds();
if (argPos < list.size()) {
ASTNode st = list.get(argPos);
if (st instanceof RubyCallArgument) {
RubyCallArgument rubyCallArgument = (RubyCallArgument) st;
st = rubyCallArgument.getValue();
}
return st;
}
}
}
return null;
}
@Override
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState state) {
if (subgoal == callsGoal) {
List<IGoal> possibles = new ArrayList<IGoal>();
if (result != null) {
ItemReference[] refs = (ItemReference[]) result;
for (int i = 0; i < refs.length; i++) { // TODO: for performance
// reasons, sort them
// somehow or leave only one
CallExpression node = ((RubyMethodReference) refs[i])
.getNode();
if (node != null) {
ASTNode arg = getArgFromCall(node);
if (arg != null) {
IResource resource = refs[i].getPosition()
.getResource();
ISourceModule module = (ISourceModule) DLTKCore
.create(resource);
if (module == null)
continue;
ModuleDeclaration decl = ASTUtils.getAST(module);
if (decl == null)
continue;
BasicContext callContext = new BasicContext(module,
decl);
ExpressionTypeGoal g = new ExpressionTypeGoal(
callContext, arg);
possibles.add(g);
}
}
}
}
return possibles.toArray(new IGoal[possibles.size()]);
} else if ((result != null) && !(result instanceof UnknownType))
results.add(result);
return IGoal.NO_GOALS;
}
}