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