blob: 75ae98295f288bf272318eaf2a6cdc93656cc909 [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.List;
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.CallExpression;
import org.eclipse.dltk.evaluation.types.UnknownType;
import org.eclipse.dltk.ruby.ast.RubySelfReference;
import org.eclipse.dltk.ruby.internal.parsers.jruby.ASTUtils;
import org.eclipse.dltk.ruby.typeinference.RubyClassType;
import org.eclipse.dltk.ruby.typeinference.RubyTypeInferencingUtils;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.ISourceModuleContext;
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.IEvaluatedType;
public class MethodCallTypeEvaluator extends RubyMixinGoalEvaluator {
private final static int STATE_INIT = 0;
private final static int STATE_WAITING_RECEIVER = 1;
private final static int STATE_GOT_RECEIVER = 2;
private final static int STATE_WAITING_ARGUMENT_0 = 3;
private final static int STATE_WAITING_ARGUMENT_LAST = 9999;
private final static int STATE_ARGS_DONE = 10000;
private final static int STATE_WAITING_METHOD = 10001;
private final static int STATE_UNKNOWN = -1;
private final static int STATE_DONE = -2;
private int state = STATE_INIT;
private IEvaluatedType receiverType;
private IEvaluatedType[] arguments;
private IEvaluatedType result;
public MethodCallTypeEvaluator(ExpressionTypeGoal goal) {
super(goal);
}
private IGoal produceNextSubgoal(IGoal previousGoal, Object previousResult) {
if (state == STATE_INIT) {
ExpressionTypeGoal typedGoal = (ExpressionTypeGoal) goal;
CallExpression expression = (CallExpression) typedGoal
.getExpression();
ASTNode receiver = expression.getReceiver();
if (receiver == null || receiver instanceof RubySelfReference) {
// handling SelfReference here just for simplicity, could be
// left to the TI engine as well
IEvaluatedType scopeType = RubyTypeInferencingUtils
.determineSelfClass(mixinModel, goal.getContext(),
expression.sourceStart());
if ((scopeType != null)
&& (!(scopeType instanceof RubyClassType) || !("Object" //$NON-NLS-1$
.equals(((RubyClassType) scopeType).getTypeName())))) {
receiverType = scopeType;
state = STATE_GOT_RECEIVER;
} else { // ssanders: Allow other evaluators to narrow type
ASTNode scopeNode = receiver;
if (scopeNode == null) {
scopeNode = ASTUtils.findMinimalNode(
((ISourceModuleContext) goal.getContext())
.getRootNode(), expression
.sourceStart(), expression
.sourceStart());
ASTNode[] wayToNode = ASTUtils.restoreWayToNode(
((ISourceModuleContext) goal.getContext())
.getRootNode(), scopeNode);
for (int cnt = (wayToNode.length - 2); cnt >= 0; cnt--) {
if ((wayToNode[cnt] instanceof TypeDeclaration)
|| (wayToNode[cnt] instanceof ModuleDeclaration)) {
scopeNode = wayToNode[cnt];
break;
}
}
}
state = STATE_WAITING_RECEIVER;
return new ExpressionTypeGoal(goal.getContext(), scopeNode);
}
} else {
state = STATE_WAITING_RECEIVER;
return new ExpressionTypeGoal(goal.getContext(), receiver);
}
}
if (state == STATE_WAITING_RECEIVER) {
receiverType = (IEvaluatedType) previousResult;
if (receiverType == null) {
state = STATE_UNKNOWN;
return null;
}
state = STATE_GOT_RECEIVER;
}
if (state == STATE_GOT_RECEIVER) {
ExpressionTypeGoal typedGoal = (ExpressionTypeGoal) goal;
CallExpression expression = (CallExpression) typedGoal
.getExpression();
List<ASTNode> arguments = expression.getArgs().getChilds();
this.arguments = new IEvaluatedType[arguments.size()];
}
if (state >= STATE_WAITING_ARGUMENT_0
&& state <= STATE_WAITING_ARGUMENT_LAST) {
arguments[state - STATE_WAITING_ARGUMENT_0] = (IEvaluatedType) previousResult;
}
if (state == STATE_GOT_RECEIVER || state >= STATE_WAITING_ARGUMENT_0
&& state <= STATE_WAITING_ARGUMENT_LAST) {
int nextArg = (state == STATE_GOT_RECEIVER ? 0 : state
- STATE_WAITING_ARGUMENT_0 + 1);
ExpressionTypeGoal typedGoal = (ExpressionTypeGoal) goal;
CallExpression expression = (CallExpression) typedGoal
.getExpression();
List<ASTNode> arguments = expression.getArgs().getChilds();
if (nextArg < arguments.size()) {
state = STATE_WAITING_ARGUMENT_0 + nextArg;
return new ExpressionTypeGoal(goal.getContext(),
arguments.get(nextArg));
} else {
state = STATE_ARGS_DONE;
}
}
if (state == STATE_ARGS_DONE) {
ExpressionTypeGoal typedGoal = (ExpressionTypeGoal) goal;
CallExpression expression = (CallExpression) typedGoal
.getExpression();
state = STATE_WAITING_METHOD;
if (receiverType == UnknownType.INSTANCE)
receiverType = null;
return new MethodReturnTypeGoal(new InstanceContext(
(ISourceModuleContext) goal.getContext(), receiverType),
expression.getName(), arguments);
}
if (state == STATE_WAITING_METHOD) {
result = (IEvaluatedType) previousResult;
state = STATE_DONE;
}
return null;
}
@Override
public Object produceResult() {
if (state == STATE_UNKNOWN)
return null;
else
return result;
}
@Override
public IGoal[] init() {
IGoal goal = produceNextSubgoal(null, null);
if (goal != null)
return new IGoal[] { goal };
return IGoal.NO_GOALS;
}
@Override
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState state) {
IGoal goal = produceNextSubgoal(subgoal, result);
if (goal != null)
return new IGoal[] { goal };
return IGoal.NO_GOALS;
}
}