125 Commits

Author SHA1 Message Date
Dave Halter
005f69390c Write the CHANGELOG for 0.15.1 2019-08-13 00:18:45 +02:00
Matthias Bussonnier
ecca190462 Remove forgotten debug/print in filename completion. (#1380)
This is in the latest 0.15, and when forwarding path completions to
jedi, print a lot of stuff on the screen.
2019-08-12 12:37:21 +02:00
Dave Halter
5d0d09bb7d staticmethod and a few other cases might not have properly returned its signatures 2019-08-12 09:37:59 +02:00
Dave Halter
972cae4859 Remove reference to a file that doesn't exist anymore 2019-08-12 00:24:35 +02:00
Dave Halter
77bc2d548a Bump version to make it clear that it's a different one than the current one 2019-08-12 00:22:48 +02:00
Dave Halter
35e5cf2c2a A small Changelog improvement 2019-08-11 20:49:49 +02:00
Dave Halter
c6f0ecd223 Cleanup Changelog for the next release 2019-08-11 20:37:50 +02:00
Dave Halter
f727e4e661 Make it possible to access functions that were inherited, see #1347 2019-08-11 20:34:21 +02:00
Dave Halter
1ad4003740 Messed up a Windows test 2019-08-11 20:12:33 +02:00
Dave Halter
1108ad9994 Again a small windows issue fixed. 2019-08-11 20:01:12 +02:00
Dave Halter
f7f9b1e5ec Need to escape the path backslash for windows slashes 2019-08-11 19:56:57 +02:00
Dave Halter
c3d40949b1 Make it possible to access properties again
This time we catch all exceptions and try to avoid issues for the user.

This is only happening when working with an Interpreter. I don't feel this is
necessary otherwise.

See #1299
2019-08-11 16:24:19 +02:00
Dave Halter
a7accf4171 A small compatibility fix 2019-08-11 01:54:26 +02:00
Dave Halter
ab80646b86 Fix an issue with type vars that might have been a problem for other things as well 2019-08-11 01:28:09 +02:00
Dave Halter
3d0ac09fc9 Don't add quotes after paths if they are already there 2019-08-10 18:37:10 +02:00
Dave Halter
0a84678a60 A small speed optimization that helps a lot with sys.version_info >= (3, 0) patterns in typeshed 2019-08-10 15:31:36 +02:00
Dave Halter
4a5c992b1a Remove an unnecessary isinstance usage 2019-08-10 14:41:47 +02:00
Dave Halter
04b7c99753 Make CompiledValue lazy
This definitely reduces debug output and it might be slightly faster, because some values are never asked for
2019-08-10 14:36:40 +02:00
Dave Halter
499408657b A python 2 fix 2019-08-08 17:07:54 +02:00
Dave Halter
4ec3fb6e12 Fix an error that occured because of some refactorings 2019-08-08 11:03:27 +02:00
Dave Halter
463cbb1595 Fix one more os.path.join issue 2019-08-08 09:31:13 +02:00
Dave Halter
03608151e8 Fix more issues with os.path path completion 2019-08-08 01:48:25 +02:00
Dave Halter
822394663c Make join detection much easier 2019-08-08 01:04:08 +02:00
Dave Halter
52517f78b1 Fix some remaining issues with file path completions 2019-08-07 23:00:27 +02:00
Dave Halter
a191b7b458 A few more tests for path completions (join) 2019-08-07 21:11:48 +02:00
Dave Halter
e68273c0ff Fix quote completions for os.path.join path completions 2019-08-07 20:55:12 +02:00
Dave Halter
aeff5faa3d Fix first param argument of os.path.join file completions 2019-08-07 20:39:47 +02:00
Dave Halter
0fd3757a51 Fix arglist/trailer issues 2019-08-07 10:16:05 +02:00
Dave Halter
1b064c1078 in os.path.join completions, directories should not end in a slash 2019-08-07 01:37:58 +02:00
Dave Halter
5726c29385 Make some file path completions in os.path.join work 2019-08-07 01:34:46 +02:00
Dave Halter
7c1c4981fb Fix os.path.join static value gathering 2019-08-06 22:48:28 +02:00
Dave Halter
81488bcd20 os.path.sep should always have a clear value 2019-08-06 19:57:16 +02:00
Dave Halter
99008eef43 Fix string name completion for stuff like dirname and abspath 2019-08-06 19:38:16 +02:00
Dave Halter
3a9dc0ca2e Fix bytes issue with file path adding 2019-08-06 01:08:57 +02:00
Dave Halter
98a550e352 Python 2 compatibility 2019-08-06 00:42:02 +02:00
Dave Halter
4b8505b78d Make __file__ return the correct value 2019-08-06 00:30:31 +02:00
Dave Halter
b7c2bacbd2 Fix string additions when used in certain ways 2019-08-05 10:11:36 +02:00
Dave Halter
8108122347 Make string additions work for file path completion
With this most simple cases of file path completions should be working now, fixes #493
2019-08-05 01:43:50 +02:00
Dave Halter
45dada9552 Fix interpeter project path 2019-08-05 00:43:37 +02:00
Dave Halter
38e0cbc1d2 Fix the REPL completer for file path completions 2019-08-04 23:08:25 +02:00
Dave Halter
e008a515e3 Fix a few more file name completion cases 2019-08-04 22:43:23 +02:00
Dave Halter
fd1e6afd07 A first iteration for file path completions 2019-08-04 13:50:23 +02:00
Dave Halter
9dd088f3db Fix a test failure 2019-08-03 14:58:57 +02:00
Dave Halter
8e1417e3ce Add Definition.execute, fixes #1076 2019-08-03 02:01:30 +02:00
Dave Halter
97526aa320 Add tests to show that #516 is not working, yet 2019-08-02 22:31:26 +02:00
Dave Halter
16e0351897 List possible Definition.type in its docstring, fixes #1069. 2019-08-02 21:16:58 +02:00
Dave Halter
c0c7c949fd Start writing the Changelog for 0.15.0 2019-08-02 17:17:25 +02:00
Dave Halter
b8bc4060dd 3.8-dev should not be allowed to fail 2019-08-02 16:15:16 +02:00
Dave Halter
c737e3ee40 Skip more Python 2 tests 2019-08-02 15:54:10 +02:00
Dave Halter
4c3d4508e9 Skipping of tests was done the wrong way again 2019-08-02 15:50:06 +02:00
Dave Halter
70bcc9405f Skip the right tests 2019-08-02 15:25:20 +02:00
Dave Halter
6a82f60901 Parameter.kind is not avaialble in Python 3.5 2019-08-02 13:49:01 +02:00
Dave Halter
814998253a Fix Python 2 test issues 2019-08-02 13:44:04 +02:00
Dave Halter
a22c6da89f Add a few docstrings to make some things clearer 2019-08-02 13:16:18 +02:00
Dave Halter
876a6a5c22 Add ParamDefinition.kind, fixes #1361 2019-08-02 13:11:41 +02:00
Dave Halter
642e8f2aa6 Make it possible to format a param to a string, fixes #1074 2019-08-02 12:17:58 +02:00
Dave Halter
a64ef2759c Add another test for signature annotations 2019-08-02 12:17:58 +02:00
Dave Halter
d58bbce24f Add Signature.to_string() with proper tests, fixes #779, fixes #780 2019-08-02 12:17:13 +02:00
Dave Halter
ca6a7215e2 Test infer_default 2019-08-02 10:41:04 +02:00
Dave Halter
93b7548f1a Use a helper to create definitions 2019-08-02 10:30:23 +02:00
Dave Halter
24db05841b Add a execute_annotation option to infer_annotation 2019-08-02 10:24:15 +02:00
Dave Halter
375d1d57fb Test infer_annotation 2019-08-02 10:00:17 +02:00
Dave Halter
c2e50e1d0d Make it possible for users to infer annotations/defaults
Fixes #1039
2019-08-01 18:27:37 +02:00
Dave Halter
7988c1d11b A first iteration of adding signatures to the API, fixes #1139 2019-08-01 17:48:10 +02:00
Dave Halter
8ab2a5320e Fix a caching issue 2019-08-01 02:10:46 +02:00
Dave Halter
b5a62825ce Forgot the right resolve_stars parameters in one place 2019-07-31 23:05:24 +02:00
Dave Halter
ec70815318 Cache getting resolved param names 2019-07-31 22:54:29 +02:00
Dave Halter
a739c17a6f Turn around resolve_stars, it shouldn't by default be resolved 2019-07-31 18:51:31 +02:00
Dave Halter
ab5f4b6774 Remove a class that is not needed anymore 2019-07-31 18:44:57 +02:00
Dave Halter
a5a544cb09 Revert "Use __str__ instead of to_string"
This reverts commit 1151700114.
2019-07-31 18:39:17 +02:00
Dave Halter
7d2374ed81 Fix the last remaining issues with function signature 2019-07-31 18:29:41 +02:00
Dave Halter
97b642a3e1 overloaded_functions should be private 2019-07-31 00:11:08 +02:00
Dave Halter
1151700114 Use __str__ instead of to_string 2019-07-31 00:07:38 +02:00
Dave Halter
75f654b944 Better repr for CallSignature 2019-07-30 23:55:58 +02:00
Dave Halter
bb852c3e85 Fix some minor signature issues 2019-07-30 23:48:54 +02:00
Dave Halter
1fbb69b35a Remove the unused function signature_matches 2019-07-30 10:01:50 +02:00
Dave Halter
0352c3250a Fix signatures for __init__ calls when used with supers, fixes #1163 2019-07-30 01:44:53 +02:00
Dave Halter
268f828963 Fix some issues for args resolving in method calls 2019-07-30 01:28:51 +02:00
Dave Halter
21508a8c79 Remove a bit of code that i sprobably unused 2019-07-30 00:38:42 +02:00
Dave Halter
f9de26f72c Move get_signatures from Function to FunctionMixin 2019-07-29 20:17:03 +02:00
Dave Halter
22580f771c Merge the signature changes
Fixes include
- Better @wraps(func) understanding
- *args, **kwargs in call signatures is now resolved as well as possible

Fixes #503, fixes #1058
Also look at #906, #634, #1163
2019-07-29 00:31:08 +02:00
Dave Halter
9b338f69a6 Add a comment about wraps 2019-07-29 00:28:12 +02:00
Dave Halter
fa0424cfd6 Fix signatures for wraps, see #1058 2019-07-29 00:13:05 +02:00
Dave Halter
f6808a96e0 Skip pre python 3.5 2019-07-28 20:40:32 +02:00
Dave Halter
02bd7e5bc7 Some small args adaptions 2019-07-28 20:22:28 +02:00
Dave Halter
e8e3e8c111 Deal better with non-functions 2019-07-28 19:52:48 +02:00
Dave Halter
c8588191f9 Some more small fixes 2019-07-28 18:09:08 +02:00
Dave Halter
97e7f608df Don't return multiple used names for signatures 2019-07-28 17:51:40 +02:00
Dave Halter
fae2c8c060 Move args resolving to a different file 2019-07-28 17:41:28 +02:00
Dave Halter
b4f2d82867 A new approach of getting arguments 2019-07-28 17:31:17 +02:00
Dave Halter
6a480780f8 Some more tests 2019-07-26 14:51:30 +02:00
Dave Halter
41dc5382fa Make nesting of *args/**kwargs possible to understand. 2019-07-26 14:42:20 +02:00
Dave Halter
ba160e72ab Some more signature progress 2019-07-26 14:29:33 +02:00
Dave Halter
0703a69369 Some progress in signature understanding 2019-07-26 12:11:45 +02:00
Dave Halter
c490d37c2d Start getting signature inferring working 2019-07-26 02:54:50 +02:00
Dave Halter
84219236a7 Remove an import 2019-07-25 14:15:52 +02:00
Dave Halter
57fd995727 Small refactoring 2019-07-25 14:15:26 +02:00
Dave Halter
a803d687e2 Skipped Python 2 Interpreter tests the wrong way 2019-07-24 13:44:26 +02:00
Dave Halter
c7927fb141 Remove a paragraph in docs that was arguing that stubs and generics (and other things) were not properly supported, fixes #1012 2019-07-24 13:41:33 +02:00
Dave Halter
05d9602032 Fix partial signatures for MixedObject
Now a MixedObject return the signatures of its CompiledObject all the time, fixes #1371
2019-07-24 12:58:20 +02:00
Dave Halter
e76120da06 Fix partial signatures, fixes #1371 2019-07-24 02:28:49 +02:00
Dave Halter
25bbecc269 Make sure with a test that the staticmethod signature is also correct 2019-07-24 01:15:48 +02:00
Dave Halter
08bb9cfae7 Fix classmethod signature, fixes #498 2019-07-24 01:06:49 +02:00
Dave Halter
703b747a31 Deal with annotation on *args and **kwargs correctly, fixes #980 2019-07-23 23:56:30 +02:00
Dave Halter
ff149b74e0 Use LazyContextWrapper more 2019-07-23 13:59:08 +02:00
Dave Halter
3d08eb92d5 Very small refactoring 2019-07-23 13:08:57 +02:00
Johannes-Maria Frank
02d16ac55c Fix for failing assertion on native modules Issue #1354 (#1370) 2019-07-23 13:02:08 +02:00
Dave Halter
18eb7622ba Skip numpydoc tests for Python 2 2019-07-22 00:49:40 +02:00
Dave Halter
13dd173664 Remove code that didn't mean anything 2019-07-22 00:39:19 +02:00
Dave Halter
73c078ec7a Fix docstrings for wrapped functions, fixes #906 2019-07-21 12:19:22 +02:00
Dave Halter
cdf50e2a69 Fix an isue about dict ordering in Python before 3.6. 2019-07-19 12:54:22 +02:00
Dave Halter
2b0b29f921 Make it clearer when get_param is used. 2019-07-19 11:57:55 +02:00
Dave Halter
0dc60fb535 A small dataclass refactoring 2019-07-19 11:44:11 +02:00
Dave Halter
5722a3458e Evaluate annotations for dataclasses when infer is called on param 2019-07-19 11:42:08 +02:00
Dave Halter
93c52f615a Get inheritance of dataclass right 2019-07-19 11:35:13 +02:00
Dave Halter
050d686a27 A first working iteration of dataclass signatures, fixes #1213 2019-07-19 02:01:36 +02:00
Dave Halter
7156ddf607 Remove an unused function 2019-07-19 01:32:27 +02:00
Dave Halter
1cccc832b6 Dataclass progress 2019-07-19 01:27:37 +02:00
Dave Halter
fd4eca5e03 Add enum changes to changelog 2019-07-18 12:19:21 +02:00
Dave Halter
1d9b9cff47 Fix a recursion error about getting metaclasses 2019-07-18 12:02:27 +02:00
Dave Halter
f4fe113c0f One test about recursion issues only applied to Python 2 2019-07-18 12:00:47 +02:00
Dave Halter
c7fc715535 Use class filters in instances differently so metaclass plugins work, fixes #1090 2019-07-18 11:20:54 +02:00
Dave Halter
eeea88046e First step in working with metaclasses in plugins, see #1090. 2019-07-18 11:20:28 +02:00
Dave Halter
dea887d27d Refactor the plugin registry 2019-07-16 12:48:54 +02:00
Dave Halter
8329e2e969 Remove classes from plugins and use decorators instead 2019-07-16 10:23:19 +02:00
58 changed files with 1956 additions and 428 deletions

View File

@@ -3,8 +3,6 @@ omit =
jedi/_compatibility.py
jedi/evaluate/compiled/subprocess/__main__.py
jedi/__main__.py
# Is statically analyzed and not actually executed.
jedi/evaluate/jedi_typing.py
# For now this is not being used.
jedi/refactoring.py

View File

@@ -15,9 +15,6 @@ env:
- JEDI_TEST_ENVIRONMENT=37
matrix:
allow_failures:
- python: pypy
- python: 3.8-dev
include:
- python: 3.6
env:

View File

@@ -52,5 +52,6 @@ Tobias Rzepka (@TobiasRzepka)
micbou (@micbou)
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
Note: (@user) means a github user name.

View File

@@ -3,6 +3,34 @@
Changelog
---------
0.15.1 (2019-08-13)
+++++++++++++++++++
- Small bugfix and removal of a print statement
0.15.0 (2019-08-11)
+++++++++++++++++++
- Added file path completions, there's a **new ``Completion.type``** ``path``,
now. Example: ``'/ho`` -> ``'/home/``
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
with the actual alternatives.
- Better support for enums/dataclasses
- When using Interpreter, properties are now executed, since a lot of people
have complained about this. Discussion in #1299, #1347.
New APIs:
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to
``CallSignature``. ``Definition.params`` is therefore deprecated.
- ``Signature.to_string()`` to format call signatures.
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the
following additional attributes ``infer_default()``, ``infer_annotation()``,
``to_string()``, and ``kind``.
- ``Definition.execute() -> List[Definition]``, makes it possible to infer
return values of functions.
0.14.1 (2019-07-13)
+++++++++++++++++++

View File

@@ -140,3 +140,19 @@ def skip_pre_python38(environment):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python37(environment):
if environment.version_info < (3, 7):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python35(environment):
if environment.version_info < (3, 5):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()

View File

@@ -140,15 +140,8 @@ Most of the features in PEP-0484 are supported including the typing module
(for Python < 3.5 you have to do ``pip install typing`` to use these),
and forward references.
Things that are missing (and this is not an exhaustive list; some of these
are planned, others might be hard to implement and provide little worth):
You can also use stub files.
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
- understanding ``typing.cast()``
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
- ``typing.Callable``
- ``typing.TypeVar``
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
**Sphinx style**

View File

@@ -33,7 +33,7 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
good text editor, while still having very good IDE features for Python.
"""
__version__ = '0.14.1'
__version__ = '0.15.1'
from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names
@@ -42,3 +42,6 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
get_default_environment, InvalidPythonEnvironment, create_environment, \
get_system_environment
from jedi.api.exceptions import InternalError
# Finally load the internal plugins. This is only internal.
from jedi.plugins import registry
del registry

View File

@@ -27,7 +27,7 @@ from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.completion import Completion
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project
from jedi.api.project import get_default_project, Project
from jedi.evaluate import Evaluator
from jedi.evaluate import imports
from jedi.evaluate import usages
@@ -144,7 +144,6 @@ class Script(object):
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
self._pos = line, column
self._path = path
cache.clear_time_caches()
debug.reset_time()
@@ -437,6 +436,7 @@ class Interpreter(Script):
>>> print(script.completions()[0].name)
upper
"""
_allow_descriptor_getattr_default = True
def __init__(self, source, namespaces, **kwds):
"""
@@ -464,8 +464,10 @@ class Interpreter(Script):
if not isinstance(environment, InterpreterEnvironment):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super(Interpreter, self).__init__(source, environment=environment, **kwds)
super(Interpreter, self).__init__(source, environment=environment,
_project=Project(os.getcwd()), **kwds)
self.namespaces = namespaces
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
def _get_module(self):
return interpreter.MixedModuleContext(

View File

@@ -4,10 +4,9 @@ These classes are the much bigger part of the whole API, because they contain
the interesting information about completion and goto operations.
"""
import re
import sys
import warnings
from parso.python.tree import search_ancestor
from jedi import settings
from jedi import debug
from jedi.evaluate.utils import unite
@@ -38,6 +37,10 @@ def defined_names(evaluator, context):
return [Definition(evaluator, n) for n in _sort_names_by_start_pos(names)]
def _contexts_to_definitions(contexts):
return [Definition(c.evaluator, c.name) for c in contexts]
class BaseDefinition(object):
_mapping = {
'posixpath': 'os.path',
@@ -150,6 +153,9 @@ class BaseDefinition(object):
>>> defs[3]
'function'
Valid values for are ``module``, ``class``, ``instance``, ``function``,
``param``, ``path`` and ``keyword``.
"""
tree_name = self._name.tree_name
resolve = False
@@ -330,6 +336,8 @@ class BaseDefinition(object):
@memoize_method
def params(self):
"""
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
Raises an ``AttributeError`` if the definition is not callable.
Otherwise returns a list of `Definition` that represents the params.
"""
@@ -337,7 +345,10 @@ class BaseDefinition(object):
# with overloading.
for context in self._name.infer():
for signature in context.get_signatures():
return [Definition(self._evaluator, n) for n in signature.get_param_names()]
return [
Definition(self._evaluator, n)
for n in signature.get_param_names(resolve_stars=True)
]
if self.type == 'function' or self.type == 'class':
# Fallback, if no signatures were defined (which is probably by
@@ -384,6 +395,12 @@ class BaseDefinition(object):
start_index = max(index - before, 0)
return ''.join(lines[start_index:index + after + 1])
def get_signatures(self):
return [Signature(self._evaluator, s) for s in self._name.infer().get_signatures()]
def execute(self):
return _contexts_to_definitions(self._name.infer().execute_evaluated())
class Completion(BaseDefinition):
"""
@@ -598,14 +615,37 @@ class Definition(BaseDefinition):
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
class CallSignature(Definition):
class Signature(Definition):
"""
`CallSignature` objects is the return value of `Script.function_definition`.
`Signature` objects is the return value of `Script.function_definition`.
It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing.
"""
def __init__(self, evaluator, signature):
super(Signature, self).__init__(evaluator, signature.name)
self._signature = signature
@property
def params(self):
"""
:return list of ParamDefinition:
"""
return [ParamDefinition(self._evaluator, n)
for n in self._signature.get_param_names(resolve_stars=True)]
def to_string(self):
return self._signature.to_string()
class CallSignature(Signature):
"""
`CallSignature` objects is the return value of `Script.call_signatures`.
It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function with its params. Without `(` it would
return nothing.
"""
def __init__(self, evaluator, signature, call_details):
super(CallSignature, self).__init__(evaluator, signature.name)
super(CallSignature, self).__init__(evaluator, signature)
self._call_details = call_details
self._signature = signature
@@ -615,34 +655,60 @@ class CallSignature(Definition):
The Param index of the current call.
Returns None if the index cannot be found in the curent call.
"""
return self._call_details.calculate_index(self._signature.get_param_names())
@property
def params(self):
return [Definition(self._evaluator, n) for n in self._signature.get_param_names()]
return self._call_details.calculate_index(
self._signature.get_param_names(resolve_stars=True)
)
@property
def bracket_start(self):
"""
The indent of the bracket that is responsible for the last function
call.
The line/column of the bracket that is responsible for the last
function call.
"""
return self._call_details.bracket_leaf.start_pos
@property
def _params_str(self):
return ', '.join([p.description[6:]
for p in self.params])
def __repr__(self):
return '<%s: %s index=%r params=[%s]>' % (
return '<%s: index=%r %s>' % (
type(self).__name__,
self._name.string_name,
self.index,
self._params_str,
self._signature.to_string(),
)
class ParamDefinition(Definition):
def infer_default(self):
"""
:return list of Definition:
"""
return _contexts_to_definitions(self._name.infer_default())
def infer_annotation(self, **kwargs):
"""
:return list of Definition:
:param execute_annotation: If False, the values are not executed and
you get classes instead of instances.
"""
return _contexts_to_definitions(self._name.infer_annotation(**kwargs))
def to_string(self):
return self._name.to_string()
@property
def kind(self):
"""
Returns an enum instance. Returns the same values as the builtin
:py:attr:`inspect.Parameter.kind`.
No support for Python < 3.4 anymore.
"""
if sys.version_info < (3, 5):
raise NotImplementedError(
'Python 2 is end-of-life, the new feature is not available for it'
)
return self._name.get_kind()
def _format_signatures(context):
return '\n'.join(
signature.to_string()

View File

@@ -1,3 +1,5 @@
import re
from parso.python.token import PythonTokenTypes
from parso.python import tree
from parso.tree import search_ancestor, Leaf
@@ -7,12 +9,13 @@ from jedi import debug
from jedi import settings
from jedi.api import classes
from jedi.api import helpers
from jedi.evaluate import imports
from jedi.api import keywords
from jedi.api.file_name import file_name_completions
from jedi.evaluate import imports
from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names
from jedi.evaluate.filters import get_global_filters
from jedi.evaluate.gradual.conversion import convert_contexts
from jedi.parser_utils import get_statement_of_position
from jedi.parser_utils import get_statement_of_position, cut_value_at_position
def get_call_signature_param_names(call_signatures):
@@ -82,7 +85,7 @@ def get_flow_scope_node(module_node, position):
class Completion:
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
def __init__(self, evaluator, module, code_lines, position, call_signatures_callback):
self._evaluator = evaluator
self._module_context = module
self._module_node = module.tree_node
@@ -92,11 +95,23 @@ class Completion:
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
# The actual cursor position is not what we need to calculate
# everything. We want the start of the name we're on.
self._original_position = position
self._position = position[0], position[1] - len(self._like_name)
self._call_signatures_method = call_signatures_method
self._call_signatures_callback = call_signatures_callback
def completions(self):
completion_names = self._get_context_completions()
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
string, start_leaf = _extract_string_while_in_string(leaf, self._position)
if string is not None:
completions = list(file_name_completions(
self._evaluator, self._module_context, start_leaf, string,
self._like_name, self._call_signatures_callback,
self._code_lines, self._original_position
))
if completions:
return completions
completion_names = self._get_context_completions(leaf)
completions = filter_names(self._evaluator, completion_names,
self.stack, self._like_name)
@@ -105,7 +120,7 @@ class Completion:
x.name.startswith('_'),
x.name.lower()))
def _get_context_completions(self):
def _get_context_completions(self, leaf):
"""
Analyzes the context that a completion is made in and decides what to
return.
@@ -121,19 +136,20 @@ class Completion:
"""
grammar = self._evaluator.grammar
self.stack = stack = None
try:
self.stack = stack = helpers.get_stack_at_position(
grammar, self._code_lines, self._module_node, self._position
grammar, self._code_lines, leaf, self._position
)
except helpers.OnErrorLeaf as e:
self.stack = stack = None
if e.error_leaf.value == '.':
value = e.error_leaf.value
if value == '.':
# After ErrorLeaf's that are dots, we will not do any
# completions since this probably just confuses the user.
return []
# If we don't have a context, just use global completion.
# If we don't have a context, just use global completion.
return self._global_completions()
allowed_transitions = \
@@ -210,7 +226,7 @@ class Completion:
completion_names += self._get_class_context_completions(is_function=False)
if 'trailer' in nonterminals:
call_signatures = self._call_signatures_method()
call_signatures = self._call_signatures_callback()
completion_names += get_call_signature_param_names(call_signatures)
return completion_names
@@ -289,3 +305,22 @@ class Completion:
# TODO we should probably check here for properties
if (name.api_type == 'function') == is_function:
yield name
def _extract_string_while_in_string(leaf, position):
if leaf.type == 'string':
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value)
quote = match.group(1)
if leaf.line == position[0] and position[1] < leaf.column + match.end():
return None, None
if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote):
return None, None
return cut_value_at_position(leaf, position)[match.end():], leaf
leaves = []
while leaf is not None and leaf.line == position[0]:
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
return ''.join(l.get_code() for l in leaves), leaf
leaves.insert(0, leaf)
leaf = leaf.get_previous_leaf()
return None, None

161
jedi/api/file_name.py Normal file
View File

@@ -0,0 +1,161 @@
import os
from jedi._compatibility import FileNotFoundError, force_unicode
from jedi.evaluate.names import AbstractArbitraryName
from jedi.api import classes
from jedi.evaluate.helpers import get_str_or_none
from jedi.parser_utils import get_string_quote
def file_name_completions(evaluator, module_context, start_leaf, string,
like_name, call_signatures_callback, code_lines, position):
# First we want to find out what can actually be changed as a name.
like_name_length = len(os.path.basename(string) + like_name)
addition = _get_string_additions(module_context, start_leaf)
if addition is None:
return
string = addition + string
# Here we use basename again, because if strings are added like
# `'foo' + 'bar`, it should complete to `foobar/`.
must_start_with = os.path.basename(string) + like_name
string = os.path.dirname(string)
sigs = call_signatures_callback()
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
if is_in_os_path_join:
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
if to_be_added is None:
is_in_os_path_join = False
else:
string = to_be_added + string
base_path = os.path.join(evaluator.project._path, string)
try:
listed = os.listdir(base_path)
except FileNotFoundError:
return
for name in listed:
if name.startswith(must_start_with):
path_for_name = os.path.join(base_path, name)
if is_in_os_path_join or not os.path.isdir(path_for_name):
if start_leaf.type == 'string':
quote = get_string_quote(start_leaf)
else:
assert start_leaf.type == 'error_leaf'
quote = start_leaf.value
potential_other_quote = \
code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
# Add a quote if it's not already there.
if quote != potential_other_quote:
name += quote
else:
name += os.path.sep
yield classes.Completion(
evaluator,
FileName(evaluator, name[len(must_start_with) - like_name_length:]),
stack=None,
like_name_length=like_name_length
)
def _get_string_additions(module_context, start_leaf):
def iterate_nodes():
node = addition.parent
was_addition = True
for child_node in reversed(node.children[:node.children.index(addition)]):
if was_addition:
was_addition = False
yield child_node
continue
if child_node != '+':
break
was_addition = True
addition = start_leaf.get_previous_leaf()
if addition != '+':
return ''
context = module_context.create_context(start_leaf)
return _add_strings(context, reversed(list(iterate_nodes())))
def _add_strings(context, nodes, add_slash=False):
string = ''
first = True
for child_node in nodes:
contexts = context.eval_node(child_node)
if len(contexts) != 1:
return None
c, = contexts
s = get_str_or_none(c)
if s is None:
return None
if not first and add_slash:
string += os.path.sep
string += force_unicode(s)
first = False
return string
class FileName(AbstractArbitraryName):
api_type = u'path'
is_context_name = False
def _add_os_path_join(module_context, start_leaf, bracket_start):
def check(maybe_bracket, nodes):
if maybe_bracket.start_pos != bracket_start:
return None
if not nodes:
return ''
context = module_context.create_context(nodes[0])
return _add_strings(context, nodes, add_slash=True) or ''
if start_leaf.type == 'error_leaf':
# Unfinished string literal, like `join('`
context_node = start_leaf.parent
index = context_node.children.index(start_leaf)
if index > 0:
error_node = context_node.children[index - 1]
if error_node.type == 'error_node' and len(error_node.children) >= 2:
index = -2
if error_node.children[-1].type == 'arglist':
arglist_nodes = error_node.children[-1].children
index -= 1
else:
arglist_nodes = []
return check(error_node.children[index + 1], arglist_nodes[::2])
return None
# Maybe an arglist or some weird error case. Therefore checked below.
searched_node_child = start_leaf
while searched_node_child.parent is not None \
and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
searched_node_child = searched_node_child.parent
if searched_node_child.get_first_leaf() is not start_leaf:
return None
searched_node = searched_node_child.parent
if searched_node is None:
return None
index = searched_node.children.index(searched_node_child)
arglist_nodes = searched_node.children[:index]
if searched_node.type == 'arglist':
trailer = searched_node.parent
if trailer.type == 'error_node':
trailer_index = trailer.children.index(searched_node)
assert trailer_index >= 2
assert trailer.children[trailer_index - 1] == '('
return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
elif trailer.type == 'trailer':
return check(trailer.children[0], arglist_nodes[::2])
elif searched_node.type == 'trailer':
return check(searched_node.children[0], [])
elif searched_node.type == 'error_node':
# Stuff like `join(""`
return check(arglist_nodes[-1], [])

View File

@@ -54,8 +54,7 @@ class OnErrorLeaf(Exception):
return self.args[0]
def _get_code_for_stack(code_lines, module_node, position):
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
def _get_code_for_stack(code_lines, leaf, position):
# It might happen that we're on whitespace or on a comment. This means
# that we would not get the right leaf.
if leaf.start_pos >= position:
@@ -95,7 +94,7 @@ def _get_code_for_stack(code_lines, module_node, position):
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
def get_stack_at_position(grammar, code_lines, module_node, pos):
def get_stack_at_position(grammar, code_lines, leaf, pos):
"""
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
"""
@@ -119,7 +118,7 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
yield token
# The code might be indedented, just remove it.
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
code = dedent(_get_code_for_stack(code_lines, leaf, pos))
# We use a word to tell Jedi when we have reached the start of the
# completion.
# Use Z as a prefix because it's not part of a number suffix.

View File

@@ -1,7 +1,7 @@
import pydoc
from jedi.evaluate.utils import ignored
from jedi.evaluate.names import AbstractNameDefinition
from jedi.evaluate.names import AbstractArbitraryName
try:
from pydoc_data import topics as pydoc_topics
@@ -19,14 +19,8 @@ def get_operator(evaluator, string, pos):
return Keyword(evaluator, string, pos)
class KeywordName(AbstractNameDefinition):
class KeywordName(AbstractArbitraryName):
api_type = u'keyword'
is_context_name = False
def __init__(self, evaluator, name):
self.evaluator = evaluator
self.string_name = name
self.parent_context = evaluator.builtins_module
def infer(self):
return [Keyword(self.evaluator, self.string_name, (0, 0))]

View File

@@ -62,8 +62,6 @@ I need to mention now that lazy evaluation is really good because it
only *evaluates* what needs to be *evaluated*. All the statements and modules
that are not used are just being ignored.
"""
from functools import partial
from parso.python import tree
import parso
from parso import python_bytes_to_unicode
@@ -84,14 +82,7 @@ from jedi.evaluate.context import ClassContext, FunctionContext, \
from jedi.evaluate.context.iterable import CompForContext
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
eval_node, check_tuple_assignments
def _execute(context, arguments):
debug.dbg('execute: %s %s', context, arguments)
with debug.increase_indent_cm():
context_set = context.py__call__(arguments=arguments)
debug.dbg('execute result: %s in %s', context_set, context)
return context_set
from jedi.plugins import plugin_manager
class Evaluator(object):
@@ -115,28 +106,26 @@ class Evaluator(object):
self.is_analysis = False
self.project = project
self.access_cache = {}
self.allow_descriptor_getattr = False
self.reset_recursion_limitations()
self.allow_different_encoding = True
# Plugin API
from jedi.plugins import plugin_manager
plugin_callbacks = plugin_manager.get_callbacks(self)
self.execute = plugin_callbacks.decorate('execute', callback=_execute)
self._import_module = partial(
plugin_callbacks.decorate(
'import_module',
callback=imports.import_module
),
self,
)
def import_module(self, import_names, parent_module_context=None,
sys_path=None, prefer_stubs=True):
if sys_path is None:
sys_path = self.get_sys_path()
return self._import_module(import_names, parent_module_context,
sys_path, prefer_stubs=prefer_stubs)
return imports.import_module(self, import_names, parent_module_context,
sys_path, prefer_stubs=prefer_stubs)
@staticmethod
@plugin_manager.decorate()
def execute(context, arguments):
debug.dbg('execute: %s %s', context, arguments)
with debug.increase_indent_cm():
context_set = context.py__call__(arguments=arguments)
debug.dbg('execute result: %s in %s', context_set, context)
return context_set
@property
@evaluator_function_cache()

View File

@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
from jedi.evaluate.names import ParamName, TreeNameDefinition
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
from jedi.evaluate.context import iterable
from jedi.evaluate.cache import evaluator_as_method_param_cache
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
@@ -35,7 +36,7 @@ class ParamIssue(Exception):
pass
def repack_with_argument_clinic(string, keep_arguments_param=False):
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False):
"""
Transforms a function or method with arguments to the signature that is
given as an argument clinic notation.
@@ -54,6 +55,8 @@ def repack_with_argument_clinic(string, keep_arguments_param=False):
arguments = kwargs['arguments']
else:
arguments = kwargs.pop('arguments')
if not keep_arguments_param:
kwargs.pop('callback', None)
try:
args += tuple(_iterate_argument_clinic(
context.evaluator,
@@ -208,6 +211,11 @@ class TreeArguments(AbstractArguments):
self._evaluator = evaluator
self.trailer = trailer # Can be None, e.g. in a class definition.
@classmethod
@evaluator_as_method_param_cache()
def create_cached(cls, *args, **kwargs):
return cls(*args, **kwargs)
def unpack(self, funcdef=None):
named_args = []
for star_count, el in unpack_arglist(self.argument_node):

View File

@@ -1,7 +1,7 @@
from jedi._compatibility import unicode
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
CompiledObjectFilter, CompiledContextName, create_from_access_path
from jedi.evaluate.base_context import ContextWrapper
from jedi.evaluate.base_context import ContextWrapper, LazyContextWrapper
def builtin_from_name(evaluator, string):
@@ -16,9 +16,9 @@ def builtin_from_name(evaluator, string):
return context
class CompiledValue(ContextWrapper):
def __init__(self, instance, compiled_obj):
super(CompiledValue, self).__init__(instance)
class CompiledValue(LazyContextWrapper):
def __init__(self, compiled_obj):
self.evaluator = compiled_obj.evaluator
self._compiled_obj = compiled_obj
def __getattribute__(self, name):
@@ -27,6 +27,11 @@ class CompiledValue(ContextWrapper):
return getattr(self._compiled_obj, name)
return super(CompiledValue, self).__getattribute__(name)
def _get_wrapped_context(self):
instance, = builtin_from_name(
self.evaluator, self._compiled_obj.name.string_name).execute_evaluated()
return instance
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
@@ -41,8 +46,7 @@ def create_simple_object(evaluator, obj):
evaluator,
evaluator.compiled_subprocess.create_simple_object(obj)
)
instance, = builtin_from_name(evaluator, compiled_obj.name.string_name).execute_evaluated()
return CompiledValue(instance, compiled_obj)
return CompiledValue(compiled_obj)
def get_string_context_set(evaluator):

View File

@@ -84,9 +84,14 @@ def safe_getattr(obj, name, default=_sentinel):
raise
return default
else:
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
# Since it's an isinstance call, code execution is still possible,
# but this is not really a security feature, but much more of a
# safety feature. Code execution is basically always possible when
# a module is imported. This is here so people don't shoot
# themselves in the foot.
return getattr(obj, name)
return attr
@@ -175,6 +180,18 @@ def _force_unicode_decorator(func):
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
def get_api_type(obj):
if inspect.isclass(obj):
return u'class'
elif inspect.ismodule(obj):
return u'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function'
# Everything else...
return u'instance'
class DirectObjectAccess(object):
def __init__(self, evaluator, obj):
self._evaluator = evaluator
@@ -328,12 +345,16 @@ class DirectObjectAccess(object):
def getattr_paths(self, name, default=_sentinel):
try:
return_obj = getattr(self._obj, name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
except Exception as e:
if default is _sentinel:
raise
if isinstance(e, AttributeError):
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
raise
# Just in case anything happens, return an AttributeError. It
# should not crash.
raise AttributeError
return_obj = default
access = self._create_access(return_obj)
if inspect.ismodule(return_obj):
@@ -352,16 +373,7 @@ class DirectObjectAccess(object):
raise ValueError("Object is type %s and not simple" % type(self._obj))
def get_api_type(self):
obj = self._obj
if self.is_class():
return u'class'
elif inspect.ismodule(obj):
return u'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function'
# Everything else...
return u'instance'
return get_api_type(self._obj)
def get_access_path_tuples(self):
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]

View File

@@ -264,6 +264,9 @@ class CompiledObject(Context):
def negate(self):
return create_from_access_path(self.evaluator, self.access_handle.negate())
def get_metaclasses(self):
return NO_CONTEXTS
class CompiledName(AbstractNameDefinition):
def __init__(self, evaluator, parent_context, name):
@@ -317,9 +320,6 @@ class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
def get_kind(self):
return getattr(Parameter, self._signature_param.kind_name)
def is_keyword_param(self):
return self._signature_param
def infer(self):
p = self._signature_param
evaluator = self.parent_context.evaluator
@@ -377,14 +377,14 @@ class CompiledObjectFilter(AbstractFilter):
def __init__(self, evaluator, compiled_object, is_instance=False):
self._evaluator = evaluator
self._compiled_object = compiled_object
self.compiled_object = compiled_object
self.is_instance = is_instance
def get(self, name):
return self._get(
name,
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
lambda: self._compiled_object.access_handle.dir(),
lambda: self.compiled_object.access_handle.is_allowed_getattr(name),
lambda: self.compiled_object.access_handle.dir(),
check_has_attribute=True
)
@@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
# Always use unicode objects in Python 2 from here.
name = force_unicode(name)
if is_descriptor or not has_attribute:
if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute:
return [self._get_cached_name(name, is_empty=True)]
if self.is_instance and name not in dir_callback():
@@ -416,7 +416,7 @@ class CompiledObjectFilter(AbstractFilter):
def values(self):
from jedi.evaluate.compiled import builtin_from_name
names = []
needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos()
needs_type_completions, dir_infos = self.compiled_object.access_handle.get_dir_infos()
for name in dir_infos:
names += self._get(
name,
@@ -431,10 +431,10 @@ class CompiledObjectFilter(AbstractFilter):
return names
def _create_name(self, name):
return self.name_class(self._evaluator, self._compiled_object, name)
return self.name_class(self._evaluator, self.compiled_object, name)
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._compiled_object)
return "<%s: %s>" % (self.__class__.__name__, self.compiled_object)
docstr_defaults = {

View File

@@ -18,7 +18,7 @@ from jedi.evaluate.context import ModuleContext
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.compiled.getattr_static import getattr_static
from jedi.evaluate.compiled.access import compiled_objects_cache, \
ALLOWED_GETITEM_TYPES
ALLOWED_GETITEM_TYPES, get_api_type
from jedi.evaluate.compiled.context import create_cached_compiled_object
from jedi.evaluate.gradual.conversion import to_stub
@@ -50,6 +50,11 @@ class MixedObject(ContextWrapper):
def get_filters(self, *args, **kwargs):
yield MixedObjectFilter(self.evaluator, self)
def get_signatures(self):
# Prefer `inspect.signature` over somehow analyzing Python code. It
# should be very precise, especially for stuff like `partial`.
return self.compiled_object.get_signatures()
def py__call__(self, arguments):
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
@@ -151,6 +156,7 @@ def _get_object_to_check(python_object):
def _find_syntax_node_name(evaluator, python_object):
original_object = python_object
try:
python_object = _get_object_to_check(python_object)
path = inspect.getsourcefile(python_object)
@@ -214,7 +220,13 @@ def _find_syntax_node_name(evaluator, python_object):
# completions at some points but will lead to mostly correct type
# inference, because people tend to define a public name in a module only
# once.
return module_node, names[-1].parent, file_io, code_lines
tree_node = names[-1].parent
if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
# If an instance is given and we're landing on a function (e.g.
# partial in 3.5), something is completely wrong and we should not
# return that.
return None
return module_node, tree_node, file_io, code_lines
@compiled_objects_cache('mixed_cache')
@@ -254,7 +266,11 @@ def _create(evaluator, access_handle, parent_context, *args):
if name is not None:
evaluator.module_cache.add(string_names, ContextSet([module_context]))
else:
assert parent_context.tree_node.get_root_node() == module_node
if parent_context.tree_node.get_root_node() != module_node:
# This happens e.g. when __module__ is wrong, or when using
# TypeVar('foo'), where Jedi uses 'foo' as the name and
# Python's TypeVar('foo').__module__ will be typing.
return ContextSet({compiled_object})
module_context = parent_context.get_root_context()
tree_contexts = ContextSet({

View File

@@ -0,0 +1,15 @@
'''
Decorators are not really contexts, however we need some wrappers to improve
docstrings and other things around decorators.
'''
from jedi.evaluate.base_context import ContextWrapper
class Decoratee(ContextWrapper):
def __init__(self, wrapped_context, original_context):
self._wrapped_context = wrapped_context
self._original_context = original_context
def py__doc__(self):
return self._original_context.py__doc__()

View File

@@ -100,6 +100,9 @@ class FunctionMixin(object):
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
def get_signatures(self):
return [TreeSignature(f) for f in self.get_signature_functions()]
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)):
"""
@@ -147,8 +150,8 @@ class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndC
def get_default_param_context(self):
return self.parent_context
def get_signatures(self):
return [TreeSignature(self)]
def get_signature_functions(self):
return [self]
class MethodContext(FunctionContext):
@@ -371,14 +374,14 @@ class FunctionExecutionContext(TreeContext):
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
def __init__(self, function, overloaded_functions):
super(OverloadedFunctionContext, self).__init__(function)
self.overloaded_functions = overloaded_functions
self._overloaded_functions = overloaded_functions
def py__call__(self, arguments):
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
function_executions = []
context_set = NO_CONTEXTS
matched = False
for f in self.overloaded_functions:
for f in self._overloaded_functions:
function_execution = f.get_function_execution(arguments)
function_executions.append(function_execution)
if function_execution.matches_signature():
@@ -393,39 +396,8 @@ class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
return NO_CONTEXTS
return ContextSet.from_sets(fe.infer() for fe in function_executions)
def get_signatures(self):
return [TreeSignature(f) for f in self.overloaded_functions]
def signature_matches(function_context, arguments):
unpacked_arguments = arguments.unpack()
key_args = {}
for param_node in function_context.tree_node.get_params():
while True:
key, argument = next(unpacked_arguments, (None, None))
if key is None or argument is None:
break
key_args[key] = argument
if argument is None:
argument = key_args.pop(param_node.name.value, None)
if argument is None:
# This signature has an parameter more than arguments were given.
return bool(param_node.star_count == 1)
if param_node.annotation is not None:
if param_node.star_count == 2:
return False # TODO allow this
annotation_contexts = function_context.evaluator.eval_element(
function_context.get_default_param_context(),
param_node.annotation
)
argument_contexts = argument.infer().py__class__()
if not any(c1.is_sub_class_of(c2)
for c1 in argument_contexts
for c2 in annotation_contexts):
return False
return True
def get_signature_functions(self):
return self._overloaded_functions
def _find_overload_functions(context, tree_node):

View File

@@ -3,6 +3,7 @@ from abc import abstractproperty
from jedi import debug
from jedi import settings
from jedi.evaluate import compiled
from jedi.evaluate.compiled.context import CompiledObjectFilter
from jedi.evaluate.helpers import contexts_from_qualified_names
from jedi.evaluate.filters import AbstractFilter
from jedi.evaluate.names import ContextName, TreeNameDefinition
@@ -12,7 +13,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.arguments import AnonymousArguments, \
ValuesArguments, TreeArgumentsWrapper
from jedi.evaluate.context.function import FunctionExecutionContext, \
from jedi.evaluate.context.function import \
FunctionContext, FunctionMixin, OverloadedFunctionContext
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
ClassFilter
@@ -136,11 +137,19 @@ class AbstractInstanceContext(Context):
# compiled objects to search for self variables.
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
for cls in class_context.py__mro__():
if isinstance(cls, compiled.CompiledObject):
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
class_filters = class_context.get_filters(
search_global=False,
origin_scope=origin_scope,
is_instance=True,
)
for f in class_filters:
if isinstance(f, ClassFilter):
yield InstanceClassFilter(self.evaluator, self, f)
elif isinstance(f, CompiledObjectFilter):
yield CompiledInstanceClassFilter(self.evaluator, self, f)
else:
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
# Propably from the metaclass.
yield f
def py__getitem__(self, index_context_set, contextualized_node):
names = self.get_function_slot_names(u'__getitem__')
@@ -223,8 +232,8 @@ class AbstractInstanceContext(Context):
return class_context
def get_signatures(self):
init_funcs = self.py__getattribute__('__call__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_context)
return [s.bind(self) for s in call_funcs.get_signatures()]
def __repr__(self):
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
@@ -328,7 +337,6 @@ class CompiledInstanceName(compiled.CompiledName):
name.string_name
)
self._instance = instance
self._class = klass
self._class_member_name = name
@iterator_to_context_set
@@ -343,11 +351,10 @@ class CompiledInstanceName(compiled.CompiledName):
class CompiledInstanceClassFilter(AbstractFilter):
name_class = CompiledInstanceName
def __init__(self, evaluator, instance, klass):
def __init__(self, evaluator, instance, f):
self._evaluator = evaluator
self._instance = instance
self._class = klass
self._class_filter = next(klass.get_filters(is_instance=True))
self._class_filter = f
def get(self, name):
return self._convert(self._class_filter.get(name))
@@ -356,8 +363,9 @@ class CompiledInstanceClassFilter(AbstractFilter):
return self._convert(self._class_filter.values())
def _convert(self, names):
klass = self._class_filter.compiled_object
return [
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
CompiledInstanceName(self._evaluator, self._instance, klass, n)
for n in names
]
@@ -382,15 +390,6 @@ class BoundMethod(FunctionMixin, ContextWrapper):
def get_function_execution(self, arguments=None):
arguments = self._get_arguments(arguments)
if isinstance(self._wrapped_context, compiled.CompiledObject):
# This is kind of weird, because it's coming from a compiled object
# and we're not sure if we want that in the future.
# TODO remove?!
return FunctionExecutionContext(
self.evaluator, self.parent_context, self, arguments
)
return super(BoundMethod, self).get_function_execution(arguments)
def py__call__(self, arguments):
@@ -400,8 +399,14 @@ class BoundMethod(FunctionMixin, ContextWrapper):
function_execution = self.get_function_execution(arguments)
return function_execution.infer()
def get_signature_functions(self):
return [
BoundMethod(self.instance, f)
for f in self._wrapped_context.get_signature_functions()
]
def get_signatures(self):
return [sig.bind(self) for sig in self._wrapped_context.get_signatures()]
return [sig.bind(self) for sig in super(BoundMethod, self).get_signatures()]
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
@@ -454,15 +459,9 @@ class InstanceClassFilter(AbstractFilter):
resulting names in LazyINstanceClassName. The idea is that the class name
filtering can be very flexible and always be reflected in instances.
"""
def __init__(self, evaluator, context, class_context, origin_scope):
self._instance = context
self._class_context = class_context
self._class_filter = next(class_context.get_filters(
search_global=False,
origin_scope=origin_scope,
is_instance=True,
))
assert isinstance(self._class_filter, ClassFilter), self._class_filter
def __init__(self, evaluator, instance, class_filter):
self._instance = instance
self._class_filter = class_filter
def get(self, name):
return self._convert(self._class_filter.get(name, from_instance=True))
@@ -471,10 +470,10 @@ class InstanceClassFilter(AbstractFilter):
return self._convert(self._class_filter.values(from_instance=True))
def _convert(self, names):
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
return [LazyInstanceClassName(self._instance, self._class_filter.context, n) for n in names]
def __repr__(self):
return '<%s for %s>' % (self.__class__.__name__, self._class_context)
return '<%s for %s>' % (self.__class__.__name__, self._class_filter.context)
class SelfAttributeFilter(ClassFilter):

View File

@@ -20,6 +20,8 @@ It is important to note that:
1. Array modfications work only in the current module.
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
"""
import sys
from jedi import debug
from jedi import settings
from jedi._compatibility import force_unicode, is_py3
@@ -35,8 +37,8 @@ from jedi.evaluate.utils import safe_property, to_list
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
publish_method
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, \
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin
from jedi.evaluate.base_context import ContextSet, Context, NO_CONTEXTS, \
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin, _sentinel
from jedi.parser_utils import get_sync_comp_fors
@@ -44,6 +46,21 @@ class IterableMixin(object):
def py__stop_iteration_returns(self):
return ContextSet([compiled.builtin_from_name(self.evaluator, u'None')])
# At the moment, safe values are simple values like "foo", 1 and not
# lists/dicts. Therefore as a small speed optimization we can just do the
# default instead of resolving the lazy wrapped contexts, that are just
# doing this in the end as well.
# This mostly speeds up patterns like `sys.version_info >= (3, 0)` in
# typeshed.
if sys.version_info[0] == 2:
# Python 2...........
def get_safe_value(self, default=_sentinel):
if default is _sentinel:
raise ValueError("There exists no safe value for context %s" % self)
return default
else:
get_safe_value = Context.get_safe_value
class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
array_type = None

View File

@@ -49,6 +49,7 @@ from jedi.evaluate.arguments import unpack_arglist, ValuesArguments
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
NO_CONTEXTS
from jedi.evaluate.context.function import FunctionAndClassBase
from jedi.plugins import plugin_manager
def apply_py__get__(context, instance, class_context):
@@ -136,8 +137,10 @@ class ClassMixin(object):
def is_class(self):
return True
def py__call__(self, arguments):
def py__call__(self, arguments=None):
from jedi.evaluate.context import TreeInstance
if arguments is None:
arguments = ValuesArguments([])
return ContextSet([TreeInstance(self.evaluator, self.parent_context, self, arguments)])
def py__class__(self):
@@ -191,13 +194,13 @@ class ClassMixin(object):
def get_filters(self, search_global=False, until_position=None,
origin_scope=None, is_instance=False):
metaclasses = self.get_metaclasses()
if metaclasses:
for f in self.get_metaclass_filters(metaclasses):
yield f
if search_global:
yield ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
)
yield self.get_global_filter(until_position, origin_scope)
else:
for cls in self.py__mro__():
if isinstance(cls, compiled.CompiledObject):
@@ -214,13 +217,25 @@ class ClassMixin(object):
type_ = builtin_from_name(self.evaluator, u'type')
assert isinstance(type_, ClassContext)
if type_ != self:
for instance in type_.py__call__(ValuesArguments([])):
for instance in type_.py__call__():
instance_filters = instance.get_filters()
# Filter out self filters
next(instance_filters)
next(instance_filters)
yield next(instance_filters)
def get_signatures(self):
init_funcs = self.py__call__().py__getattribute__('__init__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
def get_global_filter(self, until_position=None, origin_scope=None):
return ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
)
class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
"""
@@ -247,12 +262,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
found.append(type_var)
return found
@evaluator_method_cache(default=())
def py__bases__(self):
def _get_bases_arguments(self):
arglist = self.tree_node.get_super_arglist()
if arglist:
from jedi.evaluate import arguments
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
return arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
return None
@evaluator_method_cache(default=())
def py__bases__(self):
args = self._get_bases_arguments()
if args is not None:
lst = [value for key, value in args.unpack() if key is None]
if lst:
return lst
@@ -300,6 +320,25 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
)])
return ContextSet({self})
def get_signatures(self):
init_funcs = self.py__getattribute__('__init__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
@plugin_manager.decorate()
def get_metaclass_filters(self, metaclass):
debug.dbg('Unprocessed metaclass %s', metaclass)
return []
@evaluator_method_cache(default=NO_CONTEXTS)
def get_metaclasses(self):
args = self._get_bases_arguments()
if args is not None:
m = [value for key, value in args.unpack() if key == 'metaclass']
metaclasses = ContextSet.from_sets(lazy_context.infer() for lazy_context in m)
metaclasses = ContextSet(m for m in metaclasses if m.is_class())
if metaclasses:
return metaclasses
for lazy_base in self.py__bases__():
for context in lazy_base.infer():
if context.is_class():
contexts = context.get_metaclasses()
if contexts:
return contexts
return NO_CONTEXTS

View File

@@ -9,6 +9,8 @@ from jedi.evaluate import compiled
from jedi.evaluate.base_context import TreeContext
from jedi.evaluate.names import SubModuleName
from jedi.evaluate.helpers import contexts_from_qualified_names
from jedi.evaluate.compiled import create_simple_object
from jedi.evaluate.base_context import ContextSet
class _ModuleAttributeName(AbstractNameDefinition):
@@ -17,11 +19,20 @@ class _ModuleAttributeName(AbstractNameDefinition):
"""
api_type = u'instance'
def __init__(self, parent_module, string_name):
def __init__(self, parent_module, string_name, string_value=None):
self.parent_context = parent_module
self.string_name = string_name
self._string_value = string_value
def infer(self):
if self._string_value is not None:
s = self._string_value
if self.parent_context.evaluator.environment.version_info.major == 2 \
and not isinstance(s, bytes):
s = s.encode('utf-8')
return ContextSet([
create_simple_object(self.parent_context.evaluator, s)
])
return compiled.get_string_context_set(self.parent_context.evaluator)
@@ -132,9 +143,13 @@ class ModuleMixin(SubModuleDictMixin):
@evaluator_method_cache()
def _module_attributes_dict(self):
names = ['__file__', '__package__', '__doc__', '__name__']
names = ['__package__', '__doc__', '__name__']
# All the additional module attributes are strings.
return dict((n, _ModuleAttributeName(self, n)) for n in names)
dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
file = self.py__file__()
if file is not None:
dct['__file__'] = _ModuleAttributeName(self, '__file__', file)
return dct
def iter_star_filters(self, search_global=False):
for star_module in self.star_imports():

View File

@@ -51,11 +51,8 @@ def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
raise _numpy_doc_string_cache
try:
from numpydoc.docscrape import NumpyDocString
_numpy_doc_string_cache = NumpyDocString
except (ImportError, SyntaxError) as e:
raise
from numpydoc.docscrape import NumpyDocString
_numpy_doc_string_cache = NumpyDocString
return _numpy_doc_string_cache

View File

@@ -17,7 +17,6 @@ It works as follows:
- execute these calls and check the input.
"""
from parso.python import tree
from jedi import settings
from jedi import debug
from jedi.evaluate.cache import evaluator_function_cache

View File

@@ -386,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
>>> list(filters[1].values()) # package modules -> Also empty.
[]
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
['__doc__', '__file__', '__name__', '__package__']
['__doc__', '__name__', '__package__']
Finally, it yields the builtin filter, if `include_builtin` is
true (default).
@@ -409,5 +409,4 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
context = context.parent_context
# Add builtins to the global scope.
for filter in evaluator.builtins_module.get_filters():
yield filter
yield next(evaluator.builtins_module.get_filters())

View File

@@ -14,7 +14,9 @@ from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
AbstractAnnotatedClass
from jedi.evaluate.gradual.typing import GenericClass
from jedi.evaluate.helpers import is_string
from jedi.evaluate.compiled import builtin_from_name
from jedi import debug
from jedi import parser_utils
@@ -106,6 +108,25 @@ def _split_comment_param_declaration(decl_text):
@evaluator_method_cache()
def infer_param(execution_context, param):
contexts = _infer_param(execution_context, param)
evaluator = execution_context.evaluator
if param.star_count == 1:
tuple_ = builtin_from_name(evaluator, 'tuple')
return ContextSet([GenericClass(
tuple_,
generics=(contexts,),
) for c in contexts])
elif param.star_count == 2:
dct = builtin_from_name(evaluator, 'dict')
return ContextSet([GenericClass(
dct,
generics=(ContextSet([builtin_from_name(evaluator, 'str')]), contexts),
) for c in contexts])
pass
return contexts
def _infer_param(execution_context, param):
"""
Infers the type of a function parameter, using type annotations.
"""

View File

@@ -40,7 +40,8 @@ def _stub_to_python_context_set(stub_context, ignore_compiled=False):
def _infer_from_stub(stub_module, qualified_names, ignore_compiled):
assert isinstance(stub_module, StubModuleContext), stub_module
from jedi.evaluate.compiled.mixed import MixedObject
assert isinstance(stub_module, (StubModuleContext, MixedObject)), stub_module
non_stubs = stub_module.non_stub_context_set
if ignore_compiled:
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())

View File

@@ -1,5 +1,6 @@
import os
import re
from functools import wraps
from jedi.file_io import FileIO
from jedi._compatibility import FileNotFoundError, cast_path
@@ -87,6 +88,7 @@ def _cache_stub_file_map(version_info):
def import_module_decorator(func):
@wraps(func)
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
try:
python_context_set = evaluator.module_cache.get(import_names)

View File

@@ -10,7 +10,7 @@ from jedi import debug
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.compiled import builtin_from_name
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
iterator_to_context_set, HelperContextMixin, ContextWrapper
iterator_to_context_set, ContextWrapper, LazyContextWrapper
from jedi.evaluate.lazy_context import LazyKnownContexts
from jedi.evaluate.context.iterable import SequenceLiteralContext
from jedi.evaluate.arguments import repack_with_argument_clinic
@@ -209,6 +209,9 @@ class _TypingClassMixin(object):
self.evaluator.builtins_module.py__getattribute__('object')
)]
def get_metaclasses(self):
return []
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
pass
@@ -241,9 +244,9 @@ def _iter_over_arguments(maybe_tuple_context, defining_context):
yield ContextSet(resolve_forward_references(context_set))
class TypeAlias(HelperContextMixin):
def __init__(self, evaluator, parent_context, origin_tree_name, actual):
self.evaluator = evaluator
class TypeAlias(LazyContextWrapper):
def __init__(self, parent_context, origin_tree_name, actual):
self.evaluator = parent_context.evaluator
self.parent_context = parent_context
self._origin_tree_name = origin_tree_name
self._actual = actual # e.g. builtins.list
@@ -255,14 +258,10 @@ class TypeAlias(HelperContextMixin):
def py__name__(self):
return self.name.string_name
def __getattr__(self, name):
return getattr(self._get_type_alias_class(), name)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._actual)
@evaluator_method_cache()
def _get_type_alias_class(self):
def _get_wrapped_context(self):
module_name, class_name = self._actual.split('.')
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
module_name = '__builtin__'

View File

@@ -21,7 +21,7 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
force_unicode, unicode)
from jedi import debug
from jedi import settings
from jedi.file_io import KnownContentFileIO, FolderIO, FileIO
from jedi.file_io import KnownContentFileIO, FileIO
from jedi.parser_utils import get_cached_code_lines
from jedi.evaluate import sys_path
from jedi.evaluate import helpers
@@ -33,6 +33,7 @@ from jedi.evaluate.names import ImportName, SubModuleName
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.evaluate.gradual.typeshed import import_module_decorator
from jedi.evaluate.context.module import iter_module_names
from jedi.plugins import plugin_manager
class ModuleCache(object):
@@ -371,6 +372,7 @@ class Importer(object):
return names
@plugin_manager.decorate()
@import_module_decorator
def import_module(evaluator, import_names, parent_module_context, sys_path):
"""

View File

@@ -3,7 +3,7 @@ from abc import abstractmethod
from parso.tree import search_ancestor
from jedi._compatibility import Parameter
from jedi.evaluate.base_context import ContextSet
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.cache import memoize_method
@@ -58,6 +58,23 @@ class AbstractNameDefinition(object):
return self.parent_context.api_type
class AbstractArbitraryName(AbstractNameDefinition):
"""
When you e.g. want to complete dicts keys, you probably want to complete
string literals, which is not really a name, but for Jedi we use this
concept of Name for completions as well.
"""
is_context_name = False
def __init__(self, evaluator, string):
self.evaluator = evaluator
self.string_name = string
self.parent_context = evaluator.builtins_module
def infer(self):
return NO_CONTEXTS
class AbstractTreeName(AbstractNameDefinition):
def __init__(self, parent_context, tree_name):
self.parent_context = parent_context
@@ -150,8 +167,18 @@ class TreeNameDefinition(AbstractTreeName):
return self._API_TYPES.get(definition.type, 'statement')
class ParamNameInterface(object):
api_type = u'param'
class _ParamMixin(object):
def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star:
options.append(Parameter.VAR_POSITIONAL)
return self.get_kind() in options
def maybe_keyword_argument(self, include_stars=True):
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_stars:
options.append(Parameter.VAR_KEYWORD)
return self.get_kind() in options
def _kind_string(self):
kind = self.get_kind()
@@ -161,21 +188,74 @@ class ParamNameInterface(object):
return '**'
return ''
class ParamNameInterface(_ParamMixin):
api_type = u'param'
def get_kind(self):
raise NotImplementedError
def to_string(self):
raise NotImplementedError
def get_param(self):
# TODO document better where this is used and when. Currently it has
# very limited use, but is still in use. It's currently not even
# clear what values would be allowed.
return None
class ParamName(ParamNameInterface, AbstractTreeName):
def __init__(self, parent_context, tree_name):
self.parent_context = parent_context
self.tree_name = tree_name
@property
def star_count(self):
kind = self.get_kind()
if kind == Parameter.VAR_POSITIONAL:
return 1
if kind == Parameter.VAR_KEYWORD:
return 2
return 0
class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
annotation_node = None
default_node = None
def to_string(self):
output = self._kind_string() + self.string_name
annotation = self.annotation_node
default = self.default_node
if annotation is not None:
output += ': ' + annotation.get_code(include_prefix=False)
if default is not None:
output += '=' + default.get_code(include_prefix=False)
return output
class ParamName(BaseTreeParamName):
def _get_param_node(self):
return search_ancestor(self.tree_name, 'param')
@property
def annotation_node(self):
return self._get_param_node().annotation
def infer_annotation(self, execute_annotation=True):
node = self.annotation_node
if node is None:
return NO_CONTEXTS
contexts = self.parent_context.parent_context.eval_node(node)
if execute_annotation:
contexts = contexts.execute_annotation()
return contexts
def infer_default(self):
node = self.default_node
if node is None:
return NO_CONTEXTS
return self.parent_context.parent_context.eval_node(node)
@property
def default_node(self):
return self._get_param_node().default
@property
def string_name(self):
name = self.tree_name.value
@@ -213,15 +293,6 @@ class ParamName(ParamNameInterface, AbstractTreeName):
param_appeared = True
return Parameter.POSITIONAL_OR_KEYWORD
def to_string(self):
output = self._kind_string() + self.string_name
param_node = self._get_param_node()
if param_node.annotation is not None:
output += ': ' + param_node.annotation.get_code(include_prefix=False)
if param_node.default is not None:
output += '=' + param_node.default.get_code(include_prefix=False)
return output
def infer(self):
return self.get_param().infer()
@@ -231,6 +302,17 @@ class ParamName(ParamNameInterface, AbstractTreeName):
return params[param_node.position_index]
class ParamNameWrapper(_ParamMixin):
def __init__(self, param_name):
self._wrapped_param_name = param_name
def __getattr__(self, name):
return getattr(self._wrapped_param_name, name)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
class ImportName(AbstractNameDefinition):
start_pos = (1, 0)
_level = 0

View File

@@ -1,24 +1,13 @@
from jedi._compatibility import Parameter
from jedi.cache import memoize_method
class AbstractSignature(object):
def __init__(self, context, is_bound=False):
self.context = context
self.is_bound = is_bound
@property
def name(self):
return self.context.name
@property
def annotation_string(self):
return ''
class _SignatureMixin(object):
def to_string(self):
def param_strings():
is_positional = False
is_kw_only = False
for n in self.get_param_names():
for n in self.get_param_names(resolve_stars=True):
kind = n.get_kind()
is_positional |= kind == Parameter.POSITIONAL_ONLY
if is_positional and kind != Parameter.POSITIONAL_ONLY:
@@ -42,15 +31,32 @@ class AbstractSignature(object):
s += ' -> ' + annotation
return s
def bind(self, context):
raise NotImplementedError
def get_param_names(self):
class AbstractSignature(_SignatureMixin):
def __init__(self, context, is_bound=False):
self.context = context
self.is_bound = is_bound
@property
def name(self):
return self.context.name
@property
def annotation_string(self):
return ''
def get_param_names(self, resolve_stars=False):
param_names = self._function_context.get_param_names()
if self.is_bound:
return param_names[1:]
return param_names
def bind(self, context):
raise NotImplementedError
def __repr__(self):
return '<%s: %s, %s>' % (self.__class__.__name__, self.context, self._function_context)
class TreeSignature(AbstractSignature):
def __init__(self, context, function_context=None, is_bound=False):
@@ -75,6 +81,14 @@ class TreeSignature(AbstractSignature):
return ''
return a.get_code(include_prefix=False)
@memoize_method
def get_param_names(self, resolve_stars=False):
params = super(TreeSignature, self).get_param_names(resolve_stars=False)
if resolve_stars:
from jedi.evaluate.star_args import process_params
params = process_params(params)
return params
class BuiltinSignature(AbstractSignature):
def __init__(self, context, return_string, is_bound=False):
@@ -92,3 +106,11 @@ class BuiltinSignature(AbstractSignature):
def bind(self, context):
assert not self.is_bound
return BuiltinSignature(context, self._return_string, is_bound=True)
class SignatureWrapper(_SignatureMixin):
def __init__(self, wrapped_signature):
self._wrapped_signature = wrapped_signature
def __getattr__(self, name):
return getattr(self._wrapped_signature, name)

206
jedi/evaluate/star_args.py Normal file
View File

@@ -0,0 +1,206 @@
"""
This module is responsible for evaluating *args and **kwargs for signatures.
This means for example in this case::
def foo(a, b, c): ...
def bar(*args):
return foo(1, *args)
The signature here for bar should be `bar(b, c)` instead of bar(*args).
"""
from jedi._compatibility import Parameter
from jedi.evaluate.utils import to_list
from jedi.evaluate.names import ParamNameWrapper
def _iter_nodes_for_param(param_name):
from parso.python.tree import search_ancestor
from jedi.evaluate.arguments import TreeArguments
execution_context = param_name.parent_context
function_node = execution_context.tree_node
module_node = function_node.get_root_node()
start = function_node.children[-1].start_pos
end = function_node.children[-1].end_pos
for name in module_node.get_used_names().get(param_name.string_name):
if start <= name.start_pos < end:
# Is used in the function
argument = name.parent
if argument.type == 'argument' \
and argument.children[0] == '*' * param_name.star_count:
# No support for Python <= 3.4 here, but they are end-of-life
# anyway
trailer = search_ancestor(argument, 'trailer')
if trailer is not None: # Make sure we're in a function
context = execution_context.create_context(trailer)
if _goes_to_param_name(param_name, context, name):
contexts = _to_callables(context, trailer)
args = TreeArguments.create_cached(
execution_context.evaluator,
context=context,
argument_node=trailer.children[1],
trailer=trailer,
)
for c in contexts:
yield c, args
else:
assert False
def _goes_to_param_name(param_name, context, potential_name):
if potential_name.type != 'name':
return False
from jedi.evaluate.names import TreeNameDefinition
found = TreeNameDefinition(context, potential_name).goto()
return any(param_name.parent_context == p.parent_context
and param_name.start_pos == p.start_pos
for p in found)
def _to_callables(context, trailer):
from jedi.evaluate.syntax_tree import eval_trailer
atom_expr = trailer.parent
index = atom_expr.children[0] == 'await'
# Eval atom first
contexts = context.eval_node(atom_expr.children[index])
for trailer2 in atom_expr.children[index + 1:]:
if trailer == trailer2:
break
contexts = eval_trailer(context, contexts, trailer2)
return contexts
def _remove_given_params(arguments, param_names):
count = 0
used_keys = set()
for key, _ in arguments.unpack():
if key is None:
count += 1
else:
used_keys.add(key)
for p in param_names:
if count and p.maybe_positional_argument():
count -= 1
continue
if p.string_name in used_keys and p.maybe_keyword_argument():
continue
yield p
@to_list
def process_params(param_names, star_count=3): # default means both * and **
used_names = set()
arg_callables = []
kwarg_callables = []
kw_only_names = []
kwarg_names = []
arg_names = []
original_arg_name = None
original_kwarg_name = None
for p in param_names:
kind = p.get_kind()
if kind == Parameter.VAR_POSITIONAL:
if star_count & 1:
arg_callables = _iter_nodes_for_param(p)
original_arg_name = p
elif p.get_kind() == Parameter.VAR_KEYWORD:
if star_count & 2:
kwarg_callables = list(_iter_nodes_for_param(p))
original_kwarg_name = p
elif kind == Parameter.KEYWORD_ONLY:
if star_count & 2:
kw_only_names.append(p)
elif kind == Parameter.POSITIONAL_ONLY:
if star_count & 1:
yield p
else:
if star_count == 1:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
elif star_count == 2:
kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
else:
used_names.add(p.string_name)
yield p
longest_param_names = ()
found_arg_signature = False
found_kwarg_signature = False
for func_and_argument in arg_callables:
func, arguments = func_and_argument
new_star_count = star_count
if func_and_argument in kwarg_callables:
kwarg_callables.remove(func_and_argument)
else:
new_star_count = 1
for signature in func.get_signatures():
found_arg_signature = True
if new_star_count == 3:
found_kwarg_signature = True
args_for_this_func = []
for p in process_params(
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), new_star_count):
if p.get_kind() == Parameter.VAR_KEYWORD:
kwarg_names.append(p)
elif p.get_kind() == Parameter.VAR_POSITIONAL:
arg_names.append(p)
elif p.get_kind() == Parameter.KEYWORD_ONLY:
kw_only_names.append(p)
else:
args_for_this_func.append(p)
if len(args_for_this_func) > len(longest_param_names):
longest_param_names = args_for_this_func
for p in longest_param_names:
if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
else:
if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
used_names.add(p.string_name)
yield p
if not found_arg_signature and original_arg_name is not None:
yield original_arg_name
elif arg_names:
yield arg_names[0]
for p in kw_only_names:
if p.string_name in used_names:
continue
yield p
used_names.add(p.string_name)
for func, arguments in kwarg_callables:
for signature in func.get_signatures():
found_kwarg_signature = True
for p in process_params(
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), star_count=2):
if p.get_kind() != Parameter.KEYWORD_ONLY or not kwarg_names:
yield p
if not found_kwarg_signature and original_kwarg_name is not None:
yield original_kwarg_name
elif kwarg_names:
yield kwarg_names[0]
class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind):
super(ParamNameFixedKind, self).__init__(param_name)
self._new_kind = new_kind
def get_kind(self):
return self._new_kind

View File

@@ -26,6 +26,8 @@ from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.gradual.stub_context import VersionInfo
from jedi.evaluate.gradual import annotation
from jedi.evaluate.context.decorator import Decoratee
from jedi.plugins import plugin_manager
def _limit_context_infers(func):
@@ -148,28 +150,28 @@ def eval_node(context, element):
return eval_or_test(context, element)
def eval_trailer(context, base_contexts, trailer):
def eval_trailer(context, atom_contexts, trailer):
trailer_op, node = trailer.children[:2]
if node == ')': # `arglist` is optional.
node = None
if trailer_op == '[':
trailer_op, node, _ = trailer.children
return base_contexts.get_item(
return atom_contexts.get_item(
eval_subscript_list(context.evaluator, context, node),
ContextualizedNode(context, trailer)
)
else:
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
debug.dbg('eval_trailer: %s in %s', trailer, atom_contexts)
if trailer_op == '.':
return base_contexts.py__getattribute__(
return atom_contexts.py__getattribute__(
name_context=context,
name_or_str=node
)
else:
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
return base_contexts.execute(args)
return atom_contexts.execute(args)
def eval_atom(context, atom):
@@ -544,6 +546,7 @@ def _remove_statements(evaluator, context, stmt, name):
return eval_expr_stmt(context, stmt, seek_name=name)
@plugin_manager.decorate()
def tree_name_to_contexts(evaluator, context, tree_name):
context_set = NO_CONTEXTS
module_node = context.get_root_context().tree_node
@@ -666,6 +669,8 @@ def _apply_decorators(context, node):
return initial
debug.dbg('decorator end %s', values, color="MAGENTA")
if values != initial:
return ContextSet([Decoratee(c, decoratee_context) for c in values])
return values

View File

@@ -5,6 +5,7 @@ from weakref import WeakKeyDictionary
from parso.python import tree
from parso.cache import parser_cache
from parso import split_lines
from jedi._compatibility import literal_eval, force_unicode
@@ -278,3 +279,19 @@ def get_cached_code_lines(grammar, path):
to do this, but we avoid splitting all the lines again.
"""
return parser_cache[grammar._hashed][path].lines
def cut_value_at_position(leaf, position):
"""
Cuts of the value of the leaf at position
"""
lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
column = position[1]
if leaf.line == position[0]:
column -= leaf.column
lines[-1] = lines[-1][:column]
return ''.join(lines)
def get_string_quote(leaf):
return re.match('\w*("""|\'{3}|"|\')', leaf.value).group(1)

View File

@@ -1,37 +1,47 @@
from jedi.plugins.stdlib import StdlibPlugin
from jedi.plugins.flask import FlaskPlugin
from functools import wraps
class _PluginManager(object):
def __init__(self, registered_plugin_classes=()):
self._registered_plugin_classes = list(registered_plugin_classes)
def __init__(self):
self._registered_plugins = []
self._cached_base_callbacks = {}
self._built_functions = {}
def register(self, plugin_class):
def register(self, *plugins):
"""
Makes it possible to register your plugin.
"""
self._registered_plugins.append(plugin_class)
self._registered_plugins.extend(plugins)
self._build_functions()
def _build_chain(self, evaluator):
for plugin_class in self._registered_plugin_classes:
yield plugin_class(evaluator)
def decorate(self):
def decorator(callback):
@wraps(callback)
def wrapper(*args, **kwargs):
return built_functions[name](*args, **kwargs)
def get_callbacks(self, evaluator):
return _PluginCallbacks(self._build_chain(evaluator))
name = callback.__name__
assert name not in self._built_functions
built_functions = self._built_functions
built_functions[name] = callback
self._cached_base_callbacks[name] = callback
return wrapper
return decorator
def _build_functions(self):
for name, callback in self._cached_base_callbacks.items():
for plugin in reversed(self._registered_plugins):
# Need to reverse so the first plugin is run first.
try:
func = getattr(plugin, name)
except AttributeError:
pass
else:
callback = func(callback)
self._built_functions[name] = callback
class _PluginCallbacks(object):
def __init__(self, plugins):
self._plugins = list(plugins)
def decorate(self, name, callback):
for plugin in reversed(self._plugins):
# Need to reverse so the first plugin is run first.
callback = getattr(plugin, name)(callback)
return callback
plugin_manager = _PluginManager([
StdlibPlugin,
FlaskPlugin,
])
plugin_manager = _PluginManager()

View File

@@ -1,21 +0,0 @@
class BasePlugin(object):
"""
Plugins are created each time an evaluator is created.
"""
def __init__(self, evaluator):
# In __init__ you can do some caching.
self._evaluator = evaluator
def execute(self, callback):
"""
Decorates the execute(context, arguments) function.
"""
return callback
def import_module(self, callback):
"""
Decorates the
import_module(evaluator, import_path, sys_path, add_error_callback)
function.
"""
return callback

View File

@@ -1,25 +1,21 @@
from jedi.plugins.base import BasePlugin
class FlaskPlugin(BasePlugin):
def import_module(self, callback):
"""
Handle "magic" Flask extension imports:
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
"""
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
# New style.
ipath = (u'flask_' + import_names[2]),
context_set = callback(evaluator, ipath, None, *args, **kwargs)
if context_set:
return context_set
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
return callback(
evaluator,
(u'flaskext', import_names[2]),
next(iter(context_set)),
*args, **kwargs
)
return callback(evaluator, import_names, module_context, *args, **kwargs)
return wrapper
def import_module(callback):
"""
Handle "magic" Flask extension imports:
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
"""
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
# New style.
ipath = (u'flask_' + import_names[2]),
context_set = callback(evaluator, ipath, None, *args, **kwargs)
if context_set:
return context_set
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
return callback(
evaluator,
(u'flaskext', import_names[2]),
next(iter(context_set)),
*args, **kwargs
)
return callback(evaluator, import_names, module_context, *args, **kwargs)
return wrapper

10
jedi/plugins/registry.py Normal file
View File

@@ -0,0 +1,10 @@
"""
This is not a plugin, this is just the place were plugins are registered.
"""
from jedi.plugins import stdlib
from jedi.plugins import flask
from jedi.plugins import plugin_manager
plugin_manager.register(stdlib, flask)

View File

@@ -10,10 +10,11 @@ the standard library. The usual way to understand the standard library is the
compiled module that returns the types for C-builtins.
"""
import parso
import os
from jedi._compatibility import force_unicode
from jedi.plugins.base import BasePlugin
from jedi._compatibility import force_unicode, Parameter
from jedi import debug
from jedi.evaluate.utils import safe_property
from jedi.evaluate.helpers import get_str_or_none
from jedi.evaluate.arguments import ValuesArguments, \
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
@@ -24,11 +25,16 @@ from jedi.evaluate.base_context import ContextualizedNode, \
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
from jedi.evaluate.context import ClassContext, ModuleContext, \
FunctionExecutionContext
from jedi.evaluate.context.klass import ClassMixin
from jedi.evaluate.context.function import FunctionMixin
from jedi.evaluate.context import iterable
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
LazyKnownContexts
from jedi.evaluate.names import ContextName, BaseTreeParamName
from jedi.evaluate.syntax_tree import is_string
from jedi.evaluate.filters import AttributeOverwrite, publish_method
from jedi.evaluate.filters import AttributeOverwrite, publish_method, \
ParserTreeFilter, DictFilter
from jedi.evaluate.signature import AbstractSignature, SignatureWrapper
# Copied from Python 3.6's stdlib.
@@ -98,45 +104,48 @@ _NAMEDTUPLE_FIELD_TEMPLATE = '''\
'''
class StdlibPlugin(BasePlugin):
def execute(self, callback):
def wrapper(context, arguments):
try:
obj_name = context.name.string_name
except AttributeError:
pass
else:
if context.parent_context == self._evaluator.builtins_module:
module_name = 'builtins'
elif context.parent_context is not None and context.parent_context.is_module():
module_name = context.parent_context.py__name__()
else:
return callback(context, arguments=arguments)
if isinstance(context, BoundMethod):
if module_name == 'builtins':
if context.py__name__() == '__get__':
if context.class_context.py__name__() == 'property':
return builtins_property(
context,
arguments=arguments
)
elif context.py__name__() in ('deleter', 'getter', 'setter'):
if context.class_context.py__name__() == 'property':
return ContextSet([context.instance])
return callback(context, arguments=arguments)
# for now we just support builtin functions.
try:
func = _implemented[module_name][obj_name]
except KeyError:
pass
else:
return func(context, arguments=arguments)
def execute(callback):
def wrapper(context, arguments):
def call():
return callback(context, arguments=arguments)
return wrapper
try:
obj_name = context.name.string_name
except AttributeError:
pass
else:
if context.parent_context == context.evaluator.builtins_module:
module_name = 'builtins'
elif context.parent_context is not None and context.parent_context.is_module():
module_name = context.parent_context.py__name__()
else:
return call()
if isinstance(context, BoundMethod):
if module_name == 'builtins':
if context.py__name__() == '__get__':
if context.class_context.py__name__() == 'property':
return builtins_property(
context,
arguments=arguments,
callback=call,
)
elif context.py__name__() in ('deleter', 'getter', 'setter'):
if context.class_context.py__name__() == 'property':
return ContextSet([context.instance])
return call()
# for now we just support builtin functions.
try:
func = _implemented[module_name][obj_name]
except KeyError:
pass
else:
return func(context, arguments=arguments, callback=call)
return call()
return wrapper
def _follow_param(evaluator, arguments, index):
@@ -149,15 +158,18 @@ def _follow_param(evaluator, arguments, index):
def argument_clinic(string, want_obj=False, want_context=False,
want_arguments=False, want_evaluator=False):
want_arguments=False, want_evaluator=False,
want_callback=False):
"""
Works like Argument Clinic (PEP 436), to validate function params.
"""
def f(func):
@repack_with_argument_clinic(string, keep_arguments_param=True)
@repack_with_argument_clinic(string, keep_arguments_param=True,
keep_callback_param=True)
def wrapper(obj, *args, **kwargs):
arguments = kwargs.pop('arguments')
callback = kwargs.pop('callback')
assert not kwargs # Python 2...
debug.dbg('builtin start %s' % obj, color='MAGENTA')
result = NO_CONTEXTS
@@ -169,6 +181,8 @@ def argument_clinic(string, want_obj=False, want_context=False,
kwargs['evaluator'] = obj.evaluator
if want_arguments:
kwargs['arguments'] = arguments
if want_callback:
kwargs['callback'] = callback
result = func(*args, **kwargs)
debug.dbg('builtin end: %s', result, color='MAGENTA')
return result
@@ -378,6 +392,9 @@ class ClassMethodGet(AttributeOverwrite, ContextWrapper):
self._class = klass
self._function = function
def get_signatures(self):
return self._function.get_signatures()
def get_object(self):
return self._wrapped_context
@@ -405,7 +422,7 @@ def builtins_classmethod(functions, obj, arguments):
)
def collections_namedtuple(obj, arguments):
def collections_namedtuple(obj, arguments, callback):
"""
Implementation of the namedtuple function.
@@ -428,14 +445,16 @@ def collections_namedtuple(obj, arguments):
if not param_contexts:
return NO_CONTEXTS
_fields = list(param_contexts)[0]
if isinstance(_fields, compiled.CompiledValue):
fields = force_unicode(_fields.get_safe_value()).replace(',', ' ').split()
string = get_str_or_none(_fields)
if string is not None:
fields = force_unicode(string).replace(',', ' ').split()
elif isinstance(_fields, iterable.Sequence):
fields = [
force_unicode(v.get_safe_value())
force_unicode(get_str_or_none(v))
for lazy_context in _fields.py__iter__()
for v in lazy_context.infer() if is_string(v)
for v in lazy_context.infer()
]
fields = [f for f in fields if f is not None]
else:
return NO_CONTEXTS
@@ -471,17 +490,49 @@ class PartialObject(object):
def __getattr__(self, name):
return getattr(self._actual_context, name)
def py__call__(self, arguments):
key, lazy_context = next(self._arguments.unpack(), (None, None))
def _get_function(self, unpacked_arguments):
key, lazy_context = next(unpacked_arguments, (None, None))
if key is not None or lazy_context is None:
debug.warning("Partial should have a proper function %s", self._arguments)
return None
return lazy_context.infer()
def get_signatures(self):
unpacked_arguments = self._arguments.unpack()
func = self._get_function(unpacked_arguments)
if func is None:
return []
arg_count = 0
keys = set()
for key, _ in unpacked_arguments:
if key is None:
arg_count += 1
else:
keys.add(key)
return [PartialSignature(s, arg_count, keys) for s in func.get_signatures()]
def py__call__(self, arguments):
func = self._get_function(self._arguments.unpack())
if func is None:
return NO_CONTEXTS
return lazy_context.infer().execute(
return func.execute(
MergedPartialArguments(self._arguments, arguments)
)
class PartialSignature(SignatureWrapper):
def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set):
super(PartialSignature, self).__init__(wrapped_signature)
self._skipped_arg_count = skipped_arg_count
self._skipped_arg_set = skipped_arg_set
def get_param_names(self, resolve_stars=False):
names = self._wrapped_signature.get_param_names()[self._skipped_arg_count:]
return [n for n in names if n.string_name not in self._skipped_arg_set]
class MergedPartialArguments(AbstractArguments):
def __init__(self, partial_arguments, call_arguments):
self._partial_arguments = partial_arguments
@@ -498,7 +549,7 @@ class MergedPartialArguments(AbstractArguments):
yield key_lazy_context
def functools_partial(obj, arguments):
def functools_partial(obj, arguments, callback):
return ContextSet(
PartialObject(instance, arguments)
for instance in obj.py__call__(arguments)
@@ -519,6 +570,66 @@ def _random_choice(sequences):
)
def _dataclass(obj, arguments, callback):
for c in _follow_param(obj.evaluator, arguments, 0):
if c.is_class():
return ContextSet([DataclassWrapper(c)])
else:
return ContextSet([obj])
return NO_CONTEXTS
class DataclassWrapper(ContextWrapper, ClassMixin):
def get_signatures(self):
param_names = []
for cls in reversed(list(self.py__mro__())):
if isinstance(cls, DataclassWrapper):
filter_ = cls.get_global_filter()
# .values ordering is not guaranteed, at least not in
# Python < 3.6, when dicts where not ordered, which is an
# implementation detail anyway.
for name in sorted(filter_.values(), key=lambda name: name.start_pos):
d = name.tree_name.get_definition()
annassign = d.children[1]
if d.type == 'expr_stmt' and annassign.type == 'annassign':
if len(annassign.children) < 4:
default = None
else:
default = annassign.children[3]
param_names.append(DataclassParamName(
parent_context=cls.parent_context,
tree_name=name.tree_name,
annotation_node=annassign.children[1],
default_node=default,
))
return [DataclassSignature(cls, param_names)]
class DataclassSignature(AbstractSignature):
def __init__(self, context, param_names):
super(DataclassSignature, self).__init__(context)
self._param_names = param_names
def get_param_names(self, resolve_stars=False):
return self._param_names
class DataclassParamName(BaseTreeParamName):
def __init__(self, parent_context, tree_name, annotation_node, default_node):
super(DataclassParamName, self).__init__(parent_context, tree_name)
self.annotation_node = annotation_node
self.default_node = default_node
def get_kind(self):
return Parameter.POSITIONAL_OR_KEYWORD
def infer(self):
if self.annotation_node is None:
return NO_CONTEXTS
else:
return self.parent_context.eval_node(self.annotation_node)
class ItemGetterCallable(ContextWrapper):
def __init__(self, instance, args_context_set):
super(ItemGetterCallable, self).__init__(instance)
@@ -544,6 +655,33 @@ class ItemGetterCallable(ContextWrapper):
return context_set
@argument_clinic('func, /')
def _functools_wraps(funcs):
return ContextSet(WrapsCallable(func) for func in funcs)
class WrapsCallable(ContextWrapper):
# XXX this is not the correct wrapped context, it should be a weird
# partials object, but it doesn't matter, because it's always used as a
# decorator anyway.
@repack_with_argument_clinic('func, /')
def py__call__(self, funcs):
return ContextSet({Wrapped(func, self._wrapped_context) for func in funcs})
class Wrapped(ContextWrapper, FunctionMixin):
def __init__(self, func, original_function):
super(Wrapped, self).__init__(func)
self._original_function = original_function
@property
def name(self):
return self._original_function.name
def get_signature_functions(self):
return [self]
@argument_clinic('*args, /', want_obj=True, want_arguments=True)
def _operator_itemgetter(args_context_set, obj, arguments):
return ContextSet([
@@ -552,6 +690,44 @@ def _operator_itemgetter(args_context_set, obj, arguments):
])
def _create_string_input_function(func):
@argument_clinic('string, /', want_obj=True, want_arguments=True)
def wrapper(strings, obj, arguments):
def iterate():
for context in strings:
s = get_str_or_none(context)
if s is not None:
s = func(s)
yield compiled.create_simple_object(context.evaluator, s)
contexts = ContextSet(iterate())
if contexts:
return contexts
return obj.py__call__(arguments)
return wrapper
@argument_clinic('*args, /', want_callback=True)
def _os_path_join(args_set, callback):
if len(args_set) == 1:
string = u''
sequence, = args_set
is_first = True
for lazy_context in sequence.py__iter__():
string_contexts = lazy_context.infer()
if len(string_contexts) != 1:
break
s = get_str_or_none(next(iter(string_contexts)))
if s is None:
break
if not is_first:
string += os.path.sep
string += force_unicode(s)
is_first = False
else:
return ContextSet([compiled.create_simple_object(sequence.evaluator, string)])
return callback()
_implemented = {
'builtins': {
'getattr': builtins_getattr,
@@ -569,15 +745,15 @@ _implemented = {
'deepcopy': _return_first_param,
},
'json': {
'load': lambda obj, arguments: NO_CONTEXTS,
'loads': lambda obj, arguments: NO_CONTEXTS,
'load': lambda obj, arguments, callback: NO_CONTEXTS,
'loads': lambda obj, arguments, callback: NO_CONTEXTS,
},
'collections': {
'namedtuple': collections_namedtuple,
},
'functools': {
'partial': functools_partial,
'wraps': _return_first_param,
'wraps': _functools_wraps,
},
'_weakref': {
'proxy': _return_first_param,
@@ -597,10 +773,63 @@ _implemented = {
# The _alias function just leads to some annoying type inference.
# Therefore, just make it return nothing, which leads to the stubs
# being used instead. This only matters for 3.7+.
'_alias': lambda obj, arguments: NO_CONTEXTS,
'_alias': lambda obj, arguments, callback: NO_CONTEXTS,
},
'dataclasses': {
# For now this works at least better than Jedi trying to understand it.
'dataclass': lambda obj, arguments: NO_CONTEXTS,
'dataclass': _dataclass
},
'os.path': {
'dirname': _create_string_input_function(os.path.dirname),
'abspath': _create_string_input_function(os.path.abspath),
'relpath': _create_string_input_function(os.path.relpath),
'join': _os_path_join,
}
}
def get_metaclass_filters(func):
def wrapper(cls, metaclasses):
for metaclass in metaclasses:
if metaclass.py__name__() == 'EnumMeta' \
and metaclass.get_root_context().py__name__() == 'enum':
filter_ = ParserTreeFilter(cls.evaluator, context=cls)
return [DictFilter({
name.string_name: EnumInstance(cls, name).name for name in filter_.values()
})]
return func(cls, metaclasses)
return wrapper
class EnumInstance(LazyContextWrapper):
def __init__(self, cls, name):
self.evaluator = cls.evaluator
self._cls = cls # Corresponds to super().__self__
self._name = name
self.tree_node = self._name.tree_name
@safe_property
def name(self):
return ContextName(self, self._name.tree_name)
def _get_wrapped_context(self):
obj, = self._cls.execute_evaluated()
return obj
def get_filters(self, search_global=False, position=None, origin_scope=None):
yield DictFilter(dict(
name=compiled.create_simple_object(self.evaluator, self._name.string_name).name,
value=self._name,
))
for f in self._get_wrapped_context().get_filters():
yield f
def tree_name_to_contexts(func):
def wrapper(evaluator, context, tree_name):
if tree_name.value == 'sep' and context.is_module() and context.py__name__() == 'os.path':
return ContextSet({
compiled.create_simple_object(evaluator, os.path.sep),
})
return func(evaluator, context, tree_name)
return wrapper

View File

@@ -11,10 +11,7 @@ import re
import os
import sys
from parso import split_lines
from jedi import Interpreter
from jedi.api.helpers import get_on_completion_name
READLINE_DEBUG = False
@@ -86,23 +83,18 @@ def setup_readline(namespace_module=__main__):
logging.debug("Start REPL completion: " + repr(text))
interpreter = Interpreter(text, [namespace_module.__dict__])
lines = split_lines(text)
position = (len(lines), len(lines[-1]))
name = get_on_completion_name(
interpreter._module_node,
lines,
position
)
before = text[:len(text) - len(name)]
completions = interpreter.completions()
logging.debug("REPL completions: %s", completions)
self.matches = [
text[:len(text) - c._like_name_length] + c.name_with_symbols
for c in completions
]
except:
logging.error("REPL Completion error:\n" + traceback.format_exc())
raise
finally:
sys.path.pop(0)
self.matches = [before + c.name_with_symbols for c in completions]
try:
return self.matches[state]
except IndexError:

View File

@@ -1,4 +1,4 @@
import recurse_class2
from . import recurse_class2
class C(recurse_class2.C):
def a(self):

View File

@@ -1,4 +1,4 @@
import recurse_class1
from . import recurse_class1
class C(recurse_class1.C):
pass

View File

@@ -165,3 +165,19 @@ def keyword_only(a: str, *, b: str):
a.startswi
#? ['startswith']
b.startswi
def argskwargs(*args: int, **kwargs: float):
"""
This might be a bit confusing, but is part of the standard.
args is changed to Tuple[int] in this case and kwargs to Dict[str, float],
which makes sense if you think about it a bit.
"""
#? tuple()
args
#? int()
args[0]
#? str()
next(iter(kwargs.keys()))
#? float()
kwargs['']

View File

@@ -67,6 +67,18 @@ class X(): pass
#? type
type(X)
# -----------------
# type() calls with multiple parameters
# -----------------
X = type('X', (object,), dict(a=1))
# Doesn't work yet.
#?
X.a
#?
X
if os.path.isfile():
#? ['abspath']
fails = os.path.abspath
@@ -290,3 +302,31 @@ class Test(metaclass=Meta):
result = super(Test, self).test_function()
#? []
result.
# -----------------
# Enum
# -----------------
# python >= 3.4
import enum
class X(enum.Enum):
attr_x = 3
attr_y = 2.0
#? ['mro']
X.mro
#? ['attr_x', 'attr_y']
X.attr_
#? str()
X.attr_x.name
#? int()
X.attr_x.value
#? str()
X.attr_y.name
#? float()
X.attr_y.value
#? str()
X().name
#? float()
X().attr_x.attr_y.value

View File

@@ -315,7 +315,8 @@ def test_signature_is_definition(Script):
# Now compare all the attributes that a CallSignature must also have.
for attr_name in dir(definition):
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer', 'params']
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer',
'params', 'get_signatures', 'execute']
if attr_name.startswith('_') or attr_name in dont_scan:
continue

View File

@@ -442,3 +442,20 @@ def test_builtin_module_with_path(Script):
assert semlock.name == 'SemLock'
assert semlock.line is None
assert semlock.column is None
@pytest.mark.parametrize(
'code, description', [
('int', 'instance int'),
('str.index', 'instance int'),
('1', None),
]
)
def test_execute(Script, code, description):
definition, = Script(code).goto_assignments()
definitions = definition.execute()
if description is None:
assert not definitions
else:
d, = definitions
assert d.description == description

View File

@@ -1,8 +1,9 @@
import os
from os.path import join, sep as s
import sys
from textwrap import dedent
import pytest
from ..helpers import root_dir
def test_in_whitespace(Script):
@@ -69,8 +70,8 @@ def test_points_in_completion(Script):
def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpdir):
dirname = str(tmpdir.mkdir('jedi-test'))
filename1 = os.path.join(dirname, 'test1.py')
filename2 = os.path.join(dirname, 'test2.py')
filename1 = join(dirname, 'test1.py')
filename2 = join(dirname, 'test2.py')
if sys.version_info < (3, 0):
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
else:
@@ -156,3 +157,111 @@ def test_with_stmt_error_recovery(Script):
)
def test_keyword_completion(Script, code, has_keywords):
assert has_keywords == any(x.is_keyword for x in Script(code).completions())
f1 = join(root_dir, 'example.py')
f2 = join(root_dir, 'test', 'example.py')
os_path = 'from os.path import *\n'
# os.path.sep escaped
se = s * 2 if s == '\\' else s
@pytest.mark.parametrize(
'file, code, column, expected', [
# General tests / relative paths
(None, '"comp', None, ['ile', 'lex']), # No files like comp
(None, '"test', None, [s]),
(None, '"test', 4, ['t' + s]),
('example.py', '"test%scomp' % s, None, ['letion' + s]),
('example.py', 'r"comp"', None, "A LOT"),
('example.py', 'r"tes"', None, "A LOT"),
('example.py', 'r"tes"', 5, ['t' + s]),
('example.py', 'r" tes"', 6, []),
('test%sexample.py' % se, 'r"tes"', 5, ['t' + s]),
('test%sexample.py' % se, 'r"test%scomp"' % s, 5, ['t' + s]),
('test%sexample.py' % se, 'r"test%scomp"' % s, 11, ['letion' + s]),
('test%sexample.py' % se, '"%s"' % join('test', 'completion', 'basi'), 21, ['c.py']),
('example.py', 'rb"' + join('..', 'jedi', 'tes'), None, ['t' + s]),
# Absolute paths
(None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py"']),
(None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']),
# Longer quotes
('example.py', 'r"""test', None, [s]),
('example.py', 'r"""\ntest', None, []),
('example.py', 'u"""tes\n', (1, 7), ['t' + s]),
('example.py', '"""test%stest_cache.p"""' % s, 20, ['y']),
('example.py', '"""test%stest_cache.p"""' % s, 19, ['py"""']),
# Adding
('example.py', '"test" + "%stest_cac' % se, None, ['he.py"']),
('example.py', '"test" + "%s" + "test_cac' % se, None, ['he.py"']),
('example.py', 'x = 1 + "test', None, []),
('example.py', 'x = f("te" + "st)', 16, [s]),
('example.py', 'x = f("te" + "st', 16, [s]),
('example.py', 'x = f("te" + "st"', 16, [s]),
('example.py', 'x = f("te" + "st")', 16, [s]),
('example.py', 'x = f("t" + "est")', 16, [s]),
# This is actually not correct, but for now leave it here, because of
# Python 2.
('example.py', 'x = f(b"t" + "est")', 17, [s]),
('example.py', '"test" + "', None, [s]),
# __file__
(f1, os_path + 'dirname(__file__) + "%stest' % s, None, [s]),
(f2, os_path + 'dirname(__file__) + "%stest_ca' % se, None, ['che.py"']),
(f2, os_path + 'dirname(abspath(__file__)) + sep + "test_ca', None, ['che.py"']),
(f2, os_path + 'join(dirname(__file__), "completion") + sep + "basi', None, ['c.py"']),
(f2, os_path + 'join("test", "completion") + sep + "basi', None, ['c.py"']),
# inside join
(f2, os_path + 'join(dirname(__file__), "completion", "basi', None, ['c.py"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 43, ['c.py"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 43, ['c.py']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 35, ['']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 33, ['on"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 33, ['on"']),
# join with one argument. join will not get evaluated and the result is
# that directories and in a slash. This is unfortunate, but doesn't
# really matter.
(f2, os_path + 'join("tes', 9, ['t"']),
(f2, os_path + 'join(\'tes)', 9, ["t'"]),
(f2, os_path + 'join(r"tes"', 10, ['t']),
(f2, os_path + 'join("""tes""")', 11, ['t']),
# Almost like join but not really
(f2, os_path + 'join["tes', 9, ['t' + s]),
(f2, os_path + 'join["tes"', 9, ['t' + s]),
(f2, os_path + 'join["tes"]', 9, ['t' + s]),
(f2, os_path + 'join[dirname(__file__), "completi', 33, []),
(f2, os_path + 'join[dirname(__file__), "completi"', 33, []),
(f2, os_path + 'join[dirname(__file__), "completi"]', 33, []),
# With full paths
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi', 49, ['on"']),
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi"', 49, ['on']),
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi")', 49, ['on']),
# With alias
(f2, 'import os.path as p as p\np.join(p.dirname(__file__), "completi', None, ['on"']),
(f2, 'from os.path import dirname, join as j\nj(dirname(__file__), "completi',
None, ['on"']),
# Trying to break it
(f2, os_path + 'join(["tes', 10, ['t' + s]),
(f2, os_path + 'join(["tes"]', 10, ['t' + s]),
(f2, os_path + 'join(["tes"])', 10, ['t' + s]),
(f2, os_path + 'join("test", "test_cac" + x,', 22, ['he.py']),
]
)
def test_file_path_completions(Script, file, code, column, expected):
line = None
if isinstance(column, tuple):
line, column = column
comps = Script(code, path=file, line=line, column=column).completions()
if expected == "A LOT":
assert len(comps) > 100 # This is basically global completions.
else:
assert [c.complete for c in comps] == expected

View File

@@ -7,8 +7,8 @@ import pytest
import jedi
from jedi._compatibility import is_py3, py_version
from jedi.evaluate.compiled import mixed
from jedi.evaluate.compiled import mixed, context
from importlib import import_module
if py_version > 30:
def exec_(source, global_map):
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
def test_property_error_oldstyle():
@pytest.fixture(params=[False, True])
def allow_descriptor_access_or_not(request, monkeypatch):
monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param)
return request.param
def test_property_error_oldstyle(allow_descriptor_access_or_not):
lst = []
class Foo3:
@property
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])
# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []
def test_property_error_newstyle():
def test_property_error_newstyle(allow_descriptor_access_or_not):
lst = []
class Foo3(object):
@property
@@ -225,10 +234,25 @@ def test_property_error_newstyle():
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])
# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []
def test_property_content():
class Foo3(object):
@property
def bar(self):
return 1
foo = Foo3()
def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions()
assert def_.name == 'int'
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_param_completion():
def foo(bar):
pass
@@ -275,6 +299,7 @@ def test_completion_param_annotations():
assert d.name == 'bytes'
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_keyword_argument():
def f(some_keyword_argument):
pass
@@ -439,3 +464,44 @@ def test__wrapped__():
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
# Apparently the function starts on the line where the decorator starts.
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
@pytest.mark.parametrize('module_name', ['sys', 'time'])
def test_core_module_completes(module_name):
module = import_module(module_name)
assert jedi.Interpreter(module_name + '.\n', [locals()]).completions()
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
@pytest.mark.parametrize(
'code, expected, index', [
('a(', ['a', 'b', 'c'], 0),
('b(', ['b', 'c'], 0),
# Might or might not be correct, because c is given as a keyword
# argument as well, but that is just what inspect.signature returns.
('c(', ['b', 'c'], 0),
]
)
def test_partial_signatures(code, expected, index):
import functools
def func(a, b, c):
pass
a = functools.partial(func)
b = functools.partial(func, 1)
c = functools.partial(func, 1, c=2)
sig, = jedi.Interpreter(code, [locals()]).call_signatures()
assert sig.name == 'partial'
assert [p.name for p in sig.params] == expected
assert index == sig.index
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_type_var():
"""This was an issue before, see Github #1369"""
import typing
x = typing.TypeVar('myvar')
def_, = jedi.Interpreter('x', [locals()]).goto_definitions()
assert def_.name == 'TypeVar'

View File

@@ -1,6 +1,7 @@
import os
from ..helpers import get_example_dir
from ..helpers import get_example_dir, set_cwd, root_dir
from jedi import Interpreter
def test_django_default_project(Script):
@@ -13,3 +14,11 @@ def test_django_default_project(Script):
c, = script.completions()
assert c.name == "SomeModel"
assert script._evaluator.project._django is True
def test_interpreter_project_path():
# Run from anywhere it should be the cwd.
dir = os.path.join(root_dir, 'test')
with set_cwd(dir):
project = Interpreter('', [locals()])._evaluator.project
assert project._path == dir

View File

@@ -0,0 +1,74 @@
import sys
import pytest
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
@pytest.mark.parametrize(
'code, expected_params, execute_annotation', [
('def f(x: 1, y): ...\nf', [None, None], True),
('def f(x: 1, y): ...\nf', ['instance int', None], False),
('def f(x: int): ...\nf', ['instance int'], True),
('from typing import List\ndef f(x: List[int]): ...\nf', ['instance list'], True),
('from typing import List\ndef f(x: List[int]): ...\nf', ['class list'], False),
(_tuple_code, ['Tuple: _SpecialForm = ...'], True),
(_tuple_code, ['Tuple: _SpecialForm = ...'], False),
('x=str\ndef f(p: x): ...\nx=int\nf', ['instance int'], True),
('def f(*args, **kwargs): ...\nf', [None, None], False),
('def f(*args: int, **kwargs: str): ...\nf', ['class int', 'class str'], False),
]
)
def test_param_annotation(Script, code, expected_params, execute_annotation, skip_python2):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
for p, expected in zip(sig.params, expected_params):
annotations = p.infer_annotation(execute_annotation=execute_annotation)
if expected is None:
assert not annotations
else:
annotation, = annotations
assert annotation.description == expected
@pytest.mark.parametrize(
'code, expected_params', [
('def f(x=1, y=int, z): pass\nf', ['instance int', 'class int', None]),
('def f(*args, **kwargs): pass\nf', [None, None]),
('x=1\ndef f(p=x): pass\nx=""\nf', ['instance int']),
]
)
def test_param_default(Script, code, expected_params):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
for p, expected in zip(sig.params, expected_params):
annotations = p.infer_default()
if expected is None:
assert not annotations
else:
annotation, = annotations
assert annotation.description == expected
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Python <3.5 doesn't support __signature__")
@pytest.mark.parametrize(
'code, index, param_code, kind', [
('def f(x=1): pass\nf', 0, 'x=1', 'POSITIONAL_OR_KEYWORD'),
('def f(*args:int): pass\nf', 0, '*args: int', 'VAR_POSITIONAL'),
('def f(**kwargs: List[x]): pass\nf', 0, '**kwargs: List[x]', 'VAR_KEYWORD'),
('def f(*, x:int=5): pass\nf', 0, 'x: int=5', 'KEYWORD_ONLY'),
('def f(*args, x): pass\nf', 1, 'x', 'KEYWORD_ONLY'),
]
)
def test_param_kind_and_name(code, index, param_code, kind, Script, skip_python2):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
param = sig.params[index]
assert param.to_string() == param_code
assert param.kind.name == kind
def test_staticmethod(Script):
s, = Script('staticmethod(').call_signatures()
assert s.to_string() == 'staticmethod(f: Callable)'

View File

@@ -1,3 +1,6 @@
from jedi._compatibility import force_unicode
def test_module_attributes(Script):
def_, = Script('__name__').completions()
assert def_.name == '__name__'
@@ -5,3 +8,14 @@ def test_module_attributes(Script):
assert def_.column is None
str_, = def_.infer()
assert str_.name == 'str'
def test_module__file__(Script, environment):
assert not Script('__file__').goto_definitions()
def_, = Script('__file__', path='example.py').goto_definitions()
value = force_unicode(def_._name._context.get_safe_value())
assert value.endswith('example.py')
def_, = Script('import antigravity; antigravity.__file__').goto_definitions()
value = force_unicode(def_._name._context.get_safe_value())
assert value.endswith('.py')

View File

@@ -6,6 +6,7 @@ from textwrap import dedent
import jedi
import pytest
from ..helpers import unittest
import sys
try:
import numpydoc # NOQA
@@ -21,6 +22,11 @@ except ImportError:
else:
numpy_unavailable = False
if sys.version_info.major == 2:
# In Python 2 there's an issue with tox/docutils that makes the tests fail,
# Python 2 is soon end-of-life, so just don't support numpydoc for it anymore.
numpydoc_unavailable = True
def test_function_doc(Script):
defs = Script("""
@@ -385,3 +391,26 @@ def test_numpy_comp_returns():
)
names = [c.name for c in jedi.Script(s).completions()]
assert 'diagonal' in names
def test_decorator(Script):
code = dedent('''
def decorator(name=None):
def _decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""wrapper docstring"""
return func(*args, **kwargs)
return wrapper
return _decorate
@decorator('testing')
def check_user(f):
"""Nice docstring"""
pass
check_user''')
d, = Script(code).goto_definitions()
assert d.docstring(raw=True) == 'Nice docstring'

View File

@@ -472,6 +472,6 @@ def test_relative_import_star(Script):
from . import *
furl.c
"""
script = jedi.Script(source,3,len("furl.c"), 'export.py')
script = jedi.Script(source, 3, len("furl.c"), 'export.py')
assert script.completions()

View File

@@ -1,5 +1,8 @@
import pytest
from textwrap import dedent
from operator import ge, lt
import re
import pytest
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
@@ -34,6 +37,31 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
assert [n.string_name for n in signature.get_param_names()] == names
classmethod_code = '''
class X:
@classmethod
def x(cls, a, b):
pass
@staticmethod
def static(a, b):
pass
'''
partial_code = '''
import functools
def func(a, b, c):
pass
a = functools.partial(func)
b = functools.partial(func, 1)
c = functools.partial(func, 1, c=2)
d = functools.partial()
'''
@pytest.mark.parametrize(
'code, expected', [
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
@@ -41,6 +69,16 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
('def f(*, x= 3,**kwargs): pass\n f(', 'f(*, x=3, **kwargs)'),
('def f(x,/,y,* ,z): pass\n f(', 'f(x, /, y, *, z)'),
('def f(a, /, *, x=3, **kwargs): pass\n f(', 'f(a, /, *, x=3, **kwargs)'),
(classmethod_code + 'X.x(', 'x(cls, a, b)'),
(classmethod_code + 'X().x(', 'x(cls, a, b)'),
(classmethod_code + 'X.static(', 'static(a, b)'),
(classmethod_code + 'X().static(', 'static(a, b)'),
(partial_code + 'a(', 'func(a, b, c)'),
(partial_code + 'b(', 'func(b, c)'),
(partial_code + 'c(', 'func(b)'),
(partial_code + 'd(', None),
]
)
def test_tree_signature(Script, environment, code, expected):
@@ -48,15 +86,183 @@ def test_tree_signature(Script, environment, code, expected):
if environment.version_info < (3, 8):
pytest.skip()
if expected is None:
assert not Script(code).call_signatures()
else:
sig, = Script(code).call_signatures()
assert expected == sig.to_string()
@pytest.mark.parametrize(
'combination, expected', [
# Functions
('full_redirect(simple)', 'b, *, c'),
('full_redirect(simple4)', 'b, x: int'),
('full_redirect(a)', 'b, *args'),
('full_redirect(kw)', 'b, *, c, **kwargs'),
('full_redirect(akw)', 'c, *args, **kwargs'),
# Non functions
('full_redirect(lambda x, y: ...)', 'y'),
('full_redirect()', '*args, **kwargs'),
('full_redirect(1)', '*args, **kwargs'),
# Classes / inheritance
('full_redirect(C)', 'z, *, c'),
('full_redirect(C())', 'y'),
('D', 'D(a, z, /)'),
('D()', 'D(x, y)'),
('D().foo', 'foo(a, *, bar, z, **kwargs)'),
# Merging
('two_redirects(simple, simple)', 'a, b, *, c'),
('two_redirects(simple2, simple2)', 'x'),
('two_redirects(akw, kw)', 'a, c, *args, **kwargs'),
('two_redirects(kw, akw)', 'a, b, *args, c, **kwargs'),
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),
('combined_redirect(simple3, simple)', 'a, x: int, /, *, a, b, c'),
('combined_redirect(simple, kw)', 'a, b, /, *, a, b, c, **kwargs'),
('combined_redirect(kw, simple)', 'a, b, /, *, a, b, c'),
('combined_lot_of_args(kw, simple4)', '*, b'),
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
'x, /, *, y'),
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
'a, b, x: int, /, *, a, b, c, **kwargs'),
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
'a, b, /, *args, y'),
('no_redirect(kw)', '*args, **kwargs'),
('no_redirect(akw)', '*args, **kwargs'),
('no_redirect(simple)', '*args, **kwargs'),
]
)
def test_nested_signatures(Script, environment, combination, expected, skip_pre_python35):
code = dedent('''
def simple(a, b, *, c): ...
def simple2(x): ...
def simple3(a, x: int): ...
def simple4(a, b, x: int): ...
def simple5(y): ...
def a(a, b, *args): ...
def kw(a, b, *, c, **kwargs): ...
def akw(a, c, *args, **kwargs): ...
def no_redirect(func):
return lambda *args, **kwargs: func(1)
def full_redirect(func):
return lambda *args, **kwargs: func(1, *args, **kwargs)
def two_redirects(func1, func2):
return lambda *args, **kwargs: func1(*args, **kwargs) + func2(1, *args, **kwargs)
def combined_redirect(func1, func2):
return lambda *args, **kwargs: func1(*args) + func2(**kwargs)
def combined_lot_of_args(func1, func2):
return lambda *args, **kwargs: func1(1, 2, 3, 4, *args) + func2(a=3, x=1, y=1, **kwargs)
class C:
def __init__(self, a, z, *, c): ...
def __call__(self, x, y): ...
def foo(self, bar, z, **kwargs): ...
class D(C):
def __init__(self, *args):
super().__init__(*args)
def foo(self, a, **kwargs):
super().foo(**kwargs)
''')
code += 'z = ' + combination + '\nz('
sig, = Script(code).call_signatures()
assert expected == sig._signature.to_string()
computed = sig.to_string()
if not re.match(r'\w+\(', expected):
expected = '<lambda>(' + expected + ')'
assert expected == computed
def test_pow_signature(Script):
# See github #1357
sigs = Script('pow(').call_signatures()
strings = {sig._signature.to_string() for sig in sigs}
strings = {sig.to_string() for sig in sigs}
assert strings == {'pow(x: float, y: float, z: float, /) -> float',
'pow(x: float, y: float, /) -> float',
'pow(x: int, y: int, z: int, /) -> Any',
'pow(x: int, y: int, /) -> Any'}
@pytest.mark.parametrize(
'code, signature', [
[dedent('''
import functools
def f(x):
pass
def x(f):
@functools.wraps(f)
def wrapper(*args):
# Have no arguments here, but because of wraps, the signature
# should still be f's.
return f(*args)
return wrapper
x(f)('''), 'f(x, /)'],
[dedent('''
import functools
def f(x):
pass
def x(f):
@functools.wraps(f)
def wrapper():
# Have no arguments here, but because of wraps, the signature
# should still be f's.
return 1
return wrapper
x(f)('''), 'f()'],
]
)
def test_wraps_signature(Script, code, signature, skip_pre_python35):
sigs = Script(code).call_signatures()
assert {sig.to_string() for sig in sigs} == {signature}
@pytest.mark.parametrize(
'start, start_params', [
['@dataclass\nclass X:', []],
['@dataclass(eq=True)\nclass X:', []],
[dedent('''
class Y():
y: int
@dataclass
class X(Y):'''), []],
[dedent('''
@dataclass
class Y():
y: int
z = 5
@dataclass
class X(Y):'''), ['y']],
]
)
def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
code = dedent('''
name: str
foo = 3
price: float
quantity: int = 0.0
X(''')
code = 'from dataclasses import dataclass\n' + start + code
sig, = Script(code).call_signatures()
assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity']
quantity, = sig.params[-1].infer()
assert quantity.name == 'int'
price, = sig.params[-2].infer()
assert price.name == 'float'