From fadf4f44197a5d48d944d2e6b21c33a8ef274db5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 15:20:26 +0100 Subject: [PATCH 01/21] initial poc pep-0484 type hints --- jedi/evaluate/finder.py | 6 ++++++ jedi/evaluate/pep0484.py | 34 ++++++++++++++++++++++++++++++++++ test/completion/pep0484.py | 14 ++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 jedi/evaluate/pep0484.py create mode 100644 test/completion/pep0484.py diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 89de08e4..ad2a6c33 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -23,6 +23,7 @@ from jedi.evaluate import representation as er from jedi.evaluate import dynamic from jedi.evaluate import compiled from jedi.evaluate import docstrings +from jedi.evaluate import pep0484 from jedi.evaluate import iterable from jedi.evaluate import imports from jedi.evaluate import analysis @@ -387,6 +388,11 @@ def _eval_param(evaluator, param, scope): and func.instance.is_generated and str(func.name) == '__init__': param = func.var.params[param.position_nr] + # Add pep0484 type hints + pep0484_hints = pep0484.follow_param(evaluator, param) + if pep0484_hints: + return pep0484_hints + # Add docstring knowledge. doc_params = docstrings.follow_param(evaluator, param) if doc_params: diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py new file mode 100644 index 00000000..97433502 --- /dev/null +++ b/jedi/evaluate/pep0484.py @@ -0,0 +1,34 @@ +""" +PEP 0484 ( https://www.python.org/dev/peps/pep-0484/ ) describes type hints +through function annotations. There is a strong suggestion in this document +that only the type of type hinting defined in PEP0484 should be allowed +as annotations in future python versions. + +The (initial / probably incomplete) implementation todo list for pep-0484: +v Function parameter annotations with builtin/custom type classes +x Function returntype annotations with builtin/custom type classes +x Function parameter annotations with strings (forward reference) +x Function return type annotations with strings (forward reference) +x Local variable type hints +x Assigned types: `Url = str\ndef get(url:Url) -> str:` +x Type hints in `with` statements +x Stub files support +""" + +from itertools import chain + +from jedi.evaluate.cache import memoize_default + + +@memoize_default(None, evaluator_is_first_arg=True) +def follow_param(evaluator, param): + # annotation is in param.children[0] if present + # either this firstchild is a Name (if no annotation is present) or a Node + if hasattr(param.children[0], "children"): + assert len(param.children[0].children) == 3 and \ + param.children[0].children[1] == ":" + definitions = evaluator.eval_element(param.children[0].children[2]) + return list(chain.from_iterable( + evaluator.execute(d) for d in definitions)) + else: + return [] diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py new file mode 100644 index 00000000..dac4f17a --- /dev/null +++ b/test/completion/pep0484.py @@ -0,0 +1,14 @@ +""" Pep-0484 type hinting """ + +# ----------------- +# sphinx style +# ----------------- +def typehints(a, b: str, c: int, d:int = 4): + #? + a + #? str() + b + #? int() + c + #? int() + d From 5a8c46d509a49ed052ee1c969d70d77366e3bf3b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 21:13:20 +0100 Subject: [PATCH 02/21] seperate parser and testing code --- jedi/evaluate/pep0484.py | 10 ++++------ jedi/parser/tree.py | 9 +++++++-- test/completion/pep0484.py | 8 +++++--- test/test_evaluate/test_annotations.py | 12 ++++++++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 97433502..1256ef2d 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -13,6 +13,7 @@ x Local variable type hints x Assigned types: `Url = str\ndef get(url:Url) -> str:` x Type hints in `with` statements x Stub files support +x support `@no_type_check` and `@no_type_check_decorator` """ from itertools import chain @@ -22,12 +23,9 @@ from jedi.evaluate.cache import memoize_default @memoize_default(None, evaluator_is_first_arg=True) def follow_param(evaluator, param): - # annotation is in param.children[0] if present - # either this firstchild is a Name (if no annotation is present) or a Node - if hasattr(param.children[0], "children"): - assert len(param.children[0].children) == 3 and \ - param.children[0].children[1] == ":" - definitions = evaluator.eval_element(param.children[0].children[2]) + annotation = param.annotation() + if annotation: + definitions = evaluator.eval_element(annotation) return list(chain.from_iterable( evaluator.execute(d) for d in definitions)) else: diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 5a871671..7f0e1960 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -1403,8 +1403,13 @@ class Param(BaseNode): return None def annotation(self): - # Generate from tfpdef. - raise NotImplementedError + tfpdef = self._tfpdef() + if is_node(tfpdef, 'tfpdef'): + assert tfpdef.children[1] == ":" + assert len(tfpdef.children) == 3 + return tfpdef.children[2] + else: + return None def _tfpdef(self): """ diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index dac4f17a..64f656fa 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -1,10 +1,12 @@ """ Pep-0484 type hinting """ # ----------------- -# sphinx style +# simple classes # ----------------- -def typehints(a, b: str, c: int, d:int = 4): - #? + + +def typehints(a, b: str, c: int, d: int=4): + #? a #? str() b diff --git a/test/test_evaluate/test_annotations.py b/test/test_evaluate/test_annotations.py index 1fefde3c..1a26a1bc 100644 --- a/test/test_evaluate/test_annotations.py +++ b/test/test_evaluate/test_annotations.py @@ -8,8 +8,8 @@ import pytest def test_simple_annotations(): """ Annotations only exist in Python 3. - At the moment we ignore them. So they should be parsed and not interfere - with anything. + If annotations adhere to PEP-0484, we use them (they override inference), + else they are parsed but ignored """ source = dedent("""\ @@ -27,3 +27,11 @@ def test_simple_annotations(): annot_ret('')""") assert [d.name for d in jedi.Script(source, ).goto_definitions()] == ['str'] + + source = dedent("""\ + def annot(a:int): + return a + + annot('')""") + + assert [d.name for d in jedi.Script(source, ).goto_definitions()] == ['int'] From c02668a4433915aee752a4208ffc049fb38e8e21 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 21:42:45 +0100 Subject: [PATCH 03/21] Build in version-dependency in integration tests If a line is encountered with the comment or , then the tests are skipped if the current python version is less than the requested one. All tests until the end of the file, or a new comment specifying a compatibe python version are skipped --- test/run.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/run.py b/test/run.py index 6be9b5f2..2b59d85c 100755 --- a/test/run.py +++ b/test/run.py @@ -111,6 +111,7 @@ Tests look like this:: """ import os import re +import sys from ast import literal_eval from io import StringIO from functools import reduce @@ -127,7 +128,7 @@ TEST_USAGES = 3 class IntegrationTestCase(object): def __init__(self, test_type, correct, line_nr, column, start, line, - path=None): + path=None, skip=None): self.test_type = test_type self.correct = correct self.line_nr = line_nr @@ -135,7 +136,7 @@ class IntegrationTestCase(object): self.start = start self.line = line self.path = path - self.skip = None + self.skip = skip @property def module_name(self): @@ -234,10 +235,11 @@ class IntegrationTestCase(object): def collect_file_tests(lines, lines_to_execute): makecase = lambda t: IntegrationTestCase(t, correct, line_nr, column, - start, line) + start, line, path=None, skip=skip) start = None correct = None test_type = None + skip = None for line_nr, line in enumerate(lines, 1): if correct is not None: r = re.match('^(\d+)\s*(.*)$', correct) @@ -257,6 +259,15 @@ def collect_file_tests(lines, lines_to_execute): yield makecase(TEST_DEFINITIONS) correct = None else: + # check for python minimal version number + match = re.match(r" *# *python *>= *(\d+(?:\.\d+)?)$", line) + if match: + minimal_python_version = tuple( + map(int, match.group(1).split("."))) + if sys.version_info >= minimal_python_version: + skip = None + else: + skip = "Minimal python version %s" % match.groups(1) try: r = re.search(r'(?:^|(?<=\s))#([?!<])\s*([^\n]*)', line) # test_type is ? for completion and ! for goto_assignments From 68cbabe8195da456efbe28fa2e388979ff6c2591 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 21:43:34 +0100 Subject: [PATCH 04/21] pep0484 tests only on python >= 3.2 --- test/completion/pep0484.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index 64f656fa..d9d7608d 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -1,5 +1,7 @@ """ Pep-0484 type hinting """ +# python >= 3.2 + # ----------------- # simple classes # ----------------- From 7e8112d607d14bbe9334c37d8cfd59d13185d9c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 23:05:14 +0100 Subject: [PATCH 05/21] pep0484 return type support --- jedi/evaluate/pep0484.py | 2 +- jedi/evaluate/representation.py | 18 +++++++++ jedi/parser/tree.py | 4 +- test/completion/pep0484.py | 66 +++++++++++++++++++++++++++++---- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 1256ef2d..536e624b 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -6,7 +6,7 @@ as annotations in future python versions. The (initial / probably incomplete) implementation todo list for pep-0484: v Function parameter annotations with builtin/custom type classes -x Function returntype annotations with builtin/custom type classes +v Function returntype annotations with builtin/custom type classes x Function parameter annotations with strings (forward reference) x Function return type annotations with strings (forward reference) x Local variable type hints diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index dffac8c2..173cf9be 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -580,6 +580,21 @@ class Function(use_metaclass(CachedMetaClass, Wrapper)): else: return FunctionExecution(self._evaluator, self, params).get_return_types() + @memoize_default() + def py__annotations__(self): + parser_func = self.base + return_annotation = parser_func.annotation() + if return_annotation: + dct = {'return': self._evaluator.eval_element(return_annotation)} + else: + dct = {} + for function_param in parser_func.params: + param_annotation = function_param.annotation() + if param_annotation: + dct[function_param.name.value] = \ + self._evaluator.eval_element(param_annotation) + return dct + def py__class__(self): return compiled.get_special_object(self._evaluator, 'FUNCTION_CLASS') @@ -639,6 +654,9 @@ class FunctionExecution(Executed): else: returns = self.returns types = set(docstrings.find_return_types(self._evaluator, func)) + annotations = func.py__annotations__().get("return", []) + types |= set(chain.from_iterable( + self._evaluator.execute(d) for d in annotations)) for r in returns: check = flow_analysis.break_check(self._evaluator, self, r) diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 7f0e1960..c5fb0934 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -866,7 +866,9 @@ class Function(ClassOrFunc): def annotation(self): try: - return self.children[6] # 6th element: def foo(...) -> bar + if self.children[3] == "->": + return self.children[4] + assert self.children[3] == ":" except IndexError: return None diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index d9d7608d..52550405 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -2,17 +2,69 @@ # python >= 3.2 -# ----------------- -# simple classes -# ----------------- + +class A(): + pass -def typehints(a, b: str, c: int, d: int=4): - #? +def function_parameters(a: A, b, c: str, d: int=4): + #? A() a - #? str() + #? b - #? int() + #? str() c #? int() d + + +def return_unspecified(): + pass + +#? +return_unspecified() + + +def return_none() -> None: + """ + Return type None means the same as no return type as far as jedi + is concerned + """ + pass + +#? +return_none() + + +def return_str() -> str: + pass + +#? str() +return_str() + + +def return_custom_class() -> A: + pass + +#? A() +return_custom_class() + + +def return_annotation_and_docstring() -> str: + """ + :rtype: int + """ + pass + +#? str() int() +return_annotation_and_docstring() + + +def return_annotation_and_docstring_different() -> str: + """ + :rtype: str + """ + pass + +#? str() +return_annotation_and_docstring_different() From c61f39cb2b31d1ca431557cd08df88541e48cdb0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 23:45:37 +0100 Subject: [PATCH 06/21] add test for annotations to test_parser_tree --- test/test_parser/test_parser_tree.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/test_parser/test_parser_tree.py b/test/test_parser/test_parser_tree.py index 480230ba..28c7d271 100644 --- a/test/test_parser/test_parser_tree.py +++ b/test/test_parser/test_parser_tree.py @@ -12,10 +12,11 @@ from jedi.parser import tree as pt class TestsFunctionAndLambdaParsing(object): FIXTURES = [ - ('def my_function(x, y, z):\n return x + y * z\n', { + ('def my_function(x, y, z) -> str:\n return x + y * z\n', { 'name': 'my_function', 'call_sig': 'my_function(x, y, z)', 'params': ['x', 'y', 'z'], + 'annotation': "str", }), ('lambda x, y, z: x + y * z\n', { 'name': '', @@ -55,7 +56,11 @@ class TestsFunctionAndLambdaParsing(object): assert not node.yields def test_annotation(self, node, expected): - assert node.annotation() is expected.get('annotation', None) + expected_annotation = expected.get('annotation', None) + if expected_annotation is None: + assert node.annotation() is None + else: + assert node.annotation().value == expected_annotation def test_get_call_signature(self, node, expected): assert node.get_call_signature() == expected['call_sig'] From f8debace0d7add649ea4436a268afbee084f6066 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 23:47:45 +0100 Subject: [PATCH 07/21] forward reference pep-0484 --- jedi/evaluate/pep0484.py | 4 ++-- jedi/parser/tree.py | 22 ++++++++++++++++++++-- test/completion/pep0484.py | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 536e624b..a9dc748c 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -7,8 +7,8 @@ as annotations in future python versions. The (initial / probably incomplete) implementation todo list for pep-0484: v Function parameter annotations with builtin/custom type classes v Function returntype annotations with builtin/custom type classes -x Function parameter annotations with strings (forward reference) -x Function return type annotations with strings (forward reference) +v Function parameter annotations with strings (forward reference) +v Function return type annotations with strings (forward reference) x Local variable type hints x Assigned types: `Url = str\ndef get(url:Url) -> str:` x Type hints in `with` statements diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index c5fb0934..df116820 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -41,6 +41,7 @@ import textwrap from jedi._compatibility import (Python3Method, encoding, is_py3, utf8_repr, literal_eval, use_metaclass, unicode) from jedi import cache +import ast def is_node(node, *symbol_names): @@ -52,6 +53,19 @@ def is_node(node, *symbol_names): return type in symbol_names +def _fix_forward_reference(annotation): + if isinstance(annotation, String): + newannotation = Name( + annotation.position_modifier, + ast.literal_eval(annotation.value), + annotation.start_pos, + annotation.prefix) + newannotation.parent = annotation.parent + else: + newannotation = annotation + return newannotation + + class PositionModifier(object): """A start_pos modifier for the fast parser.""" def __init__(self): @@ -865,9 +879,12 @@ class Function(ClassOrFunc): return bool(self.yields) def annotation(self): + if self.children[0] == "lambda": + # lambda functions have no annotation + return None try: if self.children[3] == "->": - return self.children[4] + return _fix_forward_reference(self.children[4]) assert self.children[3] == ":" except IndexError: return None @@ -1409,7 +1426,8 @@ class Param(BaseNode): if is_node(tfpdef, 'tfpdef'): assert tfpdef.children[1] == ":" assert len(tfpdef.children) == 3 - return tfpdef.children[2] + annotation = tfpdef.children[2] + return _fix_forward_reference(annotation) else: return None diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index 52550405..6a7ce6ce 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -68,3 +68,23 @@ def return_annotation_and_docstring_different() -> str: #? str() return_annotation_and_docstring_different() + + +def annotation_forward_reference(b: "B") -> "B": + #? B() + b + +#? B() +annotation_forward_reference(1) + +class B: + pass + + +class SelfReference: + def test(x: "SelfReference") -> "SelfReference": + #? SelfReference() + x + +#? SelfReference() +SelfReference().test() From 7f8b878c8c2dc06aeda5a50124d089ca9404b010 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Dec 2015 23:55:07 +0100 Subject: [PATCH 08/21] if both docstring and annotations are present, use both for function parameters --- jedi/evaluate/finder.py | 10 +++------- test/completion/pep0484.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index ad2a6c33..2e841c99 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -388,15 +388,11 @@ def _eval_param(evaluator, param, scope): and func.instance.is_generated and str(func.name) == '__init__': param = func.var.params[param.position_nr] - # Add pep0484 type hints + # Add pep0484 and docstring knowledge. pep0484_hints = pep0484.follow_param(evaluator, param) - if pep0484_hints: - return pep0484_hints - - # Add docstring knowledge. doc_params = docstrings.follow_param(evaluator, param) - if doc_params: - return doc_params + if pep0484_hints or doc_params: + return list(set(pep0484_hints) | set(doc_params)) if isinstance(param, ExecutedParam): return res_new | param.eval(evaluator) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index 6a7ce6ce..b9ab88aa 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -7,7 +7,13 @@ class A(): pass -def function_parameters(a: A, b, c: str, d: int=4): +def function_parameters(a: A, b, c: str, d: int, e: str, f: str, g: int=4): + """ + :param e: if docstring and annotation agree, only one should be returned + :type e: str + :param f: if docstring and annotation disagree, both should be returned + :type f: int + """ #? A() a #? @@ -16,6 +22,12 @@ def function_parameters(a: A, b, c: str, d: int=4): c #? int() d + #? str() + e + #? int() str() + f + # int() + g def return_unspecified(): From 0f08dc6ac6dbbdc81ebf77b4218356ff40150798 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 00:03:07 +0100 Subject: [PATCH 09/21] Addinf myself to AUTHORS --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index bc258d6b..9b2736c1 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -34,5 +34,6 @@ Phillip Berndt (@phillipberndt) Ian Lee (@IanLee1521) Farkhad Khatamov (@hatamov) Kevin Kelley (@kelleyk) +Reinoud Elhorst (@reinhrst) Note: (@user) means a github user name. From be399c81c309c4d9e98f814ab55cc23e36a6a164 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 00:49:09 +0100 Subject: [PATCH 10/21] clean out the last_* fields of sys before importing it. The system gets confused if there were uncaught errors in previous tests without this. Particularly, it crashes (at least 2.6) if any tests during test_integrations were skipped. --- test/test_api/test_api.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 926f41a2..72ff57e3 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -9,6 +9,13 @@ from jedi._compatibility import is_py3 from pytest import raises +def _clear_last_exception_fields_in_sys(): + import sys + sys.last_type = None + sys.last_value = None + sys.last_traceback = None + + def test_preload_modules(): def check_loaded(*modules): # +1 for None module (currently used) @@ -20,6 +27,7 @@ def test_preload_modules(): temp_cache, cache.parser_cache = cache.parser_cache, {} parser_cache = cache.parser_cache + _clear_last_exception_fields_in_sys() api.preload_module('sys') check_loaded() # compiled (c_builtin) modules shouldn't be in the cache. api.preload_module('json', 'token') From 576fdf810678dc937bd27cee5cd134785f9c0fc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 12:10:00 +0100 Subject: [PATCH 11/21] better separation pep0484 code and py__annotation__() function --- jedi/evaluate/pep0484.py | 18 ++++++++++++++---- jedi/evaluate/representation.py | 12 +++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index a9dc748c..b9008a94 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -21,12 +21,22 @@ from itertools import chain from jedi.evaluate.cache import memoize_default -@memoize_default(None, evaluator_is_first_arg=True) -def follow_param(evaluator, param): - annotation = param.annotation() - if annotation: +def _evaluate_for_annotation(evaluator, annotation): + if annotation is not None: definitions = evaluator.eval_element(annotation) return list(chain.from_iterable( evaluator.execute(d) for d in definitions)) else: return [] + + +@memoize_default(None, evaluator_is_first_arg=True) +def follow_param(evaluator, param): + annotation = param.annotation() + return _evaluate_for_annotation(evaluator, annotation) + + +@memoize_default(None, evaluator_is_first_arg=True) +def find_return_types(evaluator, func): + annotation = func.py__annotations__().get("return", None) + return _evaluate_for_annotation(evaluator, annotation) diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 173cf9be..9d0026c0 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -49,6 +49,7 @@ from jedi.evaluate import compiled from jedi.evaluate import recursion from jedi.evaluate import iterable from jedi.evaluate import docstrings +from jedi.evaluate import pep0484 from jedi.evaluate import helpers from jedi.evaluate import param from jedi.evaluate import flow_analysis @@ -585,14 +586,13 @@ class Function(use_metaclass(CachedMetaClass, Wrapper)): parser_func = self.base return_annotation = parser_func.annotation() if return_annotation: - dct = {'return': self._evaluator.eval_element(return_annotation)} + dct = {'return': return_annotation} else: dct = {} for function_param in parser_func.params: param_annotation = function_param.annotation() - if param_annotation: - dct[function_param.name.value] = \ - self._evaluator.eval_element(param_annotation) + if param_annotation is not None: + dct[function_param.name.value] = param_annotation return dct def py__class__(self): @@ -654,9 +654,7 @@ class FunctionExecution(Executed): else: returns = self.returns types = set(docstrings.find_return_types(self._evaluator, func)) - annotations = func.py__annotations__().get("return", []) - types |= set(chain.from_iterable( - self._evaluator.execute(d) for d in annotations)) + types |= set(pep0484.find_return_types(self._evaluator, func)) for r in returns: check = flow_analysis.break_check(self._evaluator, self, r) From 6ce076f4134630dac6915e009e2c4ccd4377669a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 12:10:48 +0100 Subject: [PATCH 12/21] more elaborate tests --- test/completion/pep0484.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index b9ab88aa..040461e2 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -88,15 +88,25 @@ def annotation_forward_reference(b: "B") -> "B": #? B() annotation_forward_reference(1) +#? ["test_element"] +annotation_forward_reference(1).t class B: + test_element = 1 pass class SelfReference: - def test(x: "SelfReference") -> "SelfReference": + test_element = 1 + def test_method(self, x: "SelfReference") -> "SelfReference": #? SelfReference() x + #? ["test_element", "test_method"] + self.t + #? ["test_element", "test_method"] + x.t + #? ["test_element", "test_method"] + self.test_method(1).t #? SelfReference() -SelfReference().test() +SelfReference().test_method() From 0f6fb23d91890afb39b2b55d492d9b8e63582b89 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 22:02:11 +0100 Subject: [PATCH 13/21] override annotation() in Lambda, instead of checking in Function on type --- jedi/parser/tree.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index df116820..a20030cb 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -879,9 +879,6 @@ class Function(ClassOrFunc): return bool(self.yields) def annotation(self): - if self.children[0] == "lambda": - # lambda functions have no annotation - return None try: if self.children[3] == "->": return _fix_forward_reference(self.children[4]) @@ -964,6 +961,10 @@ class Lambda(Function): def is_generator(self): return False + def annotation(self): + # lambda functions do not support annotations + return None + @property def yields(self): return [] From 626fa60d03ce0ff8ad28d368612d0eaa3d73a48f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Dec 2015 22:37:20 +0100 Subject: [PATCH 14/21] Revert "clean out the last_* fields of sys before importing it." This reverts commit be399c81c309c4d9e98f814ab55cc23e36a6a164. Will break python 2.6 (possibly 2.7) tests; this is expected behaviour. See https://github.com/davidhalter/jedi/pull/661#discussion_r47543815 --- test/test_api/test_api.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 72ff57e3..926f41a2 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -9,13 +9,6 @@ from jedi._compatibility import is_py3 from pytest import raises -def _clear_last_exception_fields_in_sys(): - import sys - sys.last_type = None - sys.last_value = None - sys.last_traceback = None - - def test_preload_modules(): def check_loaded(*modules): # +1 for None module (currently used) @@ -27,7 +20,6 @@ def test_preload_modules(): temp_cache, cache.parser_cache = cache.parser_cache, {} parser_cache = cache.parser_cache - _clear_last_exception_fields_in_sys() api.preload_module('sys') check_loaded() # compiled (c_builtin) modules shouldn't be in the cache. api.preload_module('json', 'token') From 3cef8b6d557538ac7a757f6321fbaae8c1a1fca2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Dec 2015 00:31:47 +0100 Subject: [PATCH 15/21] string-annotations should only be interpreted by the pep-0484 code, not the parser --- jedi/evaluate/pep0484.py | 14 ++++++++++++-- jedi/parser/tree.py | 19 +++---------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index b9008a94..2c2cb173 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -17,13 +17,23 @@ x support `@no_type_check` and `@no_type_check_decorator` """ from itertools import chain - +from jedi.parser import Parser, load_grammar from jedi.evaluate.cache import memoize_default +from jedi.evaluate.compiled import CompiledObject def _evaluate_for_annotation(evaluator, annotation): if annotation is not None: - definitions = evaluator.eval_element(annotation) + definitions = set() + for definition in evaluator.eval_element(annotation): + if (isinstance(definition, CompiledObject) and + isinstance(definition.obj, str)): + p = Parser(load_grammar(), definition.obj) + element = p.module.children[0].children[0] + element.parent = annotation.parent + definitions |= evaluator.eval_element(element) + else: + definitions.add(definition) return list(chain.from_iterable( evaluator.execute(d) for d in definitions)) else: diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index a20030cb..2951722c 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -41,7 +41,6 @@ import textwrap from jedi._compatibility import (Python3Method, encoding, is_py3, utf8_repr, literal_eval, use_metaclass, unicode) from jedi import cache -import ast def is_node(node, *symbol_names): @@ -53,19 +52,6 @@ def is_node(node, *symbol_names): return type in symbol_names -def _fix_forward_reference(annotation): - if isinstance(annotation, String): - newannotation = Name( - annotation.position_modifier, - ast.literal_eval(annotation.value), - annotation.start_pos, - annotation.prefix) - newannotation.parent = annotation.parent - else: - newannotation = annotation - return newannotation - - class PositionModifier(object): """A start_pos modifier for the fast parser.""" def __init__(self): @@ -881,8 +867,9 @@ class Function(ClassOrFunc): def annotation(self): try: if self.children[3] == "->": - return _fix_forward_reference(self.children[4]) + return self.children[4] assert self.children[3] == ":" + return None except IndexError: return None @@ -1428,7 +1415,7 @@ class Param(BaseNode): assert tfpdef.children[1] == ":" assert len(tfpdef.children) == 3 annotation = tfpdef.children[2] - return _fix_forward_reference(annotation) + return annotation else: return None From 12588753000baba2dcf68530c70cd6e7d5817b4e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Dec 2015 00:37:23 +0100 Subject: [PATCH 16/21] add test that jedi doesn't break in case of non-pep-0484 comments --- test/completion/pep0484.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index 040461e2..ed195b15 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -110,3 +110,13 @@ class SelfReference: #? SelfReference() SelfReference().test_method() + +def function_with_non_pep_0484_annotation(x: "I can put anything here", y: 3 + 3) -> int("42"): + # infers int from function call + #? int() + x + # infers str from function call + #? str() + y +#? +function_with_non_pep_0484_annotation(1, "force string") From 35fda3823edd2b4225b9f82048cca71ac43620a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Dec 2015 11:53:48 +0100 Subject: [PATCH 17/21] test dynamic annotation and dynamic forward reference --- test/completion/pep0484.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index ed195b15..9e1c5e00 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -120,3 +120,16 @@ def function_with_non_pep_0484_annotation(x: "I can put anything here", y: 3 + 3 y #? function_with_non_pep_0484_annotation(1, "force string") + +def function_forward_reference_dynamic( + x: return_str_type(), + y: "return_str_type()") -> None: + # technically should not be resolvable since out of scope, + # but jedi is not smart enough for that + #? str() + x + #? str() + y + +def return_str_type(): + return str From 1e6397b1639bb048711c1ad9db6f0b9f58713dee Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Dec 2015 11:56:54 +0100 Subject: [PATCH 18/21] check 'assigned types'-support (comes out of the jedi-box), and add tests for that --- jedi/evaluate/pep0484.py | 2 +- test/completion/pep0484.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 2c2cb173..e31e842a 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -10,7 +10,7 @@ v Function returntype annotations with builtin/custom type classes v Function parameter annotations with strings (forward reference) v Function return type annotations with strings (forward reference) x Local variable type hints -x Assigned types: `Url = str\ndef get(url:Url) -> str:` +v Assigned types: `Url = str\ndef get(url:Url) -> str:` x Type hints in `with` statements x Stub files support x support `@no_type_check` and `@no_type_check_decorator` diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index 9e1c5e00..bba6f50a 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -133,3 +133,12 @@ def function_forward_reference_dynamic( def return_str_type(): return str + + +X = str +def function_with_assined_class_in_reference(x: X, y: "Y"): + #? str() + x + #? int() + y +Y = int From 8bf2fe77e235e322365f178d2a54a79a21f3129d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Dec 2015 15:06:20 +0100 Subject: [PATCH 19/21] add some more non-pep0484-junk to the test --- test/completion/pep0484.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index bba6f50a..d49f8c58 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -111,7 +111,11 @@ class SelfReference: #? SelfReference() SelfReference().test_method() -def function_with_non_pep_0484_annotation(x: "I can put anything here", y: 3 + 3) -> int("42"): +def function_with_non_pep_0484_annotation( + x: "I can put anything here", + xx: "", + yy: "\r\n;+*&^564835(---^&*34", + y: 3 + 3) -> int("42"): # infers int from function call #? int() x @@ -119,7 +123,7 @@ def function_with_non_pep_0484_annotation(x: "I can put anything here", y: 3 + 3 #? str() y #? -function_with_non_pep_0484_annotation(1, "force string") +function_with_non_pep_0484_annotation(1, 2, 3, "force string") def function_forward_reference_dynamic( x: return_str_type(), From 6bee2149485ef3d91a0ebb8006bb59e06fcfd359 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Dec 2015 15:23:40 +0100 Subject: [PATCH 20/21] catch error in certain non-pep0484 annotations --- jedi/evaluate/pep0484.py | 5 ++++- test/completion/pep0484.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index e31e842a..cb3e8a4c 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -29,7 +29,10 @@ def _evaluate_for_annotation(evaluator, annotation): if (isinstance(definition, CompiledObject) and isinstance(definition.obj, str)): p = Parser(load_grammar(), definition.obj) - element = p.module.children[0].children[0] + try: + element = p.module.children[0].children[0] + except (AttributeError, IndexError): + continue element.parent = annotation.parent definitions |= evaluator.eval_element(element) else: diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index d49f8c58..d6ec8291 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -114,14 +114,23 @@ SelfReference().test_method() def function_with_non_pep_0484_annotation( x: "I can put anything here", xx: "", - yy: "\r\n;+*&^564835(---^&*34", - y: 3 + 3) -> int("42"): + yy: "\r\n\0;+*&^564835(---^&*34", + y: 3 + 3, + zz: float) -> int("42"): # infers int from function call #? int() x + # infers int from function call + #? int() + xx + # infers int from function call + #? int() + yy # infers str from function call #? str() y + #? float() + zz #? function_with_non_pep_0484_annotation(1, 2, 3, "force string") From 160b6fca516a6a8925757f131541acf16096291e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Dec 2015 15:29:49 +0100 Subject: [PATCH 21/21] show off some power :) --- test/completion/pep0484.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/completion/pep0484.py b/test/completion/pep0484.py index d6ec8291..a090b7fa 100644 --- a/test/completion/pep0484.py +++ b/test/completion/pep0484.py @@ -155,3 +155,7 @@ def function_with_assined_class_in_reference(x: X, y: "Y"): #? int() y Y = int + +def just_because_we_can(x: "flo" + "at"): + #? float() + x