package org.rubypeople.rdt.internal.ti;
import org.jruby.ast.CallNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.ConstNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.IterNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.LocalVarNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.rubypeople.rdt.internal.core.parser.InOrderVisitor;
import org.rubypeople.rdt.internal.ti.data.LiteralNodeTypeNames;
import org.rubypeople.rdt.internal.ti.data.TypicalMethodReturnNames;
public class TypeInferenceVisitor extends InOrderVisitor {
private Scope globalScope;
private Scope currentScope;
// TODO: init globalScope to null, push in first non-null node as
// globalScope
public TypeInferenceVisitor( Node rootNode ) {
globalScope = new Scope( rootNode, null );
currentScope = globalScope;
}
/**
* Visit a ModuleNode, and extract its local variables from the embedded
* body ScopeNode
*/
public Object visitModuleNode(ModuleNode iVisited) {
Scope newScope = pushScope( iVisited );
Variable.insertLocalsFromScopeNode(iVisited.getScope(), newScope);
return super.visitModuleNode(iVisited);
}
/**
* Visit a ClassNode, and extract its local variables from the embedded body
* ScopeNode
*/
public Object visitClassNode(ClassNode iVisited) {
Scope newScope = pushScope( iVisited );
Variable.insertLocalsFromScopeNode(iVisited.getScope(), newScope);
return super.visitClassNode(iVisited);
}
/**
* Visit a DefnNode, and extract its local variables from the embedded body
* ScopeNode
*/
public Object visitDefnNode(DefnNode iVisited) {
Scope newScope = pushScope( iVisited );
Variable.insertLocalsFromScopeNode(iVisited.getScope(), newScope);
// TODO: insert from argsNodes
return super.visitDefnNode(iVisited);
}
/**
* Visit a DefsNode, and extract its local variables from the embedded body
* ScopeNode
*/
public Object visitDefsNode(DefsNode iVisited) {
Scope newScope = pushScope( iVisited );
Variable.insertLocalsFromScopeNode(iVisited.getScope(), newScope);
// TODO: insert from argsNodes
return super.visitDefsNode(iVisited);
}
/**
* Visit an IterNode, and extract variable references from it
*/
public Object visitIterNode(IterNode iVisited) {
// TODO: push iterator var into the iter's scope.
// Scope newScope = pushScope(iVisited);
// newScope.getVariables().add( new Variable( newScope, ))
// TODO: insert from varNode; either DAsgnNode or LocalAsgnNode
// depending... (see: block local var ambiguity)
pushScope( iVisited );
return super.visitIterNode(iVisited);
}
/**
* Pushes a new scope onto the stack based on the specified node.
*
* @param node
* Node which signifies the scope being pushed
* @return newly pushed Scope
*/
private Scope pushScope( Node node ) {
Scope newScope = new Scope( node, currentScope );
currentScope = newScope;
return newScope;
}
// TODO: how to tell when to do this?
// TODO: perhaps model IndexUpdater rather than InOrderVisitor
private void popScope() {
currentScope = currentScope.getParentScope();
}
/**
* Used to build STIGuess instances by recording instances of method
* invocation against variables.
*/
public Object visitCallNode(CallNode iVisited) {
Variable var = getVariableByVarNode( iVisited.getReceiverNode() );
if ( var != null ) {
// TODO: add call to list
}
return super.visitCallNode(iVisited);
}
/**
* Gets a Variable reference by a Node
*
* @param node -
* LocalVarNode, InstVarNode, GlobalVarNode, ClassVarNode, or
* DVarNode
* @return Variable or null
*/
private Variable getVariableByVarNode( Node node )
{
// For local variables, search current scope for the variable by count.
if (node instanceof LocalVarNode) {
LocalVarNode localVarNode = (LocalVarNode) node;
return currentScope.getLocalVariableByCount(localVarNode.getIndex());
}
// TODO: InstVarNode
// TODO: GlobalVarNode
// TODO: ClassVarNode
// TODO: DVarNode
return null;
}
/**
* Local assignment may provide a concrete type from the rvalue
*/
public Object visitLocalAsgnNode(LocalAsgnNode iVisited) {
Variable var = currentScope.getLocalVariableByCount( iVisited.getIndex() );
if ( var == null )
{
// Local Variable cannot be found... are we in the global scope?
// (i.e. no localNames given by JRuby)
if ( currentScope == globalScope )
{
// Yes - stick this variable into the global scope.
// TODO: Shouldn't JRuby give a ScopeNode w/ a .getLocalNames()
// for the global script?
var = new Variable( globalScope, iVisited.getName(), iVisited.getIndex() );
currentScope.getVariables().add(var);
}
}
System.out.print("Associating a type to Variable " + var.getName() + ": " );
Node valueNode = iVisited.getValueNode();
// Try seeing if the rvalue is a constant (5, "foo", [1,2,3], etc.)
String concreteGuess = LiteralNodeTypeNames.get(valueNode.getClass().getSimpleName());
if ( concreteGuess != null )
{
var.getTypeGuesses().add( new BasicTypeGuess( concreteGuess, 100 ) );
}
// else if ( valueNode instanceof LocalVarNode ) {
// // TODO: this method needs to be fixed... see
// // ReferenceTypeGuess.java
// LocalVarNode rhsNode = (LocalVarNode)valueNode;
// Variable rhsVar = currentScope.getLocalVariableByCount(rhsNode.getCount());
// var.getTypeGuesses().add( new ReferenceTypeGuess( rhsVar ) );
// }
// Try seeing if the rvalue is a well-known method call such as 5.to_s or FooClass.new
else if (valueNode instanceof CallNode)
{
CallNode callValueNode = (CallNode)valueNode;
String method = callValueNode.getName();
// Try ConstNode.new
if ( method.equals("new") && callValueNode.getReceiverNode() instanceof ConstNode)
{
var.getTypeGuesses().add( new BasicTypeGuess( ((ConstNode)callValueNode.getReceiverNode()).getName() , 100 ) );
}
else {
// Try some well-known method from built-in classes. I.e. 5.to_s yields a String
var.getTypeGuesses().addAll(TypicalMethodReturnNames.get(method));
}
}
return super.visitLocalAsgnNode(iVisited);
}
}