diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 29d73818..3c5b1fdf 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -391,8 +391,7 @@ class Evaluator(object): new_types = set() if trailer_op == '[': - for trailer_typ in iterable.create_index_types(self, node): - new_types |= iterable.py__getitem__(self, types, trailer_typ, trailer_op) + new_types |= iterable.py__getitem__(self, types, trailer) else: for typ in types: debug.dbg('eval_trailer: %s in scope %s', trailer, typ) diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index d6f0da0f..957f9784 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -560,20 +560,17 @@ def py__iter__types(evaluator, types, node=None): return unite(py__iter__(evaluator, types, node)) -def py__getitem__(evaluator, types, index, node): +def py__getitem__(evaluator, types, trailer): from jedi.evaluate.representation import Class result = set() - # Index handling. - if isinstance(index, (compiled.CompiledObject, Slice)): - index = index.obj # special case: PEP0484 typing module, see # https://github.com/davidhalter/jedi/issues/663 for typ in list(types): if isinstance(typ, Class): typing_module_types = \ - pep0484.get_types_for_typing_module(evaluator, typ, index) + pep0484.get_types_for_typing_module(evaluator, typ, trailer) if typing_module_types is not None: types.remove(typ) result |= typing_module_types @@ -582,30 +579,40 @@ def py__getitem__(evaluator, types, index, node): # all consumed by special cases return result - if type(index) not in (float, int, str, unicode, slice): - # If the index is not clearly defined, we have to get all the - # possiblities. - for typ in list(types): - if isinstance(typ, Array) and typ.type == 'dict': - types.remove(typ) - result |= typ.dict_values() - return result | py__iter__types(evaluator, types) + trailer_op, node, trailer_cl = trailer.children[:3] + assert trailer_op == "[" + if trailer_cl != "]": + debug.warning("No support for complex indices: %s" % trailer) + return result - for typ in types: - # The actual getitem call. - try: - getitem = typ.py__getitem__ - except AttributeError: - analysis.add(evaluator, 'type-error-not-subscriptable', node, - message="TypeError: '%s' object is not subscriptable" % typ) - else: + for index in create_index_types(evaluator, node): + if isinstance(index, (compiled.CompiledObject, Slice)): + index = index.obj + + if type(index) not in (float, int, str, unicode, slice): + # If the index is not clearly defined, we have to get all the + # possiblities. + for typ in list(types): + if isinstance(typ, Array) and typ.type == 'dict': + types.remove(typ) + result |= typ.dict_values() + return result | py__iter__types(evaluator, types) + + for typ in types: + # The actual getitem call. try: - result |= getitem(index) - except IndexError: - result |= py__iter__types(evaluator, set([typ])) - except KeyError: - # Must be a dict. Lists don't raise KeyErrors. - result |= typ.dict_values() + getitem = typ.py__getitem__ + except AttributeError: + analysis.add(evaluator, 'type-error-not-subscriptable', trailer_op, + message="TypeError: '%s' object is not subscriptable" % typ) + else: + try: + result |= getitem(index) + except IndexError: + result |= py__iter__types(evaluator, set([typ])) + except KeyError: + # Must be a dict. Lists don't raise KeyErrors. + result |= typ.dict_values() return result diff --git a/jedi/evaluate/jedi_typing.py b/jedi/evaluate/jedi_typing.py index 9538624a..14c74570 100644 --- a/jedi/evaluate/jedi_typing.py +++ b/jedi/evaluate/jedi_typing.py @@ -8,10 +8,11 @@ from collections import abc def factory(typing_name, indextype): class Sequence(abc.Sequence): - def __getitem__(self) -> indextype: - pass + def __getitem__(self, index: int): + return indextype() class MutableSequence(Sequence, abc.MutableSequence): + def __setitem__(self, index: int, value: indextype): pass class List(MutableSequence, list): diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index d3fb46e0..771a7a14 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -21,8 +21,7 @@ x support for type hint comments `# type: (int, str) -> int`. See comment from from itertools import chain import os -from jedi.parser import \ - Parser, load_grammar, ParseError, tree, ParserWithRecovery +from jedi.parser import Parser, load_grammar, ParseError, ParserWithRecovery from jedi.evaluate.cache import memoize_default from jedi.evaluate import compiled from jedi import debug @@ -31,28 +30,34 @@ from jedi import debug def _evaluate_for_annotation(evaluator, annotation): if annotation is not None: definitions = set() + module = annotation.get_parent_until() for definition in evaluator.eval_element(annotation): - if (isinstance(definition, compiled.CompiledObject) and - isinstance(definition.obj, str)): - try: - p = Parser( - load_grammar(), definition.obj, start='eval_input') - element = p.get_parsed_node() - except ParseError: - debug.warning('Annotation not parsed: %s' % definition.obj) - else: - module = annotation.get_parent_until() - p.position_modifier.line = module.end_pos[0] - element.parent = module - definitions |= evaluator.eval_element(element) - else: - definitions.add(definition) + definitions |= \ + _fix_forward_reference(evaluator, definition, module) return list(chain.from_iterable( evaluator.execute(d) for d in definitions)) else: return [] +def _fix_forward_reference(evaluator, item, module): + if (isinstance(item, compiled.CompiledObject) and + isinstance(item.obj, str)): + try: + p = Parser( + load_grammar(), item.obj, start='eval_input') + element = p.get_parsed_node() + except ParseError: + debug.warning('Annotation not parsed: %s' % item.obj) + return set() + else: + p.position_modifier.line = module.end_pos[0] + element.parent = module + return evaluator.eval_element(element) + else: + return {item} + + @memoize_default(None, evaluator_is_first_arg=True) def follow_param(evaluator, param): annotation = param.annotation() @@ -66,7 +71,7 @@ def find_return_types(evaluator, func): # TODO: Memoize -def get_typing_replacement_module(): +def _get_typing_replacement_module(): """ The idea is to return our jedi replacement for the PEP-0484 typing module as discussed at https://github.com/davidhalter/jedi/issues/663 @@ -79,27 +84,35 @@ def get_typing_replacement_module(): return p.module -def get_types_for_typing_module(evaluator, typ, index): - from jedi.evaluate.representation import Class - if not typ.base.get_parent_until(tree.Module).name.value == "typing": +def get_types_for_typing_module(evaluator, typ, trailer): + if not typ.base.get_parent_until().name.value == "typing": return None # we assume that any class using [] in a module called # "typing" with a name for which we have a replacement # should be replaced by that class. This is not 100% # airtight but I don't have a better idea to check that it's # actually the PEP-0484 typing module and not some other - typing = get_typing_replacement_module() + indextypes = evaluator.eval_element(trailer.children[1]) + if not isinstance(indextypes, set): + indextypes = {indextypes} + + module = trailer.get_parent_until() + dereferencedindextypes = set() + for indextyp in indextypes: + dereferencedindextypes |= \ + _fix_forward_reference(evaluator, indextyp, module) + + typing = _get_typing_replacement_module() factories = evaluator.find_types(typing, "factory") assert len(factories) == 1 factory = list(factories)[0] assert factory compiled_classname = compiled.create(evaluator, typ.name.value) - if isinstance(index, Class): - index_obj = index - else: - index_obj = compiled.create(evaluator, index) - result = \ - evaluator.execute_evaluated(factory, compiled_classname, index_obj) + + result = set() + for indextyp in dereferencedindextypes: + result |= \ + evaluator.execute_evaluated(factory, compiled_classname, indextyp) if result: return result else: