/******************************************************************************* * 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; } }