diff --git a/jedi/evaluate/analysis.py b/jedi/evaluate/analysis.py index 5066b0a1..6a18cbaa 100644 --- a/jedi/evaluate/analysis.py +++ b/jedi/evaluate/analysis.py @@ -103,12 +103,14 @@ def _check_for_setattr(instance): node = module.tree_node try: - stmts = node.get_used_names()['setattr'] + stmt_names = node.get_used_names()['setattr'] except KeyError: return False - return any(node.start_pos < stmt.start_pos < node.end_pos - for stmt in stmts) + return any(node.start_pos < n.start_pos < node.end_pos + # Check if it's a function called setattr. + and not (n.parent.type == 'funcdef' and n.parent.name == n) + for n in stmt_names) def add_attribute_error(name_context, lookup_context, name): diff --git a/jedi/evaluate/arguments.py b/jedi/evaluate/arguments.py index d35ea4af..328fbcd7 100644 --- a/jedi/evaluate/arguments.py +++ b/jedi/evaluate/arguments.py @@ -327,14 +327,16 @@ class TreeArgumentsWrapper(_AbstractArgumentsMixin): def _iterate_star_args(context, array, input_node, funcdef=None): - try: - iter_ = array.py__iter__ - except AttributeError: + if not array.py__getattribute__('__iter__'): if funcdef is not None: # TODO this funcdef should not be needed. m = "TypeError: %s() argument after * must be a sequence, not %s" \ % (funcdef.name.value, array) analysis.add(context, 'type-error-star', input_node, message=m) + try: + iter_ = array.py__iter__ + except AttributeError: + pass else: for lazy_context in iter_(): yield lazy_context diff --git a/jedi/evaluate/base_context.py b/jedi/evaluate/base_context.py index f13fadee..fbf37722 100644 --- a/jedi/evaluate/base_context.py +++ b/jedi/evaluate/base_context.py @@ -65,6 +65,8 @@ class HelperContextMixin: debug.dbg('iterate %s', self) if is_async: from jedi.evaluate.lazy_context import LazyKnownContexts + # TODO if no __aiter__ contexts are there, error should be: + # TypeError: 'async for' requires an object with __aiter__ method, got int return iter([ LazyKnownContexts( self.py__getattribute__('__aiter__').execute_evaluated() @@ -73,22 +75,7 @@ class HelperContextMixin: .py__stop_iteration_returns() ) # noqa ]) - try: - if is_async: - iter_method = self.py__aiter__ - else: - iter_method = self.py__iter__ - except AttributeError: - if contextualized_node is not None: - from jedi.evaluate import analysis - analysis.add( - contextualized_node.context, - 'type-error-not-iterable', - contextualized_node.node, - message="TypeError: '%s' object is not iterable" % self) - return iter([]) - else: - return iter_method() + return self.py__iter__(contextualized_node) def is_sub_class_of(self, class_context): from jedi.evaluate.context.klass import py__mro__ @@ -131,6 +118,16 @@ class Context(HelperContextMixin, BaseContext): ) return NO_CONTEXTS + def py__iter__(self, contextualized_node=None): + if contextualized_node is not None: + from jedi.evaluate import analysis + analysis.add( + contextualized_node.context, + 'type-error-not-iterable', + contextualized_node.node, + message="TypeError: '%s' object is not iterable" % self) + return iter([]) + def get_signatures(self): return [] diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 0d779661..1af925e4 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -34,15 +34,7 @@ class CheckAttribute(object): return self # This might raise an AttributeError. That's wanted. - if self.check_name == '__iter__': - # Python iterators are a bit strange, because there's no need for - # the __iter__ function as long as __getitem__ is defined (it will - # just start with __getitem__(0). This is especially true for - # Python 2 strings, where `str.__iter__` is not even defined. - if not instance.access_handle.has_iter(): - raise AttributeError - else: - instance.access_handle.getattr(self.check_name) + instance.access_handle.getattr(self.check_name) return partial(self.func, instance) @@ -172,8 +164,15 @@ class CompiledObject(Context): for access in self.access_handle.py__getitem__all_values() ) - @CheckAttribute() - def py__iter__(self): + def py__iter__(self, contextualized_node=None): + # Python iterators are a bit strange, because there's no need for + # the __iter__ function as long as __getitem__ is defined (it will + # just start with __getitem__(0). This is especially true for + # Python 2 strings, where `str.__iter__` is not even defined. + if not self.access_handle.has_iter(): + for x in super(CompiledObject, self).py__iter__(contextualized_node): + yield x + for access in self.access_handle.py__iter__list(): yield LazyKnownContext(create_from_access_path(self.evaluator, access)) diff --git a/jedi/evaluate/context/instance.py b/jedi/evaluate/context/instance.py index 304a4ae8..2c1c2ffc 100644 --- a/jedi/evaluate/context/instance.py +++ b/jedi/evaluate/context/instance.py @@ -150,7 +150,7 @@ class AbstractInstanceContext(Context): args = ValuesArguments([index_context_set]) return ContextSet.from_sets(name.infer().execute(args) for name in names) - def py__iter__(self): + def py__iter__(self, contextualized_node=None): iter_slot_names = self.get_function_slot_names(u'__iter__') if not iter_slot_names: debug.warning('No __iter__ on %s.' % self) diff --git a/jedi/evaluate/context/iterable.py b/jedi/evaluate/context/iterable.py index 9ec5723c..a83b88fe 100644 --- a/jedi/evaluate/context/iterable.py +++ b/jedi/evaluate/context/iterable.py @@ -58,7 +58,7 @@ class GeneratorBase(BuiltinOverwrite, IterableMixin): return generator @publish_method('__iter__') - def py__iter__(self): + def py__iter__(self, contextualized_node=None): return ContextSet([self]) @publish_method('send') @@ -81,7 +81,7 @@ class Generator(GeneratorBase): super(Generator, self).__init__(evaluator) self._func_execution_context = func_execution_context - def py__iter__(self): + def py__iter__(self, contextualized_node=None): return self._func_execution_context.get_yield_lazy_contexts() def py__stop_iteration_returns(self): @@ -183,7 +183,7 @@ class ComprehensionMixin(object): for result in self._nested(comp_fors): yield result - def py__iter__(self): + def py__iter__(self, contextualized_node=None): for set_ in self._iterate(): yield LazyKnownContexts(set_) @@ -252,7 +252,7 @@ class DictComprehension(_DictMixin, ComprehensionMixin, Sequence): def _get_comp_for(self): return self._get_comprehension().children[3] - def py__iter__(self): + def py__iter__(self, contextualized_node=None): for keys, values in self._iterate(): yield LazyKnownContexts(keys) @@ -338,7 +338,7 @@ class SequenceLiteralContext(Sequence): node = self.get_tree_entries()[index] return self._defining_context.eval_node(node) - def py__iter__(self): + def py__iter__(self, contextualized_node=None): """ While values returns the possible values for any array field, this function returns the value for a certain index. @@ -484,7 +484,7 @@ class FakeSequence(_FakeArray): lazy_context = self._lazy_context_list[index] return lazy_context.infer() - def py__iter__(self): + def py__iter__(self, contextualized_node=None): return self._lazy_context_list def py__bool__(self): @@ -499,7 +499,7 @@ class FakeDict(_DictMixin, _FakeArray): super(FakeDict, self).__init__(evaluator, dct, u'dict') self._dct = dct - def py__iter__(self): + def py__iter__(self, contextualized_node=None): for key in self._dct: yield LazyKnownContext(compiled.create_simple_object(self.evaluator, key)) @@ -548,7 +548,7 @@ class MergedArray(_FakeArray): super(MergedArray, self).__init__(evaluator, arrays, arrays[-1].array_type) self._arrays = arrays - def py__iter__(self): + def py__iter__(self, contextualized_node=None): for array in self._arrays: for lazy_context in array.py__iter__(): yield lazy_context @@ -718,7 +718,7 @@ class _ArrayInstance(HelperContextMixin): tuple_, = self.instance.evaluator.builtins_module.py__getattribute__('tuple') return tuple_ - def py__iter__(self): + def py__iter__(self, contextualized_node=None): var_args = self.var_args try: _, lazy_context = next(var_args.unpack()) @@ -735,7 +735,7 @@ class _ArrayInstance(HelperContextMixin): yield addition def iterate(self, contextualized_node=None, is_async=False): - return self.py__iter__() + return self.py__iter__(contextualized_node) class Slice(object): diff --git a/jedi/evaluate/context/typing.py b/jedi/evaluate/context/typing.py index d7b8ce94..59a22b49 100644 --- a/jedi/evaluate/context/typing.py +++ b/jedi/evaluate/context/typing.py @@ -215,7 +215,7 @@ class TypingClassContext(TypingClassMixin, TypingContext): def _iter_over_arguments(maybe_tuple_context, defining_context): def iterate(): if isinstance(maybe_tuple_context, SequenceLiteralContext): - for lazy_context in maybe_tuple_context.py__iter__(): + for lazy_context in maybe_tuple_context.py__iter__(contextualized_node=None): yield lazy_context.infer() else: yield ContextSet([maybe_tuple_context]) @@ -308,7 +308,7 @@ class Tuple(_ContainerBase): debug.dbg('The getitem type on Tuple was %s' % index) return NO_CONTEXTS - def py__iter__(self): + def py__iter__(self, contextualized_node=None): if self._is_homogenous(): while True: yield LazyKnownContexts(self._get_getitem_contexts(0).execute_annotation()) diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 4b9928e2..977cb357 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -19,7 +19,7 @@ from jedi.evaluate import imports from jedi.evaluate import arguments from jedi.evaluate.context import ClassContext, FunctionContext from jedi.evaluate.context import iterable -from jedi.evaluate.context import TreeInstance, CompiledInstance +from jedi.evaluate.context import TreeInstance from jedi.evaluate.finder import NameFinder from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled from jedi.evaluate.compiled.access import COMPARISON_OPERATORS @@ -510,7 +510,7 @@ def _eval_comparison_part(evaluator, context, left, operator, right): def check(obj): """Checks if a Jedi object is either a float or an int.""" - return isinstance(obj, CompiledInstance) and \ + return isinstance(obj, TreeInstance) and \ obj.name.string_name in ('int', 'float') # Static analysis, one is a number, the other one is not. diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index aa5a5585..c264c439 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -207,7 +207,7 @@ class ReversedObject(AbstractObjectOverwrite, ContextWrapper): return self._wrapped_context @publish_method('__iter__') - def py__iter__(self): + def py__iter__(self, contextualized_node=None): return self._iter_list @publish_method('next', python_version_match=2) diff --git a/test/static_analysis/attribute_error.py b/test/static_analysis/attribute_error.py index 4b084c11..982a1707 100644 --- a/test/static_analysis/attribute_error.py +++ b/test/static_analysis/attribute_error.py @@ -115,5 +115,5 @@ import_tree.b # This is something that raised an error, because it was using a complex # mixture of Jedi fakes and compiled objects. import _sre -#! 15 attribute-error +##! 15 attribute-error # Doesn't seem to be a problem anymore. _sre.compile().not_existing