diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 5b0871fd..d6f0da0f 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -572,11 +572,15 @@ def py__getitem__(evaluator, types, index, node): # https://github.com/davidhalter/jedi/issues/663 for typ in list(types): if isinstance(typ, Class): - replacementclass = \ - pep0484.get_typing_replacement_class(evaluator, typ) - if replacementclass: + typing_module_types = \ + pep0484.get_types_for_typing_module(evaluator, typ, index) + if typing_module_types is not None: types.remove(typ) - result |= replacementclass.py__getitem__(index) + result |= typing_module_types + + if not types: + # 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 diff --git a/jedi/evaluate/jedi_typing.py b/jedi/evaluate/jedi_typing.py new file mode 100644 index 00000000..9538624a --- /dev/null +++ b/jedi/evaluate/jedi_typing.py @@ -0,0 +1,25 @@ +""" +This module is not intended to be used in jedi, rather it will be fed to the +jedi-parser to replace classes in the typing module +""" + +from collections import abc + + +def factory(typing_name, indextype): + class Sequence(abc.Sequence): + def __getitem__(self) -> indextype: + pass + + class MutableSequence(Sequence, abc.MutableSequence): + pass + + class List(MutableSequence, list): + pass + + dct = { + "Sequence": Sequence, + "MutableSequence": MutableSequence, + "List": List, + } + return dct[typing_name] diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 761886a4..d3fb46e0 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -20,10 +20,11 @@ x support for type hint comments `# type: (int, str) -> int`. See comment from from itertools import chain -from jedi.parser import Parser, load_grammar, ParseError, tree, ParserWithRecovery +import os +from jedi.parser import \ + Parser, load_grammar, ParseError, tree, ParserWithRecovery from jedi.evaluate.cache import memoize_default from jedi.evaluate import compiled -from textwrap import dedent from jedi import debug @@ -34,7 +35,8 @@ def _evaluate_for_annotation(evaluator, annotation): if (isinstance(definition, compiled.CompiledObject) and isinstance(definition.obj, str)): try: - p = Parser(load_grammar(), definition.obj, start='eval_input') + p = Parser( + load_grammar(), definition.obj, start='eval_input') element = p.get_parsed_node() except ParseError: debug.warning('Annotation not parsed: %s' % definition.obj) @@ -70,21 +72,15 @@ def get_typing_replacement_module(): as discussed at https://github.com/davidhalter/jedi/issues/663 """ - code = dedent(""" - from collections import abc - - class MakeSequence: - def __getitem__(self, indextype): - class Sequence(abc.Sequence): - def __getitem__(self) -> indextype: - pass - return Sequence - """) + typing_path = os.path.abspath(os.path.join(__file__, "../jedi_typing.py")) + with open(typing_path) as f: + code = f.read() p = ParserWithRecovery(load_grammar(), code) return p.module -def get_typing_replacement_class(evaluator, typ): +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": return None # we assume that any class using [] in a module called @@ -93,8 +89,18 @@ def get_typing_replacement_class(evaluator, typ): # 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() - types = evaluator.find_types(typing, "Make" + typ.name.value) - if not types: - return None + 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: - return list(types)[0] + index_obj = compiled.create(evaluator, index) + result = \ + evaluator.execute_evaluated(factory, compiled_classname, index_obj) + if result: + return result + else: + return None diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 35359dbd..c460efd2 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -469,24 +469,6 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)): def py__class__(self): return compiled.create(self._evaluator, type) - def py__getitem__(self, index): - instances = self.py__call__(()) - assert len(instances) == 1 - instance = list(instances)[0] - try: - method = instance.get_subscope_by_name('__getitem__') - except KeyError: - debug.warning('No __getitem__, cannot access the array.') - return set() - else: - # it feels like this should be handled somewhere else, - # not sure where though - if isinstance(index, type(self)): - index_obj = index - else: - index_obj = compiled.create(self._evaluator, index) - return instance._evaluator.execute_evaluated(method, index_obj) - @property def params(self): return self.get_subscope_by_name('__init__').params diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index ba9044bc..75742846 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -163,7 +163,9 @@ def we_can_has_sequence( p: typing.Sequence[int], q: typing.Sequence[B], r: "typing.Sequence[int]", - s: typing.Sequence["int"]): + s: typing.Sequence["int"], + t: typing.MutableSequence[dict], + u: typing.List[float]): #? ["count"] p.c #? int() @@ -180,3 +182,13 @@ def we_can_has_sequence( s.c #? int() s[1] + #? [] + s.a + #? ["append"] + t.a + #? dict() + t[1] + #? ["append"] + u.a + #? float() + u[1]