import rope.base.builtins import rope.base.pynames import rope.base.pyobjects from rope.base import ast, astutils, exceptions, pyobjects, arguments, worder from rope.base.utils import pycompat BadIdentifierError = exceptions.BadIdentifierError def eval_location(pymodule, offset): """Find the pyname at the offset""" return eval_location2(pymodule, offset)[1] def eval_location2(pymodule, offset): """Find the primary and pyname at offset""" pyname_finder = ScopeNameFinder(pymodule) return pyname_finder.get_primary_and_pyname_at(offset) def eval_node(scope, node): """Evaluate a `ast.AST` node and return a PyName Return `None` if the expression cannot be evaluated. """ return eval_node2(scope, node)[1] def eval_node2(scope, node): evaluator = StatementEvaluator(scope) ast.walk(node, evaluator) return evaluator.old_result, evaluator.result def eval_str(holding_scope, name): return eval_str2(holding_scope, name)[1] def eval_str2(holding_scope, name): try: # parenthesizing for handling cases like 'a_var.\nattr' node = ast.parse('(%s)' % name) except SyntaxError: raise BadIdentifierError( 'Not a resolvable python identifier selected.') return eval_node2(holding_scope, node) class ScopeNameFinder(object): def __init__(self, pymodule): self.module_scope = pymodule.get_scope() self.lines = pymodule.lines self.worder = worder.Worder(pymodule.source_code, True) def _is_defined_in_class_body(self, holding_scope, offset, lineno): if lineno == holding_scope.get_start() and \ holding_scope.parent is not None and \ holding_scope.parent.get_kind() == 'Class' and \ self.worder.is_a_class_or_function_name_in_header(offset): return True if lineno != holding_scope.get_start() and \ holding_scope.get_kind() == 'Class' and \ self.worder.is_name_assigned_in_class_body(offset): return True return False def _is_function_name_in_function_header(self, scope, offset, lineno): if scope.get_start() <= lineno <= scope.get_body_start() and \ scope.get_kind() == 'Function' and \ self.worder.is_a_class_or_function_name_in_header(offset): return True return False def get_pyname_at(self, offset): return self.get_primary_and_pyname_at(offset)[1] def get_primary_and_pyname_at(self, offset): lineno = self.lines.get_line_number(offset) holding_scope = self.module_scope.get_inner_scope_for_line(lineno) # function keyword parameter if self.worder.is_function_keyword_parameter(offset): keyword_name = self.worder.get_word_at(offset) pyobject = self.get_enclosing_function(offset) if isinstance(pyobject, pyobjects.PyFunction): return (None, pyobject.get_parameters().get(keyword_name, None)) # class body if self._is_defined_in_class_body(holding_scope, offset, lineno): class_scope = holding_scope if lineno == holding_scope.get_start(): class_scope = holding_scope.parent name = self.worder.get_primary_at(offset).strip() try: return (None, class_scope.pyobject[name]) except rope.base.exceptions.AttributeNotFoundError: return (None, None) # function header if self._is_function_name_in_function_header(holding_scope, offset, lineno): name = self.worder.get_primary_at(offset).strip() return (None, holding_scope.parent[name]) # module in a from statement or an imported name that is aliased if (self.worder.is_from_statement_module(offset) or self.worder.is_import_statement_aliased_module(offset)): module = self.worder.get_primary_at(offset) module_pyname = self._find_module(module) return (None, module_pyname) if self.worder.is_from_aliased(offset): name = self.worder.get_from_aliased(offset) else: name = self.worder.get_primary_at(offset) return eval_str2(holding_scope, name) def get_enclosing_function(self, offset): function_parens = self.worder.find_parens_start_from_inside(offset) try: function_pyname = self.get_pyname_at(function_parens - 1) except BadIdentifierError: function_pyname = None if function_pyname is not None: pyobject = function_pyname.get_object() if isinstance(pyobject, pyobjects.AbstractFunction): return pyobject elif isinstance(pyobject, pyobjects.AbstractClass) and \ '__init__' in pyobject: return pyobject['__init__'].get_object() elif '__call__' in pyobject: return pyobject['__call__'].get_object() return None def _find_module(self, module_name): dots = 0 while module_name[dots] == '.': dots += 1 return rope.base.pynames.ImportedModule( self.module_scope.pyobject, module_name[dots:], dots) class StatementEvaluator(object): def __init__(self, scope): self.scope = scope self.result = None self.old_result = None def _Name(self, node): self.result = self.scope.lookup(node.id) def _Attribute(self, node): pyname = eval_node(self.scope, node.value) if pyname is None: pyname = rope.base.pynames.UnboundName() self.old_result = pyname if pyname.get_object() != rope.base.pyobjects.get_unknown(): try: self.result = pyname.get_object()[node.attr] except exceptions.AttributeNotFoundError: self.result = None def _Call(self, node): primary, pyobject = self._get_primary_and_object_for_node(node.func) if pyobject is None: return def _get_returned(pyobject): args = arguments.create_arguments(primary, pyobject, node, self.scope) return pyobject.get_returned_object(args) if isinstance(pyobject, rope.base.pyobjects.AbstractClass): result = None if '__new__' in pyobject: new_function = pyobject['__new__'].get_object() result = _get_returned(new_function) if result is None or \ result == rope.base.pyobjects.get_unknown(): result = rope.base.pyobjects.PyObject(pyobject) self.result = rope.base.pynames.UnboundName(pyobject=result) return pyfunction = None if isinstance(pyobject, rope.base.pyobjects.AbstractFunction): pyfunction = pyobject elif '__call__' in pyobject: pyfunction = pyobject['__call__'].get_object() if pyfunction is not None: self.result = rope.base.pynames.UnboundName( pyobject=_get_returned(pyfunction)) def _Str(self, node): self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_str()) def _Num(self, node): type_name = type(node.n).__name__ self.result = self._get_builtin_name(type_name) def _Constant(self, node): type_name = type(node.n).__name__ try: self.result = self._get_builtin_name(type_name) except exceptions.AttributeNotFoundError: # XXX: Right way to fix this is to add missing NoneType to builtins? pass def _get_builtin_name(self, type_name): pytype = rope.base.builtins.builtins[type_name].get_object() return rope.base.pynames.UnboundName( rope.base.pyobjects.PyObject(pytype)) def _BinOp(self, node): self.result = rope.base.pynames.UnboundName( self._get_object_for_node(node.left)) def _BoolOp(self, node): pyobject = self._get_object_for_node(node.values[0]) if pyobject is None: pyobject = self._get_object_for_node(node.values[1]) self.result = rope.base.pynames.UnboundName(pyobject) def _Repr(self, node): self.result = self._get_builtin_name('str') def _UnaryOp(self, node): self.result = rope.base.pynames.UnboundName( self._get_object_for_node(node.operand)) def _Compare(self, node): self.result = self._get_builtin_name('bool') def _Dict(self, node): keys = None values = None if node.keys: keys = self._get_object_for_node(node.keys[0]) values = self._get_object_for_node(node.values[0]) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_dict(keys, values)) def _List(self, node): holding = None if node.elts: holding = self._get_object_for_node(node.elts[0]) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_list(holding)) def _ListComp(self, node): pyobject = self._what_does_comprehension_hold(node) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_list(pyobject)) def _GeneratorExp(self, node): pyobject = self._what_does_comprehension_hold(node) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_iterator(pyobject)) def _what_does_comprehension_hold(self, node): scope = self._make_comprehension_scope(node) pyname = eval_node(scope, node.elt) return pyname.get_object() if pyname is not None else None def _make_comprehension_scope(self, node): scope = self.scope module = scope.pyobject.get_module() names = {} for comp in node.generators: new_names = _get_evaluated_names(comp.target, comp.iter, module, '.__iter__().next()', node.lineno) names.update(new_names) return rope.base.pyscopes.TemporaryScope(scope.pycore, scope, names) def _Tuple(self, node): objects = [] if len(node.elts) < 4: for stmt in node.elts: pyobject = self._get_object_for_node(stmt) objects.append(pyobject) else: objects.append(self._get_object_for_node(node.elts[0])) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_tuple(*objects)) def _get_object_for_node(self, stmt): pyname = eval_node(self.scope, stmt) pyobject = None if pyname is not None: pyobject = pyname.get_object() return pyobject def _get_primary_and_object_for_node(self, stmt): primary, pyname = eval_node2(self.scope, stmt) pyobject = None if pyname is not None: pyobject = pyname.get_object() return primary, pyobject def _Subscript(self, node): if isinstance(node.slice, ast.Index): self._call_function(node.value, '__getitem__', [node.slice.value]) elif isinstance(node.slice, ast.Slice): self._call_function(node.value, '__getitem__', [node.slice]) def _Slice(self, node): self.result = self._get_builtin_name('slice') def _call_function(self, node, function_name, other_args=None): pyname = eval_node(self.scope, node) if pyname is not None: pyobject = pyname.get_object() else: return if function_name in pyobject: called = pyobject[function_name].get_object() if not called or \ not isinstance(called, pyobjects.AbstractFunction): return args = [node] if other_args: args += other_args arguments_ = arguments.Arguments(args, self.scope) self.result = rope.base.pynames.UnboundName( pyobject=called.get_returned_object(arguments_)) def _Lambda(self, node): self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.Lambda(node, self.scope)) def _get_evaluated_names(targets, assigned, module, evaluation, lineno): result = {} for name, levels in astutils.get_name_levels(targets): assignment = rope.base.pynames.AssignmentValue(assigned, levels, evaluation) # XXX: this module should not access `rope.base.pynamesdef`! pyname = rope.base.pynamesdef.AssignedName(lineno, module) pyname.assignments.append(assignment) result[name] = pyname return result