diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 3a4b3973..593f354a 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -511,6 +511,9 @@ class FakeSequence(_FakeArray): def py__iter__(self): return self._lazy_context_list + def py__bool__(self): + return bool(len(self._lazy_context_list)) + def __repr__(self): return "<%s of %s>" % (type(self).__name__, self._lazy_context_list) diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py index 84bd3335..d6a506f2 100644 --- a/jedi/evaluate/stdlib.py +++ b/jedi/evaluate/stdlib.py @@ -25,6 +25,23 @@ from jedi.evaluate import param from jedi.evaluate import analysis from jedi.evaluate.context import LazyTreeContext, ContextualizedNode +# Now this is all part of fake tuples in Jedi. However super doesn't work on +# __init__ and __new__ doesn't work at all. So adding this to nametuples is +# just the easiest way. +_NAMEDTUPLE_INIT = """ + def __init__(_cls, {arg_list}): + 'A helper function for namedtuple.' + self.__iterable = ({arg_list}) + + def __iter__(self): + for i in self.__iterable: + yield i + + def __getitem__(self, y): + return self.__iterable[y] + +""" + class NotInStdLib(LookupError): pass @@ -259,20 +276,24 @@ def collections_namedtuple(evaluator, obj, arguments): else: return set() + base = collections._class_template + base += _NAMEDTUPLE_INIT # Build source - source = collections._class_template.format( + source = base.format( typename=name, - field_names=fields, + field_names=tuple(fields), num_fields=len(fields), - arg_list=', '.join(fields), + arg_list = repr(tuple(fields)).replace("'", "")[1:-1], repr_fmt=', '.join(collections._repr_template.format(name=name) for name in fields), field_defs='\n'.join(collections._field_template.format(index=index, name=name) for index, name in enumerate(fields)) ) # Parse source - generated_class = next(evaluator.grammar.parse(source).iter_classdefs()) - return set([er.ClassContext(evaluator, generated_class, evaluator.BUILTINS)]) + module = evaluator.grammar.parse(source) + generated_class = next(module.iter_classdefs()) + parent_context = er.ModuleContext(evaluator, module, '') + return set([er.ClassContext(evaluator, generated_class, parent_context)]) @argument_clinic('first, /') diff --git a/test/test_evaluate/test_stdlib.py b/test/test_evaluate/test_stdlib.py index 3cbe6582..7347d840 100644 --- a/test/test_evaluate/test_stdlib.py +++ b/test/test_evaluate/test_stdlib.py @@ -43,3 +43,35 @@ def test_namedtuple_list(): assert completions == set() else: assert completions == set(['legs', 'length', 'large']) + + +def test_namedtuple_content(): + source = dedent("""\ + import collections + Foo = collections.namedtuple('Foo', ['bar', 'baz']) + named = Foo(baz=4, bar=3.0) + unnamed = Foo(4, '') + """) + + def d(source): + x, = Script(source).goto_definitions() + return x.name + + assert d(source + 'unnamed.bar') == 'int' + assert d(source + 'unnamed.baz') == 'str' + assert d(source + 'named.bar') == 'float' + assert d(source + 'named.baz') == 'int' + + +def test_nested_namedtuples(): + """ + From issue #730. + """ + s = Script(dedent(''' + import collections + Dataset = collections.namedtuple('Dataset', ['data']) + Datasets = collections.namedtuple('Datasets', ['train']) + train_x = Datasets(train=Dataset('data_value')) + train_x.train.''' + )) + assert 'data' in [c.name for c in s.completions()]