From d11ea73ef44d712b2b2efaa993d1b73f23dbf275 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 11 Dec 2014 19:18:00 +0100 Subject: [PATCH] Re-enable AttributeError/NameError detection for more complicated occurances than just statements. --- jedi/api/__init__.py | 22 +++++++++++-- jedi/evaluate/analysis.py | 50 ++++++++++++++++++++++++------ jedi/evaluate/compiled/__init__.py | 2 +- jedi/evaluate/helpers.py | 2 +- jedi/evaluate/iterable.py | 3 ++ test/static_analysis/generators.py | 2 +- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index b527a5cc..1359b15c 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -609,17 +609,33 @@ class Script(object): for o in origins if hasattr(o, 'py__call__')] def _analysis(self): + def check_types(types): + for typ in types: + try: + f = typ.iter_content + except AttributeError: + pass + else: + check_types(f()) + #statements = set(chain(*self._parser.module().used_names.values())) - stmts, imp_names = analysis.get_module_statements(self._parser.module()) + nodes, imp_names, decorated_funcs = \ + analysis.get_module_statements(self._parser.module()) # Sort the statements so that the results are reproducible. for n in imp_names: iw = imports.ImportWrapper(self._evaluator, n).follow() i = n.get_definition() if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw): analysis.add(self._evaluator, 'import-error', i.namespace_names[-1]) - for stmt in sorted(stmts, key=lambda obj: obj.start_pos): + for node in sorted(nodes, key=lambda obj: obj.start_pos): #if not (isinstance(stmt.parent, pr.ForFlow) and stmt.parent.set_stmt == stmt): - self._evaluator.eval_statement(stmt) + if node.type == 'expr_stmt': + check_types(self._evaluator.eval_statement(node)) + else: + self._evaluator.eval_element(node) + + for dec_func in decorated_funcs: + er.Function(self._evaluator, dec_func).get_decorated_func() ana = [a for a in self._evaluator.analysis if self.path == a.path] return sorted(set(ana), key=lambda x: x.line) diff --git a/jedi/evaluate/analysis.py b/jedi/evaluate/analysis.py index 8d9932a1..8dacb984 100644 --- a/jedi/evaluate/analysis.py +++ b/jedi/evaluate/analysis.py @@ -64,8 +64,8 @@ class Error(object): def __repr__(self): return '<%s %s: %s@%s,%s>' % (self.__class__.__name__, - self.name, self.path, - self._start_pos[0], self._start_pos[1]) + self.name, self.path, + self._start_pos[0], self._start_pos[1]) class Warning(Error): @@ -195,6 +195,28 @@ def get_module_statements(module): Returns the statements used in a module. All these statements should be evaluated to check for potential exceptions. """ + def check_children(node): + try: + children = node.children + except AttributeError: + return [] + else: + nodes = [] + for child in children: + nodes += check_children(child) + if child.type == 'trailer': + c = child.children + if c[0] == '(' and c[1] != ')': + if c[1].type != 'arglist': + nodes.append(c[1]) + else: + for argument in c[1].children: + if argument.type == 'argument': + nodes.append(argument.children[-1]) + elif argument.type != 'operator': + nodes.append(argument) + return nodes + def add_stmts(stmts): new = set() for stmt in stmts: @@ -203,28 +225,36 @@ def get_module_statements(module): new |= add_stmts(stmt.inputs) stmt = stmt.next continue - if isinstance(stmt, pr.KeywordStatement): + stmt = stmt.stmt if stmt is None: continue - new.add(stmt) + if stmt.type == 'expr_stmt': + new.add(stmt) + + for node in stmt.children: + new.update(check_children(node)) + if node.type != 'keyword' and stmt.type != 'expr_stmt': + new.add(node) return new - stmts = set() + nodes = set() import_names = set() + decorated_funcs = [] for scope in module.walk(): for imp in set(scope.imports): import_names |= set(imp.get_defined_names()) if imp.is_nested(): import_names |= set(path[-1] for path in imp.paths()) - stmts |= add_stmts(scope.statements) - stmts |= add_stmts(r for r in scope.returns if r is not None) + nodes |= add_stmts(scope.statements) + nodes |= add_stmts(r for r in scope.returns if r is not None) try: - decorators = scope.decorators + decorators = scope.get_decorators() except AttributeError: pass else: - stmts |= add_stmts(decorators) - return stmts, import_names + if decorators: + decorated_funcs.append(scope) + return nodes, import_names, decorated_funcs diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 09736cbc..b5402e28 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -159,7 +159,7 @@ class CompiledObject(Base): else: raise KeyError("CompiledObject doesn't have an attribute '%s'." % name) - def get_index_types(self, evaluator, index_array): + def get_index_types(self, evaluator, index_array=()): # If the object doesn't have `__getitem__`, just raise the # AttributeError. if not hasattr(self.obj, '__getitem__'): diff --git a/jedi/evaluate/helpers.py b/jedi/evaluate/helpers.py index 5df1c0ea..52997468 100644 --- a/jedi/evaluate/helpers.py +++ b/jedi/evaluate/helpers.py @@ -375,7 +375,7 @@ class FakeImport(pr.Import): def start_pos(self): return 0, 0 - def _paths(self): + def paths(self): return [[self.name]] diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index d2ac67ed..9136e6c3 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -248,6 +248,9 @@ class Array(IterableWrapper): # Can raise an IndexError return self._evaluator.eval_element(self._items()[mixed_index]) + def iter_content(self): + return self.values() + def scope_names_generator(self, position=None): """ It returns e.g. for a list: append, pop, ... diff --git a/test/static_analysis/generators.py b/test/static_analysis/generators.py index 82b4d476..6da66079 100644 --- a/test/static_analysis/generators.py +++ b/test/static_analysis/generators.py @@ -1,7 +1,7 @@ def generator(): yield 1 -#! 11 type-error-generator +#! 12 type-error-generator generator()[0] list(generator())[0]