# getTypeOf(scope,fqn) and getTypeOfExpr(scope,ast)
from bike.parsing.fastparserast import Class,Function,Module,Root,getRoot,Package,Instance,getModule
from bike.parsing.parserutils import generateLogicalLines,makeLineParseable,splitLogicalLines,makeLineParseable
from bike.parsing import visitor
from bike import log
from bike.parsing.newstuff import getModuleOrPackageUsingFQN
from bike.parsing.pathutils import getPackageBaseDirectory
from bike.parsing.load import Cache
import os
import re
import compiler
# used if an assignment exists, but cant find type
# e.g. a = SomeFunctionNotLoaded()
# (as opposed to 'None' if cant find an assignment)
class UnfoundType: pass
getTypeOfStack = []
# name is the fqn of the reference, scope is the scope ast object from
# which the question is being asked.
# returns an fastparser-ast object representing the type
# or None if type not found
def getTypeOf(scope, fqn):
if isinstance(scope, Root):
assert False, "Can't use getTypeOf to resolve from Root. Use getModuleOrPackageUsingFQN instead"
#print "getTypeOf:"+fqn+" -- "+str(scope)
#print
#print str(getTypeOfStack)
#print
if (fqn,scope) in getTypeOfStack: # loop protection
return None
# this is crap!
hashcode = str(scope)+fqn
try:
getTypeOfStack.append((fqn,scope))
try:
type = Cache.instance.typecache[hashcode]
except KeyError:
type = getTypeOf_impl(scope, fqn)
Cache.instance.typecache[hashcode] = type
return type
finally:
del getTypeOfStack[-1]
def getTypeOf_impl(scope, fqn):
#print "getTypeOf_impl",scope,fqn
if fqn == "None":
return None
if "."in fqn:
rcdr = ".".join(fqn.split(".")[:-1])
rcar = fqn.split(".")[-1]
newscope = getTypeOf(scope,rcdr)
if newscope is not None:
return getTypeOf(newscope, rcar)
else:
#print "couldnt find "+rcdr+" in "+str(scope)
pass
assert scope is not None
#assert not ("." in fqn)
if isinstance(scope,UnfoundType):
return UnfoundType()
if isinstance(scope, Package):
#assert 0,scope
return handlePackageScope(scope, fqn)
elif isinstance(scope,Instance):
return handleClassInstanceAttribute(scope, fqn)
else:
return handleModuleClassOrFunctionScope(scope,fqn)
def handleModuleClassOrFunctionScope(scope,name):
if name == "self" and isinstance(scope,Function) and \
isinstance(scope.getParent(),Class):
return Instance(scope.getParent())
matches = [c for c in scope.getChildNodes()if c.name == name]
if matches != []:
return matches[0]
type = scanScopeSourceForType(scope, name)
if type != None:
return type
#print "name = ",name,"scope = ",scope
type = getImportedType(scope, name) # try imported types
#print "type=",type
if type != None:
return type
parentScope = scope.getParent()
while isinstance(parentScope,Class):
# don't search class scope, since this is not accessible except
# through self (is this true?)
parentScope = parentScope.getParent()
if not (isinstance(parentScope,Package) or isinstance(parentScope,Root)):
return getTypeOf(parentScope, name)
def handleClassInstanceAttribute(instance, attrname):
theClass = instance.getType()
# search methods and inner classes
match = theClass.getChild(attrname)
if match:
return match
#search methods for assignments with self.foo getattrs
for child in theClass.getChildNodes():
if not isinstance(child,Function):
continue
res = scanScopeAST(child,attrname,
SelfAttributeAssignmentVisitor(child,attrname))
if res is not None:
return res
def handlePackageScope(package, fqn):
#print "handlePackageScope",package,fqn
child = package.getChild(fqn)
if child:
return child
if isinstance(package,Root):
return getModuleOrPackageUsingFQN(fqn)
# try searching the fs
node = getModuleOrPackageUsingFQN(fqn,package.path)
if node:
return node
# try the package init module
initmod = package.getChild("__init__")
if initmod is not None:
type = getImportedType(initmod, fqn)
if type:
return type
# maybe fqn is absolute
return getTypeOf(getRoot(), fqn)
wordRE = re.compile("\w+")
def isWordInLine(word, line):
if line.find(word) != -1:
words = wordRE.findall(line)
if word in words:
return 1
return 0
def getImportedType(scope, fqn):
lines = scope.module.getSourceNode().getLines()
for lineno in scope.getImportLineNumbers():
logicalline = generateLogicalLines(lines[lineno-1:]).next()
logicalline = makeLineParseable(logicalline)
ast = compiler.parse(logicalline)
match = visitor.walk(ast, ImportVisitor(scope,fqn)).match
if match:
return match
#else loop
class ImportVisitor:
def __init__(self,scope,fqn):
self.match = None
self.targetfqn = fqn
self.scope = scope
def visitImport(self, node):
# if target fqn is an import, then it must be a module or package
for name, alias in node.names:
if name == self.targetfqn:
self.match = resolveImportedModuleOrPackage(self.scope,name)
elif alias is not None and alias == self.targetfqn:
self.match = resolveImportedModuleOrPackage(self.scope,name)
def visitFrom(self, node):
if node.names[0][0] == '*': # e.g. from foo import *
if not "."in self.targetfqn:
module = resolveImportedModuleOrPackage(self.scope,
node.modname)
if module:
self.match = getTypeOf(module, self.targetfqn)
else:
for name, alias in node.names:
if alias == self.targetfqn or \
(alias is None and name == self.targetfqn):
scope = resolveImportedModuleOrPackage(self.scope,
node.modname)
if scope is not None:
if isinstance(scope,Package):
self.match = getModuleOrPackageUsingFQN(name,scope.path)
else:
assert isinstance(scope,Module)
self.match = getTypeOf(scope, name)
class TypeNotSupportedException:
def __init__(self,msg):
self.msg = msg
def __str__(self):
return self.msg
# attempts to evaluate the type of the expression
def getTypeOfExpr(scope, ast):
if isinstance(ast, compiler.ast.Name):
return getTypeOf(scope, ast.name)
elif isinstance(ast, compiler.ast.Getattr) or \
isinstance(ast, compiler.ast.AssAttr):
# need to do this in order to match foo.bah.baz as
# a string in import statements
fqn = attemptToConvertGetattrToFqn(ast)
if fqn is not None:
return getTypeOf(scope,fqn)
expr = getTypeOfExpr(scope, ast.expr)
if expr is not None:
attrnametype = getTypeOf(expr, ast.attrname)
return attrnametype
return None
elif isinstance(ast, compiler.ast.CallFunc):
node = getTypeOfExpr(scope,ast.node)
if isinstance(node,Class):
return Instance(node)
elif isinstance(node,Function):
return getReturnTypeOfFunction(node)
else:
#raise TypeNotSupportedException, \
# "Evaluation of "+str(ast)+" not supported. scope="+str(scope)
print >> log.warning, "Evaluation of "+str(ast)+" not supported. scope="+str(scope)
return None
def attemptToConvertGetattrToFqn(ast):
fqn = ast.attrname
ast = ast.expr
while isinstance(ast,compiler.ast.Getattr):
fqn = ast.attrname + "." + fqn
ast = ast.expr
if isinstance(ast,compiler.ast.Name):
return ast.name + "." + fqn
else:
return None
getReturnTypeOfFunction_stack = []
def getReturnTypeOfFunction(function):
if function in getReturnTypeOfFunction_stack: # loop protection
return None
try:
getReturnTypeOfFunction_stack.append(function)
return getReturnTypeOfFunction_impl(function)
finally:
del getReturnTypeOfFunction_stack[-1]
def getReturnTypeOfFunction_impl(function):
return scanScopeAST(function,"return",ReturnTypeVisitor(function))
# does parse of scope sourcecode to deduce type
def scanScopeSourceForType(scope, name):
return scanScopeAST(scope,name,AssignmentVisitor(scope,name))
# scans for lines containing keyword, and then runs the visitor over
# the parsed AST for that line
def scanScopeAST(scope,keyword,astvisitor):
lines = scope.getLinesNotIncludingThoseBelongingToChildScopes()
src = ''.join(lines)
match = None
#print "scanScopeAST:"+str(scope)
for line in splitLogicalLines(src):
if isWordInLine(keyword, line):
#print "scanning for "+keyword+" in line:"+line[:-1]
doctoredline = makeLineParseable(line)
ast = compiler.parse(doctoredline)
match = visitor.walk(ast,astvisitor).getMatch()
if match:
return match
return match
class AssignmentVisitor:
def __init__(self,scope,targetName):
self.match=None
self.scope = scope
self.targetName = targetName
def getMatch(self):
return self.match
def visitAssign(self,node):
if isinstance(node.expr,compiler.ast.CallFunc):
for assnode in node.nodes:
if isinstance(assnode,compiler.ast.AssName) and \
assnode.name == self.targetName:
self.match = getTypeOfExpr(self.scope,node.expr)
if self.match is None:
self.match = UnfoundType()
class SelfAttributeAssignmentVisitor:
def __init__(self,scope,targetName):
self.match=None
self.scope = scope
self.targetName = targetName
def getMatch(self):
return self.match
def visitAssign(self,node):
if isinstance(node.expr,compiler.ast.CallFunc):
for assnode in node.nodes:
if isinstance(assnode,compiler.ast.AssAttr) and \
isinstance(assnode.expr,compiler.ast.Name) and \
assnode.expr.name == "self" and \
assnode.attrname == self.targetName:
self.match = getTypeOfExpr(self.scope,node.expr)
#print "here!",self.match.getType().fqn
class ReturnTypeVisitor:
def __init__(self,fn):
self.match=None
self.fn = fn
def getMatch(self):
return self.match
def visitReturn(self,node):
try:
self.match = getTypeOfExpr(self.fn,node.value)
except TypeNotSupportedException, ex:
pass
def resolveImportedModuleOrPackage(scope,fqn):
# try searching from directory containing scope module
path = os.path.dirname(scope.module.filename)
node = getModuleOrPackageUsingFQN(fqn,path)
if node is not None:
return node
# try searching in same package hierarchy
basedir = getPackageBaseDirectory(scope.module.filename)
if fqn.split('.')[0] == os.path.split(basedir)[-1]:
# base package in fqn matches base directory
restOfFqn = ".".join(fqn.split('.')[1:])
node = getModuleOrPackageUsingFQN(restOfFqn,basedir)
if node is not None:
return node
# try searching the python path
node = getModuleOrPackageUsingFQN(fqn)
if node is not None:
return node
|