Compare commits

..
259 Commits
Author SHA1 Message Date
ef5d361121 Fix typos in comments and docstrings (#2101)
Found by codespell:
- curent -> current (classes.py)
- suppport -> support, propeties -> properties (inference/__init__.py)
- existance -> existence (inference/analysis.py)
- Propably -> Probably (inference/value/instance.py)

Co-authored-by: maxtaran2010 <ocotifuzo727@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 23:42:11 +00:00
Dave Halter 3102215478 Move the type parameter syntax tests so that it works for all versions 2026-05-02 01:24:54 +02:00
Dave Halter 1b37f2eb94 Prepare for the 0.20.0 release 2026-05-02 01:14:51 +02:00
Dave Halter 8e4df5cc0e Make sure the new generic syntax does not fail with latest parso 2026-05-02 01:08:27 +02:00
Dave HalterandGitHub 4c9dbcca03 Merge pull request #2098 from davidhalter/updates
Typeshed upgrade
2026-05-01 22:40:26 +00:00
Dave Halter fedb1a5eb0 Fix 3.10 tests in one more case 2026-05-02 00:22:06 +02:00
Dave Halter 87e782f9c8 Fix flake8 2026-05-02 00:13:19 +02:00
Dave Halter cd52d982e1 Fixes to get the tests passing for 3.10 2026-05-02 00:11:16 +02:00
Dave Halter d0b11806d4 Finally make tests work for 3.14 2026-05-01 23:49:36 +02:00
Dave Halter 8520a9958b Implement support for TypeVar inference for __new__ 2026-05-01 22:52:05 +02:00
Dave Halter 55e5f0cb92 Implement new-style unions with TypeVars 2026-05-01 21:01:27 +02:00
Dave Halter 8ba5b67622 Change some more tests for typeshed 2026-05-01 17:07:36 +02:00
Dave Halter d87a4af50f Implement unions with forward references 2026-05-01 16:40:25 +02:00
Dave Halter 01dc123ea1 Allow unions when inferring annotations 2026-05-01 16:33:24 +02:00
Dave Halter 418598d8c1 Implement type[...] 2026-05-01 15:44:26 +02:00
Dave Halter 0702da22f2 Change some tests 2026-05-01 14:43:59 +02:00
Dave Halter b7652708ec Add more tests about newstyle unions 2026-05-01 14:37:46 +02:00
Dave Halter e7d29065bd Change a test about ellipsis slightly 2026-05-01 14:37:35 +02:00
Dave Halter 1820aa9476 Change some tests slightly 2026-05-01 14:30:00 +02:00
Dave Halter 696df90daf Change some tests slightly 2026-05-01 14:14:12 +02:00
Dave Halter f8fb2d1230 Basic support for TypeAlias, fixes #1969 2026-05-01 13:05:22 +02:00
Dave Halter 590ed56c6e Revert a small change to tests 2026-04-30 00:26:29 +02:00
Dave Halter a1d9da0a7f Make sure tuple behaves similar to Tuple, fixes #2040 2026-04-30 00:15:56 +02:00
Dave Halter 4f7dfd14b3 Small improvements to generator/async completions 2026-04-29 23:38:07 +02:00
Dave Halter be993d132e Attempt to improve tuple unpackings 2026-04-29 23:10:31 +02:00
Dave Halter 0d79865a0f Fix __enter__ Self resolving 2026-04-29 18:15:57 +02:00
Dave Halter 3375d48f8c Improve typing_extensions usages 2026-04-29 18:08:59 +02:00
Dave Halter aa72381ed1 Implement Self, fixes #2023, fixes #2068 2026-04-29 17:51:01 +02:00
Dave Halter c30732eb04 Add a tuple[...] test for the future, see #2040 2026-04-29 17:10:02 +02:00
Dave Halter fe0369436e Change a few tests to match new typeshed 2026-04-29 09:30:49 +02:00
Dave Halter 04e5f5b3b8 Change a few tests for new typeshed 2026-04-28 17:14:42 +02:00
Dave Halter 75f1d064d5 Ignore Final/ClassVar if they don't have a generic assignment 2026-04-28 17:05:43 +02:00
Dave Halter 6473ddc28c Implement Final[...] in a way so it doesn't completely fail 2026-04-28 15:49:26 +02:00
Dave Halter b27a7dde18 Fix file path completions 2026-04-28 15:25:25 +02:00
Dave Halter 3365d0763b Implement __new__ signatures, fixes #2073 2026-04-28 02:25:25 +02:00
Dave Halter ff581e8403 Start fixing some of the problems with new typeshed 2026-04-28 01:41:40 +02:00
Dave Halter 2455414d1d Upgrade Typeshed 2026-04-28 01:38:26 +02:00
Dave Halter 38122a7fd3 Fix issues around whitespace after dot completions, fixes #1954 2026-04-27 18:15:45 +02:00
Dave Halter 40c685c52b Fix star import completions, fixes #2087 2026-04-27 16:10:56 +02:00
Dave Halter 83a232e945 Change the CHANGELOG slightly 2026-04-27 16:09:39 +02:00
Dave HalterandGitHub 5bbbc53f19 Merge pull request #2097 from davidhalter/py314
Python 3.14; fixes #2093, fixes #2070, will fix #2064 after release.
2026-04-27 13:52:06 +00:00
Dave Halter 6e17c85a57 Avoid flake8 issues 2026-04-27 15:29:08 +02:00
Dave Halter 44600ea194 Fix union acess for 3.14 2026-04-27 15:21:26 +02:00
Dave Halter 04d45a8e1e Remove a 3, 9 artifact 2026-04-27 14:42:24 +02:00
Dave Halter f1ab9d9539 Remove some 3.8 artifacts 2026-04-27 14:41:58 +02:00
Dave Halter 7bac12c125 Remove some 3.7/3.6 artifacts 2026-04-27 14:35:10 +02:00
Dave Halter d4233732be Format unions with | consistently 2026-04-27 14:24:23 +02:00
Dave Halter 74fb7ff279 Enable tests for 3.14 in CI, fixes #2070 2026-04-27 14:18:17 +02:00
Dave Halter 375dd1bacc Add 3.14 to the supported environments 2026-04-27 14:01:39 +02:00
Dave Halter 94f7e540ff Merge branch 'py314' of https://github.com/frenzymadness/jedi into py314 2026-04-27 12:22:31 +02:00
Dave HalterandGitHub 4cca2ed774 Merge pull request #2095 from davidhalter/typing
Use Zuban instead of Mypy for subsecond type checking and editor integration
2026-04-27 00:05:43 +00:00
Dave Halter f8c8ef8c66 Upgrade all the sphinx dependencies 2026-04-27 01:48:02 +02:00
Dave Halter 5938262227 Avoid 3.8/3.9 in tests 2026-04-27 01:07:39 +02:00
Dave Halter a662298e2f Remove a bit more python3.8/3.9 specific code 2026-04-27 00:42:07 +02:00
Dave Halter 1eddf24a50 Remove Python 3.9 and 3.10 2026-04-27 00:34:17 +02:00
Dave Halter edb5462cf5 Fix an issue with tests 2026-04-26 03:05:30 +02:00
Dave Halter 30b3acf6d3 Change the flake8 version to hopefully fix the version issue in CI 2026-04-26 02:44:39 +02:00
Dave Halter 56e55e9ed5 Upgrade zuban 2026-04-26 02:27:28 +02:00
Lumir Balhar 04737b2637 Fix Python 3.15 compatibility
- Fix getattr_static for Python 3.15 __dict__ GetSetDescriptorType
- Accept abs() parameter name change ('x' → 'number')
- Add Python 3.15 os module constants to test expectations

Fixes instance attribute introspection and stdlib changes in Python 3.15.
2026-04-08 08:19:15 +02:00
Dave Halter 68be64b992 Use forward references because 3.8 is still a bit annoying 2026-03-26 23:31:24 +01:00
Dave Halter 9e582586fa Merge testing and qa extras_require into dev 2026-03-26 23:22:39 +01:00
Dave Halter 9f91506947 Enable --strict and then disable some of the errors 2026-03-26 23:11:19 +01:00
Dave Halter ffe4ae5877 Replace the mypy check with zuban check 2026-03-26 23:02:08 +01:00
Lumir Balhar 9b24443787 Fix Python 3.14 compatibility for typing.Union annotations
In Python 3.14, typing.Union changed its repr from 'typing.Union[X, Y]'
to 'X | Y' (PEP 604), breaking annotation inference.

Changes:
- Use getattr() instead of safe_getattr() for __module__ retrieval
  (getattr_static fails on Union types in Python 3.14)
- Add fallback to typing.get_origin() when regex fails to match
- Normalize Union display back to 'Union[X, Y]' format for consistency
- Update test expectations for invalid annotation edge case in 3.14

Fixes: https://github.com/davidhalter/jedi/issues/2064
2026-03-09 15:39:23 +01:00
Dave Halter 3176c1dcb8 A bit more solid typing for goto_or_help_or_infer 2026-02-16 22:54:32 +01:00
Dave Halter ea09983566 Some small typing improvements for tests 2026-02-16 21:20:27 +01:00
David BrochartandDave Halter 76c1e03f07 Fix typo 2026-02-13 11:01:08 +00:00
Dave Halter 8cbb817b12 Fix a lot of test related typing issues 2026-02-10 19:38:55 +01:00
Dave Halter 6903bc25d5 Remove an outdated script 2026-02-10 19:02:51 +01:00
Dave Halter e7fdbcc834 Fix a few more typing issues 2026-02-04 02:24:41 +01:00
Dave Halter 3ffed76884 Improve a weird typing issue 2026-02-04 01:28:30 +01:00
Dave Halter 30ef824abd Introduce some stricter typing 2026-02-04 01:19:14 +01:00
Dave Halter c7481b3319 Fix a linter issue 2026-01-20 02:05:51 +01:00
Dave Halter 3ac1632a5c Avoid the need to import search_ancestor 2026-01-19 20:30:03 +01:00
Dave Halter 4a7b5f4879 Increase the required vesion of parso 2026-01-19 17:08:11 +01:00
Dave Halter d4fb9c4531 Move Mypy config to pyproject.toml 2026-01-19 14:47:06 +01:00
Dave Halter 2b37bc3518 Change the Zuban link to GitHub 2026-01-19 13:26:43 +01:00
Dave HalterandGitHub ade9131d04 Merge pull request #2083 from diekhans/issue-2082-None-file-attr
Handle object with __file__ attribute having a None value (issue #2082)
2025-12-21 01:47:23 +00:00
Mark Diekhans a89757a966 Handle object with __file__ attribute having a None value (issue #2082) 2025-12-20 17:24:25 -08:00
Dave HalterandGitHub b80c0b8992 Merge pull request #2079 from Hoblovski/fix/star-unpack
Fix unhandled '*' operator in infer_node
2025-11-13 15:53:08 +00:00
Hoblovski 1b33f0d77c fix: move test to arrays.py 2025-11-13 21:12:15 +08:00
Hoblovski 3454ebb1de fix: let star_expr infer to NO_VALUES instead of crashing 2025-11-13 20:41:58 +08:00
Hoblovski 3d2ce2e01f tests: add bad case 2025-11-13 20:33:00 +08:00
Dave HalterandGitHub 88d3da4ef6 Merge pull request #2049 from Morikko/support-dataclass-transform
Support dataclass transform
2025-09-03 13:15:39 +00:00
Eric Masseran 15a7513fd0 Improve code comment 2025-08-29 18:54:30 +02:00
Eric Masseran 0f35a1b18b Split dataclass and dataclass_transform logic 2025-08-29 18:54:14 +02:00
Eric Masseran 4ea7981680 Add complete test 2025-08-29 18:37:51 +02:00
Eric Masseran 3a436df7ac Remove property usage 2025-08-29 18:37:37 +02:00
Eric Masseran c1e9aee15b Clean code comments 2025-08-29 18:37:23 +02:00
Eric Masseran 6e5f201f6c Use future annotations 2025-08-29 18:36:54 +02:00
Eric Masseran 356923e40d Merge remote-tracking branch 'origin' into support-dataclass-transform
* origin:
  Fix pip install -e in docs
  Upgrade Mypy
  Fix a few flake8 issues
  Upgrade flake8
  Upgrade other test runners
  Remove 3.6/3.7 references and change tests slightly
  Upgrade OS's that it is tested on
  Try to add something to the README
2025-08-28 10:33:17 +02:00
Dave Halter 86c3a02c8c Fix pip install -e in docs 2025-06-24 12:28:18 +02:00
Dave HalterandGitHub f4ca099afb Merge pull request #2066 from davidhalter/ci
Upgrade test runners, Mypy and flake8
2025-06-16 15:50:37 +00:00
Dave Halter d411290dff Upgrade Mypy 2025-06-16 16:49:46 +02:00
Dave Halter 7c27da8d68 Fix a few flake8 issues 2025-06-16 16:41:36 +02:00
Dave Halter 13063221f2 Upgrade flake8 2025-06-16 16:31:14 +02:00
Dave Halter e83228478e Upgrade other test runners 2025-06-16 16:21:50 +02:00
Dave Halter e5a72695a8 Remove 3.6/3.7 references and change tests slightly 2025-06-16 16:18:15 +02:00
Dave Halter 4238198eea Upgrade OS's that it is tested on 2025-06-16 16:07:20 +02:00
Dave Halter a10b158bcc Try to add something to the README 2025-06-16 15:13:01 +02:00
Eric Masseran 503c88d987 Merge remote-tracking branch 'origin' into support-dataclass-transform
* origin:
  Don't remove `sys.path[0]`.
  perf: improve performance by replacing list to set
  Explicit sphinx config path
2025-05-05 02:03:07 +02:00
Eric Masseran d53a8ef81c Support init customization on dataclass_transform source 2025-05-05 02:02:17 +02:00
Eric Masseran eb80dc08f3 Add decorator tests - sandwich mode 2025-05-05 00:37:38 +02:00
Eric Masseran 5f4afa27e5 Documentation and better naming 2025-05-04 23:34:58 +02:00
Eric Masseran e49032ed6b Dataclass transform typing extension without Final support 2025-03-18 00:59:27 +01:00
Eric Masseran e20c3c955f Dataclass 3.7 mode without Final 2025-03-18 00:52:01 +01:00
Eric Masseran a3fd90d734 Fix dataclass decorator other parameters 2025-03-18 00:42:58 +01:00
Eric Masseran 999332ef77 Dataclass transform change init False 2025-03-18 00:30:50 +01:00
Eric Masseran e140523211 Fix attrs + remove dataclass_transform init=false tests 2025-03-17 23:51:53 +01:00
Eric Masseran bd1edfce78 Fix test 2025-03-17 19:48:42 +01:00
Eric Masseran 7dcb944b05 Fix decorator transformed case 2025-03-15 16:42:16 +01:00
Eric Masseran 50778c390f Fix init=false for transform and exclude fields on base transform 2025-03-15 16:23:32 +01:00
Eric Masseran e0797be681 Check final+classvar support for dataclass transform 2025-03-15 16:02:23 +01:00
Eric Masseran 8912a35502 Support init=False for dataclass_transform 2025-03-15 16:00:51 +01:00
Eric Masseran 77cf382a1b Support init=False for dataclass 2025-03-15 15:53:51 +01:00
Eric Masseran 70efe2134c Check final support for dataclass 2025-03-15 13:17:18 +01:00
Eric Masseran 472ee75e3c Add ClassVar support for dataclass 2025-03-15 13:15:19 +01:00
Eric Masseran 68c7bf35ce Add init cases for dataclass 2025-03-15 13:07:35 +01:00
Eric Masseran efc7248175 Fix mypy 2025-03-15 12:05:05 +01:00
Dave HalterandGitHub c4f0538930 Merge pull request #2055 from zuckerruebe/dont-remove-sys-path-0
Don't remove `sys.path[0]`.
2025-03-14 17:20:41 +00:00
Damian BirchlerandGitHub 35a12fab7a Don't remove sys.path[0].
Fixes https://github.com/davidhalter/jedi/issues/2053.
2025-03-13 09:52:49 +01:00
Dave HalterandGitHub a856a93bd1 Merge pull request #2051 from allrob23/perf-list-to-set
perf: optimize transitions and path handling using sets
2025-03-06 09:04:07 +00:00
RobinandGitHub 60f0894f66 Merge branch 'davidhalter:master' into perf-list-to-set 2025-03-05 09:53:36 -03:00
Robin 699c930bd4 perf: improve performance by replacing list to set 2025-03-05 12:53:08 +00:00
Peter Law 9dd76c7ce5 Explicit sphinx config path
This is now required by ReadTheDocs; see
https://about.readthedocs.com/blog/2024/12/deprecate-config-files-without-sphinx-or-mkdocs-config/
2025-03-04 22:07:56 +00:00
Eric Masseran 74b46f3ee3 Add doc 2025-02-15 20:27:08 +01:00
Eric Masseran 027e29ec50 Support base class and metaclass mode 2025-02-15 20:12:53 +01:00
Eric Masseran f9beef0f6b Add fixture to skip pre 3.11 2025-02-15 20:09:11 +01:00
Eric Masseran d866ec0f80 Add support for dataclass_transform decorator 2025-02-14 17:05:28 +01:00
Dave HalterandGitHub 6aee460b1d Merge pull request #2042 from bluthej/docs/fix-inheritance-diagram
Fix inheritance diagram
2024-12-29 15:00:40 +00:00
bluthej 0315e6ee8f Add graphviz to installed APT packages 2024-12-28 16:43:05 +01:00
Dave Halter ce109a8cdf Fix a small fail in test_duplicated_import 2024-11-25 09:49:44 +01:00
Dave Halter ecb922c6ff Fix a few issues around duplicated import paths, fixes #2033 2024-11-25 00:53:09 +01:00
Dave Halter 41e9e957e7 Increase Jedi version 2024-11-11 02:39:18 +01:00
Dave Halter b225678a42 Add a release for Python 3.13 2024-11-10 23:04:28 +01:00
Dave HalterandGitHub 30adf43a89 Merge pull request #2027 from WutingjiaX/feat/filterImported
Filter duplicate imports when completing
2024-10-17 21:10:55 +00:00
wutingjia be6df62434 filter imported names during completion 2024-10-17 19:20:39 +08:00
Dave Halter e53359ad88 Fix a test that had issues with a minor upgrade of Python 3.12 2024-10-16 12:56:10 +02:00
Dave Halter 6e5d5b779c Enable workflow_dispatch in CI 2024-10-16 12:39:33 +02:00
Nguyễn Hồng QuânandGitHub 91ffdead32 Sort completions by input resemblance. (#2018)
* Sort completions by input resemblance.

Fixes #2017

* Clean code
2024-07-15 08:15:20 +00:00
2859e4f409 Support inferring not expr to bool (#2016)
* support inferring some not expr

* format

---------

Co-authored-by: wutingjia <wutingjia@bytedance.com>
2024-07-12 12:58:39 +00:00
Dave HalterandGitHub 8ee4c26ae4 Merge pull request #2014 from WutingjiaX/feat/in_operator
When inferring comparison operators, return a definite type instead of NO_VALUES for the in/not in operator
2024-07-10 18:03:32 +00:00
wutingjia 4d09ac07e4 When inferring comparison operators, return a definite type instead of NO_VALUES for the in/not in operator 2024-07-10 11:20:11 +08:00
Peter Law 82d1902f38 Merge branch 'python-3.13' 2024-07-06 11:39:06 +01:00
Peter Law 857c9be500 Ignore py__name__ issues for functools.partial in Python 3.13.0b3+
See https://github.com/davidhalter/jedi/issues/2012 for details.
2024-07-05 21:38:28 +01:00
Peter Law e839683e91 Merge branch 'ensure-unique-subprocess-reference-ids' 2024-07-04 22:39:29 +01:00
Peter Law 255186376e Cope with Python 3.13 moving pathlib's implementation
Jedi passes pickles to subprocesses which are running the target
version of Python and thus may not be the same as the version
under which Jedi itself is running. In Python 3.13, pathlib is
being refactored to allow for easier extension and has thus moved
most of its internal implementation to a submodule. Unfortunately
this changes the paths of the symbols, causing pickles of those
types to fail to load in earlier versions of Python.

This commit introduces a custom unpickler which accounts for this
move, allowing bi-directional passing of pickles to work.
2024-07-02 21:37:34 +01:00
Peter Law a67deeb602 Fix race condition around subprocess inference state tidyup
There was a race condition due to the combination of Python's
object ids being re-usable and Jedi persisting such ids beyond
the real lifeteime of some objects. This could lead to the
subprocess' view of the lifetime of `InferenceState` contexts
getting out of step with that in the parent process and
resulting in errors when removing them. It is also possible
that this could result in erroneous results being reported,
however this was not directly observed.

The race was specifically:
- `InferenceState` A created, gets id 1
- `InferenceStateSubprocess` A' created, uses `InferenceState`
  A which it stores as a weakref and an id
- `InferenceStateSubprocess` A' is used, the sub-process learns
  about an `InferenceState` with id 1
- `InferenceState` A goes away, `InferenceStateSubprocess` A' is
  not yet garbage collected
- `InferenceState` B created, gets id 1
- `InferenceStateSubprocess` B' created, uses `InferenceState` B
  which it stores as a weakref and an id
- `InferenceStateSubprocess` B' is used, the sub-process re-uses
  its entry for an `InferenceState` with id 1

At this point the order of operations between the two
`InferenceStateSubprocess` instances going away is immaterial --
both will trigger a removal of a state with id 1. As long as B'
doesn't try to use the sub-process again after the first removal
has happened then the second removal will fail.

This commit resolves the race condition by coupling the context
in the subprocess to the corresponding manager class instance
in the parent process, rather than to the consumer `InferenceState`.

See inline comments for further details.
2024-07-02 21:37:34 +01:00
Peter Law d543d1d004 Support Python 3.13
This moves to using the 3.13 grammar as well as testing 3.13 in CI.
2024-07-02 21:37:34 +01:00
Peter Law 9d18b7c36d Document how Jedi manages its subprocesses
This is derived from my understanding of the code, plus a bit of
experimentation.
2024-07-02 21:37:34 +01:00
Peter Law 340dedd021 Use an explicit mapping for locals in this test
In Python 3.13 the `locals` function now returns a fresh mapping
each time it's called (when called in a function). We thus need
to store a reference to the mapping being used, rather than
re-fetching it each time.

Since we don't actually need to modify the locals within the scope
of the test function itself, it suffices to use our own mapping
here rather than the result of calling `locals`, which fully
isolates this test from the nature of that function.

Fixes https://github.com/davidhalter/jedi/issues/2002
2024-07-02 21:37:34 +01:00
Peter Law fff6e0ce2e Drop unused member
I'm not sure where this was used in the past, however it appears
to be unused now. Removing this simplifies a change I'm about to
make to _InferenceStateProcess.
2024-07-02 21:37:34 +01:00
Peter Law 473b35e6ec Ignore more items extra in Python 3.13 2024-07-02 21:37:34 +01:00
Peter Law a0527a5af5 Pass through the inference state id rather than recomputing it
This removes some of the coupling between the management of the
underlying process and the inference state itself, which intends
to enable changing the origin of the id. This will be useful in
the next commit.
2024-07-02 21:37:34 +01:00
Peter Law bbbaad21e8 Clarify filter by flipping the boolean logic 2024-07-02 21:37:34 +01:00
Peter Law ee90cd97b6 Name this list of accepted symbol differences
This should make it easier to add new entries as well as clarifying
the intent of this filter.
2024-07-02 21:37:34 +01:00
Peter Law 68e435cc66 Update 'next' signature in Python 3.13 where it's been fixed 2024-07-02 21:37:34 +01:00
Dave HalterandGitHub b69d4d87c3 Merge pull request #2006 from PeterJCLaw/lint-tests
Enable linting of our test code, but not our test data files
2024-06-30 23:31:16 +00:00
Peter Law 0fcb4468e7 Fix or ignore lints in tests 2024-06-30 19:05:38 +01:00
Peter Law 5c578e1899 Enable linting of our test code, but not our test data files 2024-06-30 19:01:03 +01:00
Peter Law 9bad42c0db Update actions/setup-python to avoid Node JS deprecations 2024-06-29 16:57:25 +01:00
Peter Law 3118462a93 Allow more recent version of pytest
pytest 7.x and 8.x work without changes.
2024-06-23 13:20:10 +01:00
Dave Halter 065580b5d4 Use the proper super call for setUp 2024-05-24 21:29:08 +02:00
Dave HalterandGitHub 39c8317922 Merge pull request #1996 from frenzymadness/patch-2
Fix compatibility with pytest 8
2024-05-22 22:44:47 +00:00
Lumír 'Frenzy' BalharandGitHub ab97e9f784 Fix compatibility with pytest 8
Fixes #1995
2024-05-22 23:13:19 +02:00
Dave HalterandGitHub f7c9ee9433 Merge pull request #1994 from HairlessVillager/patch-1
Add a windows path in `_get_executable_path()`
2024-05-06 10:06:18 +00:00
HairlessVillagerandGitHub 8792c6d432 Add a windows path in _get_executable_path() 2024-05-06 17:04:38 +08:00
pyscripterandGitHub a4574a50d0 Fix resolving of names that are not annotations (#1989), fixes #1988
* Fix #1988

* Fix failing code quality test.

* Fix  flake W504 line break after binary operator.  Now as formatted by Black.

* Added test to test/completion/pep0484_basic.py
Addressed feedback from Dave
2024-04-25 09:11:24 +00:00
Dave Halter f11014fc5d Wording 2024-04-21 10:40:00 +02:00
Ehsan Iran-NejadandGitHub 54a6dadde3 properties with setters are now reported as 'property' for completion (#1983)
* properties with setters are now reported as 'property' for completion

* code cleanups

* fixed test

* fixed tests

* Revert "fixed test"

This reverts commit a80c955a489bce4a649452f7db49e59eab3ba38c.

* code quality cleanup

* so picky

* Revert "Revert "fixed test""

This reverts commit 58dfc5292e9df5415a212f6bf28356e76d515157.

* updated test per maintainer comments #1983

* removed extra char
2024-02-19 12:07:47 +00:00
Dave HalterandGitHub 740b474eda Merge pull request #1907 from moser/support-fixtures-from-pytest-plugins
Add support for pytest fixtures from local pytest plugins.
2024-01-26 16:15:37 +00:00
Martin Vielsmaier 950ce70239 Prioritize conftest over plugins for pytest fixtures 2024-01-26 10:39:15 +01:00
Martin Vielsmaier 6982a49977 Fix codestyle 2024-01-24 13:11:26 +01:00
Martin Vielsmaier 9b8cece7ef Improve pytest plugin loading 2024-01-23 20:54:21 +01:00
Martin Vielsmaier 162034b387 Fix line length. 2024-01-23 20:54:21 +01:00
Martin Vielsmaier 7494c9495e Update contributors. 2024-01-23 20:54:21 +01:00
Martin Vielsmaier 7d77f61040 Add support for pytest fixtures from local pytest plugins. 2024-01-23 20:54:21 +01:00
Dave HalterandGitHub 11280ef502 Merge pull request #1977 from HairlessVillager/master
[Typo] Fix some misalignment of docstrings
2023-12-28 10:20:12 +00:00
HairlessVillagerandGitHub 94ec4b873a Fix some misalignment of docstrings 2023-12-28 13:26:12 +08:00
Dave Halter f8e502f90c Apparently read the docs also needs this 2023-10-04 09:41:55 +02:00
Dave Halter dc20f2e5a0 Try to fix readthedocs 2023-10-04 09:40:41 +02:00
Dave Halter 4e175ca82b Prepare for release 0.19.1 2023-10-02 10:55:17 +02:00
Dave HalterandGitHub 30e9b7b5be Merge pull request #1961 from PeterJCLaw/docs-link
Include a link to the docs in the package metadata
2023-09-23 23:56:11 +00:00
Peter Law 83545bc9ec Include a link to the docs in the package metadata 2023-09-22 21:33:52 +01:00
Peter Law 57e7b83455 Add missing import
I suspect this got lost in a merge somewhere, probably the
combination of 7e533ca7e1 and the
Python 3.12 work in a60fdba1d4.
2023-09-22 21:29:31 +01:00
Dave HalterandGitHub 0770372857 Merge pull request #1956 from PeterJCLaw/python-3.12
Support Python 3.12
2023-09-17 19:25:45 +00:00
Peter Law 7e533ca7e1 Drop redundant conditional skips for unsupported Python versions 2023-09-17 18:38:12 +01:00
Peter Law a60fdba1d4 Adjust for change to documention change of next in Python 3.12
The signature of the builtin isn't actually changing in Python 3.12,
however its documentation has changed.
2023-09-17 18:27:53 +01:00
Dave HalterandGitHub 9d399a9229 Merge pull request #1959 from davidhalter/unpin-test-django
Unpin Django in tests
2023-09-17 12:37:00 +00:00
Peter Law 770cdade00 Claim support for Python 3.12 2023-09-16 21:41:06 +01:00
Peter Law 29890c1f29 Ignore linux-only os.CLONE_* constants in Python 3.12 in import test 2023-09-16 21:41:06 +01:00
Peter Law 159566e1a0 Add Python 3.12 as a tested platform 2023-09-16 21:41:06 +01:00
Peter Law a80618a2df Unpin Django in tests
It's not completely clear why this was pinned originally, though
at the time Jedi supported Python 2.7 as well as 3.5-3.8, so that
may have had something to do with it.

Removing this pin now seems to work in CI and unblocks some issues
we're seeing around Python 3.12 (specifically that Django<3.1
implicitly relies on distutils, which is no longer available by
default, and possibly other issues).
2023-09-16 21:40:34 +01:00
Peter Law 4bc1b6ef99 Bump use of actions/checkout to avoid Node JS deprecations 2023-09-16 18:12:28 +01:00
Peter Law d655d65d3a Fix typo in comment 2023-09-16 18:03:56 +01:00
Dave Halter 51f4a99a1e Bump version to 0.19.0 2023-07-29 00:57:34 +02:00
Dave Halter 93c14d2e6e Add release notes for 0.19.0 2023-07-29 00:46:38 +02:00
Dave Halter 57aefed6ea Allow unsafe custom __getitem__ executions when allow unsafe executions is on 2023-07-29 00:33:09 +02:00
Dave Halter 8a4b079d0f allow_descriptor_getattr -> allow_unsafe_interpreter_executions 2023-07-29 00:06:55 +02:00
Dave Halter 62cbcb0844 Make nested dict completions possible.
See also https://github.com/ipython/ipython/issues/13866
2023-07-28 23:50:38 +02:00
Dave Halter d8420d0f72 Add a note to the changelog 2023-07-28 22:59:03 +02:00
Dave Halter 886279fb6d Try to use the return annotations of properties, if available, fixes #1933 2023-07-28 22:35:15 +02:00
Dave Halter ff3a7f367f Avoid evaluating properties just for the api type, improves #1933 2023-07-28 22:11:15 +02:00
Dave Halter 1f70e3301e Revert "Avoid one layer of caching that is probably useless"
This reverts commit a34c348a55.
2023-07-28 16:10:30 +02:00
Dave Halter a34c348a55 Avoid one layer of caching that is probably useless 2023-07-28 16:05:56 +02:00
Dave Halter 972123c9c9 Introduce the property return annotation 2023-07-28 15:54:54 +02:00
Dave Halter 6455a14841 Avoid multiple getattrs instead of a single one, see also #1933 2023-07-28 15:10:37 +02:00
Dave Halter 8d9e3ab3a7 Simplify 2023-07-28 13:10:26 +02:00
Dave Halter 048173e467 Remove a piece of unimportant code, see discussion in #1933 2023-07-28 10:15:28 +02:00
Dave Halter 1947e7dd56 Avoid dynamic params search for Interpreter, fixes #1899 2023-07-27 13:49:27 +02:00
Dave Halter 01d8da8f73 Reset the recursion limitations at the start of the main Script calls, fixes #1796 2023-07-27 13:14:24 +02:00
Dave Halter 6ea5ad7b19 Fix issue around completions with multiple with with_items, fixes 1931 2023-07-27 11:54:39 +02:00
Dave Halter cd4ca74d7a Satisfy flake8 2023-07-27 11:36:16 +02:00
Dave Halter 67d6262f45 Skip the namespace package test correctly 2023-07-27 10:07:16 +02:00
Dave Halter 5f19237a3e Fix renaming of namespace packages, fixes #1779 2023-07-27 03:09:25 +02:00
Dave HalterandGitHub f2444b4be5 Merge pull request #1943 from diegorodriguezv/patch-1
Fix language servers reference
2023-06-22 08:44:15 +00:00
diegorodriguezvandGitHub 7028bbb5d5 Fix language servers reference 2023-06-21 19:06:53 -05:00
Dave HalterandGitHub 3699ba0aa7 Merge pull request #1942 from lkh42t/inference-annotated
Support typing.Annotated inference
2023-06-19 00:28:27 +00:00
Luc Khai Hai 72d34f3d7d Support typing.Annotated inference 2023-06-17 20:46:03 +09:00
Peter Law a28bd24bef Merge branch 'importlib-metadata-entry-points' 2023-05-28 12:08:09 +01:00
Peter Law 54cb64292c Support importlib.metadata entry points for newer python
pkg_resources is deprecated and liable to be dropped at some point.
2023-05-28 11:48:19 +01:00
Dave HalterandGitHub d421b920fa Merge pull request #1937 from PeterJCLaw/update-importlib-usage
Modernise importlib usage
2023-05-27 22:54:45 +00:00
Peter Law c137eb6918 Modernise importlib usage
`find_module` is deprecated in all supported version of Python and
is slated for removal in the upcoming 3.12. Happily it seems we
can move to the related `find_spec` and just hoist the loader from
the spec which that returns. (This is mostly what current `find_module`
implementations do anyway).
2023-05-27 22:03:51 +01:00
Peter Law d67facc922 Merge branch 'update-github-actions' 2023-05-27 22:03:03 +01:00
Dave HalterandGitHub 7023b645b1 Merge pull request #1935 from PeterJCLaw/fix-attrs-as-dataclass
Teach Jedi that `attrs`' `frozen` decorator also acts like a dataclass
2023-05-27 21:01:21 +00:00
Peter Law b5120cc90b Update GitHub Actions for Node 16 support 2023-05-27 00:18:38 +01:00
Peter Law 483e78993d attrs' frozen decorator also acts like a dataclass
In 23.1.0 (specifically in https://github.com/python-attrs/attrs/commit/46053d703d99a6e7419fd291cbf546340690d69d)
the definition of the `frozen` decorator was tweaked slightly, such
that its type stub is separate from that for `define`. This means
that Jedi needs to be told about it as a separate member.

I've manually checked that this still works with the prior version
of `attrs`.

Fixes https://github.com/davidhalter/jedi/issues/1929
2023-05-27 00:13:55 +01:00
Peter Law 3dbcd2c6de Whitespace 2023-05-27 00:10:00 +01:00
Peter Law ca36fcfa4b Fix typo in comment 2023-05-26 21:38:42 +01:00
Dave HalterandGitHub 825c6b93bf Merge pull request #1930 from tachikoma-li/doc-fix
docs: Fix typo in acknowledgements
2023-04-21 21:55:14 +00:00
Li Li c22585c6f2 small doc fix 2023-04-19 15:12:39 +10:00
Dave HalterandGitHub 431d1e104d Merge pull request #1926 from dijonkitchen/patch-1
Update usage.rst to match Readme language servers
2023-04-12 15:50:21 +00:00
Dave HalterandGitHub adcd6ade8b Merge pull request #1927 from dijonkitchen/patch-2
docs: fix spelling
2023-04-12 15:49:40 +00:00
JC (Jonathan Chen)andGitHub 32a1dd33a6 docs: fix spelling 2023-04-12 11:01:10 -04:00
JC (Jonathan Chen)andGitHub 9ea01bcc69 Update usage.rst to match Readme language servers 2023-04-12 10:58:57 -04:00
Dave Halter 77cfefc1cc Add a security policy 2023-03-22 00:47:58 +01:00
Dave HalterandGitHub ff7d6c6e4c Merge pull request #1922 from zerocewl/add_pylsp_link
Added link to the python-lsp-server
2023-03-21 23:24:04 +00:00
Dave HalterandGitHub 6ee33bd385 Merge pull request #1923 from dimbleby/help-after-newline
fix help when in column zero
2023-03-13 20:17:13 +00:00
David Hotham 0fbc2aafa3 fix help when in column zero 2023-03-12 14:21:09 +00:00
zerocewlandGitHub fe7e350051 Added link to the python-lsp-server 2023-03-01 16:27:11 +01:00
Dave HalterandGitHub b814ca2951 Merge pull request #1917 from PeterJCLaw/python-3.11
Support Python 3.11
2023-02-14 00:48:04 +00:00
Peter Law aae2a8e3ed Cope with Windows virtualenvs different casing 2023-02-13 20:25:31 +00:00
Peter Law 67e0bec597 Support Python 3.11
This adds support for targetting Python 3.11 via picking up the
latest grammar from parso while also validating support for running
on 3.11 by adding it to the CI matrix.
2023-02-13 19:58:35 +00:00
Peter Law c71e06fcb3 Clarify that this is also the latest flake8 version which supports 3.6 2023-02-13 19:57:38 +00:00
Peter Law bbd5bcf3ca Merge branch 'update-mypy' 2023-02-13 19:57:20 +00:00
Dave HalterandGitHub d888c1b266 Merge pull request #1915 from PeterJCLaw/update-flake8
Update flake8 and fix issue found
2023-02-13 19:49:36 +00:00
Peter Law 83d0e23800 Type check setup.py too now we can 2023-02-13 19:40:16 +00:00
Peter Law dc4e48d7c7 Be stricter about mypy needing error codes
These make it clearer what's being ignored and harder to
accidentally ignore more than expected.
2023-02-13 19:40:16 +00:00
Peter Law 664b10a5c6 Update mypy to the latest
This includes updating the ignore comments for things which mypy
now knows about or now complains about, as well as pulling in some
typeshed packages for things outside the standard library.
2023-02-13 19:40:16 +00:00
Peter Law 36a4b7d48c Update flake8 and fix issue found 2023-02-13 19:15:35 +00:00
Dave HalterandGitHub b0025ee6ba Merge pull request #1911 from krpatter-intc/allow_descriptor_getattr_official_support
Make allow_descriptor_getattr a non-private variable for more official
2023-02-10 22:30:33 +00:00
Patterson, Kevin R fac0b7f068 instance_allow_descriptor_getattr as public setting 2023-02-10 05:43:21 -06:00
Dave HalterandGitHub aeadba7cad Merge pull request #1910 from ghrist8p/1909-fix-sys-path-is-tuple
Replaced tuple passed as sys_path actual argument with list
2023-02-07 23:21:16 +00:00
Georgi Hristov fd0e6aed96 Replaced tuple passed as sys_path actual argument with list
Fixes davidhalter#1909
2023-02-05 15:46:23 -08:00
Dave HalterandGitHub c89fa8e927 Merge pull request #1903 from s-t-e-v-e-n-k/python-311-string-typing
Support Python 3.11 typing changes
2023-01-10 19:57:58 +00:00
Steve Kowalik 00e23ddcee Support Python 3.11 typing changes
Python 3.11 has changed typing so that unions now  return forward
refrences instead of erroring, and typing.Any is now an _AnyMeta class.
Correct the parameters for both of those.

Fixes #1858
2023-01-10 14:52:24 +11:00
Dave Halter 66e97e5b93 Jedi is now a fixed part of the Eric IDE 2022-12-16 15:37:22 +01:00
Dave Halter 0f5ea3de5f Revert "Removed all usages of Eric IDE, because apparently it's not using Jedi anymore"
This reverts commit e47bbbb851.
2022-12-16 15:36:31 +01:00
Dave Halter e47bbbb851 Removed all usages of Eric IDE, because apparently it's not using Jedi anymore 2022-12-16 15:33:07 +01:00
137 changed files with 2672 additions and 900 deletions
+18 -16
View File
@@ -1,31 +1,33 @@
name: ci name: ci
on: [push, pull_request] on: [push, pull_request, workflow_dispatch]
jobs: jobs:
tests: tests:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, windows-2019] os: [ubuntu-24.04, windows-2022]
python-version: ["3.10", "3.9", "3.8", "3.7", "3.6"] python-version: ["3.14", "3.13", "3.12", "3.11", "3.10"]
environment: ['3.8', '3.10', '3.9', '3.7', '3.6', 'interpreter'] environment: ['3.14', '3.13', '3.12', '3.11', '3.10', 'interpreter']
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-python@v2 - uses: actions/setup-python@v5
if: ${{ matrix.environment != 'interpreter' }} if: ${{ matrix.environment != 'interpreter' }}
with: with:
python-version: ${{ matrix.environment }} python-version: ${{ matrix.environment }}
allow-prereleases: true
- uses: actions/setup-python@v2 - uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies - name: Install dependencies
run: 'pip install .[testing]' run: 'pip install .[dev]'
- name: Run tests - name: Run tests
run: python -m pytest run: python -m pytest
@@ -33,32 +35,32 @@ jobs:
JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }} JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }}
code-quality: code-quality:
runs-on: ubuntu-20.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: 'pip install .[qa]' run: 'pip install .[dev]'
- name: Run tests - name: Run tests
run: | run: |
python -m flake8 jedi setup.py python -m flake8 jedi test setup.py
python -m mypy jedi sith.py zuban check
coverage: coverage:
runs-on: ubuntu-20.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: 'pip install .[testing] coverage' run: 'pip install .[dev] coverage'
- name: Run tests - name: Run tests
run: | run: |
+10
View File
@@ -9,3 +9,13 @@ python:
submodules: submodules:
include: all include: all
sphinx:
configuration: docs/conf.py
build:
os: ubuntu-24.04
tools:
python: "3.14"
apt_packages:
- graphviz
+3
View File
@@ -63,6 +63,9 @@ Code Contributors
- Leo Ryu (@Leo-Ryu) - Leo Ryu (@Leo-Ryu)
- Joseph Birkner (@josephbirkner) - Joseph Birkner (@josephbirkner)
- Márcio Mazza (@marciomazza) - Márcio Mazza (@marciomazza)
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.com>
- Nguyễn Hồng Quân <ng.hong.quan@gmail.com>
And a few more "anonymous" contributors. And a few more "anonymous" contributors.
+30
View File
@@ -6,6 +6,36 @@ Changelog
Unreleased Unreleased
++++++++++ ++++++++++
0.20.0 (2026-05-02)
+++++++++++++++++++
- Python 3.14 support
- Removed support for Python 3.8 and 3.9
- Upgraded Typeshed
- Better support for Final/ClassVar
- ``__new__`` is now also recognized as a signature and TypeVar inference
- Support for ``Self``
- Support for ``TypeAlias``, generics for ``type[...]`` and ``tuple[...]``
0.19.2 (2024-11-10)
+++++++++++++++++++
- Python 3.13 support
0.19.1 (2023-10-02)
+++++++++++++++++++
- Python 3.12 support (Thanks Peter!)
0.19.0 (2023-07-29)
+++++++++++++++++++
- Python 3.11 support
- Massive improvements in performance for ``Interpreter`` (e.g. IPython) users.
This especially affects ``pandas`` users with large datasets.
- Add ``jedi.settings.allow_unsafe_interpreter_executions`` to make it easier
for IPython users to avoid unsafe executions.
0.18.2 (2022-11-21) 0.18.2 (2022-11-21)
+++++++++++++++++++ +++++++++++++++++++
+10 -6
View File
@@ -2,6 +2,9 @@
Jedi - an awesome autocompletion, static analysis and refactoring library for Python Jedi - an awesome autocompletion, static analysis and refactoring library for Python
#################################################################################### ####################################################################################
**I released the successor to Jedi: A
Mypy-Compatible Python Language Server Built in Rust** - `Zuban <https://github.com/zubanls/zuban>`_
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg .. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues :target: https://github.com/davidhalter/jedi/issues
:alt: The percentage of open issues and pull requests :alt: The percentage of open issues and pull requests
@@ -10,7 +13,7 @@ Jedi - an awesome autocompletion, static analysis and refactoring library for Py
:target: https://github.com/davidhalter/jedi/issues :target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open. :alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master .. image:: https://github.com/davidhalter/jedi/actions/workflows/ci.yml/badge.svg?branch=master
:target: https://github.com/davidhalter/jedi/actions :target: https://github.com/davidhalter/jedi/actions
:alt: Tests :alt: Tests
@@ -42,7 +45,7 @@ Jedi can currently be used with the following editors/projects:
- `GNOME Builder`_ (with support for GObject Introspection) - `GNOME Builder`_ (with support for GObject Introspection)
- Gedit (gedi_) - Gedit (gedi_)
- wdb_ - Web Debugger - wdb_ - Web Debugger
- `Eric IDE`_ (Available as a plugin) - `Eric IDE`_
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_ - `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
- `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_ - `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_
@@ -51,7 +54,8 @@ and many more!
There are a few language servers that use Jedi: There are a few language servers that use Jedi:
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_ - `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_ - `python-language-server <https://github.com/palantir/python-language-server>`_ (currently unmaintained)
- `python-lsp-server <https://github.com/python-lsp/python-lsp-server>`_ (fork from python-language-server)
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_ - `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
Here are some pictures taken from jedi-vim_: Here are some pictures taken from jedi-vim_:
@@ -98,7 +102,7 @@ Features and Limitations
Jedi's features are listed here: Jedi's features are listed here:
`Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_. `Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_.
You can run Jedi on Python 3.6+ but it should also You can run Jedi on Python 3.10+ but it should also
understand code that is older than those versions. Additionally you should be understand code that is older than those versions. Additionally you should be
able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_ able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
very well. very well.
@@ -179,10 +183,10 @@ The test suite uses ``pytest``::
pip install pytest pip install pytest
If you want to test only a specific Python version (e.g. Python 3.8), it is as If you want to test only a specific Python version (e.g. Python 3.14), it is as
easy as:: easy as::
python3.8 -m pytest python3.14 -m pytest
For more detailed information visit the `testing documentation For more detailed information visit the `testing documentation
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_. <https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
+9
View File
@@ -0,0 +1,9 @@
# Security Policy
If security issues arise, we will try to fix those as soon as possible.
Due to Jedi's nature, Security Issues will probably be extremely rare, but we will of course treat them seriously.
## Reporting Security Problems
If you need to report a security vulnerability, please send an email to davidhalter88@gmail.com. Typically, I will respond in the next few business days.
+9 -15
View File
@@ -42,7 +42,7 @@ def pytest_addoption(parser):
help="Warnings are treated as errors.") help="Warnings are treated as errors.")
parser.addoption("--env", action='store', parser.addoption("--env", action='store',
help="Execute the tests in that environment (e.g. 39 for python3.9).") help="Execute the tests in that environment (e.g. 314 for python3.14).")
parser.addoption("--interpreter-env", "-I", action='store_true', parser.addoption("--interpreter-env", "-I", action='store_true',
help="Don't use subprocesses to guarantee having safe " help="Don't use subprocesses to guarantee having safe "
"code execution. Useful for debugging.") "code execution. Useful for debugging.")
@@ -133,11 +133,13 @@ def goto_or_help(request, Script):
@pytest.fixture(scope='session', params=['goto', 'help', 'infer']) @pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
def goto_or_help_or_infer(request, Script): def goto_or_help_or_infer(request, Script):
def do(code, *args, **kwargs): class GotoOrHelpOrInfer:
return getattr(Script(code), request.param)(*args, **kwargs) def __call__(self, code, *args, **kwargs):
return getattr(Script(code), request.param)(*args, **kwargs)
do.type = request.param type = request.param
return do
return GotoOrHelpOrInfer()
@pytest.fixture(scope='session', params=['goto', 'complete', 'help']) @pytest.fixture(scope='session', params=['goto', 'complete', 'help'])
@@ -157,16 +159,8 @@ def jedi_path():
@pytest.fixture() @pytest.fixture()
def skip_pre_python38(environment): def skip_pre_python311(environment):
if environment.version_info < (3, 8): if environment.version_info < (3, 11):
# 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 # This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions. # they should for all Python versions.
pytest.skip() pytest.skip()
+2 -2
View File
@@ -35,7 +35,7 @@ to write my own version of a completion engine.
The first idea was to execute non-dangerous code. But I soon realized, that The first idea was to execute non-dangerous code. But I soon realized, that
this would not work. So I started to build a static analysis tool. this would not work. So I started to build a static analysis tool.
The biggest problem that I had at the time was that I did not know a thing The biggest problem that I had at the time was that I did not know a thing
about parsers.I did not did not even know the word static analysis. It turns about parsers. I did not even know the word static analysis. It turns
out they are the foundation of a good static analysis tool. I of course did not out they are the foundation of a good static analysis tool. I of course did not
know that and tried to write my own poor version of a parser that I ended up know that and tried to write my own poor version of a parser that I ended up
throwing away two years later. throwing away two years later.
@@ -53,7 +53,7 @@ quick and is pretty much feature complete.
-------- --------
I will leave you with a small annectote that happend in 2012, if I remember I will leave you with a small anecdote that happened in 2012, if I remember
correctly. After I explained Guido van Rossum, how some parts of my correctly. After I explained Guido van Rossum, how some parts of my
auto-completion work, he said: auto-completion work, he said:
+1 -1
View File
@@ -107,7 +107,7 @@ Completions
>>> code = '''import json; json.l''' >>> code = '''import json; json.l'''
>>> script = jedi.Script(code, path='example.py') >>> script = jedi.Script(code, path='example.py')
>>> script >>> script
<Script: 'example.py' <SameEnvironment: 3.9.0 in /usr>> <Script: 'example.py' <SameEnvironment: 3.14.0 in /usr>>
>>> completions = script.complete(1, 19) >>> completions = script.complete(1, 19)
>>> completions >>> completions
[<Completion: load>, <Completion: loads>] [<Completion: load>, <Completion: loads>]
+2 -2
View File
@@ -16,7 +16,7 @@ Jedi's main API calls and features are:
Basic Features Basic Features
-------------- --------------
- Python 3.6+ support - Python 3.10+ support
- Ignores syntax errors and wrong indentation - Ignores syntax errors and wrong indentation
- Can deal with complex module / function / class structures - Can deal with complex module / function / class structures
- Great ``virtualenv``/``venv`` support - Great ``virtualenv``/``venv`` support
@@ -77,7 +77,7 @@ Performance Issues
Importing ``numpy`` can be quite slow sometimes, as well as loading the Importing ``numpy`` can be quite slow sometimes, as well as loading the
builtins the first time. If you want to speed things up, you could preload builtins the first time. If you want to speed things up, you could preload
libriaries in |jedi|, with :func:`.preload_module`. However, once loaded, this libraries in |jedi|, with :func:`.preload_module`. However, once loaded, this
should not be a problem anymore. The same is true for huge modules like should not be a problem anymore. The same is true for huge modules like
``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc. ``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc.
+1 -1
View File
@@ -38,7 +38,7 @@ using pip::
If you want to install the current development version (master branch):: If you want to install the current development version (master branch)::
sudo pip install -e git://github.com/davidhalter/jedi.git#egg=jedi sudo pip install -e git+https://github.com/davidhalter/jedi.git#egg=jedi
System-wide installation via a package manager System-wide installation via a package manager
+2 -2
View File
@@ -7,10 +7,10 @@ The test suite depends on ``pytest``::
pip install pytest pip install pytest
If you want to test only a specific Python version (e.g. Python 3.8), it is as If you want to test only a specific Python version (e.g. Python 3.14), it is as
easy as:: easy as::
python3.8 -m pytest python3.14 -m pytest
Tests are also run automatically on `GitHub Actions Tests are also run automatically on `GitHub Actions
<https://github.com/davidhalter/jedi/actions>`_. <https://github.com/davidhalter/jedi/actions>`_.
+7 -6
View File
@@ -3,8 +3,8 @@
Using Jedi Using Jedi
========== ==========
|jedi| is can be used with a variety of :ref:`plugins <editor-plugins>`, |jedi| can be used with a variety of :ref:`plugins <editor-plugins>`,
`language servers <language-servers>` and other software. :ref:`language servers <language-servers>` and other software.
It is also possible to use |jedi| in the :ref:`Python shell or with IPython It is also possible to use |jedi| in the :ref:`Python shell or with IPython
<repl-completion>`. <repl-completion>`.
@@ -13,10 +13,11 @@ Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
.. _language-servers: .. _language-servers:
Language Servers Language Servers
-------------- ----------------
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_ - `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_ - `python-language-server <https://github.com/palantir/python-language-server>`_ (currently unmaintained)
- `python-lsp-server <https://github.com/python-lsp/python-lsp-server>`_ (fork from python-language-server)
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_ - `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
.. _editor-plugins: .. _editor-plugins:
@@ -86,7 +87,7 @@ Gedit
Eric IDE Eric IDE
~~~~~~~~ ~~~~~~~~
- `Eric IDE`_ (Available as a plugin) - `Eric IDE`_
Web Debugger Web Debugger
~~~~~~~~~~~~ ~~~~~~~~~~~~
@@ -96,7 +97,7 @@ Web Debugger
xonsh shell xonsh shell
~~~~~~~~~~~ ~~~~~~~~~~~
Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_. Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_.
Run the following command to enable: Run the following command to enable:
:: ::
+1 -1
View File
@@ -27,7 +27,7 @@ ad
load load
""" """
__version__ = '0.18.2' __version__ = '0.20.0'
from jedi.api import Script, Interpreter, set_debug_function, preload_module from jedi.api import Script, Interpreter, set_debug_function, preload_module
from jedi import settings from jedi import settings
+14 -1
View File
@@ -5,11 +5,24 @@ different Python versions.
import errno import errno
import sys import sys
import pickle import pickle
from typing import Any
class Unpickler(pickle.Unpickler):
def find_class(self, module: str, name: str) -> Any:
# Python 3.13 moved pathlib implementation out of __init__.py as part of
# generalising its implementation. Ensure that we support loading
# pickles from 3.13 on older version of Python. Since 3.13 maintained a
# compatible API, pickles from older Python work natively on the newer
# version.
if module == 'pathlib._local':
module = 'pathlib'
return super().find_class(module, name)
def pickle_load(file): def pickle_load(file):
try: try:
return pickle.load(file) return Unpickler(file).load()
# Python on Windows don't throw EOF errors for pipes. So reraise them with # Python on Windows don't throw EOF errors for pipes. So reraise them with
# the correct type, which is caught upwards. # the correct type, which is caught upwards.
except OSError: except OSError:
+25 -5
View File
@@ -206,6 +206,7 @@ class Script:
before magic methods and name mangled names that start with ``__``. before magic methods and name mangled names that start with ``__``.
:rtype: list of :class:`.Completion` :rtype: list of :class:`.Completion`
""" """
self._inference_state.reset_recursion_limitations()
with debug.increase_indent_cm('complete'): with debug.increase_indent_cm('complete'):
completion = Completion( completion = Completion(
self._inference_state, self._get_module_context(), self._code_lines, self._inference_state, self._get_module_context(), self._code_lines,
@@ -230,6 +231,7 @@ class Script:
:param prefer_stubs: Prefer stubs to Python objects for this method. :param prefer_stubs: Prefer stubs to Python objects for this method.
:rtype: list of :class:`.Name` :rtype: list of :class:`.Name`
""" """
self._inference_state.reset_recursion_limitations()
pos = line, column pos = line, column
leaf = self._module_node.get_name_of_position(pos) leaf = self._module_node.get_name_of_position(pos)
if leaf is None: if leaf is None:
@@ -273,6 +275,7 @@ class Script:
:param prefer_stubs: Prefer stubs to Python objects for this method. :param prefer_stubs: Prefer stubs to Python objects for this method.
:rtype: list of :class:`.Name` :rtype: list of :class:`.Name`
""" """
self._inference_state.reset_recursion_limitations()
tree_name = self._module_node.get_name_of_position((line, column)) tree_name = self._module_node.get_name_of_position((line, column))
if tree_name is None: if tree_name is None:
# Without a name we really just want to jump to the result e.g. # Without a name we really just want to jump to the result e.g.
@@ -365,10 +368,17 @@ class Script:
:rtype: list of :class:`.Name` :rtype: list of :class:`.Name`
""" """
self._inference_state.reset_recursion_limitations()
definitions = self.goto(line, column, follow_imports=True) definitions = self.goto(line, column, follow_imports=True)
if definitions: if definitions:
return definitions return definitions
leaf = self._module_node.get_leaf_for_position((line, column)) leaf = self._module_node.get_leaf_for_position((line, column))
if leaf is not None and leaf.end_pos == (line, column) and leaf.type == 'newline':
next_ = leaf.get_next_leaf()
if next_ is not None and next_.start_pos == leaf.end_pos:
leaf = next_
if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'): if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
def need_pydoc(): def need_pydoc():
if leaf.value in ('(', ')', '[', ']'): if leaf.value in ('(', ')', '[', ']'):
@@ -400,6 +410,7 @@ class Script:
the current module only. the current module only.
:rtype: list of :class:`.Name` :rtype: list of :class:`.Name`
""" """
self._inference_state.reset_recursion_limitations()
def _references(include_builtins=True, scope='project'): def _references(include_builtins=True, scope='project'):
if scope not in ('project', 'file'): if scope not in ('project', 'file'):
@@ -434,6 +445,7 @@ class Script:
:rtype: list of :class:`.Signature` :rtype: list of :class:`.Signature`
""" """
self._inference_state.reset_recursion_limitations()
pos = line, column pos = line, column
call_details = helpers.get_signature_details(self._module_node, pos) call_details = helpers.get_signature_details(self._module_node, pos)
if call_details is None: if call_details is None:
@@ -471,7 +483,7 @@ class Script:
module_context = self._get_module_context() module_context = self._get_module_context()
n = tree.search_ancestor(leaf, 'funcdef', 'classdef') n = leaf.search_ancestor('funcdef', 'classdef')
if n is not None and n.start_pos < pos <= n.children[-1].start_pos: if n is not None and n.start_pos < pos <= n.children[-1].start_pos:
# This is a bit of a special case. The context of a function/class # This is a bit of a special case. The context of a function/class
# name/param/keyword is always it's parent context, not the # name/param/keyword is always it's parent context, not the
@@ -553,6 +565,7 @@ class Script:
return parso_to_jedi_errors(self._inference_state.grammar, self._module_node) return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)
def _names(self, all_scopes=False, definitions=True, references=False): def _names(self, all_scopes=False, definitions=True, references=False):
self._inference_state.reset_recursion_limitations()
# Set line/column to a random position, because they don't matter. # Set line/column to a random position, because they don't matter.
module_context = self._get_module_context() module_context = self._get_module_context()
defs = [ defs = [
@@ -708,7 +721,6 @@ class Interpreter(Script):
:param namespaces: A list of namespace dictionaries such as the one :param namespaces: A list of namespace dictionaries such as the one
returned by :func:`globals` and :func:`locals`. returned by :func:`globals` and :func:`locals`.
""" """
_allow_descriptor_getattr_default = True
def __init__(self, code, namespaces, *, project=None, **kwds): def __init__(self, code, namespaces, *, project=None, **kwds):
try: try:
@@ -729,7 +741,16 @@ class Interpreter(Script):
super().__init__(code, environment=environment, project=project, **kwds) super().__init__(code, environment=environment, project=project, **kwds)
self.namespaces = namespaces self.namespaces = namespaces
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default self._inference_state.allow_unsafe_executions = \
settings.allow_unsafe_interpreter_executions
# Dynamic params search is important when we work on functions that are
# called by other pieces of code. However for interpreter completions
# this is not important at all, because the current code is always new
# and will never be called by something.
# Also sometimes this logic goes a bit too far like in
# https://github.com/ipython/ipython/issues/13866, where it takes
# seconds to do a simple completion.
self._inference_state.do_dynamic_params_search = False
@cache.memoize_method @cache.memoize_method
def _get_module_context(self): def _get_module_context(self):
@@ -758,8 +779,7 @@ def preload_module(*modules):
:param modules: different module names, list of string. :param modules: different module names, list of string.
""" """
for m in modules: for m in modules:
s = "import %s as x; x." % m Script(f"import {m}").infer()
Script(s).complete(1, len(s))
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
+4 -7
View File
@@ -17,8 +17,6 @@ import re
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from parso.tree import search_ancestor
from jedi import settings from jedi import settings
from jedi import debug from jedi import debug
from jedi.inference.utils import unite from jedi.inference.utils import unite
@@ -98,15 +96,14 @@ class BaseName:
@property @property
def module_path(self) -> Optional[Path]: def module_path(self) -> Optional[Path]:
""" """
Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` Shows the file path of a module. e.g. ``/usr/lib/python3.14/os.py``
""" """
module = self._get_module_context() module = self._get_module_context()
if module.is_stub() or not module.is_compiled(): if module.is_stub() or not module.is_compiled():
# Compiled modules should not return a module path even if they # Compiled modules should not return a module path even if they
# have one. # have one.
path: Optional[Path] = self._get_module_context().py__file__() path: Optional[Path] = self._get_module_context().py__file__()
if path is not None: return path
return path
return None return None
@@ -510,7 +507,7 @@ class BaseName:
# - param: The parent_context of a param is not its function but # - param: The parent_context of a param is not its function but
# e.g. the outer class or module. # e.g. the outer class or module.
cls_or_func_node = self._name.tree_name.get_definition() cls_or_func_node = self._name.tree_name.get_definition()
parent = search_ancestor(cls_or_func_node, 'funcdef', 'classdef', 'file_input') parent = cls_or_func_node.search_ancestor('funcdef', 'classdef', 'file_input')
context = self._get_module_context().create_value(parent).as_context() context = self._get_module_context().create_value(parent).as_context()
else: else:
context = self._name.parent_context context = self._name.parent_context
@@ -834,7 +831,7 @@ class Signature(BaseSignature):
def index(self): def index(self):
""" """
Returns the param index of the current cursor position. Returns the param index of the current cursor position.
Returns None if the index cannot be found in the curent call. Returns None if the index cannot be found in the current call.
:rtype: int :rtype: int
""" """
+42 -9
View File
@@ -1,10 +1,11 @@
import re import re
from textwrap import dedent from textwrap import dedent
from typing import Any
from inspect import Parameter from inspect import Parameter
from parso.python.token import PythonTokenTypes from parso.python.token import PythonTokenTypes
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor, Leaf from parso.tree import Leaf
from parso import split_lines from parso import split_lines
from jedi import debug from jedi import debug
@@ -65,12 +66,15 @@ def _must_be_kwarg(signatures, positional_count, used_kwargs):
return must_be_kwarg return must_be_kwarg
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name): def filter_names(inference_state, completion_names, stack, like_name, fuzzy,
imported_names, cached_name):
comp_dct = set() comp_dct = set()
if settings.case_insensitive_completion: if settings.case_insensitive_completion:
like_name = like_name.lower() like_name = like_name.lower()
for name in completion_names: for name in completion_names:
string = name.string_name string = name.string_name
if string in imported_names and string != like_name:
continue
if settings.case_insensitive_completion: if settings.case_insensitive_completion:
string = string.lower() string = string.lower()
if helpers.match(string, like_name, fuzzy=fuzzy): if helpers.match(string, like_name, fuzzy=fuzzy):
@@ -138,6 +142,11 @@ class Completion:
self._fuzzy = fuzzy self._fuzzy = fuzzy
# Return list of completions in this order:
# - Beginning with what user is typing
# - Public (alphabet)
# - Private ("_xxx")
# - Dunder ("__xxx")
def complete(self): def complete(self):
leaf = self._module_node.get_leaf_for_position( leaf = self._module_node.get_leaf_for_position(
self._original_position, self._original_position,
@@ -169,14 +178,19 @@ class Completion:
cached_name, completion_names = self._complete_python(leaf) cached_name, completion_names = self._complete_python(leaf)
imported_names = []
if leaf.parent is not None and leaf.parent.type in ['import_as_names', 'dotted_as_names']:
imported_names.extend(extract_imported_names(leaf.parent))
completions = list(filter_names(self._inference_state, completion_names, completions = list(filter_names(self._inference_state, completion_names,
self.stack, self._like_name, self.stack, self._like_name,
self._fuzzy, cached_name=cached_name)) self._fuzzy, imported_names, cached_name=cached_name))
return ( return (
# Removing duplicates mostly to remove False/True/None duplicates. # Removing duplicates mostly to remove False/True/None duplicates.
_remove_duplicates(prefixed_completions, completions) _remove_duplicates(prefixed_completions, completions)
+ sorted(completions, key=lambda x: (x.name.startswith('__'), + sorted(completions, key=lambda x: (not x.name.startswith(self._like_name),
x.name.startswith('__'),
x.name.startswith('_'), x.name.startswith('_'),
x.name.lower())) x.name.lower()))
) )
@@ -231,8 +245,8 @@ class Completion:
if previous_leaf is not None: if previous_leaf is not None:
stmt = previous_leaf stmt = previous_leaf
while True: while True:
stmt = search_ancestor( stmt = stmt.search_ancestor(
stmt, 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt', 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt',
'error_node', 'error_node',
) )
if stmt is None: if stmt is None:
@@ -252,7 +266,7 @@ class Completion:
elif type_ == 'for_stmt': elif type_ == 'for_stmt':
allowed_transitions.append('else') allowed_transitions.append('else')
completion_names = [] completion_names: list[Any] = []
kwargs_only = False kwargs_only = False
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME, if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
@@ -277,6 +291,8 @@ class Completion:
) )
elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.': elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
dot = self._module_node.get_leaf_for_position(self._position) dot = self._module_node.get_leaf_for_position(self._position)
if dot.type == "newline":
dot = dot.get_previous_leaf()
if dot.type == "endmarker": if dot.type == "endmarker":
# This is a bit of a weird edge case, maybe we can somehow # This is a bit of a weird edge case, maybe we can somehow
# generalize this. # generalize this.
@@ -343,7 +359,7 @@ class Completion:
stack_node = self.stack[-3] stack_node = self.stack[-3]
if stack_node.nonterminal == 'funcdef': if stack_node.nonterminal == 'funcdef':
context = get_user_context(self._module_context, self._position) context = get_user_context(self._module_context, self._position)
node = search_ancestor(leaf, 'error_node', 'funcdef') node = leaf.search_ancestor('error_node', 'funcdef')
if node is not None: if node is not None:
if node.type == 'error_node': if node.type == 'error_node':
n = node.children[0] n = node.children[0]
@@ -413,7 +429,7 @@ class Completion:
Autocomplete inherited methods when overriding in child class. Autocomplete inherited methods when overriding in child class.
""" """
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
cls = tree.search_ancestor(leaf, 'classdef') cls = leaf.search_ancestor('classdef')
if cls is None: if cls is None:
return return
@@ -442,6 +458,7 @@ class Completion:
- Having some doctest code that starts with `>>>` - Having some doctest code that starts with `>>>`
- Having backticks that doesn't have whitespace inside it - Having backticks that doesn't have whitespace inside it
""" """
def iter_relevant_lines(lines): def iter_relevant_lines(lines):
include_next_line = False include_next_line = False
for l in code_lines: for l in code_lines:
@@ -664,3 +681,19 @@ def search_in_module(inference_state, module_context, names, wanted_names,
def_ = classes.Name(inference_state, n2) def_ = classes.Name(inference_state, n2)
if not wanted_type or wanted_type == def_.type: if not wanted_type or wanted_type == def_.type:
yield def_ yield def_
def extract_imported_names(node):
imported_names = []
if node.type in ['import_as_names', 'dotted_as_names', 'dotted_as_name', 'import_as_name']:
for index, child in enumerate(node.children):
if child.type == 'name':
if (index > 1 and node.children[index - 1].type == "keyword"
and node.children[index - 1].value == "as"):
continue
imported_names.append(child.value)
elif child.type in ('import_as_name', 'dotted_as_name'):
imported_names.extend(extract_imported_names(child))
return imported_names
+48 -31
View File
@@ -8,6 +8,7 @@ import hashlib
import filecmp import filecmp
from collections import namedtuple from collections import namedtuple
from shutil import which from shutil import which
from typing import TYPE_CHECKING, Any
from jedi.cache import memoize_method, time_cache from jedi.cache import memoize_method, time_cache
from jedi.inference.compiled.subprocess import CompiledSubprocess, \ from jedi.inference.compiled.subprocess import CompiledSubprocess, \
@@ -15,9 +16,13 @@ from jedi.inference.compiled.subprocess import CompiledSubprocess, \
import parso import parso
_VersionInfo = namedtuple('VersionInfo', 'major minor micro') if TYPE_CHECKING:
from jedi.inference import InferenceState
_SUPPORTED_PYTHONS = ['3.10', '3.9', '3.8', '3.7', '3.6']
_VersionInfo = namedtuple('VersionInfo', 'major minor micro') # type: ignore[name-match]
_SUPPORTED_PYTHONS = ['3.14', '3.13', '3.12', '3.11', '3.10']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin'] _SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CONDA_VAR = 'CONDA_PREFIX' _CONDA_VAR = 'CONDA_PREFIX'
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor) _CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
@@ -31,6 +36,9 @@ class InvalidPythonEnvironment(Exception):
class _BaseEnvironment: class _BaseEnvironment:
version_info: Any
executable: Any
@memoize_method @memoize_method
def get_grammar(self): def get_grammar(self):
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
@@ -102,7 +110,10 @@ class Environment(_BaseEnvironment):
version = '.'.join(str(i) for i in self.version_info) version = '.'.join(str(i) for i in self.version_info)
return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path) return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)
def get_inference_state_subprocess(self, inference_state): def get_inference_state_subprocess(
self,
inference_state: 'InferenceState',
) -> InferenceStateSubprocess:
return InferenceStateSubprocess(inference_state, self._get_subprocess()) return InferenceStateSubprocess(inference_state, self._get_subprocess())
@memoize_method @memoize_method
@@ -134,7 +145,10 @@ class SameEnvironment(_SameEnvironmentMixin, Environment):
class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment): class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
def get_inference_state_subprocess(self, inference_state): def get_inference_state_subprocess(
self,
inference_state: 'InferenceState',
) -> InferenceStateSameProcess:
return InferenceStateSameProcess(inference_state) return InferenceStateSameProcess(inference_state)
def get_sys_path(self): def get_sys_path(self):
@@ -240,7 +254,7 @@ def get_cached_default_environment():
# /path/to/env so we need to fully resolve the paths in order to # /path/to/env so we need to fully resolve the paths in order to
# compare them. # compare them.
if var and os.path.realpath(var) != os.path.realpath(environment.path): if var and os.path.realpath(var) != os.path.realpath(environment.path):
_get_cached_default_environment.clear_cache() _get_cached_default_environment.clear_cache() # type: ignore[attr-defined]
return _get_cached_default_environment() return _get_cached_default_environment()
return environment return environment
@@ -344,7 +358,7 @@ def get_system_environment(version, *, env_vars=None):
return SameEnvironment() return SameEnvironment()
return Environment(exe) return Environment(exe)
if os.name == 'nt': if sys.platform == "win32":
for exe in _get_executables_from_windows_registry(version): for exe in _get_executables_from_windows_registry(version):
try: try:
return Environment(exe, env_vars=env_vars) return Environment(exe, env_vars=env_vars)
@@ -372,39 +386,42 @@ def _get_executable_path(path, safe=True):
Returns None if it's not actually a virtual env. Returns None if it's not actually a virtual env.
""" """
if os.name == 'nt': if sys.platform == "win32":
python = os.path.join(path, 'Scripts', 'python.exe') pythons = [os.path.join(path, 'Scripts', 'python.exe'), os.path.join(path, 'python.exe')]
else:
pythons = [os.path.join(path, 'bin', 'python')]
for python in pythons:
if os.path.exists(python):
break
else: else:
python = os.path.join(path, 'bin', 'python')
if not os.path.exists(python):
raise InvalidPythonEnvironment("%s seems to be missing." % python) raise InvalidPythonEnvironment("%s seems to be missing." % python)
_assert_safe(python, safe) _assert_safe(python, safe)
return python return python
def _get_executables_from_windows_registry(version): if sys.platform == "win32":
# https://github.com/python/typeshed/pull/3794 adds winreg def _get_executables_from_windows_registry(version):
import winreg # type: ignore[import] import winreg
# TODO: support Python Anaconda. # TODO: support Python Anaconda.
sub_keys = [ sub_keys = [
r'SOFTWARE\Python\PythonCore\{version}\InstallPath', r'SOFTWARE\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath', r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath', r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath' r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
] ]
for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]: for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]:
for sub_key in sub_keys: for sub_key in sub_keys:
sub_key = sub_key.format(version=version) sub_key = sub_key.format(version=version)
try: try:
with winreg.OpenKey(root_key, sub_key) as key: with winreg.OpenKey(root_key, sub_key) as key:
prefix = winreg.QueryValueEx(key, '')[0] prefix = winreg.QueryValueEx(key, '')[0]
exe = os.path.join(prefix, 'python.exe') exe = os.path.join(prefix, 'python.exe')
if os.path.isfile(exe): if os.path.isfile(exe):
yield exe yield exe
except WindowsError: except WindowsError:
pass pass
def _assert_safe(executable_path, safe): def _assert_safe(executable_path, safe):
+8
View File
@@ -61,6 +61,7 @@ class MixedModuleContext(ModuleContext):
) )
def get_filters(self, until_position=None, origin_scope=None): def get_filters(self, until_position=None, origin_scope=None):
yield MergedFilter( yield MergedFilter(
MixedParserTreeFilter( MixedParserTreeFilter(
parent_context=self, parent_context=self,
@@ -72,3 +73,10 @@ class MixedModuleContext(ModuleContext):
for mixed_object in self.mixed_values: for mixed_object in self.mixed_values:
yield from mixed_object.get_filters(until_position, origin_scope) yield from mixed_object.get_filters(until_position, origin_scope)
# Now that we have merged the filter for this mixed context we have to
# remove the first entry (which is the module itself), but we want to
# add the other filters like the star imports.
filters = self._value.get_filters(origin_scope)
next(filters, None)
yield from filters
+2 -3
View File
@@ -5,8 +5,7 @@ from typing import Dict, Optional
from jedi.inference.names import AbstractArbitraryName from jedi.inference.names import AbstractArbitraryName
try: try:
# https://github.com/python/typeshed/pull/4351 adds pydoc_data from pydoc_data import topics
from pydoc_data import topics # type: ignore[import]
pydoc_topics: Optional[Dict[str, str]] = topics.topics pydoc_topics: Optional[Dict[str, str]] = topics.topics
except ImportError: except ImportError:
# Python 3.6.8 embeddable does not have pydoc_data. # Python 3.6.8 embeddable does not have pydoc_data.
@@ -42,7 +41,7 @@ def imitate_pydoc(string):
try: try:
# is a tuple now # is a tuple now
label, related = string label, related = string # type: ignore[misc]
except TypeError: except TypeError:
return '' return ''
+22 -6
View File
@@ -5,6 +5,7 @@ from typing import Dict, Iterable, Tuple
from parso import split_lines from parso import split_lines
from jedi.api.exceptions import RefactoringError from jedi.api.exceptions import RefactoringError
from jedi.inference.value.namespace import ImplicitNSName
EXPRESSION_PARTS = ( EXPRESSION_PARTS = (
'or_test and_test not_test comparison ' 'or_test and_test not_test comparison '
@@ -102,7 +103,12 @@ class Refactoring:
to_path=calculate_to_path(path), to_path=calculate_to_path(path),
module_node=next(iter(map_)).get_root_node(), module_node=next(iter(map_)).get_root_node(),
node_to_str_map=map_ node_to_str_map=map_
) for path, map_ in sorted(self._file_to_node_changes.items()) )
# We need to use `or`, because the path can be None
for path, map_ in sorted(
self._file_to_node_changes.items(),
key=lambda x: x[0] or Path("")
)
} }
def get_renames(self) -> Iterable[Tuple[Path, Path]]: def get_renames(self) -> Iterable[Tuple[Path, Path]]:
@@ -116,7 +122,7 @@ class Refactoring:
project_path = self._inference_state.project.path project_path = self._inference_state.project.path
for from_, to in self.get_renames(): for from_, to in self.get_renames():
text += 'rename from %s\nrename to %s\n' \ text += 'rename from %s\nrename to %s\n' \
% (from_.relative_to(project_path), to.relative_to(project_path)) % (_try_relative_to(from_, project_path), _try_relative_to(to, project_path))
return text + ''.join(f.get_diff() for f in self.get_changed_files().values()) return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
@@ -146,13 +152,16 @@ def rename(inference_state, definitions, new_name):
raise RefactoringError("There is no name under the cursor") raise RefactoringError("There is no name under the cursor")
for d in definitions: for d in definitions:
# This private access is ok in a way. It's not public to
# protect Jedi users from seeing it.
tree_name = d._name.tree_name tree_name = d._name.tree_name
if d.type == 'module' and tree_name is None: if d.type == 'module' and tree_name is None and d.module_path is not None:
p = None if d.module_path is None else Path(d.module_path) p = Path(d.module_path)
file_renames.add(_calculate_rename(p, new_name)) file_renames.add(_calculate_rename(p, new_name))
elif isinstance(d._name, ImplicitNSName):
for p in d._name._value.py__path__():
file_renames.add(_calculate_rename(Path(p), new_name))
else: else:
# This private access is ok in a way. It's not public to
# protect Jedi users from seeing it.
if tree_name is not None: if tree_name is not None:
fmap = file_tree_name_map.setdefault(d.module_path, {}) fmap = file_tree_name_map.setdefault(d.module_path, {})
fmap[tree_name] = tree_name.prefix + new_name fmap[tree_name] = tree_name.prefix + new_name
@@ -246,3 +255,10 @@ def _remove_indent_of_prefix(prefix):
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n". Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
""" """
return ''.join(split_lines(prefix, keepends=True)[:-1]) return ''.join(split_lines(prefix, keepends=True)[:-1])
def _try_relative_to(path: Path, base: Path) -> Path:
try:
return path.relative_to(base)
except ValueError:
return path
+1 -1
View File
@@ -9,7 +9,7 @@ just use IPython instead::
Then you will be able to use Jedi completer in your Python interpreter:: Then you will be able to use Jedi completer in your Python interpreter::
$ python $ python
Python 3.9.2+ (default, Jul 20 2020, 22:15:08) Python 3.14.0+ (default, Jul 20 2020, 22:15:08)
[GCC 4.6.1] on linux2 [GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> import os >>> import os
+5 -2
View File
@@ -36,8 +36,11 @@ def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
string = cut_value_at_position(leaf, position) string = cut_value_at_position(leaf, position)
context = module_context.create_context(bracket_leaf) context = module_context.create_context(bracket_leaf)
before_bracket_leaf = bracket_leaf.get_previous_leaf()
if before_bracket_leaf.type in ('atom', 'trailer', 'name'): before_node = before_bracket_leaf = bracket_leaf.get_previous_leaf()
if before_node in (')', ']', '}'):
before_node = before_node.parent
if before_node.type in ('atom', 'trailer', 'name'):
values = infer_call_of_leaf(context, before_bracket_leaf) values = infer_call_of_leaf(context, before_bracket_leaf)
return list(_completions_for_dicts( return list(_completions_for_dicts(
module_context.inference_state, module_context.inference_state,
+2 -2
View File
@@ -28,7 +28,7 @@ def clear_time_caches(delete_all: bool = False) -> None:
:param delete_all: Deletes also the cache that is normally not deleted, :param delete_all: Deletes also the cache that is normally not deleted,
like parser cache, which is important for faster parsing. like parser cache, which is important for faster parsing.
""" """
global _time_caches global _time_caches # noqa: F824
if delete_all: if delete_all:
for cache in _time_caches.values(): for cache in _time_caches.values():
@@ -93,7 +93,7 @@ def time_cache(seconds):
cache[key] = time.time(), result cache[key] = time.time(), result
return result return result
wrapper.clear_cache = lambda: cache.clear() wrapper.clear_cache = lambda: cache.clear() # type: ignore[attr-defined]
return wrapper return wrapper
return decorator return decorator
+2 -2
View File
@@ -21,7 +21,7 @@ try:
raise ImportError raise ImportError
else: else:
# Use colorama for nicer console output. # Use colorama for nicer console output.
from colorama import Fore, init # type: ignore[import] from colorama import Fore, init # type: ignore[import, unused-ignore]
from colorama import initialise from colorama import initialise
def _lazy_colorama_init(): # noqa: F811 def _lazy_colorama_init(): # noqa: F811
@@ -36,7 +36,7 @@ try:
# pytest resets the stream at the end - causes troubles. Since # pytest resets the stream at the end - causes troubles. Since
# after every output the stream is reset automatically we don't # after every output the stream is reset automatically we don't
# need this. # need this.
initialise.atexit_done = True initialise.atexit_done = True # type: ignore[attr-defined]
try: try:
init(strip=False) init(strip=False)
except Exception: except Exception:
+3
View File
@@ -1,4 +1,5 @@
import os import os
from typing import Any
from parso import file_io from parso import file_io
@@ -58,6 +59,8 @@ class FolderIO(AbstractFolderIO):
class FileIOFolderMixin: class FileIOFolderMixin:
path: Any
def get_parent_folder(self): def get_parent_folder(self):
return FolderIO(os.path.dirname(self.path)) return FolderIO(os.path.dirname(self.path))
+25 -6
View File
@@ -62,6 +62,8 @@ I need to mention now that lazy type inference is really good because it
only *inferes* what needs to be *inferred*. All the statements and modules only *inferes* what needs to be *inferred*. All the statements and modules
that are not used are just being ignored. that are not used are just being ignored.
""" """
from typing import Any
import parso import parso
from jedi.file_io import FileIO from jedi.file_io import FileIO
@@ -82,6 +84,8 @@ from jedi.plugins import plugin_manager
class InferenceState: class InferenceState:
analysis_modules: "list[Any]"
def __init__(self, project, environment=None, script_path=None): def __init__(self, project, environment=None, script_path=None):
if environment is None: if environment is None:
environment = project.get_environment() environment = project.get_environment()
@@ -90,7 +94,7 @@ class InferenceState:
self.compiled_subprocess = environment.get_inference_state_subprocess(self) self.compiled_subprocess = environment.get_inference_state_subprocess(self)
self.grammar = environment.get_grammar() self.grammar = environment.get_grammar()
self.latest_grammar = parso.load_grammar(version='3.10') self.latest_grammar = parso.load_grammar(version='3.13')
self.memoize_cache = {} # for memoize decorators self.memoize_cache = {} # for memoize decorators
self.module_cache = imports.ModuleCache() # does the job of `sys.modules`. self.module_cache = imports.ModuleCache() # does the job of `sys.modules`.
self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]] self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]]
@@ -99,10 +103,11 @@ class InferenceState:
self.mixed_cache = {} # see `inference.compiled.mixed._create()` self.mixed_cache = {} # see `inference.compiled.mixed._create()`
self.analysis = [] self.analysis = []
self.dynamic_params_depth = 0 self.dynamic_params_depth = 0
self.do_dynamic_params_search = settings.dynamic_params
self.is_analysis = False self.is_analysis = False
self.project = project self.project = project
self.access_cache = {} self.access_cache = {}
self.allow_descriptor_getattr = False self.allow_unsafe_executions = False
self.flow_analysis_enabled = True self.flow_analysis_enabled = True
self.reset_recursion_limitations() self.reset_recursion_limitations()
@@ -120,20 +125,34 @@ class InferenceState:
debug.dbg('execute result: %s in %s', value_set, value) debug.dbg('execute result: %s in %s', value_set, value)
return value_set return value_set
# mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362) # mypy doesn't support decorated properties (https://github.com/python/mypy/issues/1362)
@property # type: ignore[misc] @property
@inference_state_function_cache() @inference_state_function_cache()
def builtins_module(self): def builtins_module(self):
module_name = 'builtins' module_name = 'builtins'
builtins_module, = self.import_module((module_name,), sys_path=()) builtins_module, = self.import_module((module_name,), sys_path=[])
return builtins_module return builtins_module
@property # type: ignore[misc] @property
@inference_state_function_cache() @inference_state_function_cache()
def typing_module(self): def typing_module(self):
typing_module, = self.import_module(('typing',)) typing_module, = self.import_module(('typing',))
return typing_module return typing_module
@property
@inference_state_function_cache()
def types_module(self):
typing_module, = self.import_module(('types',))
return typing_module
@inference_state_function_cache()
def typing_tuple(self):
return self.typing_module.py__getattribute__("Tuple")
@inference_state_function_cache()
def typing_type(self):
return self.typing_module.py__getattribute__("Type")
def reset_recursion_limitations(self): def reset_recursion_limitations(self):
self.recursion_detector = recursion.RecursionDetector() self.recursion_detector = recursion.RecursionDetector()
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
+1 -1
View File
@@ -110,7 +110,7 @@ def _check_for_setattr(instance):
def add_attribute_error(name_context, lookup_value, name): def add_attribute_error(name_context, lookup_value, name):
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name)) message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
# Check for __getattr__/__getattribute__ existance and issue a warning # Check for __getattr__/__getattribute__ existence and issue a warning
# instead of an error, if that happens. # instead of an error, if that happens.
typ = Error typ = Error
if lookup_value.is_instance() and not lookup_value.is_compiled(): if lookup_value.is_instance() and not lookup_value.is_compiled():
+4 -1
View File
@@ -1,5 +1,6 @@
import re import re
from itertools import zip_longest from itertools import zip_longest
from typing import Any
from parso.python import tree from parso.python import tree
@@ -134,7 +135,7 @@ class _AbstractArgumentsMixin:
class AbstractArguments(_AbstractArgumentsMixin): class AbstractArguments(_AbstractArgumentsMixin):
context = None context = None
argument_node = None argument_node: Any = None
trailer = None trailer = None
@@ -164,6 +165,8 @@ def unpack_arglist(arglist):
class TreeArguments(AbstractArguments): class TreeArguments(AbstractArguments):
context: Any
def __init__(self, inference_state, context, argument_node, trailer=None): def __init__(self, inference_state, context, argument_node, trailer=None):
""" """
:param argument_node: May be an argument_node or a list of nodes. :param argument_node: May be an argument_node or a list of nodes.
+26 -7
View File
@@ -9,6 +9,7 @@ just one.
from functools import reduce from functools import reduce
from operator import add from operator import add
from itertools import zip_longest from itertools import zip_longest
from typing import TYPE_CHECKING, Any
from parso.python.tree import Name from parso.python.tree import Name
@@ -21,12 +22,25 @@ from jedi.cache import memoize_method
sentinel = object() sentinel = object()
if TYPE_CHECKING:
from jedi.inference import InferenceState
class HasNoContext(Exception): class HasNoContext(Exception):
pass pass
class HelperValueMixin: class HelperValueMixin:
parent_context: Any
inference_state: "InferenceState"
name: Any
get_filters: Any
is_stub: Any
py__getattribute__alternatives: Any
py__iter__: Any
py__mro__: Any
_as_context: Any
def get_root_context(self): def get_root_context(self):
value = self value = self
if value.parent_context is None: if value.parent_context is None:
@@ -45,7 +59,7 @@ class HelperValueMixin:
arguments = ValuesArguments([ValueSet([value]) for value in value_list]) arguments = ValuesArguments([ValueSet([value]) for value in value_list])
return self.inference_state.execute(self, arguments) return self.inference_state.execute(self, arguments)
def execute_annotation(self): def execute_annotation(self, context):
return self.execute_with_values() return self.execute_with_values()
def gather_annotation_classes(self): def gather_annotation_classes(self):
@@ -337,11 +351,16 @@ class _ValueWrapperBase(HelperValueMixin):
class LazyValueWrapper(_ValueWrapperBase): class LazyValueWrapper(_ValueWrapperBase):
@safe_property if TYPE_CHECKING:
@memoize_method @property
def _wrapped_value(self): def _wrapped_value(self) -> Any:
with debug.increase_indent_cm('Resolve lazy value wrapper'): return
return self._get_wrapped_value() else:
@safe_property
@memoize_method
def _wrapped_value(self):
with debug.increase_indent_cm('Resolve lazy value wrapper'):
return self._get_wrapped_value()
def __repr__(self): def __repr__(self):
return '<%s>' % (self.__class__.__name__) return '<%s>' % (self.__class__.__name__)
@@ -499,7 +518,7 @@ class ValueSet:
return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set) return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set)
def try_merge(self, function_name): def try_merge(self, function_name):
value_set = self.__class__([]) value_set = ValueSet([])
for c in self._set: for c in self._set:
try: try:
method = getattr(c, function_name) method = getattr(c, function_name)
+3 -2
View File
@@ -14,8 +14,9 @@ def builtin_from_name(inference_state, string):
else: else:
filter_ = next(typing_builtins_module.get_filters()) filter_ = next(typing_builtins_module.get_filters())
name, = filter_.get(string) name, = filter_.get(string)
value, = name.infer() # Most of the time there is only symbol, but sometimes there are different
return value # sys.version_infos, where there are multiple ones, just use the first one.
return next(iter(name.infer()))
class ExactValue(LazyValueWrapper): class ExactValue(LazyValueWrapper):
+51 -30
View File
@@ -9,7 +9,7 @@ import re
import builtins import builtins
import typing import typing
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, Tuple
from jedi.inference.compiled.getattr_static import getattr_static from jedi.inference.compiled.getattr_static import getattr_static
@@ -40,7 +40,7 @@ NOT_CLASS_TYPES = (
MethodDescriptorType = type(str.replace) MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__) WrapperDescriptorType = type(set.__iter__)
# `object.__subclasshook__` is an already executed descriptor. # `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object) object_class_dict = type.__dict__["__dict__"].__get__(object) # type: ignore[index]
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__']) ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
_sentinel = object() _sentinel = object()
@@ -147,7 +147,7 @@ class AccessPath:
self.accesses = accesses self.accesses = accesses
def create_access_path(inference_state, obj): def create_access_path(inference_state, obj) -> AccessPath:
access = create_access(inference_state, obj) access = create_access(inference_state, obj)
return AccessPath(access.get_access_path_tuples()) return AccessPath(access.get_access_path_tuples())
@@ -175,7 +175,7 @@ class DirectObjectAccess:
def _create_access(self, obj): def _create_access(self, obj):
return create_access(self._inference_state, obj) return create_access(self._inference_state, obj)
def _create_access_path(self, obj): def _create_access_path(self, obj) -> AccessPath:
return create_access_path(self._inference_state, obj) return create_access_path(self._inference_state, obj)
def py__bool__(self): def py__bool__(self):
@@ -184,7 +184,7 @@ class DirectObjectAccess:
def py__file__(self) -> Optional[Path]: def py__file__(self) -> Optional[Path]:
try: try:
return Path(self._obj.__file__) return Path(self._obj.__file__)
except AttributeError: except (AttributeError, TypeError):
return None return None
def py__doc__(self): def py__doc__(self):
@@ -230,8 +230,8 @@ class DirectObjectAccess:
return [annotation] return [annotation]
return None return None
def py__simple_getitem__(self, index): def py__simple_getitem__(self, index, *, safe=True):
if type(self._obj) not in ALLOWED_GETITEM_TYPES: if safe and type(self._obj) not in ALLOWED_GETITEM_TYPES:
# Get rid of side effects, we won't call custom `__getitem__`s. # Get rid of side effects, we won't call custom `__getitem__`s.
return None return None
@@ -329,33 +329,37 @@ class DirectObjectAccess:
except TypeError: except TypeError:
return False return False
def is_allowed_getattr(self, name, safe=True): def is_allowed_getattr(self, name, safe=True) -> Tuple[bool, bool, Optional[AccessPath]]:
# TODO this API is ugly. # TODO this API is ugly.
if not safe:
# Unsafe is mostly used to check for __getattr__/__getattribute__.
# getattr_static works for properties, but the underscore methods
# are just ignored (because it's safer and avoids more code
# execution). See also GH #1378.
# Avoid warnings, see comment in the next function.
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
try:
return hasattr(self._obj, name), False
except Exception:
# Obviously has an attribute (propably a property) that
# gets executed, so just avoid all exceptions here.
return False, False
try: try:
attr, is_get_descriptor = getattr_static(self._obj, name) attr, is_get_descriptor = getattr_static(self._obj, name)
except AttributeError: except AttributeError:
return False, False if not safe:
# Unsafe is mostly used to check for __getattr__/__getattribute__.
# getattr_static works for properties, but the underscore methods
# are just ignored (because it's safer and avoids more code
# execution). See also GH #1378.
# Avoid warnings, see comment in the next function.
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
try:
return hasattr(self._obj, name), False, None
except Exception:
# Obviously has an attribute (probably a property) that
# gets executed, so just avoid all exceptions here.
pass
return False, False, None
else: else:
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS: if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
if isinstance(attr, property):
if hasattr(attr.fget, '__annotations__'):
a = DirectObjectAccess(self._inference_state, attr.fget)
return True, True, a.get_return_annotation()
# In case of descriptors that have get methods we cannot return # In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution. # it's value, because that would mean code execution.
return True, True return True, True, None
return True, False return True, False, None
def getattr_paths(self, name, default=_sentinel): def getattr_paths(self, name, default=_sentinel):
try: try:
@@ -467,18 +471,23 @@ class DirectObjectAccess:
op = _OPERATORS[operator] op = _OPERATORS[operator]
return self._create_access_path(op(self._obj, other_access._obj)) return self._create_access_path(op(self._obj, other_access._obj))
def get_annotation_name_and_args(self): def get_annotation_name_and_args(self) -> tuple[str | None, tuple[AccessPath, ...]]:
""" """
Returns Tuple[Optional[str], Tuple[AccessPath, ...]] Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
""" """
name = None name = None
args = () args = ()
if safe_getattr(self._obj, '__module__', default='') == 'typing': if type(self._obj) is typing.Union: # zuban: ignore[comparison-overlap] # TODO zuban
# This is mostly formatted like `int | str` and we therefor need to
# check the type.
args = typing.get_args(self._obj)
name = "Union"
elif safe_getattr(self._obj, '__module__', default='') == 'typing':
# Try regex first (works for most types)
m = re.match(r'typing.(\w+)\[', repr(self._obj)) m = re.match(r'typing.(\w+)\[', repr(self._obj))
if m is not None: if m is not None:
name = m.group(1) name = m.group(1)
import typing
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
args = typing.get_args(self._obj) args = typing.get_args(self._obj)
else: else:
@@ -489,6 +498,18 @@ class DirectObjectAccess:
return inspect.isclass(self._obj) and self._obj != type return inspect.isclass(self._obj) and self._obj != type
def _annotation_to_str(self, annotation): def _annotation_to_str(self, annotation):
# In Python 3.14+, Union types are displayed as X | Y instead of Union[X, Y]
# We normalize to that for consistency
import typing
origin = typing.get_origin(annotation)
if origin is typing.Union:
# Get the args and format them as Union[...]
args = typing.get_args(annotation)
return ' | '.join(
self._annotation_to_str(arg) if hasattr(arg, '__origin__')
else getattr(arg, '__name__', str(arg))
for arg in args
)
return inspect.formatannotation(annotation) return inspect.formatannotation(annotation)
def get_signature_params(self): def get_signature_params(self):
@@ -515,7 +536,7 @@ class DirectObjectAccess:
# the signature. In that case we just want a simple escape for now. # the signature. In that case we just want a simple escape for now.
raise ValueError raise ValueError
def get_return_annotation(self): def get_return_annotation(self) -> Optional[AccessPath]:
try: try:
o = self._obj.__annotations__.get('return') o = self._obj.__annotations__.get('return')
except AttributeError: except AttributeError:
+6 -3
View File
@@ -39,7 +39,7 @@ def _is_type(obj):
def _shadowed_dict(klass): def _shadowed_dict(klass):
dict_attr = type.__dict__["__dict__"] dict_attr = type.__dict__["__dict__"] # type: ignore[index]
for entry in _static_getmro(klass): for entry in _static_getmro(klass):
try: try:
class_dict = dict_attr.__get__(entry)["__dict__"] class_dict = dict_attr.__get__(entry)["__dict__"]
@@ -54,7 +54,7 @@ def _shadowed_dict(klass):
def _static_getmro(klass): def _static_getmro(klass):
mro = type.__dict__['__mro__'].__get__(klass) mro = type.__dict__['__mro__'].__get__(klass) # type: ignore[index]
if not isinstance(mro, (tuple, list)): if not isinstance(mro, (tuple, list)):
# There are unfortunately no tests for this, I was not able to # There are unfortunately no tests for this, I was not able to
# reproduce this in pure Python. However should still solve the issue # reproduce this in pure Python. However should still solve the issue
@@ -90,7 +90,10 @@ def getattr_static(obj, attr, default=_sentinel):
if not _is_type(obj): if not _is_type(obj):
klass = type(obj) klass = type(obj)
dict_attr = _shadowed_dict(klass) dict_attr = _shadowed_dict(klass)
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType): # In Python 3.15+, __dict__ is a GetSetDescriptorType instead of being _sentinel
if (dict_attr is _sentinel
or type(dict_attr) is types.MemberDescriptorType
or type(dict_attr) is types.GetSetDescriptorType):
instance_result = _check_instance(obj, attr) instance_result = _check_instance(obj, attr)
else: else:
klass = obj klass = obj
+2 -2
View File
@@ -142,9 +142,9 @@ class MixedObjectFilter(compiled.CompiledValueFilter):
super().__init__(inference_state, compiled_value) super().__init__(inference_state, compiled_value)
self._tree_value = tree_value self._tree_value = tree_value
def _create_name(self, name): def _create_name(self, *args, **kwargs):
return MixedName( return MixedName(
super()._create_name(name), super()._create_name(*args, **kwargs),
self._tree_value, self._tree_value,
) )
+142 -19
View File
@@ -5,6 +5,23 @@ goals:
1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can 1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can
be ignored and dealt with. be ignored and dealt with.
2. Make it possible to handle different Python versions as well as virtualenvs. 2. Make it possible to handle different Python versions as well as virtualenvs.
The architecture here is briefly:
- For each Jedi `Environment` there is a corresponding subprocess which
operates within the target environment. If the subprocess dies it is replaced
at this level.
- `CompiledSubprocess` manages exactly one subprocess and handles communication
from the parent side.
- `Listener` runs within the subprocess, processing each request and yielding
results.
- `InterpreterEnvironment` provides an API which matches that of `Environment`,
but runs functionality inline rather than within a subprocess. It is thus
used both directly in places where a subprocess is unnecessary and/or
undesirable and also within subprocesses themselves.
- `InferenceStateSubprocess` (or `InferenceStateSameProcess`) provide high
level access to functionality within the subprocess from within the parent.
Each `InterpreterState` has an instance of one of these, provided by its
environment.
""" """
import collections import collections
@@ -16,6 +33,7 @@ import traceback
import weakref import weakref
from functools import partial from functools import partial
from threading import Thread from threading import Thread
from typing import Dict, TYPE_CHECKING, Any
from jedi._compatibility import pickle_dump, pickle_load from jedi._compatibility import pickle_dump, pickle_load
from jedi import debug from jedi import debug
@@ -25,13 +43,16 @@ from jedi.inference.compiled.access import DirectObjectAccess, AccessPath, \
SignatureParam SignatureParam
from jedi.api.exceptions import InternalError from jedi.api.exceptions import InternalError
if TYPE_CHECKING:
from jedi.inference import InferenceState
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py') _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
PICKLE_PROTOCOL = 4 PICKLE_PROTOCOL = 4
def _GeneralizedPopen(*args, **kwargs): def _GeneralizedPopen(*args, **kwargs):
if os.name == 'nt': if sys.platform == "win32":
try: try:
# Was introduced in Python 3.7. # Was introduced in Python 3.7.
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
@@ -83,10 +104,11 @@ def _cleanup_process(process, thread):
class _InferenceStateProcess: class _InferenceStateProcess:
def __init__(self, inference_state): get_compiled_method_return: Any
def __init__(self, inference_state: 'InferenceState') -> None:
self._inference_state_weakref = weakref.ref(inference_state) self._inference_state_weakref = weakref.ref(inference_state)
self._inference_state_id = id(inference_state) self._handles: Dict[int, AccessHandle] = {}
self._handles = {}
def get_or_create_access_handle(self, obj): def get_or_create_access_handle(self, obj):
id_ = id(obj) id_ = id(obj)
@@ -116,11 +138,49 @@ class InferenceStateSameProcess(_InferenceStateProcess):
class InferenceStateSubprocess(_InferenceStateProcess): class InferenceStateSubprocess(_InferenceStateProcess):
def __init__(self, inference_state, compiled_subprocess): """
API to functionality which will run in a subprocess.
This mediates the interaction between an `InferenceState` and the actual
execution of functionality running within a `CompiledSubprocess`. Available
functions are defined in `.functions`, though should be accessed via
attributes on this class of the same name.
This class is responsible for indicating that the `InferenceState` within
the subprocess can be removed once the corresponding instance in the parent
goes away.
"""
def __init__(
self,
inference_state: 'InferenceState',
compiled_subprocess: 'CompiledSubprocess',
) -> None:
super().__init__(inference_state) super().__init__(inference_state)
self._used = False self._used = False
self._compiled_subprocess = compiled_subprocess self._compiled_subprocess = compiled_subprocess
# Opaque id we'll pass to the subprocess to identify the context (an
# `InferenceState`) which should be used for the request. This allows us
# to make subsequent requests which operate on results from previous
# ones, while keeping a single subprocess which can work with several
# contexts in the parent process. Once it is no longer needed(i.e: when
# this class goes away), we also use this id to indicate that the
# subprocess can discard the context.
#
# Note: this id is deliberately coupled to this class (and not to
# `InferenceState`) as this class manages access handle mappings which
# must correspond to those in the subprocess. This approach also avoids
# race conditions from successive `InferenceState`s with the same object
# id (as observed while adding support for Python 3.13).
#
# This value does not need to be the `id()` of this instance, we merely
# need to ensure that it enables the (visible) lifetime of the context
# within the subprocess to match that of this class. We therefore also
# depend on the semantics of `CompiledSubprocess.delete_inference_state`
# for correctness.
self._inference_state_id = id(self)
def __getattr__(self, name): def __getattr__(self, name):
func = _get_function(name) func = _get_function(name)
@@ -128,7 +188,7 @@ class InferenceStateSubprocess(_InferenceStateProcess):
self._used = True self._used = True
result = self._compiled_subprocess.run( result = self._compiled_subprocess.run(
self._inference_state_weakref(), self._inference_state_id,
func, func,
args=args, args=args,
kwargs=kwargs, kwargs=kwargs,
@@ -164,6 +224,17 @@ class InferenceStateSubprocess(_InferenceStateProcess):
class CompiledSubprocess: class CompiledSubprocess:
"""
A subprocess which runs inference within a target environment.
This class manages the interface to a single instance of such a process as
well as the lifecycle of the process itself. See `.__main__` and `Listener`
for the implementation of the subprocess and details of the protocol.
A single live instance of this is maintained by `jedi.api.environment.Environment`,
so that typically a single subprocess is used at a time.
"""
is_crashed = False is_crashed = False
def __init__(self, executable, env_vars=None): def __init__(self, executable, env_vars=None):
@@ -213,18 +284,18 @@ class CompiledSubprocess:
t) t)
return process return process
def run(self, inference_state, function, args=(), kwargs={}): def run(self, inference_state_id, function, args=(), kwargs={}):
# Delete old inference_states. # Delete old inference_states.
while True: while True:
try: try:
inference_state_id = self._inference_state_deletion_queue.pop() delete_id = self._inference_state_deletion_queue.pop()
except IndexError: except IndexError:
break break
else: else:
self._send(inference_state_id, None) self._send(delete_id, None)
assert callable(function) assert callable(function)
return self._send(id(inference_state), function, args, kwargs) return self._send(inference_state_id, function, args, kwargs)
def get_sys_path(self): def get_sys_path(self):
return self._send(None, functions.get_sys_path, (), {}) return self._send(None, functions.get_sys_path, (), {})
@@ -272,21 +343,65 @@ class CompiledSubprocess:
def delete_inference_state(self, inference_state_id): def delete_inference_state(self, inference_state_id):
""" """
Currently we are not deleting inference_state instantly. They only get Indicate that an inference state (in the subprocess) is no longer
deleted once the subprocess is used again. It would probably a better needed.
solution to move all of this into a thread. However, the memory usage
of a single inference_state shouldn't be that high. The state corresponding to the given id will become inaccessible and the
id may safely be re-used to refer to a different context.
Note: it is not guaranteed that the corresponding state will actually be
deleted immediately.
""" """
# With an argument - the inference_state gets deleted. # Warning: if changing the semantics of context deletion see the comment
# in `InferenceStateSubprocess.__init__` regarding potential race
# conditions.
# Currently we are not deleting the related state instantly. They only
# get deleted once the subprocess is used again. It would probably a
# better solution to move all of this into a thread. However, the memory
# usage of a single inference_state shouldn't be that high.
self._inference_state_deletion_queue.append(inference_state_id) self._inference_state_deletion_queue.append(inference_state_id)
class Listener: class Listener:
"""
Main loop for the subprocess which actually does the inference.
This class runs within the target environment. It listens to instructions
from the parent process, runs inference and returns the results.
The subprocess has a long lifetime and is expected to process several
requests, including for different `InferenceState` instances in the parent.
See `CompiledSubprocess` for the parent half of the system.
Communication is via pickled data sent serially over stdin and stdout.
Stderr is read only if the child process crashes.
The request protocol is a 4-tuple of:
* inference_state_id | None: an opaque identifier of the parent's
`InferenceState`. An `InferenceState` operating over an
`InterpreterEnvironment` is created within this process for each of
these, ensuring that each parent context has a corresponding context
here. This allows context to be persisted between requests. Unless
`None`, the local `InferenceState` will be passed to the given function
as the first positional argument.
* function | None: the function to run. This is expected to be a member of
`.functions`. `None` indicates that the corresponding inference state is
no longer needed and should be dropped.
* args: positional arguments to the `function`. If any of these are
`AccessHandle` instances they will be adapted to the local
`InferenceState` before being passed.
* kwargs: keyword arguments to the `function`. If any of these are
`AccessHandle` instances they will be adapted to the local
`InferenceState` before being passed.
The result protocol is a 3-tuple of either:
* (False, None, function result): if the function returns without error, or
* (True, traceback, exception): if the function raises an exception
"""
def __init__(self): def __init__(self):
self._inference_states = {} self._inference_states = {}
# TODO refactor so we don't need to process anymore just handle
# controlling.
self._process = _InferenceStateProcess(Listener)
def _get_inference_state(self, function, inference_state_id): def _get_inference_state(self, function, inference_state_id):
from jedi.inference import InferenceState from jedi.inference import InferenceState
@@ -308,6 +423,9 @@ class Listener:
if inference_state_id is None: if inference_state_id is None:
return function(*args, **kwargs) return function(*args, **kwargs)
elif function is None: elif function is None:
# Warning: if changing the semantics of context deletion see the comment
# in `InferenceStateSubprocess.__init__` regarding potential race
# conditions.
del self._inference_states[inference_state_id] del self._inference_states[inference_state_id]
else: else:
inference_state = self._get_inference_state(function, inference_state_id) inference_state = self._get_inference_state(function, inference_state_id)
@@ -348,7 +466,12 @@ class Listener:
class AccessHandle: class AccessHandle:
def __init__(self, subprocess, access, id_): def __init__(
self,
subprocess: _InferenceStateProcess,
access: DirectObjectAccess,
id_: int,
) -> None:
self.access = access self.access = access
self._subprocess = subprocess self._subprocess = subprocess
self.id = id_ self.id = id_
@@ -3,10 +3,6 @@ import sys
from importlib.abc import MetaPathFinder from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder from importlib.machinery import PathFinder
# Remove the first entry, because it's simply a directory entry that equals
# this directory.
del sys.path[0]
def _get_paths(): def _get_paths():
# Get the path to jedi. # Get the path to jedi.
@@ -21,11 +17,11 @@ class _ExactImporter(MetaPathFinder):
def __init__(self, path_dct): def __init__(self, path_dct):
self._path_dct = path_dct self._path_dct = path_dct
def find_module(self, fullname, path=None): def find_spec(self, fullname, path=None, target=None):
if path is None and fullname in self._path_dct: if path is None and fullname in self._path_dct:
p = self._path_dct[fullname] p = self._path_dct[fullname]
loader = PathFinder.find_module(fullname, path=[p]) spec = PathFinder.find_spec(fullname, path=[p], target=target)
return loader return spec
return None return None
+11 -10
View File
@@ -2,7 +2,6 @@ import sys
import os import os
import inspect import inspect
import importlib import importlib
import warnings
from pathlib import Path from pathlib import Path
from zipfile import ZipFile from zipfile import ZipFile
from zipimport import zipimporter, ZipImportError from zipimport import zipimporter, ZipImportError
@@ -159,7 +158,10 @@ def _find_module(string, path=None, full_name=None, is_global_search=True):
if loader is None and not spec.has_location: if loader is None and not spec.has_location:
# This is a namespace package. # This is a namespace package.
full_name = string if not path else full_name full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path) implicit_ns_info = ImplicitNSInfo(
full_name,
spec.submodule_search_locations._path, # type: ignore[union-attr]
)
return implicit_ns_info, True return implicit_ns_info, True
break break
@@ -167,17 +169,16 @@ def _find_module(string, path=None, full_name=None, is_global_search=True):
def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True): def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
loader = loader or importlib.machinery.PathFinder.find_module(string, path) if not loader:
spec = importlib.machinery.PathFinder.find_spec(string, path)
if spec is not None:
loader = spec.loader
if loader is None and path is None: # Fallback to find builtins if loader is None and path is None: # Fallback to find builtins
try: try:
with warnings.catch_warnings(record=True): spec = importlib.util.find_spec(string)
# Mute "DeprecationWarning: Use importlib.util.find_spec() if spec is not None:
# instead." While we should replace that in the future, it's loader = spec.loader
# probably good to wait until we deprecate Python 3.3, since
# it was added in Python 3.4 and find_loader hasn't been
# removed in 3.6.
loader = importlib.find_loader(string)
except ValueError as e: except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we # See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue. # just raise an ImportError to fix the issue.
+39 -23
View File
@@ -51,11 +51,10 @@ class CompiledValue(Value):
def py__call__(self, arguments): def py__call__(self, arguments):
return_annotation = self.access_handle.get_return_annotation() return_annotation = self.access_handle.get_return_annotation()
if return_annotation is not None: if return_annotation is not None:
# TODO the return annotation may also be a string.
return create_from_access_path( return create_from_access_path(
self.inference_state, self.inference_state,
return_annotation return_annotation
).execute_annotation() ).execute_annotation(arguments.context)
try: try:
self.access_handle.getattr_paths('__call__') self.access_handle.getattr_paths('__call__')
@@ -163,7 +162,10 @@ class CompiledValue(Value):
def py__simple_getitem__(self, index): def py__simple_getitem__(self, index):
with reraise_getitem_errors(IndexError, KeyError, TypeError): with reraise_getitem_errors(IndexError, KeyError, TypeError):
try: try:
access = self.access_handle.py__simple_getitem__(index) access = self.access_handle.py__simple_getitem__(
index,
safe=not self.inference_state.allow_unsafe_executions
)
except AttributeError: except AttributeError:
return super().py__simple_getitem__(index) return super().py__simple_getitem__(index)
if access is None: if access is None:
@@ -239,7 +241,7 @@ class CompiledValue(Value):
except TypeError: except TypeError:
return NO_VALUES return NO_VALUES
def execute_annotation(self): def execute_annotation(self, context):
if self.access_handle.get_repr() == 'None': if self.access_handle.get_repr() == 'None':
# None as an annotation doesn't need to be executed. # None as an annotation doesn't need to be executed.
return ValueSet([self]) return ValueSet([self])
@@ -250,7 +252,9 @@ class CompiledValue(Value):
for path in args for path in args
] ]
if name == 'Union': if name == 'Union':
return ValueSet.from_sets(arg.execute_annotation() for arg in arguments) return ValueSet.from_sets(
arg.execute_annotation(context)
for arg in arguments)
elif name: elif name:
# While with_generics only exists on very specific objects, we # While with_generics only exists on very specific objects, we
# should probably be fine, because we control all the typing # should probably be fine, because we control all the typing
@@ -258,8 +262,8 @@ class CompiledValue(Value):
return ValueSet([ return ValueSet([
v.with_generics(arguments) v.with_generics(arguments)
for v in self.inference_state.typing_module.py__getattribute__(name) for v in self.inference_state.typing_module.py__getattribute__(name)
]).execute_annotation() ]).execute_annotation(context)
return super().execute_annotation() return super().execute_annotation(context)
def negate(self): def negate(self):
return create_from_access_path(self.inference_state, self.access_handle.negate()) return create_from_access_path(self.inference_state, self.access_handle.negate())
@@ -311,11 +315,12 @@ class CompiledModule(CompiledValue):
class CompiledName(AbstractNameDefinition): class CompiledName(AbstractNameDefinition):
def __init__(self, inference_state, parent_value, name): def __init__(self, inference_state, parent_value, name, is_descriptor):
self._inference_state = inference_state self._inference_state = inference_state
self.parent_context = parent_value.as_context() self.parent_context = parent_value.as_context()
self._parent_value = parent_value self._parent_value = parent_value
self.string_name = name self.string_name = name
self.is_descriptor = is_descriptor
def py__doc__(self): def py__doc__(self):
return self.infer_compiled_value().py__doc__() return self.infer_compiled_value().py__doc__()
@@ -342,6 +347,11 @@ class CompiledName(AbstractNameDefinition):
@property @property
def api_type(self): def api_type(self):
if self.is_descriptor:
# In case of properties we want to avoid executions as much as
# possible. Since the api_type can be wrong for other reasons
# anyway, we just return instance here.
return "instance"
return self.infer_compiled_value().api_type return self.infer_compiled_value().api_type
def infer(self): def infer(self):
@@ -432,9 +442,10 @@ class CompiledValueFilter(AbstractFilter):
def get(self, name): def get(self, name):
access_handle = self.compiled_value.access_handle access_handle = self.compiled_value.access_handle
safe = not self._inference_state.allow_unsafe_executions
return self._get( return self._get(
name, name,
lambda name, safe: access_handle.is_allowed_getattr(name, safe=safe), lambda name: access_handle.is_allowed_getattr(name, safe=safe),
lambda name: name in access_handle.dir(), lambda name: name in access_handle.dir(),
check_has_attribute=True check_has_attribute=True
) )
@@ -443,30 +454,34 @@ class CompiledValueFilter(AbstractFilter):
""" """
To remove quite a few access calls we introduced the callback here. To remove quite a few access calls we introduced the callback here.
""" """
if self._inference_state.allow_descriptor_getattr: has_attribute, is_descriptor, property_return_annotation = allowed_getattr_callback(
pass
has_attribute, is_descriptor = allowed_getattr_callback(
name, name,
safe=not self._inference_state.allow_descriptor_getattr
) )
if property_return_annotation is not None:
values = create_from_access_path(
self._inference_state,
property_return_annotation
).execute_annotation(None)
if values:
return [CompiledValueName(v, name) for v in values]
if check_has_attribute and not has_attribute: if check_has_attribute and not has_attribute:
return [] return []
if (is_descriptor or not has_attribute) \ if (is_descriptor or not has_attribute) \
and not self._inference_state.allow_descriptor_getattr: and not self._inference_state.allow_unsafe_executions:
return [self._get_cached_name(name, is_empty=True)] return [self._get_cached_name(name, is_empty=True)]
if self.is_instance and not in_dir_callback(name): if self.is_instance and not in_dir_callback(name):
return [] return []
return [self._get_cached_name(name)] return [self._get_cached_name(name, is_descriptor=is_descriptor)]
@memoize_method @memoize_method
def _get_cached_name(self, name, is_empty=False): def _get_cached_name(self, name, is_empty=False, *, is_descriptor=False):
if is_empty: if is_empty:
return EmptyCompiledName(self._inference_state, name) return EmptyCompiledName(self._inference_state, name)
else: else:
return self._create_name(name) return self._create_name(name, is_descriptor=is_descriptor)
def values(self): def values(self):
from jedi.inference.compiled import builtin_from_name from jedi.inference.compiled import builtin_from_name
@@ -480,7 +495,7 @@ class CompiledValueFilter(AbstractFilter):
for name in dir_infos: for name in dir_infos:
names += self._get( names += self._get(
name, name,
lambda name, safe: dir_infos[name], lambda name: dir_infos[name],
lambda name: name in dir_infos, lambda name: name in dir_infos,
) )
@@ -490,11 +505,12 @@ class CompiledValueFilter(AbstractFilter):
names += filter.values() names += filter.values()
return names return names
def _create_name(self, name): def _create_name(self, name, is_descriptor):
return CompiledName( return CompiledName(
self._inference_state, self._inference_state,
self.compiled_value, self.compiled_value,
name name,
is_descriptor,
) )
def __repr__(self): def __repr__(self):
@@ -578,7 +594,7 @@ def create_from_name(inference_state, compiled_value, name):
value = create_cached_compiled_value( value = create_cached_compiled_value(
inference_state, inference_state,
access_path, access_path,
parent_context=None if value is None else value.as_context(), parent_context=None if value is None else value.as_context(), # type: ignore # TODO
) )
return value return value
@@ -596,7 +612,7 @@ def create_from_access_path(inference_state, access_path):
value = create_cached_compiled_value( value = create_cached_compiled_value(
inference_state, inference_state,
access, access,
parent_context=None if value is None else value.as_context() parent_context=None if value is None else value.as_context() # type: ignore # TODO
) )
return value return value
+13 -6
View File
@@ -1,9 +1,8 @@
from abc import abstractmethod from abc import abstractmethod
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, Any
from parso.tree import search_ancestor
from parso.python.tree import Name from parso.python.tree import Name
from jedi.inference.filters import ParserTreeFilter, MergedFilter, \ from jedi.inference.filters import ParserTreeFilter, MergedFilter, \
@@ -17,6 +16,8 @@ from jedi import parser_utils
class AbstractContext: class AbstractContext:
# Must be defined: inference_state and tree_node and parent_context as an attribute/property # Must be defined: inference_state and tree_node and parent_context as an attribute/property
tree_node: Any
parent_context: Any
def __init__(self, inference_state): def __init__(self, inference_state):
self.inference_state = inference_state self.inference_state = inference_state
@@ -219,6 +220,13 @@ class ValueContext(AbstractContext):
class TreeContextMixin: class TreeContextMixin:
tree_node: Any
is_module: Any
get_value: Any
inference_state: Any
is_class: Any
parent_context: Any
def infer_node(self, node): def infer_node(self, node):
from jedi.inference.syntax_tree import infer_node from jedi.inference.syntax_tree import infer_node
return infer_node(self, node) return infer_node(self, node)
@@ -290,7 +298,7 @@ class TreeContextMixin:
def create_name(self, tree_name): def create_name(self, tree_name):
definition = tree_name.get_definition() definition = tree_name.get_definition()
if definition and definition.type == 'param' and definition.name == tree_name: if definition and definition.type == 'param' and definition.name == tree_name:
funcdef = search_ancestor(definition, 'funcdef', 'lambdef') funcdef = definition.search_ancestor('funcdef', 'lambdef')
func = self.create_value(funcdef) func = self.create_value(funcdef)
return AnonymousParamName(func, tree_name) return AnonymousParamName(func, tree_name)
else: else:
@@ -301,7 +309,6 @@ class TreeContextMixin:
class FunctionContext(TreeContextMixin, ValueContext): class FunctionContext(TreeContextMixin, ValueContext):
def get_filters(self, until_position=None, origin_scope=None): def get_filters(self, until_position=None, origin_scope=None):
yield ParserTreeFilter( yield ParserTreeFilter(
self.inference_state,
parent_context=self, parent_context=self,
until_position=until_position, until_position=until_position,
origin_scope=origin_scope origin_scope=origin_scope
@@ -416,13 +423,13 @@ def _get_global_filters_for_name(context, name_or_none, position):
# function and get inferred in the value before the function. So # function and get inferred in the value before the function. So
# make sure to exclude the function/class name. # make sure to exclude the function/class name.
if name_or_none is not None: if name_or_none is not None:
ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef', 'lambdef') ancestor = name_or_none.search_ancestor('funcdef', 'classdef', 'lambdef')
lambdef = None lambdef = None
if ancestor == 'lambdef': if ancestor == 'lambdef':
# For lambdas it's even more complicated since parts will # For lambdas it's even more complicated since parts will
# be inferred later. # be inferred later.
lambdef = ancestor lambdef = ancestor
ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef') ancestor = name_or_none.search_ancestor('funcdef', 'classdef')
if ancestor is not None: if ancestor is not None:
colon = ancestor.children[-2] colon = ancestor.children[-2]
if position is not None and position < colon.start_pos: if position is not None and position < colon.start_pos:
+1 -1
View File
@@ -16,6 +16,6 @@ class DocstringModuleContext(ModuleContext):
super().__init__(module_value) super().__init__(module_value)
self._in_module_context = in_module_context self._in_module_context = in_module_context
def get_filters(self, origin_scope=None, until_position=None): def get_filters(self, until_position=None, origin_scope=None):
yield from super().get_filters(until_position=until_position) yield from super().get_filters(until_position=until_position)
yield from self._in_module_context.get_filters() yield from self._in_module_context.get_filters()
+3 -3
View File
@@ -48,7 +48,7 @@ def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)): if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
raise _numpy_doc_string_cache raise _numpy_doc_string_cache
from numpydoc.docscrape import NumpyDocString # type: ignore[import] from numpydoc.docscrape import NumpyDocString # type: ignore[import, unused-ignore]
_numpy_doc_string_cache = NumpyDocString _numpy_doc_string_cache = NumpyDocString
return _numpy_doc_string_cache return _numpy_doc_string_cache
@@ -109,7 +109,7 @@ def _expand_typestr(type_str):
yield type_str.split('of')[0] yield type_str.split('of')[0]
# Check if type has is a set of valid literal values eg: {'C', 'F', 'A'} # Check if type has is a set of valid literal values eg: {'C', 'F', 'A'}
elif type_str.startswith('{'): elif type_str.startswith('{'):
node = parse(type_str, version='3.7').children[0] node = parse(type_str, version='3.13').children[0]
if node.type == 'atom': if node.type == 'atom':
for leaf in getattr(node.children[1], "children", []): for leaf in getattr(node.children[1], "children", []):
if leaf.type == 'number': if leaf.type == 'number':
@@ -246,7 +246,7 @@ def _execute_array_values(inference_state, array):
cls = FakeTuple if array.array_type == 'tuple' else FakeList cls = FakeTuple if array.array_type == 'tuple' else FakeList
return {cls(inference_state, values)} return {cls(inference_state, values)}
else: else:
return array.execute_annotation() return array.execute_annotation(None)
@inference_state_method_cache() @inference_state_method_cache()
+5 -5
View File
@@ -66,11 +66,11 @@ def dynamic_param_lookup(function_value, param_index):
have to look for all calls to ``func`` to find out what ``foo`` possibly have to look for all calls to ``func`` to find out what ``foo`` possibly
is. is.
""" """
funcdef = function_value.tree_node if not function_value.inference_state.do_dynamic_params_search:
if not settings.dynamic_params:
return NO_VALUES return NO_VALUES
funcdef = function_value.tree_node
path = function_value.get_root_context().py__file__() path = function_value.get_root_context().py__file__()
if path is not None and is_stdlib_path(path): if path is not None and is_stdlib_path(path):
# We don't want to search for references in the stdlib. Usually people # We don't want to search for references in the stdlib. Usually people
@@ -109,7 +109,7 @@ def _search_function_arguments(module_context, funcdef, string_name):
if string_name == '__init__': if string_name == '__init__':
cls = get_parent_scope(funcdef) cls = get_parent_scope(funcdef)
if cls.type == 'classdef': if cls.type == 'classdef':
string_name = cls.name.value string_name = cls.name.value # type: ignore[union-attr]
compare_node = cls compare_node = cls
found_arguments = False found_arguments = False
@@ -203,7 +203,7 @@ def _check_name_for_execution(inference_state, context, compare_node, name, trai
# Here we're trying to find decorators by checking the first # Here we're trying to find decorators by checking the first
# parameter. It's not very generic though. Should find a better # parameter. It's not very generic though. Should find a better
# solution that also applies to nested decorators. # solution that also applies to nested decorators.
param_names = value.parent_context.get_param_names() param_names = value.parent_context.get_param_names() # type: ignore[attr-defined]
if len(param_names) != 1: if len(param_names) != 1:
continue continue
values = param_names[0].infer() values = param_names[0].infer()
+7 -5
View File
@@ -3,10 +3,9 @@ Filters are objects that you can use to filter names in different scopes. They
are needed for name resolution. are needed for name resolution.
""" """
from abc import abstractmethod from abc import abstractmethod
from typing import List, MutableMapping, Type from typing import MutableMapping, Type, Any
import weakref import weakref
from parso.tree import search_ancestor
from parso.python.tree import Name, UsedNamesMapping from parso.python.tree import Name, UsedNamesMapping
from jedi.inference import flow_analysis from jedi.inference import flow_analysis
@@ -17,8 +16,8 @@ from jedi.inference.utils import to_list
from jedi.inference.names import TreeNameDefinition, ParamName, \ from jedi.inference.names import TreeNameDefinition, ParamName, \
AnonymousParamName, AbstractNameDefinition, NameWrapper AnonymousParamName, AbstractNameDefinition, NameWrapper
_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]] _definition_name_cache: 'MutableMapping[UsedNamesMapping, dict[str, tuple[Name, ...]]]' \
_definition_name_cache = weakref.WeakKeyDictionary() = weakref.WeakKeyDictionary()
class AbstractFilter: class AbstractFilter:
@@ -181,7 +180,7 @@ class _FunctionExecutionFilter(ParserTreeFilter):
@to_list @to_list
def _convert_names(self, names): def _convert_names(self, names):
for name in names: for name in names:
param = search_ancestor(name, 'param') param = name.search_ancestor('param')
# Here we don't need to check if the param is a default/annotation, # Here we don't need to check if the param is a default/annotation,
# because those are not definitions and never make it to this # because those are not definitions and never make it to this
# point. # point.
@@ -347,6 +346,9 @@ class _OverwriteMeta(type):
class _AttributeOverwriteMixin: class _AttributeOverwriteMixin:
overwritten_methods: Any
_wrapped_value: Any
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value) yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
yield from self._wrapped_value.get_filters(*args, **kwargs) yield from self._wrapped_value.get_filters(*args, **kwargs)
+1 -2
View File
@@ -15,7 +15,6 @@ Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
check for -> a is a string). There's big potential in these checks. check for -> a is a string). There's big potential in these checks.
""" """
from parso.tree import search_ancestor
from parso.python.tree import Name from parso.python.tree import Name
from jedi import settings from jedi import settings
@@ -76,7 +75,7 @@ def check_flow_information(value, flow, search_name, pos):
]) ])
for name in names: for name in names:
ass = search_ancestor(name, 'assert_stmt') ass = name.search_ancestor('assert_stmt')
if ass is not None: if ass is not None:
result = _check_isinstance_type(value, ass.assertion, search_name) result = _check_isinstance_type(value, ass.assertion, search_name)
if result is not None: if result is not None:
+22 -15
View File
@@ -32,17 +32,20 @@ def infer_annotation(context, annotation):
Also checks for forward references (strings) Also checks for forward references (strings)
""" """
value_set = context.infer_node(annotation) value_set = context.infer_node(annotation)
if len(value_set) != 1: if len(value_set) == 0:
debug.warning("Inferred typing index %s should lead to 1 object, " debug.warning(
" not %s" % (annotation, value_set)) "Inferred typing index %s should lead to 1 object, not %s" % (annotation, value_set))
return value_set return value_set
inferred_value = list(value_set)[0] strings_removed = NO_VALUES
if is_string(inferred_value): for part in value_set:
result = _get_forward_reference_node(context, inferred_value.get_safe_value()) if is_string(part):
if result is not None: result = _get_forward_reference_node(context, part.get_safe_value())
return context.infer_node(result) if result is not None:
return value_set strings_removed |= context.infer_node(result)
continue
strings_removed |= ValueSet([part])
return strings_removed
def _infer_annotation_string(context, string, index=None): def _infer_annotation_string(context, string, index=None):
@@ -249,12 +252,12 @@ def infer_return_types(function, arguments):
return _infer_annotation_string( return _infer_annotation_string(
context, context,
match.group(1).strip() match.group(1).strip()
).execute_annotation() ).execute_annotation(context)
unknown_type_vars = find_unknown_type_vars(context, annotation) unknown_type_vars = find_unknown_type_vars(context, annotation)
annotation_values = infer_annotation(context, annotation) annotation_values = infer_annotation(context, annotation)
if not unknown_type_vars: if not unknown_type_vars:
return annotation_values.execute_annotation() return annotation_values.execute_annotation(context)
type_var_dict = infer_type_vars_for_execution(function, arguments, all_annotations) type_var_dict = infer_type_vars_for_execution(function, arguments, all_annotations)
@@ -262,7 +265,7 @@ def infer_return_types(function, arguments):
ann.define_generics(type_var_dict) ann.define_generics(type_var_dict)
if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann}) if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann})
for ann in annotation_values for ann in annotation_values
).execute_annotation() ).execute_annotation(context)
def infer_type_vars_for_execution(function, arguments, annotation_dict): def infer_type_vars_for_execution(function, arguments, annotation_dict):
@@ -315,7 +318,7 @@ def infer_return_for_callable(arguments, param_values, result_values):
if isinstance(v, (DefineGenericBaseClass, TypeVar)) if isinstance(v, (DefineGenericBaseClass, TypeVar))
else ValueSet({v}) else ValueSet({v})
for v in result_values for v in result_values
).execute_annotation() ).execute_annotation(arguments.context)
def _infer_type_vars_for_callable(arguments, lazy_params): def _infer_type_vars_for_callable(arguments, lazy_params):
@@ -391,7 +394,7 @@ def merge_pairwise_generics(annotation_value, annotated_argument_class):
for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics): for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics):
merge_type_var_dicts( merge_type_var_dicts(
type_var_dict, type_var_dict,
annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation()), annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation(None)),
) )
return type_var_dict return type_var_dict
@@ -402,6 +405,10 @@ def find_type_from_comment_hint_for(context, node, name):
def find_type_from_comment_hint_with(context, node, name): def find_type_from_comment_hint_with(context, node, name):
if len(node.children) > 4:
# In case there are multiple with_items, we do not want a type hint for
# now.
return []
assert len(node.children[1].children) == 3, \ assert len(node.children[1].children) == 3, \
"Can only be here when children[1] is 'foo() as f'" "Can only be here when children[1] is 'foo() as f'"
varlist = node.children[1].children[2] varlist = node.children[1].children[2]
@@ -434,7 +441,7 @@ def _find_type_from_comment_hint(context, node, varlist, name):
return [] return []
return _infer_annotation_string( return _infer_annotation_string(
context, match.group(1).strip(), index context, match.group(1).strip(), index
).execute_annotation() ).execute_annotation(context)
def find_unknown_type_vars(context, node): def find_unknown_type_vars(context, node):
+4 -4
View File
@@ -195,7 +195,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin):
@to_list @to_list
def py__bases__(self): def py__bases__(self):
for base in self._wrapped_value.py__bases__(): for base in self._wrapped_value.py__bases__(): # type: ignore[attr-defined]
yield _LazyGenericBaseClass(self, base, self._generics_manager) yield _LazyGenericBaseClass(self, base, self._generics_manager)
def _create_instance_with_generics(self, generics_manager): def _create_instance_with_generics(self, generics_manager):
@@ -306,7 +306,7 @@ class _GenericInstanceWrapper(ValueWrapper):
if cls.py__name__() == 'Generator': if cls.py__name__() == 'Generator':
generics = cls.get_generics() generics = cls.get_generics()
try: try:
return generics[2].execute_annotation() return generics[2].execute_annotation(None)
except IndexError: except IndexError:
pass pass
elif cls.py__name__() == 'Iterator': elif cls.py__name__() == 'Iterator':
@@ -384,7 +384,7 @@ class BaseTypingValue(LazyValueWrapper):
return _PseudoTreeNameClass(self.parent_context, self._tree_name) return _PseudoTreeNameClass(self.parent_context, self._tree_name)
def get_signatures(self): def get_signatures(self):
return self._wrapped_value.get_signatures() return self._wrapped_value.get_signatures() # type: ignore[attr-defined]
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
@@ -427,7 +427,7 @@ class BaseTypingInstance(LazyValueWrapper):
return ValueName(self, self._tree_name) return ValueName(self, self._tree_name)
def _get_wrapped_value(self): def _get_wrapped_value(self):
object_, = builtin_from_name(self.inference_state, 'object').execute_annotation() object_, = builtin_from_name(self.inference_state, 'object').execute_annotation(None)
return object_ return object_
def __repr__(self): def __repr__(self):
+10 -1
View File
@@ -2,6 +2,7 @@
This module is about generics, like the `int` in `List[int]`. It's not about This module is about generics, like the `int` in `List[int]`. It's not about
the Generic class. the Generic class.
""" """
from abc import abstractmethod
from jedi import debug from jedi import debug
from jedi.cache import memoize_method from jedi.cache import memoize_method
@@ -24,9 +25,17 @@ def _resolve_forward_references(context, value_set):
class _AbstractGenericManager: class _AbstractGenericManager:
@abstractmethod
def __getitem__(self, index):
raise NotImplementedError
@abstractmethod
def to_tuple(self):
raise NotImplementedError
def get_index_and_execute(self, index): def get_index_and_execute(self, index):
try: try:
return self[index].execute_annotation() return self[index].execute_annotation(None)
except IndexError: except IndexError:
debug.warning('No param #%s found for annotation %s', index, self) debug.warning('No param #%s found for annotation %s', index, self)
return NO_VALUES return NO_VALUES
+3 -3
View File
@@ -100,8 +100,8 @@ class TypeVar(BaseTypingValue):
return found return found
return ValueSet({self}) return ValueSet({self})
def execute_annotation(self): def execute_annotation(self, context):
return self._get_classes().execute_annotation() return self._get_classes().execute_annotation(context)
def infer_type_vars(self, value_set): def infer_type_vars(self, value_set):
def iterate(): def iterate():
@@ -123,5 +123,5 @@ class TypeWrapper(ValueWrapper):
super().__init__(wrapped_value) super().__init__(wrapped_value)
self._original_value = original_value self._original_value = original_value
def execute_annotation(self): def execute_annotation(self, context):
return ValueSet({self._original_value}) return ValueSet({self._original_value})
+3 -15
View File
@@ -1,5 +1,4 @@
import os import os
import re
from functools import wraps from functools import wraps
from collections import namedtuple from collections import namedtuple
from typing import Dict, Mapping, Tuple from typing import Dict, Mapping, Tuple
@@ -58,19 +57,8 @@ def _create_stub_map(directory_path_info):
def _get_typeshed_directories(version_info): def _get_typeshed_directories(version_info):
check_version_list = ['2and3', '3'] yield PathInfo(str(TYPESHED_PATH.joinpath("stdlib")), False)
for base in ['stdlib', 'third_party']: yield PathInfo(str(TYPESHED_PATH.joinpath("stubs")), True)
base_path = TYPESHED_PATH.joinpath(base)
base_list = os.listdir(base_path)
for base_list_entry in base_list:
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
if match is not None:
if match.group(1) == '3' and int(match.group(2)) <= version_info.minor:
check_version_list.append(base_list_entry)
for check_version in check_version_list:
is_third_party = base != 'stdlib'
yield PathInfo(str(base_path.joinpath(check_version)), is_third_party)
_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {} _version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}
@@ -293,7 +281,7 @@ def parse_stub_module(inference_state, file_io):
def create_stub_module(inference_state, grammar, python_value_set, def create_stub_module(inference_state, grammar, python_value_set,
stub_module_node, file_io, import_names): stub_module_node, file_io, import_names):
if import_names == ('typing',): if import_names in [('typing',), ('typing_extensions',)]:
module_cls = TypingModuleWrapper module_cls = TypingModuleWrapper
else: else:
module_cls = StubModuleValue module_cls = StubModuleValue
+31 -15
View File
@@ -6,6 +6,7 @@ values.
This file deals with all the typing.py cases. This file deals with all the typing.py cases.
""" """
import itertools import itertools
from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.compiled import builtin_from_name, create_simple_object
@@ -32,7 +33,8 @@ _TYPE_ALIAS_TYPES = {
'DefaultDict': 'collections.defaultdict', 'DefaultDict': 'collections.defaultdict',
'Deque': 'collections.deque', 'Deque': 'collections.deque',
} }
_PROXY_TYPES = 'Optional Union ClassVar'.split() _PROXY_TYPES = ['Optional', 'Union', 'ClassVar', 'Annotated', 'Final']
IGNORE_ANNOTATION_PARTS = ['ClassVar', 'Annotated', 'Final']
class TypingModuleName(NameWrapper): class TypingModuleName(NameWrapper):
@@ -81,6 +83,9 @@ class TypingModuleName(NameWrapper):
elif name == 'cast': elif name == 'cast':
cast_fn, = self._wrapped_name.infer() cast_fn, = self._wrapped_name.infer()
yield CastFunction.create_cached(inference_state, cast_fn) yield CastFunction.create_cached(inference_state, cast_fn)
elif name == 'Self':
yield SelfClass.create_cached(
inference_state, self.parent_context, self.tree_name)
elif name == 'TypedDict': elif name == 'TypedDict':
# TODO doesn't even exist in typeshed/typing.py, yet. But will be # TODO doesn't even exist in typeshed/typing.py, yet. But will be
# added soon. # added soon.
@@ -98,24 +103,24 @@ class TypingModuleFilterWrapper(FilterWrapper):
class ProxyWithGenerics(BaseTypingClassWithGenerics): class ProxyWithGenerics(BaseTypingClassWithGenerics):
def execute_annotation(self): def execute_annotation(self, context):
string_name = self._tree_name.value string_name = self._tree_name.value
if string_name == 'Union': if string_name == 'Union':
# This is kind of a special case, because we have Unions (in Jedi # This is kind of a special case, because we have Unions (in Jedi
# ValueSets). # ValueSets).
return self.gather_annotation_classes().execute_annotation() return self.gather_annotation_classes().execute_annotation(context)
elif string_name == 'Optional': elif string_name == 'Optional':
# Optional is basically just saying it's either None or the actual # Optional is basically just saying it's either None or the actual
# type. # type.
return self.gather_annotation_classes().execute_annotation() \ return self.gather_annotation_classes().execute_annotation(context) \
| ValueSet([builtin_from_name(self.inference_state, 'None')]) | ValueSet([builtin_from_name(self.inference_state, 'None')])
elif string_name == 'Type': elif string_name == 'Type':
# The type is actually already given in the index_value # The type is actually already given in the index_value
return self._generics_manager[0] return self._generics_manager[0]
elif string_name == 'ClassVar': elif string_name in IGNORE_ANNOTATION_PARTS:
# For now don't do anything here, ClassVars are always used. # For now don't do anything here, ClassVars are always used.
return self._generics_manager[0].execute_annotation() return self._generics_manager[0].execute_annotation(context)
mapped = { mapped = {
'Tuple': Tuple, 'Tuple': Tuple,
@@ -186,6 +191,8 @@ class ProxyTypingValue(BaseTypingValue):
class _TypingClassMixin(ClassMixin): class _TypingClassMixin(ClassMixin):
_tree_name: Any
def py__bases__(self): def py__bases__(self):
return [LazyKnownValues( return [LazyKnownValues(
self.inference_state.builtins_module.py__getattribute__('object') self.inference_state.builtins_module.py__getattribute__('object')
@@ -213,17 +220,17 @@ class TypingClassWithGenerics(ProxyWithGenerics, _TypingClassMixin):
# This is basically a trick to avoid extra code: We execute the # This is basically a trick to avoid extra code: We execute the
# incoming classes to be able to use the normal code for type # incoming classes to be able to use the normal code for type
# var inference. # var inference.
value_set.execute_annotation(), value_set.execute_annotation(None),
) )
elif annotation_name == 'Callable': elif annotation_name == 'Callable':
if len(annotation_generics) == 2: if len(annotation_generics) == 2:
return annotation_generics[1].infer_type_vars( return annotation_generics[1].infer_type_vars(
value_set.execute_annotation(), value_set.execute_annotation(None),
) )
elif annotation_name == 'Tuple': elif annotation_name == 'Tuple':
tuple_annotation, = self.execute_annotation() tuple_annotation, = self.execute_annotation(None)
return tuple_annotation.infer_type_vars(value_set) return tuple_annotation.infer_type_vars(value_set)
return type_var_dict return type_var_dict
@@ -319,7 +326,7 @@ class Tuple(BaseTypingInstance):
yield LazyKnownValues(self._generics_manager.get_index_and_execute(0)) yield LazyKnownValues(self._generics_manager.get_index_and_execute(0))
else: else:
for v in self._generics_manager.to_tuple(): for v in self._generics_manager.to_tuple():
yield LazyKnownValues(v.execute_annotation()) yield LazyKnownValues(v.execute_annotation(None))
def py__getitem__(self, index_value_set, contextualized_node): def py__getitem__(self, index_value_set, contextualized_node):
if self._is_homogenous(): if self._is_homogenous():
@@ -327,11 +334,11 @@ class Tuple(BaseTypingInstance):
return ValueSet.from_sets( return ValueSet.from_sets(
self._generics_manager.to_tuple() self._generics_manager.to_tuple()
).execute_annotation() ).execute_annotation(None)
def _get_wrapped_value(self): def _get_wrapped_value(self):
tuple_, = self.inference_state.builtins_module \ tuple_, = self.inference_state.builtins_module \
.py__getattribute__('tuple').execute_annotation() .py__getattribute__('tuple').execute_annotation(None)
return tuple_ return tuple_
@property @property
@@ -388,11 +395,20 @@ class Protocol(BaseTypingInstance):
class AnyClass(BaseTypingValue): class AnyClass(BaseTypingValue):
def execute_annotation(self): def execute_annotation(self, context):
debug.warning('Used Any - returned no results') debug.warning('Used Any - returned no results')
return NO_VALUES return NO_VALUES
class SelfClass(BaseTypingValue):
def execute_annotation(self, context):
debug.warning('Used Self')
if context is not None:
# Execute the class of Self
return context.get_value().execute_annotation(None)
return NO_VALUES
class OverloadFunction(BaseTypingValue): class OverloadFunction(BaseTypingValue):
@repack_with_argument_clinic('func, /') @repack_with_argument_clinic('func, /')
def py__call__(self, func_value_set): def py__call__(self, func_value_set):
@@ -427,7 +443,7 @@ class NewType(Value):
return c return c
def py__call__(self, arguments): def py__call__(self, arguments):
return self._type_value_set.execute_annotation() return self._type_value_set.execute_annotation(arguments.context)
@property @property
def name(self): def name(self):
@@ -441,7 +457,7 @@ class NewType(Value):
class CastFunction(ValueWrapper): class CastFunction(ValueWrapper):
@repack_with_argument_clinic('type, object, /') @repack_with_argument_clinic('type, object, /')
def py__call__(self, type_value_set, object_value_set): def py__call__(self, type_value_set, object_value_set):
return type_value_set.execute_annotation() return type_value_set.execute_annotation(None)
class TypedDictClass(BaseTypingValue): class TypedDictClass(BaseTypingValue):
+1 -1
View File
@@ -19,7 +19,7 @@ def load_proper_stub_module(inference_state, grammar, file_io, import_names, mod
# /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__ # /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__
rest = relative_path.with_suffix('') rest = relative_path.with_suffix('')
# Remove the stdlib/3 or third_party/3.6 part # Remove the stdlib/3 or third_party/3.6 part
import_names = rest.parts[2:] import_names = rest.parts[1:]
if rest.name == '__init__': if rest.name == '__init__':
import_names = import_names[:-1] import_names = import_names[:-1]
+2 -2
View File
@@ -5,12 +5,12 @@ import os
from itertools import chain from itertools import chain
from contextlib import contextmanager from contextlib import contextmanager
from parso.python import tree from parso import tree
def is_stdlib_path(path): def is_stdlib_path(path):
# Python standard library paths look like this: # Python standard library paths look like this:
# /usr/lib/python3.9/... # /usr/lib/python3.14/...
# TODO The implementation below is probably incorrect and not complete. # TODO The implementation below is probably incorrect and not complete.
parts = path.parts parts = path.parts
if 'dist-packages' in parts or 'site-packages' in parts: if 'dist-packages' in parts or 'site-packages' in parts:
+8 -9
View File
@@ -12,7 +12,6 @@ import os
from pathlib import Path from pathlib import Path
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
@@ -95,7 +94,7 @@ def goto_import(context, tree_name):
def _prepare_infer_import(module_context, tree_name): def _prepare_infer_import(module_context, tree_name):
import_node = search_ancestor(tree_name, 'import_name', 'import_from') import_node = tree_name.search_ancestor('import_name', 'import_from')
import_path = import_node.get_path_for_name(tree_name) import_path = import_node.get_path_for_name(tree_name)
from_import_name = None from_import_name = None
try: try:
@@ -371,16 +370,16 @@ def import_module_by_names(inference_state, import_names, sys_path=None,
i.value if isinstance(i, tree.Name) else i i.value if isinstance(i, tree.Name) else i
for i in import_names for i in import_names
) )
value_set = [None] base = [None]
for i, name in enumerate(import_names): for i, name in enumerate(import_names):
value_set = ValueSet.from_sets([ base = value_set = ValueSet.from_sets([
import_module( import_module(
inference_state, inference_state,
str_import_names[:i+1], str_import_names[:i+1],
parent_module_value, parent_module_value,
sys_path, sys_path,
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs, # type: ignore[call-arg]
) for parent_module_value in value_set ) for parent_module_value in base
]) ])
if not value_set: if not value_set:
message = 'No module named ' + '.'.join(str_import_names) message = 'No module named ' + '.'.join(str_import_names)
@@ -475,12 +474,12 @@ def _load_python_module(inference_state, file_io,
) )
def _load_builtin_module(inference_state, import_names=None, sys_path=None): def _load_builtin_module(inference_state, import_names, sys_path):
project = inference_state.project project = inference_state.project
if sys_path is None: if sys_path is None:
sys_path = inference_state.get_sys_path() sys_path = inference_state.get_sys_path()
if not project._load_unsafe_extensions: if not project._load_unsafe_extensions:
safe_paths = project._get_base_sys_path(inference_state) safe_paths = set(project._get_base_sys_path(inference_state))
sys_path = [p for p in sys_path if p in safe_paths] sys_path = [p for p in sys_path if p in safe_paths]
dotted_name = '.'.join(import_names) dotted_name = '.'.join(import_names)
@@ -549,7 +548,7 @@ def load_namespace_from_path(inference_state, folder_io):
def follow_error_node_imports_if_possible(context, name): def follow_error_node_imports_if_possible(context, name):
error_node = tree.search_ancestor(name, 'error_node') error_node = name.search_ancestor('error_node')
if error_node is not None: if error_node is not None:
# Get the first command start of a started simple_stmt. The error # Get the first command start of a started simple_stmt. The error
# node is sometimes a small_stmt and sometimes a simple_stmt. Check # node is sometimes a small_stmt and sometimes a simple_stmt. Check
+26 -19
View File
@@ -1,11 +1,8 @@
from abc import abstractmethod from abc import abstractmethod
from inspect import Parameter from inspect import Parameter
from typing import Optional, Tuple from typing import Optional, Tuple, Any
from parso.tree import search_ancestor
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
from jedi.inference.utils import unite
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference import docstrings from jedi.inference import docstrings
@@ -39,7 +36,6 @@ class AbstractNameDefinition:
def infer(self): def infer(self):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def goto(self): def goto(self):
# Typically names are already definitions and therefore a goto on that # Typically names are already definitions and therefore a goto on that
# name will always result on itself. # name will always result on itself.
@@ -107,12 +103,15 @@ class AbstractArbitraryName(AbstractNameDefinition):
class AbstractTreeName(AbstractNameDefinition): class AbstractTreeName(AbstractNameDefinition):
tree_name: Any
parent_context: Any
def __init__(self, parent_context, tree_name): def __init__(self, parent_context, tree_name):
self.parent_context = parent_context self.parent_context = parent_context
self.tree_name = tree_name self.tree_name = tree_name
def get_qualified_names(self, include_module_names=False): def get_qualified_names(self, include_module_names=False):
import_node = search_ancestor(self.tree_name, 'import_name', 'import_from') import_node = self.tree_name.search_ancestor('import_name', 'import_from')
# For import nodes we cannot just have names, because it's very unclear # For import nodes we cannot just have names, because it's very unclear
# how they would look like. For now we just ignore them in most cases. # how they would look like. For now we just ignore them in most cases.
# In case of level == 1, it works always, because it's like a submodule # In case of level == 1, it works always, because it's like a submodule
@@ -196,24 +195,23 @@ class AbstractTreeName(AbstractNameDefinition):
new_dotted = deep_ast_copy(par) new_dotted = deep_ast_copy(par)
new_dotted.children[index - 1:] = [] new_dotted.children[index - 1:] = []
values = context.infer_node(new_dotted) values = context.infer_node(new_dotted)
return unite( return [
value.goto(name, name_context=context) n
for value in values for value in values
) for n in value.goto(name, name_context=context)
]
if node_type == 'trailer' and par.children[0] == '.': if node_type == 'trailer' and par.children[0] == '.':
values = infer_call_of_leaf(context, name, cut_own_trailer=True) values = infer_call_of_leaf(context, name, cut_own_trailer=True)
return values.goto(name, name_context=context) return values.goto(name, name_context=context)
else: else:
stmt = search_ancestor( stmt = name.search_ancestor('expr_stmt', 'lambdef') or name
name, 'expr_stmt', 'lambdef'
) or name
if stmt.type == 'lambdef': if stmt.type == 'lambdef':
stmt = name stmt = name
return context.goto(name, position=stmt.start_pos) return context.goto(name, position=stmt.start_pos)
def is_import(self): def is_import(self):
imp = search_ancestor(self.tree_name, 'import_from', 'import_name') imp = self.tree_name.search_ancestor('import_from', 'import_name')
return imp is not None return imp is not None
@property @property
@@ -226,6 +224,9 @@ class AbstractTreeName(AbstractNameDefinition):
class ValueNameMixin: class ValueNameMixin:
_value: Any
parent_context: Any
def infer(self): def infer(self):
return ValueSet([self._value]) return ValueSet([self._value])
@@ -244,7 +245,7 @@ class ValueNameMixin:
def get_root_context(self): def get_root_context(self):
if self.parent_context is None: # A module if self.parent_context is None: # A module
return self._value.as_context() return self._value.as_context()
return super().get_root_context() return super().get_root_context() # type: ignore
def get_defining_qualified_value(self): def get_defining_qualified_value(self):
context = self.parent_context context = self.parent_context
@@ -361,14 +362,16 @@ class TreeNameDefinition(AbstractTreeName):
class _ParamMixin: class _ParamMixin:
get_kind: Any
def maybe_positional_argument(self, include_star=True): def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options: list[int] = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star: if include_star:
options.append(Parameter.VAR_POSITIONAL) options.append(Parameter.VAR_POSITIONAL)
return self.get_kind() in options return self.get_kind() in options
def maybe_keyword_argument(self, include_stars=True): def maybe_keyword_argument(self, include_stars=True):
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options: list[int] = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_stars: if include_stars:
options.append(Parameter.VAR_KEYWORD) options.append(Parameter.VAR_KEYWORD)
return self.get_kind() in options return self.get_kind() in options
@@ -451,7 +454,7 @@ class _ActualTreeParamName(BaseTreeParamName):
self.function_value = function_value self.function_value = function_value
def _get_param_node(self): def _get_param_node(self):
return search_ancestor(self.tree_name, 'param') return self.tree_name.search_ancestor('param')
@property @property
def annotation_node(self): def annotation_node(self):
@@ -463,7 +466,7 @@ class _ActualTreeParamName(BaseTreeParamName):
self.function_value, self._get_param_node(), self.function_value, self._get_param_node(),
ignore_stars=ignore_stars) ignore_stars=ignore_stars)
if execute_annotation: if execute_annotation:
values = values.execute_annotation() values = values.execute_annotation(self.function_value.get_default_param_context())
return values return values
def infer_default(self): def infer_default(self):
@@ -633,6 +636,10 @@ class NameWrapper:
class StubNameMixin: class StubNameMixin:
api_type: str
tree_name: Any
infer: Any
def py__doc__(self): def py__doc__(self):
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
# Stubs are not complicated and we can just follow simple statements # Stubs are not complicated and we can just follow simple statements
@@ -644,7 +651,7 @@ class StubNameMixin:
names = convert_names(names, prefer_stub_to_compiled=False) names = convert_names(names, prefer_stub_to_compiled=False)
if self in names: if self in names:
return super().py__doc__() return super().py__doc__() # type: ignore
else: else:
# We have signatures ourselves in stubs, so don't use signatures # We have signatures ourselves in stubs, so don't use signatures
# from the implementation. # from the implementation.
+7
View File
@@ -1,4 +1,5 @@
from inspect import Parameter from inspect import Parameter
from typing import Any
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi import debug from jedi import debug
@@ -6,6 +7,10 @@ from jedi import parser_utils
class _SignatureMixin: class _SignatureMixin:
get_param_names: Any
name: Any
annotation_string: Any
def to_string(self): def to_string(self):
def param_strings(): def param_strings():
is_positional = False is_positional = False
@@ -36,6 +41,8 @@ class _SignatureMixin:
class AbstractSignature(_SignatureMixin): class AbstractSignature(_SignatureMixin):
_function_value: Any
def __init__(self, value, is_bound=False): def __init__(self, value, is_bound=False):
self.value = value self.value = value
self.is_bound = is_bound self.is_bound = is_bound
+2 -5
View File
@@ -12,15 +12,12 @@ The signature here for bar should be `bar(b, c)` instead of bar(*args).
""" """
from inspect import Parameter from inspect import Parameter
from parso import tree
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi.inference.names import ParamNameWrapper from jedi.inference.names import ParamNameWrapper
from jedi.inference.helpers import is_big_annoying_library from jedi.inference.helpers import is_big_annoying_library
def _iter_nodes_for_param(param_name): def _iter_nodes_for_param(param_name):
from parso.python.tree import search_ancestor
from jedi.inference.arguments import TreeArguments from jedi.inference.arguments import TreeArguments
execution_context = param_name.parent_context execution_context = param_name.parent_context
@@ -28,7 +25,7 @@ def _iter_nodes_for_param(param_name):
# tree rather than going via the execution context so that we're agnostic of # tree rather than going via the execution context so that we're agnostic of
# the specific scope we're evaluating within (i.e: module or function, # the specific scope we're evaluating within (i.e: module or function,
# etc.). # etc.).
function_node = tree.search_ancestor(param_name.tree_name, 'funcdef', 'lambdef') function_node = param_name.tree_name.search_ancestor('funcdef', 'lambdef')
module_node = function_node.get_root_node() module_node = function_node.get_root_node()
start = function_node.children[-1].start_pos start = function_node.children[-1].start_pos
end = function_node.children[-1].end_pos end = function_node.children[-1].end_pos
@@ -38,7 +35,7 @@ def _iter_nodes_for_param(param_name):
argument = name.parent argument = name.parent
if argument.type == 'argument' \ if argument.type == 'argument' \
and argument.children[0] == '*' * param_name.star_count: and argument.children[0] == '*' * param_name.star_count:
trailer = search_ancestor(argument, 'trailer') trailer = argument.search_ancestor('trailer')
if trailer is not None: # Make sure we're in a function if trailer is not None: # Make sure we're in a function
context = execution_context.create_context(trailer) context = execution_context.create_context(trailer)
if _goes_to_param_name(param_name, context, name): if _goes_to_param_name(param_name, context, name):
+48 -16
View File
@@ -30,6 +30,8 @@ from jedi.inference.names import TreeNameDefinition
from jedi.inference.context import CompForContext from jedi.inference.context import CompForContext
from jedi.inference.value.decorator import Decoratee from jedi.inference.value.decorator import Decoratee
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
from jedi.inference.gradual.typing import ProxyTypingValue, IGNORE_ANNOTATION_PARTS
from jedi.inference.gradual.type_var import TypeVar
operator_to_magic_method = { operator_to_magic_method = {
'+': '__add__', '+': '__add__',
@@ -89,6 +91,7 @@ def infer_node(context, element):
if isinstance(context, CompForContext): if isinstance(context, CompForContext):
return _infer_node(context, element) return _infer_node(context, element)
name_dicts = [{}]
if_stmt = element if_stmt = element
while if_stmt is not None: while if_stmt is not None:
if_stmt = if_stmt.parent if_stmt = if_stmt.parent
@@ -104,7 +107,6 @@ def infer_node(context, element):
if predefined_if_name_dict is None and if_stmt \ if predefined_if_name_dict is None and if_stmt \
and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis: and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis:
if_stmt_test = if_stmt.children[1] if_stmt_test = if_stmt.children[1]
name_dicts = [{}]
# If we already did a check, we don't want to do it again -> If # If we already did a check, we don't want to do it again -> If
# value.predefined_names is filled, we stop. # value.predefined_names is filled, we stop.
# We don't want to check the if stmt itself, it's just about # We don't want to check the if stmt itself, it's just about
@@ -238,7 +240,7 @@ def _infer_node(context, element):
return context.infer_node(element.children[0]) return context.infer_node(element.children[0])
elif typ == 'annassign': elif typ == 'annassign':
return annotation.infer_annotation(context, element.children[1]) \ return annotation.infer_annotation(context, element.children[1]) \
.execute_annotation() .execute_annotation(context)
elif typ == 'yield_expr': elif typ == 'yield_expr':
if len(element.children) and element.children[1].type == 'yield_arg': if len(element.children) and element.children[1].type == 'yield_arg':
# Implies that it's a yield from. # Implies that it's a yield from.
@@ -251,6 +253,8 @@ def _infer_node(context, element):
return NO_VALUES return NO_VALUES
elif typ == 'namedexpr_test': elif typ == 'namedexpr_test':
return context.infer_node(element.children[2]) return context.infer_node(element.children[2])
elif typ == 'star_expr':
return NO_VALUES
else: else:
return infer_or_test(context, element) return infer_or_test(context, element)
@@ -288,7 +292,7 @@ def infer_atom(context, atom):
state = context.inference_state state = context.inference_state
if atom.type == 'name': if atom.type == 'name':
# This is the first global lookup. # This is the first global lookup.
stmt = tree.search_ancestor(atom, 'expr_stmt', 'lambdef', 'if_stmt') or atom stmt = atom.search_ancestor('expr_stmt', 'lambdef', 'if_stmt') or atom
if stmt.type == 'if_stmt': if stmt.type == 'if_stmt':
if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()): if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()):
stmt = atom stmt = atom
@@ -329,8 +333,8 @@ def infer_atom(context, atom):
c = atom.children c = atom.children
# Parentheses without commas are not tuples. # Parentheses without commas are not tuples.
if c[0] == '(' and not len(c) == 2 \ if c[0] == '(' and not len(c) == 2 \
and not(c[1].type == 'testlist_comp' and not (c[1].type == 'testlist_comp'
and len(c[1].children) > 1): and len(c[1].children) > 1):
return context.infer_node(c[1]) return context.infer_node(c[1])
try: try:
@@ -434,7 +438,7 @@ def _infer_expr_stmt(context, stmt, seek_name=None):
else: else:
operator = copy.copy(first_operator) operator = copy.copy(first_operator)
operator.value = operator.value[:-1] operator.value = operator.value[:-1]
for_stmt = tree.search_ancestor(stmt, 'for_stmt') for_stmt = stmt.search_ancestor('for_stmt')
if for_stmt is not None and for_stmt.type == 'for_stmt' and value_set \ if for_stmt is not None and for_stmt.type == 'for_stmt' and value_set \
and parser_utils.for_stmt_defines_one_name(for_stmt): and parser_utils.for_stmt_defines_one_name(for_stmt):
# Iterate through result and add the values, that's possible # Iterate through result and add the values, that's possible
@@ -493,8 +497,10 @@ def infer_factor(value_set, operator):
elif operator == 'not': elif operator == 'not':
b = value.py__bool__() b = value.py__bool__()
if b is None: # Uncertainty. if b is None: # Uncertainty.
return yield list(value.inference_state.builtins_module.py__getattribute__('bool')
yield compiled.create_simple_object(value.inference_state, not b) .execute_annotation(None)).pop()
else:
yield compiled.create_simple_object(value.inference_state, not b)
else: else:
yield value yield value
@@ -525,7 +531,7 @@ def _infer_comparison(context, left_values, operator, right_values):
result = (left_values or NO_VALUES) | (right_values or NO_VALUES) result = (left_values or NO_VALUES) | (right_values or NO_VALUES)
return _literals_to_types(state, result) return _literals_to_types(state, result)
elif operator_str == "|" and all( elif operator_str == "|" and all(
value.is_class() or value.is_compiled() value.is_class() or value.is_compiled() or isinstance(value, TypeVar)
for value in itertools.chain(left_values, right_values) for value in itertools.chain(left_values, right_values)
): ):
# ^^^ A naive hack for PEP 604 # ^^^ A naive hack for PEP 604
@@ -545,7 +551,7 @@ def _infer_comparison(context, left_values, operator, right_values):
def _is_annotation_name(name): def _is_annotation_name(name):
ancestor = tree.search_ancestor(name, 'param', 'funcdef', 'expr_stmt') ancestor = name.search_ancestor('param', 'funcdef', 'expr_stmt')
if ancestor is None: if ancestor is None:
return False return False
@@ -645,7 +651,9 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
_bool_to_value(inference_state, False) _bool_to_value(inference_state, False)
]) ])
elif str_operator in ('in', 'not in'): elif str_operator in ('in', 'not in'):
return NO_VALUES return inference_state.builtins_module.py__getattribute__('bool').execute_annotation(
context
)
def check(obj): def check(obj):
"""Checks if a Jedi object is either a float or an int.""" """Checks if a Jedi object is either a float or an int."""
@@ -695,11 +703,29 @@ def tree_name_to_values(inference_state, context, tree_name):
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign": if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign":
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
if correct_scope: ann_assign = expr_stmt.children[1]
found_annotation = True first = ann_assign.children[1]
value_set |= annotation.infer_annotation( code = first.get_code()
if correct_scope and not (code.endswith(".TypeAlias")
or code.strip() == "TypeAlias"):
if (
(first.type == 'name')
and (ann_assign.children[1].value == tree_name.value)
and context.parent_context
):
context = context.parent_context
found = annotation.infer_annotation(
context, expr_stmt.children[1].children[1] context, expr_stmt.children[1].children[1]
).execute_annotation() )
set_found_annotation = True
if len(found) == 1:
first = next(iter(found))
set_found_annotation = not (
isinstance(first, ProxyTypingValue)
and first.name.string_name in IGNORE_ANNOTATION_PARTS
)
found_annotation = set_found_annotation
value_set |= found.execute_annotation(context)
if found_annotation: if found_annotation:
return value_set return value_set
@@ -757,7 +783,7 @@ def tree_name_to_values(inference_state, context, tree_name):
coro = enter_methods.execute_with_values() coro = enter_methods.execute_with_values()
return coro.py__await__().py__stop_iteration_returns() return coro.py__await__().py__stop_iteration_returns()
enter_methods = value_managers.py__getattribute__('__enter__') enter_methods = value_managers.py__getattribute__('__enter__')
return enter_methods.execute_with_values() return enter_methods.execute_annotation(context)
elif typ in ('import_from', 'import_name'): elif typ in ('import_from', 'import_name'):
types = imports.infer_import(context, tree_name) types = imports.infer_import(context, tree_name)
elif typ in ('funcdef', 'classdef'): elif typ in ('funcdef', 'classdef'):
@@ -843,10 +869,16 @@ def check_tuple_assignments(name, value_set):
# For no star unpacking is not possible. # For no star unpacking is not possible.
return NO_VALUES return NO_VALUES
i = 0 i = 0
lazy_value = None
while i <= index: while i <= index:
try: try:
lazy_value = next(iterated) lazy_value = next(iterated)
except StopIteration: except StopIteration:
# A desperate attempt to fix inference for tuples from an
# iterator.
if lazy_value is not None:
return lazy_value.infer()
# We could do this with the default param in next. But this # We could do this with the default param in next. But this
# would allow this loop to run for a very long time if the # would allow this loop to run for a very long time if the
# index number is high. Therefore break if the loop is # index number is high. Therefore break if the loop is
-1
View File
@@ -74,7 +74,6 @@ class PushBackIterator:
def __init__(self, iterator): def __init__(self, iterator):
self.pushes = [] self.pushes = []
self.iterator = iterator self.iterator = iterator
self.current = None
def push_back(self, value): def push_back(self, value):
self.pushes.append(value) self.pushes.append(value)
+11 -7
View File
@@ -1,4 +1,4 @@
from parso.python import tree from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass from jedi.inference.cache import inference_state_method_cache, CachedMetaClass
@@ -55,6 +55,10 @@ class FunctionAndClassBase(TreeValue):
class FunctionMixin: class FunctionMixin:
api_type = 'function' api_type = 'function'
tree_node: Any
py__class__: Any
as_context: Any
get_signature_functions: Any
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):
cls = self.py__class__() cls = self.py__class__()
@@ -262,8 +266,8 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
@recursion.execution_recursion_decorator(default=iter([])) @recursion.execution_recursion_decorator(default=iter([]))
def get_yield_lazy_values(self, is_async=False): def get_yield_lazy_values(self, is_async=False):
# TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend # TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend
for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef', for_parents = [(y, y.search_ancestor('for_stmt', 'funcdef',
'while_stmt', 'if_stmt')) 'while_stmt', 'if_stmt'))
for y in get_yield_exprs(self.inference_state, self.tree_node)] for y in get_yield_exprs(self.inference_state, self.tree_node)]
# Calculate if the yields are placed within the same for loop. # Calculate if the yields are placed within the same for loop.
@@ -334,15 +338,15 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
return ValueSet( return ValueSet(
GenericClass(c, TupleGenericManager(generics)) GenericClass(c, TupleGenericManager(generics))
for c in async_generator_classes for c in async_generator_classes
).execute_annotation() ).execute_annotation(None)
else: else:
async_classes = inference_state.typing_module.py__getattribute__('Coroutine') async_classes = inference_state.types_module.py__getattribute__('CoroutineType')
return_values = self.get_return_values() return_values = self.get_return_values()
# Only the first generic is relevant. # Only the first generic is relevant.
generics = (return_values.py__class__(), NO_VALUES, NO_VALUES) generics = (NO_VALUES, NO_VALUES, return_values.py__class__())
return ValueSet( return ValueSet(
GenericClass(c, TupleGenericManager(generics)) for c in async_classes GenericClass(c, TupleGenericManager(generics)) for c in async_classes
).execute_annotation() ).execute_annotation(None)
else: else:
# If there are annotations, prefer them over anything else. # If there are annotations, prefer them over anything else.
if self.is_generator() and not self.infer_annotations(): if self.is_generator() and not self.infer_annotations():
+13 -10
View File
@@ -1,6 +1,5 @@
from abc import abstractproperty from abc import abstractproperty
from typing import Any
from parso.tree import search_ancestor
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
@@ -18,7 +17,7 @@ from jedi.inference.arguments import ValuesArguments, TreeArgumentsWrapper
from jedi.inference.value.function import \ from jedi.inference.value.function import \
FunctionValue, FunctionMixin, OverloadedFunctionValue, \ FunctionValue, FunctionMixin, OverloadedFunctionValue, \
BaseFunctionExecutionContext, FunctionExecutionContext, FunctionNameInClass BaseFunctionExecutionContext, FunctionExecutionContext, FunctionNameInClass
from jedi.inference.value.klass import ClassFilter from jedi.inference.value.klass import ClassFilter, init_or_new_func
from jedi.inference.value.dynamic_arrays import get_dynamic_array_instance from jedi.inference.value.dynamic_arrays import get_dynamic_array_instance
from jedi.parser_utils import function_is_staticmethod, function_is_classmethod from jedi.parser_utils import function_is_staticmethod, function_is_classmethod
@@ -156,8 +155,9 @@ class AbstractInstanceValue(Value):
return super().py__iter__(contextualized_node) return super().py__iter__(contextualized_node)
def iterate(): def iterate():
for generator in self.execute_function_slots(iter_slot_names): yield LazyKnownValues(
yield from generator.py__next__(contextualized_node) self.execute_function_slots(iter_slot_names).py__next__(contextualized_node).infer()
)
return iterate() return iterate()
def __repr__(self): def __repr__(self):
@@ -189,6 +189,9 @@ class CompiledInstance(AbstractInstanceValue):
class _BaseTreeInstance(AbstractInstanceValue): class _BaseTreeInstance(AbstractInstanceValue):
get_defined_names: Any
_arguments: Any
@property @property
def array_type(self): def array_type(self):
name = self.class_value.py__name__() name = self.class_value.py__name__()
@@ -221,7 +224,7 @@ class _BaseTreeInstance(AbstractInstanceValue):
elif isinstance(f, CompiledValueFilter): elif isinstance(f, CompiledValueFilter):
yield CompiledInstanceClassFilter(self, f) yield CompiledInstanceClassFilter(self, f)
else: else:
# Propably from the metaclass. # Probably from the metaclass.
yield f yield f
@inference_state_method_cache() @inference_state_method_cache()
@@ -229,7 +232,7 @@ class _BaseTreeInstance(AbstractInstanceValue):
new = node new = node
while True: while True:
func_node = new func_node = new
new = search_ancestor(new, 'funcdef', 'classdef') new = new.search_ancestor('funcdef', 'classdef')
if class_context.tree_node is new: if class_context.tree_node is new:
func = FunctionValue.from_context(class_context, func_node) func = FunctionValue.from_context(class_context, func_node)
bound_method = BoundMethod(self, class_context, func) bound_method = BoundMethod(self, class_context, func)
@@ -324,7 +327,7 @@ class TreeInstance(_BaseTreeInstance):
infer_type_vars_for_execution infer_type_vars_for_execution
args = InstanceArguments(self, self._arguments) args = InstanceArguments(self, self._arguments)
for signature in self.class_value.py__getattribute__('__init__').get_signatures(): for signature in init_or_new_func(self.class_value).get_signatures():
# Just take the first result, it should always be one, because we # Just take the first result, it should always be one, because we
# control the typeshed code. # control the typeshed code.
funcdef = signature.value.tree_node funcdef = signature.value.tree_node
@@ -498,13 +501,13 @@ class SelfName(TreeNameDefinition):
return self._instance return self._instance
def infer(self): def infer(self):
stmt = search_ancestor(self.tree_name, 'expr_stmt') stmt = self.tree_name.search_ancestor('expr_stmt')
if stmt is not None: if stmt is not None:
if stmt.children[1].type == "annassign": if stmt.children[1].type == "annassign":
from jedi.inference.gradual.annotation import infer_annotation from jedi.inference.gradual.annotation import infer_annotation
values = infer_annotation( values = infer_annotation(
self.parent_context, stmt.children[1].children[1] self.parent_context, stmt.children[1].children[1]
).execute_annotation() ).execute_annotation(None)
if values: if values:
return values return values
return super().infer() return super().infer()
+20 -4
View File
@@ -2,6 +2,8 @@
Contains all classes and functions to deal with lists, dicts, generators and Contains all classes and functions to deal with lists, dicts, generators and
iterators in general. iterators in general.
""" """
from typing import Any
from jedi.inference import compiled from jedi.inference import compiled
from jedi.inference import analysis from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \ from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
@@ -20,6 +22,9 @@ from jedi.inference.value.dynamic_arrays import check_array_additions
class IterableMixin: class IterableMixin:
py__iter__: Any
inference_state: Any
def py__next__(self, contextualized_node=None): def py__next__(self, contextualized_node=None):
return self.py__iter__(contextualized_node) return self.py__iter__(contextualized_node)
@@ -39,11 +44,11 @@ class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
array_type = None array_type = None
def _get_wrapped_value(self): def _get_wrapped_value(self):
instance, = self._get_cls().execute_annotation() instance, = self._get_cls().execute_annotation(None)
return instance return instance
def _get_cls(self): def _get_cls(self):
generator, = self.inference_state.typing_module.py__getattribute__('Generator') generator, = self.inference_state.types_module.py__getattribute__('GeneratorType')
return generator return generator
def py__bool__(self): def py__bool__(self):
@@ -128,6 +133,12 @@ def comprehension_from_atom(inference_state, value, atom):
class ComprehensionMixin: class ComprehensionMixin:
_defining_context: Any
_entry_node: Any
array_type: Any
_value_node: Any
_sync_comp_for_node: Any
@inference_state_method_cache() @inference_state_method_cache()
def _get_comp_for_context(self, parent_context, comp_for): def _get_comp_for_context(self, parent_context, comp_for):
return CompForContext(parent_context, comp_for) return CompForContext(parent_context, comp_for)
@@ -176,6 +187,8 @@ class ComprehensionMixin:
class _DictMixin: class _DictMixin:
get_mapping_item_values: Any
def _get_generics(self): def _get_generics(self):
return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values()) return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values())
@@ -201,7 +214,7 @@ class Sequence(LazyAttributeOverwrite, IterableMixin):
c, = GenericClass( c, = GenericClass(
klass, klass,
TupleGenericManager(self._cached_generics()) TupleGenericManager(self._cached_generics())
).execute_annotation() ).execute_annotation(None)
return c return c
def py__bool__(self): def py__bool__(self):
@@ -220,7 +233,7 @@ class Sequence(LazyAttributeOverwrite, IterableMixin):
class _BaseComprehension(ComprehensionMixin): class _BaseComprehension(ComprehensionMixin):
def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node): def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node):
assert sync_comp_for_node.type == 'sync_comp_for' assert sync_comp_for_node.type == 'sync_comp_for'
super().__init__(inference_state) super().__init__(inference_state) # type: ignore[call-arg]
self._defining_context = defining_context self._defining_context = defining_context
self._sync_comp_for_node = sync_comp_for_node self._sync_comp_for_node = sync_comp_for_node
self._entry_node = entry_node self._entry_node = entry_node
@@ -248,6 +261,9 @@ class GeneratorComprehension(_BaseComprehension, GeneratorBase):
class _DictKeyMixin: class _DictKeyMixin:
_dict_keys: Any
_dict_values: Any
# TODO merge with _DictMixin? # TODO merge with _DictMixin?
def get_mapping_item_values(self): def get_mapping_item_values(self):
return self._dict_keys(), self._dict_values() return self._dict_keys(), self._dict_values()
+341 -5
View File
@@ -36,6 +36,10 @@ py__doc__() Returns the docstring for a value.
====================================== ======================================== ====================================== ========================================
""" """
from __future__ import annotations
from typing import List, Optional, Tuple, TYPE_CHECKING, Any
from jedi import debug from jedi import debug
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \ from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
function_is_property function_is_property
@@ -47,11 +51,18 @@ from jedi.inference.filters import ParserTreeFilter
from jedi.inference.names import TreeNameDefinition, ValueName from jedi.inference.names import TreeNameDefinition, ValueName
from jedi.inference.arguments import unpack_arglist, ValuesArguments from jedi.inference.arguments import unpack_arglist, ValuesArguments
from jedi.inference.base_value import ValueSet, iterator_to_value_set, \ from jedi.inference.base_value import ValueSet, iterator_to_value_set, \
NO_VALUES NO_VALUES, ValueWrapper
from jedi.inference.context import ClassContext from jedi.inference.context import ClassContext
from jedi.inference.value.function import FunctionAndClassBase from jedi.inference.value.function import FunctionAndClassBase, FunctionMixin
from jedi.inference.value.decorator import Decoratee
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
from inspect import Parameter
from jedi.inference.names import BaseTreeParamName
from jedi.inference.signature import AbstractSignature
if TYPE_CHECKING:
from jedi.inference import InferenceState
class ClassName(TreeNameDefinition): class ClassName(TreeNameDefinition):
@@ -129,7 +140,75 @@ class ClassFilter(ParserTreeFilter):
return [name for name in names if self._access_possible(name)] return [name for name in names if self._access_possible(name)]
def init_param_value(arg_nodes) -> Optional[bool]:
"""
Returns:
- ``True`` if ``@dataclass(init=True)``
- ``False`` if ``@dataclass(init=False)``
- ``None`` if not specified ``@dataclass()``
"""
for arg_node in arg_nodes:
if (
arg_node.type == "argument"
and arg_node.children[0].value == "init"
):
if arg_node.children[2].value == "False":
return False
elif arg_node.children[2].value == "True":
return True
return None
def get_dataclass_param_names(cls) -> List[DataclassParamName]:
"""
``cls`` is a :class:`ClassMixin`. The type is only documented as mypy would
complain that some fields are missing.
.. code:: python
@dataclass
class A:
a: int
b: str = "toto"
For the previous example, the param names would be ``a`` and ``b``.
"""
param_names = []
filter_ = cls.as_context().get_global_filter()
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':
node = annassign.children[1]
if node.type == "atom_expr" and node.children[0].value == "ClassVar":
continue
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 param_names
class ClassMixin: class ClassMixin:
tree_node: Any
parent_context: Any
inference_state: InferenceState
py__bases__: Any
get_metaclasses: Any
get_metaclass_filters: Any
get_metaclass_signatures: Any
list_type_vars: Any
def is_class(self): def is_class(self):
return True return True
@@ -206,7 +285,6 @@ class ClassMixin:
if not is_instance and include_type_when_class: if not is_instance and include_type_when_class:
from jedi.inference.compiled import builtin_from_name from jedi.inference.compiled import builtin_from_name
type_ = builtin_from_name(self.inference_state, 'type') type_ = builtin_from_name(self.inference_state, 'type')
assert isinstance(type_, ClassValue)
if type_ != self: if type_ != self:
# We are not using execute_with_values here, because the # We are not using execute_with_values here, because the
# plugin function for type would get executed instead of an # plugin function for type would get executed instead of an
@@ -221,6 +299,73 @@ class ClassMixin:
assert x is not None assert x is not None
yield x yield x
def _has_dataclass_transform_metaclasses(self) -> Tuple[bool, Optional[bool]]:
for meta in self.get_metaclasses(): # type: ignore[attr-defined]
if (
isinstance(meta, Decoratee)
# Internal leakage :|
and isinstance(meta._wrapped_value, DataclassTransformer)
):
return True, meta._wrapped_value.init_mode_from_new()
return False, None
def _get_dataclass_transform_signatures(self) -> List[DataclassSignature]:
"""
Returns: A non-empty list if the class has dataclass semantics else an
empty list.
The dataclass-like semantics will be assumed for any class that directly
or indirectly derives from the decorated class or uses the decorated
class as a metaclass.
"""
param_names = []
is_dataclass_transform = False
default_init_mode: Optional[bool] = None
for cls in reversed(list(self.py__mro__())):
if not is_dataclass_transform:
# If dataclass_transform is applied to a class, dataclass-like semantics
# will be assumed for any class that directly or indirectly derives from
# the decorated class or uses the decorated class as a metaclass.
if (
isinstance(cls, DataclassTransformer)
and cls.init_mode_from_init_subclass
):
is_dataclass_transform = True
default_init_mode = cls.init_mode_from_init_subclass
elif (
# Some object like CompiledValues would not be compatible
isinstance(cls, ClassMixin)
):
is_dataclass_transform, default_init_mode = (
cls._has_dataclass_transform_metaclasses()
)
# Attributes on the decorated class and its base classes are not
# considered to be fields.
if is_dataclass_transform:
continue
# All inherited classes behave like dataclass semantics
if (
is_dataclass_transform
and isinstance(cls, ClassValue)
and (
cls.init_param_mode()
or (cls.init_param_mode() is None and default_init_mode)
)
):
param_names.extend(
get_dataclass_param_names(cls)
)
if is_dataclass_transform:
return [DataclassSignature(cls, param_names)]
else:
return []
def get_signatures(self): def get_signatures(self):
# Since calling staticmethod without a function is illegal, the Jedi # Since calling staticmethod without a function is illegal, the Jedi
# plugin doesn't return anything. Therefore call directly and get what # plugin doesn't return anything. Therefore call directly and get what
@@ -231,8 +376,14 @@ class ClassMixin:
if sigs: if sigs:
return sigs return sigs
args = ValuesArguments([]) args = ValuesArguments([])
init_funcs = self.py__call__(args).py__getattribute__('__init__') instance = self.py__call__(args)
return [sig.bind(self) for sig in init_funcs.get_signatures()] init_funcs = init_or_new_func(instance)
dataclass_sigs = self._get_dataclass_transform_signatures()
if dataclass_sigs:
return dataclass_sigs
else:
return [sig.bind(self) for sig in init_funcs.get_signatures()]
def _as_context(self): def _as_context(self):
return ClassContext(self) return ClassContext(self)
@@ -319,6 +470,175 @@ class ClassMixin:
return ValueSet({self}) return ValueSet({self})
def init_or_new_func(value):
init_funcs = value.py__getattribute__('__init__')
if len(init_funcs) == 1:
init = next(iter(init_funcs))
try:
class_context = init.class_context
except AttributeError:
pass
else:
# In the case where we are on object.__init__, we try to use
# __new__.
if class_context.get_root_context().is_builtins_module() \
and init.class_context.name.string_name == "object":
return value.py__getattribute__('__new__')
return init_funcs
class DataclassParamName(BaseTreeParamName):
"""
Represent a field declaration on a class with dataclass semantics.
"""
def __init__(self, parent_context, tree_name, annotation_node, default_node):
super().__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_VALUES
else:
return self.parent_context.infer_node(self.annotation_node)
class DataclassSignature(AbstractSignature):
"""
It represents the ``__init__`` signature of a class with dataclass semantics.
.. code:: python
"""
def __init__(self, value, param_names):
super().__init__(value)
self._param_names = param_names
def get_param_names(self, resolve_stars=False):
return self._param_names
class DataclassDecorator(ValueWrapper, FunctionMixin):
"""
A dataclass(-like) decorator with custom parameters.
.. code:: python
@dataclass(init=True) # this
class A: ...
@dataclass_transform
def create_model(*, init=False): pass
@create_model(init=False) # or this
class B: ...
"""
def __init__(self, function, arguments, default_init: bool = True):
"""
Args:
function: Decoratee | function
arguments: The parameters to the dataclass function decorator
default_init: Boolean to indicate the default init value
"""
super().__init__(function)
argument_init = self._init_param_value(arguments)
self.init_param_mode = (
argument_init if argument_init is not None else default_init
)
def _init_param_value(self, arguments) -> Optional[bool]:
if not arguments.argument_node:
return None
arg_nodes = (
arguments.argument_node.children
if arguments.argument_node.type == "arglist"
else [arguments.argument_node]
)
return init_param_value(arg_nodes)
class DataclassTransformer(ValueWrapper, ClassMixin):
"""
A class decorated with the ``dataclass_transform`` decorator. dataclass-like
semantics will be assumed for any class that directly or indirectly derives
from the decorated class or uses the decorated class as a metaclass.
Attributes on the decorated class and its base classes are not considered to
be fields.
"""
def __init__(self, wrapped_value):
super().__init__(wrapped_value)
def init_mode_from_new(self) -> bool:
"""Default value if missing is ``True``"""
new_methods = self._wrapped_value.py__getattribute__("__new__")
if not new_methods:
return True
new_method = list(new_methods)[0]
for param in new_method.get_param_names():
if (
param.string_name == "init"
and param.default_node
and param.default_node.type == "keyword"
):
if param.default_node.value == "False":
return False
elif param.default_node.value == "True":
return True
return True
@property
def init_mode_from_init_subclass(self) -> Optional[bool]:
# def __init_subclass__(cls) -> None: ... is hardcoded in the typeshed
# so the extra parameters can not be inferred.
return True
class DataclassWrapper(ValueWrapper, ClassMixin):
"""
A class with dataclass semantics from a decorator. The init parameters are
only from the current class and parent classes decorated where the ``init``
parameter was ``True``.
.. code:: python
@dataclass
class A: ... # this
@dataclass_transform
def create_model(): pass
@create_model()
class B: ... # or this
"""
def __init__(
self, wrapped_value, should_generate_init: bool
):
super().__init__(wrapped_value)
self.should_generate_init = should_generate_init
def get_signatures(self):
param_names = []
for cls in reversed(list(self.py__mro__())):
if (
isinstance(cls, DataclassWrapper)
and cls.should_generate_init
):
param_names.extend(get_dataclass_param_names(cls))
return [DataclassSignature(cls, param_names)]
class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass): class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
api_type = 'class' api_type = 'class'
@@ -385,6 +705,22 @@ class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
return values return values
return NO_VALUES return NO_VALUES
def init_param_mode(self) -> Optional[bool]:
"""
It returns ``True`` if ``class X(init=False):`` else ``False``.
"""
bases_arguments = self._get_bases_arguments()
if bases_arguments is None:
return None
if bases_arguments.argument_node.type != "arglist":
# If it is not inheriting from the base model and having
# extra parameters, then init behavior is not changed.
return None
return init_param_value(bases_arguments.argument_node.children)
@plugin_manager.decorate() @plugin_manager.decorate()
def get_metaclass_signatures(self, metaclasses): def get_metaclass_signatures(self, metaclasses):
return [] return []
+16 -4
View File
@@ -1,6 +1,6 @@
import os import os
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, TYPE_CHECKING, Any
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName from jedi.inference.names import AbstractNameDefinition, ModuleName
@@ -13,6 +13,9 @@ from jedi.inference.compiled import create_simple_object
from jedi.inference.base_value import ValueSet from jedi.inference.base_value import ValueSet
from jedi.inference.context import ModuleContext from jedi.inference.context import ModuleContext
if TYPE_CHECKING:
from jedi.inference import InferenceState
class _ModuleAttributeName(AbstractNameDefinition): class _ModuleAttributeName(AbstractNameDefinition):
""" """
@@ -35,6 +38,11 @@ class _ModuleAttributeName(AbstractNameDefinition):
class SubModuleDictMixin: class SubModuleDictMixin:
inference_state: "InferenceState"
is_package: Any
py__path__: Any
as_context: Any
@inference_state_method_cache() @inference_state_method_cache()
def sub_modules_dict(self): def sub_modules_dict(self):
""" """
@@ -57,6 +65,10 @@ class SubModuleDictMixin:
class ModuleMixin(SubModuleDictMixin): class ModuleMixin(SubModuleDictMixin):
_module_name_class = ModuleName _module_name_class = ModuleName
tree_node: Any
string_names: Any
sub_modules_dict: Any
py__file__: Any
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):
yield MergedFilter( yield MergedFilter(
@@ -80,7 +92,7 @@ class ModuleMixin(SubModuleDictMixin):
def is_stub(self): def is_stub(self):
return False return False
@property # type: ignore[misc] @property
@inference_state_method_cache() @inference_state_method_cache()
def name(self): def name(self):
return self._module_name_class(self, self.string_names[-1]) return self._module_name_class(self, self.string_names[-1])
@@ -138,7 +150,7 @@ class ModuleValue(ModuleMixin, TreeValue):
api_type = 'module' api_type = 'module'
def __init__(self, inference_state, module_node, code_lines, file_io=None, def __init__(self, inference_state, module_node, code_lines, file_io=None,
string_names=None, is_package=False): string_names=None, is_package=False) -> None:
super().__init__( super().__init__(
inference_state, inference_state,
parent_context=None, parent_context=None,
@@ -149,7 +161,7 @@ class ModuleValue(ModuleMixin, TreeValue):
self._path: Optional[Path] = None self._path: Optional[Path] = None
else: else:
self._path = file_io.path self._path = file_io.path
self.string_names = string_names # Optional[Tuple[str, ...]] self.string_names: Optional[tuple[str, ...]] = string_names
self.code_lines = code_lines self.code_lines = code_lines
self._is_package = is_package self._is_package = is_package
+1 -1
View File
@@ -38,7 +38,7 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin):
def get_qualified_names(self): def get_qualified_names(self):
return () return ()
@property # type: ignore[misc] @property
@inference_state_method_cache() @inference_state_method_cache()
def name(self): def name(self):
string_name = self.py__package__()[-1] string_name = self.py__package__()[-1]
+10 -6
View File
@@ -259,7 +259,7 @@ def get_parent_scope(node, include_flows=False):
# the if, but the parent of the if. # the if, but the parent of the if.
if not (scope.type == 'if_stmt' if not (scope.type == 'if_stmt'
and any(n.start_pos <= node.start_pos < n.end_pos and any(n.start_pos <= node.start_pos < n.end_pos
for n in scope.get_test_nodes())): for n in scope.get_test_nodes())): # type: ignore[attr-defined]
return scope return scope
scope = scope.parent scope = scope.parent
@@ -320,7 +320,7 @@ def expr_is_dotted(node):
return node.type == 'name' return node.type == 'name'
def _function_is_x_method(*method_names): def _function_is_x_method(decorator_checker):
def wrapper(function_node): def wrapper(function_node):
""" """
This is a heuristic. It will not hold ALL the times, but it will be This is a heuristic. It will not hold ALL the times, but it will be
@@ -330,12 +330,16 @@ def _function_is_x_method(*method_names):
""" """
for decorator in function_node.get_decorators(): for decorator in function_node.get_decorators():
dotted_name = decorator.children[1] dotted_name = decorator.children[1]
if dotted_name.get_code() in method_names: if decorator_checker(dotted_name.get_code()):
return True return True
return False return False
return wrapper return wrapper
function_is_staticmethod = _function_is_x_method('staticmethod') function_is_staticmethod = _function_is_x_method(lambda m: m == "staticmethod")
function_is_classmethod = _function_is_x_method('classmethod') function_is_classmethod = _function_is_x_method(lambda m: m == "classmethod")
function_is_property = _function_is_x_method('property', 'cached_property') function_is_property = _function_is_x_method(
lambda m: m == "property"
or m == "cached_property"
or (m.endswith(".setter"))
)
+4 -3
View File
@@ -2,6 +2,7 @@
Module is used to infer Django model fields. Module is used to infer Django model fields.
""" """
from inspect import Parameter from inspect import Parameter
from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.cache import inference_state_function_cache from jedi.inference.cache import inference_state_function_cache
@@ -45,7 +46,7 @@ _FILTER_LIKE_METHODS = ('create', 'filter', 'exclude', 'update', 'get',
def _get_deferred_attributes(inference_state): def _get_deferred_attributes(inference_state):
return inference_state.import_module( return inference_state.import_module(
('django', 'db', 'models', 'query_utils') ('django', 'db', 'models', 'query_utils')
).py__getattribute__('DeferredAttribute').execute_annotation() ).py__getattribute__('DeferredAttribute').execute_annotation(None)
def _infer_scalar_field(inference_state, field_name, field_tree_instance, is_instance): def _infer_scalar_field(inference_state, field_name, field_tree_instance, is_instance):
@@ -129,7 +130,7 @@ def _create_manager_for(cls, manager_cls='BaseManager'):
for m in managers: for m in managers:
if m.is_class_mixin(): if m.is_class_mixin():
generics_manager = TupleGenericManager((ValueSet([cls]),)) generics_manager = TupleGenericManager((ValueSet([cls]),))
for c in GenericClass(m, generics_manager).execute_annotation(): for c in GenericClass(m, generics_manager).execute_annotation(None):
return c return c
return None return None
@@ -140,7 +141,7 @@ def _new_dict_filter(cls, is_instance):
include_metaclasses=False, include_metaclasses=False,
include_type_when_class=False) include_type_when_class=False)
) )
dct = { dct: dict[str, Any] = {
name.string_name: DjangoModelName(cls, name, is_instance) name.string_name: DjangoModelName(cls, name, is_instance)
for filter_ in reversed(filters) for filter_ in reversed(filters)
for name in filter_.values() for name in filter_.values()
+33 -8
View File
@@ -1,6 +1,7 @@
import sys
from typing import List
from pathlib import Path from pathlib import Path
from parso.tree import search_ancestor
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.imports import goto_import, load_module_from_path from jedi.inference.imports import goto_import, load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter
@@ -36,7 +37,7 @@ def infer_anonymous_param(func):
== ('typing', 'Generator') == ('typing', 'Generator')
for v in result): for v in result):
return ValueSet.from_sets( return ValueSet.from_sets(
v.py__getattribute__('__next__').execute_annotation() v.py__getattribute__('__next__').execute_annotation(None)
for v in result for v in result
) )
return result return result
@@ -118,7 +119,7 @@ def _is_a_pytest_param_and_inherited(param_name):
This is a heuristic and will work in most cases. This is a heuristic and will work in most cases.
""" """
funcdef = search_ancestor(param_name.tree_name, 'funcdef') funcdef = param_name.tree_name.search_ancestor('funcdef')
if funcdef is None: # A lambda if funcdef is None: # A lambda
return False, False return False, False
decorators = funcdef.get_decorators() decorators = funcdef.get_decorators()
@@ -131,15 +132,20 @@ def _is_pytest_func(func_name, decorator_nodes):
or any('fixture' in n.get_code() for n in decorator_nodes) or any('fixture' in n.get_code() for n in decorator_nodes)
def _find_pytest_plugin_modules(): def _find_pytest_plugin_modules() -> List[List[str]]:
""" """
Finds pytest plugin modules hooked by setuptools entry points Finds pytest plugin modules hooked by setuptools entry points
See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
""" """
from pkg_resources import iter_entry_points from importlib.metadata import entry_points
return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")] if sys.version_info >= (3, 10):
pytest_entry_points = entry_points(group="pytest11")
else:
pytest_entry_points = entry_points().get("pytest11", ())
return [ep.module.split(".") for ep in pytest_entry_points]
@inference_state_method_cache() @inference_state_method_cache()
@@ -160,13 +166,19 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
if Path(file_io.path) != module_context.py__file__(): if Path(file_io.path) != module_context.py__file__():
try: try:
m = load_module_from_path(module_context.inference_state, file_io) m = load_module_from_path(module_context.inference_state, file_io)
yield m.as_context() conftest_module = m.as_context()
yield conftest_module
plugins_list = m.tree_node.get_used_names().get("pytest_plugins")
if plugins_list:
name = conftest_module.create_name(plugins_list[0])
yield from _load_pytest_plugins(module_context, name)
except FileNotFoundError: except FileNotFoundError:
pass pass
folder = folder.get_parent_folder() folder = folder.get_parent_folder()
# prevent an infinite for loop if the same parent folder is return twice # prevent an infinite for loop if the same parent folder is return twice
if last_folder is not None and folder.path == last_folder.path: if last_folder is not None and folder.path == last_folder.path: # type: ignore # TODO
break break
last_folder = folder # keep track of the last found parent name last_folder = folder # keep track of the last found parent name
@@ -175,6 +187,19 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
yield module_value.as_context() yield module_value.as_context()
def _load_pytest_plugins(module_context, name):
from jedi.inference.helpers import get_str_or_none
for inferred in name.infer():
for seq_value in inferred.py__iter__():
for value in seq_value.infer():
fq_name = get_str_or_none(value)
if fq_name:
names = fq_name.split(".")
for module_value in module_context.inference_state.import_module(names):
yield module_value.as_context()
class FixtureFilter(ParserTreeFilter): class FixtureFilter(ParserTreeFilter):
def _filter(self, names): def _filter(self, names):
for name in super()._filter(names): for name in super()._filter(names):
+138 -69
View File
@@ -11,7 +11,6 @@ compiled module that returns the types for C-builtins.
""" """
import parso import parso
import os import os
from inspect import Parameter
from jedi import debug from jedi import debug
from jedi.inference.utils import safe_property from jedi.inference.utils import safe_property
@@ -25,15 +24,20 @@ from jedi.inference.value.instance import \
from jedi.inference.base_value import ContextualizedNode, \ from jedi.inference.base_value import ContextualizedNode, \
NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper
from jedi.inference.value import ClassValue, ModuleValue from jedi.inference.value import ClassValue, ModuleValue
from jedi.inference.value.klass import ClassMixin from jedi.inference.value.decorator import Decoratee
from jedi.inference.value.klass import (
DataclassWrapper,
DataclassDecorator,
DataclassTransformer,
)
from jedi.inference.value.function import FunctionMixin from jedi.inference.value.function import FunctionMixin
from jedi.inference.value import iterable from jedi.inference.value import iterable
from jedi.inference.lazy_value import LazyTreeValue, LazyKnownValue, \ from jedi.inference.lazy_value import LazyTreeValue, LazyKnownValue, \
LazyKnownValues LazyKnownValues
from jedi.inference.names import ValueName, BaseTreeParamName from jedi.inference.names import ValueName
from jedi.inference.filters import AttributeOverwrite, publish_method, \ from jedi.inference.filters import AttributeOverwrite, publish_method, \
ParserTreeFilter, DictFilter ParserTreeFilter, DictFilter
from jedi.inference.signature import AbstractSignature, SignatureWrapper from jedi.inference.signature import SignatureWrapper
# Copied from Python 3.6's stdlib. # Copied from Python 3.6's stdlib.
@@ -130,7 +134,7 @@ def execute(callback):
except KeyError: except KeyError:
pass pass
else: else:
return func(value, arguments=arguments, callback=call) return func(value, arguments=arguments, callback=call) # type: ignore
return call() return call()
return wrapper return wrapper
@@ -591,63 +595,101 @@ def _random_choice(sequences):
def _dataclass(value, arguments, callback): def _dataclass(value, arguments, callback):
"""
Decorator entry points for dataclass.
1. dataclass decorator declaration with parameters
2. dataclass semantics on a class from a dataclass(-like) decorator
"""
for c in _follow_param(value.inference_state, arguments, 0): for c in _follow_param(value.inference_state, arguments, 0):
if c.is_class(): if c.is_class():
return ValueSet([DataclassWrapper(c)]) # Declare dataclass semantics on a class from a dataclass decorator
should_generate_init = (
# Customized decorator, init may be disabled
value.init_param_mode
if isinstance(value, DataclassDecorator)
# Bare dataclass decorator, always with init mode
else True
)
return ValueSet([DataclassWrapper(c, should_generate_init)])
else: else:
return ValueSet([value]) # @dataclass(init=False)
# dataclass decorator customization
return ValueSet(
[
DataclassDecorator(
value,
arguments=arguments,
default_init=True,
)
]
)
return NO_VALUES return NO_VALUES
class DataclassWrapper(ValueWrapper, ClassMixin): def _dataclass_transform(value, arguments, callback):
def get_signatures(self): """
param_names = [] Decorator entry points for dataclass_transform.
for cls in reversed(list(self.py__mro__())):
if isinstance(cls, DataclassWrapper):
filter_ = cls.as_context().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)]
1. dataclass-like decorator instantiation from a dataclass_transform decorator
2. dataclass_transform decorator declaration with parameters
3. dataclass-like decorator declaration with parameters
4. dataclass-like semantics on a class from a dataclass-like decorator
"""
for c in _follow_param(value.inference_state, arguments, 0):
if c.is_class():
is_dataclass_transform = (
value.name.string_name == "dataclass_transform"
# The decorator function from dataclass_transform acting as the
# dataclass decorator.
and not isinstance(value, Decoratee)
# The decorator function from dataclass_transform acting as the
# dataclass decorator with customized parameters
and not isinstance(value, DataclassDecorator)
)
class DataclassSignature(AbstractSignature): if is_dataclass_transform:
def __init__(self, value, param_names): # Declare base class
super().__init__(value) return ValueSet([DataclassTransformer(c)])
self._param_names = param_names else:
# Declare dataclass-like semantics on a class from a
def get_param_names(self, resolve_stars=False): # dataclass-like decorator
return self._param_names should_generate_init = value.init_param_mode
return ValueSet([DataclassWrapper(c, should_generate_init)])
elif c.is_function():
class DataclassParamName(BaseTreeParamName): # dataclass-like decorator instantiation:
def __init__(self, parent_context, tree_name, annotation_node, default_node): # @dataclass_transform
super().__init__(parent_context, tree_name) # def create_model()
self.annotation_node = annotation_node return ValueSet(
self.default_node = default_node [
DataclassDecorator(
def get_kind(self): value,
return Parameter.POSITIONAL_OR_KEYWORD arguments=arguments,
default_init=True,
def infer(self): )
if self.annotation_node is None: ]
return NO_VALUES )
elif (
# @dataclass_transform
# def create_model(): pass
# @create_model(init=...)
isinstance(value, Decoratee)
):
# dataclass (or like) decorator customization
return ValueSet(
[
DataclassDecorator(
value,
arguments=arguments,
default_init=value._wrapped_value.init_param_mode,
)
]
)
else: else:
return self.parent_context.infer_node(self.annotation_node) # dataclass_transform decorator with parameters; nothing impactful
return ValueSet([value])
return NO_VALUES
class ItemGetterCallable(ValueWrapper): class ItemGetterCallable(ValueWrapper):
@@ -747,6 +789,13 @@ def _os_path_join(args_set, callback):
return callback() return callback()
_path_overrides = {
'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,
}
_implemented = { _implemented = {
'builtins': { 'builtins': {
'getattr': builtins_getattr, 'getattr': builtins_getattr,
@@ -798,26 +847,19 @@ _implemented = {
# runtime_checkable doesn't really change anything and is just # runtime_checkable doesn't really change anything and is just
# adding logs for infering stuff, so we can safely ignore it. # adding logs for infering stuff, so we can safely ignore it.
'runtime_checkable': lambda value, arguments, callback: NO_VALUES, 'runtime_checkable': lambda value, arguments, callback: NO_VALUES,
# Python 3.11+
'dataclass_transform': _dataclass_transform,
},
'typing_extensions': {
# Python <3.11
'dataclass_transform': _dataclass_transform,
}, },
'dataclasses': { 'dataclasses': {
# For now this works at least better than Jedi trying to understand it. # For now this works at least better than Jedi trying to understand it.
'dataclass': _dataclass 'dataclass': _dataclass
}, },
# attrs exposes declaration interface roughly compatible with dataclasses 'posixpath': _path_overrides,
# via attrs.define, attrs.frozen and attrs.mutable 'ntpath': _path_overrides,
# https://www.attrs.org/en/stable/names.html
'attr': {
'define': _dataclass,
},
'attrs': {
'define': _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,
}
} }
@@ -867,11 +909,38 @@ class EnumInstance(LazyValueWrapper):
yield f yield f
# Make sure tuple[...] behaves like Tuple[...]
class TupleClassWrapper(ValueWrapper):
def py__getitem__(self, index_value_set, contextualized_node):
return self.inference_state.typing_tuple().py__getitem__(
index_value_set,
contextualized_node,
)
# Make sure type[...] behaves like Type[...]
class TypeClassWrapper(ValueWrapper):
def py__getitem__(self, index_value_set, contextualized_node):
return self.inference_state.typing_type().py__getitem__(
index_value_set,
contextualized_node,
)
def tree_name_to_values(func): def tree_name_to_values(func):
def wrapper(inference_state, context, tree_name): def wrapper(inference_state, context, tree_name):
if tree_name.value == 'sep' and context.is_module() and context.py__name__() == 'os.path': if tree_name.value == 'sep' \
and context.is_module() and context.py__name__() in ('posixpath', 'ntpath'):
return ValueSet({ return ValueSet({
compiled.create_simple_object(inference_state, os.path.sep), compiled.create_simple_object(inference_state, os.path.sep),
}) })
if tree_name.value == 'tuple' \
and context.is_module() and context.py__name__() == 'builtins':
tup, = func(inference_state, context, tree_name)
return ValueSet([TupleClassWrapper(tup)])
if tree_name.value == 'type' \
and context.is_module() and context.py__name__() == 'builtins':
tup, = func(inference_state, context, tree_name)
return ValueSet([TypeClassWrapper(tup)])
return func(inference_state, context, tree_name) return func(inference_state, context, tree_name)
return wrapper return wrapper
+9
View File
@@ -143,6 +143,15 @@ This improves autocompletion for libraries that use ``setattr`` or
``globals()`` modifications a lot. ``globals()`` modifications a lot.
""" """
allow_unsafe_interpreter_executions = True
"""
Controls whether descriptors are evaluated when using an Interpreter. This is
something you might want to control when using Jedi from a Repl (e.g. IPython)
Generally this setting allows Jedi to execute __getitem__ and descriptors like
`property`.
"""
# ---------------- # ----------------
# Caching Validity # Caching Validity
# ---------------- # ----------------
+1 -1
View File
@@ -2,7 +2,7 @@
Utilities for end-users. Utilities for end-users.
""" """
import __main__ # type: ignore[import] import __main__
from collections import namedtuple from collections import namedtuple
import logging import logging
import traceback import traceback
+14
View File
@@ -0,0 +1,14 @@
[tool.zuban]
strict = true
enable_error_code = ["ignore-without-code"]
# Revert some --strict specific flags:
allow_untyped_calls = true
allow_untyped_defs = true
allow_incomplete_defs = true
allow_untyped_globals = true
untyped_strict_optional = false
implicit_reexport = true
# Exclude our copies of external stubs
exclude = "^jedi/third_party|^test/(completion|refactor|static_analysis|examples)/"
-50
View File
@@ -1,50 +0,0 @@
#!/usr/bin/env python
"""
Profile a piece of Python code with ``cProfile`` that uses the diff parser.
Usage:
profile.py <file> [-d] [-s <sort>]
profile.py -h | --help
Options:
-h --help Show this screen.
-d --debug Enable Jedi internal debugging.
-s <sort> Sort the profile results, e.g. cumtime, name [default: time].
"""
import cProfile
from docopt import docopt
from jedi.parser.python import load_grammar
from jedi.parser.diff import DiffParser
from jedi.parser.python import ParserWithRecovery
from jedi.common import splitlines
import jedi
def run(parser, lines):
diff_parser = DiffParser(parser)
diff_parser.update(lines)
# Make sure used_names is loaded
parser.module.used_names
def main(args):
if args['--debug']:
jedi.set_debug_function(notices=True)
with open(args['<file>']) as f:
code = f.read()
grammar = load_grammar()
parser = ParserWithRecovery(grammar, code)
# Make sure used_names is loaded
parser.module.used_names
code = code + '\na\n' # Add something so the diff parser needs to run.
lines = splitlines(code, keepends=True)
cProfile.runctx('run(parser, lines)', globals(), locals(), sort=args['-s'])
if __name__ == '__main__':
args = docopt(__doc__)
main(args)
+1 -1
View File
@@ -13,7 +13,7 @@ Note: This requires the psutil library, available on PyPI.
import time import time
import sys import sys
import os import os
import psutil import psutil # type: ignore[import-untyped]
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..')) sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..'))
import jedi import jedi
+1 -1
View File
@@ -56,7 +56,7 @@ def main(args):
run(code, i, infer=infer) run(code, i, infer=infer)
if args['--precision']: if args['--precision']:
pstats.f8 = f8 pstats.f8 = f8 # type: ignore[attr-defined] # TODO this does not seem to exist?!
jedi.set_debug_function(notices=args['--debug']) jedi.set_debug_function(notices=args['--debug'])
if args['--omit']: if args['--omit']:
+2 -2
View File
@@ -17,11 +17,11 @@ import sys
try: try:
import urllib.request as urllib2 import urllib.request as urllib2
except ImportError: except ImportError:
import urllib2 import urllib2 # type: ignore[import-not-found, no-redef]
import gc import gc
from os.path import abspath, dirname from os.path import abspath, dirname
import objgraph import objgraph # type: ignore[import-untyped]
sys.path.insert(0, dirname(dirname(abspath(__file__)))) sys.path.insert(0, dirname(dirname(abspath(__file__))))
import jedi import jedi
+7 -28
View File
@@ -21,34 +21,13 @@ per-file-ignores =
jedi/__init__.py:F401 jedi/__init__.py:F401
jedi/inference/compiled/__init__.py:F401 jedi/inference/compiled/__init__.py:F401
jedi/inference/value/__init__.py:F401 jedi/inference/value/__init__.py:F401
exclude = jedi/third_party/* .tox/* exclude =
.tox/*
jedi/third_party/*
test/completion/*
test/examples/*
test/refactor/*
test/static_analysis/*
[pycodestyle] [pycodestyle]
max-line-length = 100 max-line-length = 100
[mypy]
# Ensure generics are explicit about what they are (e.g: `List[str]` rather than
# just `List`)
disallow_any_generics = True
disallow_subclassing_any = True
# Avoid creating future gotchas emerging from bad typing
warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
warn_unused_configs = True
warn_unreachable = True
# Require values to be explicitly re-exported; this makes things easier for
# Flake8 too and avoids accidentally importing thing from the "wrong" place
# (which helps avoid circular imports)
implicit_reexport = False
strict_equality = True
[mypy-jedi,jedi.inference.compiled,jedi.inference.value,parso]
# Various __init__.py files which contain re-exports we want to implicitly make.
implicit_reexport = True
+48 -39
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from typing import cast
from setuptools import setup, find_packages from setuptools import setup, find_packages
from setuptools.depends import get_module_constant from setuptools.depends import get_module_constant
@@ -9,7 +10,7 @@ __AUTHOR__ = 'David Halter'
__AUTHOR_EMAIL__ = 'davidhalter88@gmail.com' __AUTHOR_EMAIL__ = 'davidhalter88@gmail.com'
# Get the version from within jedi. It's defined in exactly one place now. # Get the version from within jedi. It's defined in exactly one place now.
version = get_module_constant("jedi", "__version__") version = cast(str, get_module_constant("jedi", "__version__"))
readme = open('README.rst').read() + '\n\n' + open('CHANGELOG.rst').read() readme = open('README.rst').read() + '\n\n' + open('CHANGELOG.rst').read()
@@ -27,53 +28,61 @@ setup(name='jedi',
maintainer=__AUTHOR__, maintainer=__AUTHOR__,
maintainer_email=__AUTHOR_EMAIL__, maintainer_email=__AUTHOR_EMAIL__,
url='https://github.com/davidhalter/jedi', url='https://github.com/davidhalter/jedi',
project_urls={
"Documentation": 'https://jedi.readthedocs.io/en/latest/',
},
license='MIT', license='MIT',
keywords='python completion refactoring vim', keywords='python completion refactoring vim',
long_description=readme, long_description=readme,
packages=find_packages(exclude=['test', 'test.*']), packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=3.6', python_requires='>=3.10',
install_requires=['parso>=0.8.0,<0.9.0'], # Python 3.13 grammars are added to parso in 0.8.4
install_requires=['parso>=0.8.6,<0.9.0'],
extras_require={ extras_require={
'testing': [ 'dev': [
'pytest<7.0.0', 'pytest<9.0.0',
# docopt for sith doctests # docopt for sith doctests
'docopt', 'docopt',
# coloroma for colored debug output # coloroma for colored debug output
'colorama', 'colorama',
'Django<3.1', # For now pin this. 'Django',
'attrs', 'attrs',
], 'typing_extensions',
'qa': [ # latest version on 2025-06-16
'flake8==3.8.3', 'flake8==7.1.2',
'mypy==0.782', 'zuban==0.7.0',
# Arbitrary pins, latest at the time of pinning
'types-setuptools==80.9.0.20250529',
], ],
'docs': [ 'docs': [
# Just pin all of these. # Just pin all of these.
'Jinja2==2.11.3', 'alabaster==1.0.0',
'MarkupSafe==1.1.1', 'babel==2.18.0',
'Pygments==2.8.1', 'certifi==2026.4.22',
'alabaster==0.7.12', 'charset-normalizer==3.4.7',
'babel==2.9.1', 'docutils==0.22.4',
'chardet==4.0.0', 'idna==3.13',
'commonmark==0.8.1', 'imagesize==2.0.0',
'docutils==0.17.1', 'iniconfig==2.3.0',
'future==0.18.2', 'Jinja2==3.1.6',
'idna==2.10', 'MarkupSafe==3.0.3',
'imagesize==1.2.0', 'packaging==26.2',
'mock==1.0.1', 'pluggy==1.6.0',
'packaging==20.9', 'Pygments==2.20.0',
'pyparsing==2.4.7', 'pytest==9.0.3',
'pytz==2021.1', 'requests==2.33.1',
'readthedocs-sphinx-ext==2.1.4', 'roman-numerals==4.1.0',
'recommonmark==0.5.0', 'snowballstemmer==3.0.1',
'requests==2.25.1', 'Sphinx==9.1.0',
'six==1.15.0', 'sphinx_rtd_theme==3.1.0',
'snowballstemmer==2.1.0', 'sphinxcontrib-applehelp==2.0.0',
'sphinx==1.8.5', 'sphinxcontrib-devhelp==2.0.0',
'sphinx-rtd-theme==0.4.3', 'sphinxcontrib-htmlhelp==2.1.0',
'sphinxcontrib-serializinghtml==1.1.4', 'sphinxcontrib-jquery==4.1',
'sphinxcontrib-websupport==1.2.4', 'sphinxcontrib-jsmath==1.0.1',
'urllib3==1.26.4', 'sphinxcontrib-qthelp==2.0.0',
'sphinxcontrib-serializinghtml==2.0.0',
'urllib3==2.6.3',
], ],
}, },
package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE', package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE',
@@ -86,11 +95,11 @@ setup(name='jedi',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Text Editors :: Integrated Development Environments (IDE)',
'Topic :: Utilities', 'Topic :: Utilities',
+1 -3
View File
@@ -35,7 +35,6 @@ Usage:
Options: Options:
-h --help Show this screen. -h --help Show this screen.
--record=<file> Exceptions are recorded in here [default: record.json]. --record=<file> Exceptions are recorded in here [default: record.json].
-f, --fs-cache By default, file system cache is off for reproducibility.
-n, --maxtries=<nr> Maximum of random tries [default: 100] -n, --maxtries=<nr> Maximum of random tries [default: 100]
-d, --debug Jedi print debugging when an error is raised. -d, --debug Jedi print debugging when an error is raised.
-s Shows the path/line numbers of every completion before it starts. -s Shows the path/line numbers of every completion before it starts.
@@ -44,7 +43,7 @@ Options:
--pudb Launch pudb when error is raised. --pudb Launch pudb when error is raised.
""" """
from docopt import docopt # type: ignore[import] from docopt import docopt # type: ignore[import, unused-ignore]
import json import json
import os import os
@@ -187,7 +186,6 @@ def main(arguments):
'pudb' if arguments['--pudb'] else None 'pudb' if arguments['--pudb'] else None
record = arguments['--record'] record = arguments['--record']
jedi.settings.use_filesystem_cache = arguments['--fs-cache']
if arguments['--debug']: if arguments['--debug']:
jedi.set_debug_function() jedi.set_debug_function()
+17 -5
View File
@@ -207,16 +207,16 @@ C().a
(f, g) = (1,) (f, g) = (1,)
#? int() #? int()
f f
#? [] #? int()
g. g
(f, g, h) = (1,'') (f, g, h) = (1,'')
#? int() #? int()
f f
#? str() #? str()
g g
#? [] #? str()
h. h
(f1, g1) = 1 (f1, g1) = 1
#? [] #? []
@@ -311,9 +311,13 @@ for x in {1: 3.0, '': 1j}:
dict().values().__iter__ dict().values().__iter__
d = dict(a=3, b='') d = dict(a=3, b='')
x, = d.values() x, y, z = d.values()
#? int() str() #? int() str()
x x
#? int() str()
y
#? int() str()
z
#? int() #? int()
d['a'] d['a']
#? int() str() None #? int() str() None
@@ -527,3 +531,11 @@ lc = [x for a, *x in [(1, '', 1.0)]]
lc[0][0] lc[0][0]
#? #?
lc[0][1] lc[0][1]
xy = (1,)
x, y = *xy, None
# whatever it is should not crash
#?
x
+19 -3
View File
@@ -232,13 +232,14 @@ def a():
#? #?
# str literals in comment """ upper # str literals in comment """ upper
# python >= 3.11
def completion_in_comment(): def completion_in_comment():
#? ['Exception'] #? ['Exception', 'ExceptionGroup']
# might fail because the comment is not a leaf: Exception # might fail because the comment is not a leaf: Exception
pass pass
some_word some_word
#? ['Exception'] #? ['Exception', 'ExceptionGroup']
# Very simple comment completion: Exception # Very simple comment completion: Exception
# Commment after it # Commment after it
@@ -388,7 +389,8 @@ with open('') as f:
#? ['closed'] #? ['closed']
f.closed f.closed
for line in f: for line in f:
#? str() bytes() # TODO this is wrong
#? bytes()
line line
with open('') as f1, open('') as f2: with open('') as f1, open('') as f2:
@@ -413,6 +415,10 @@ with Foo() as f3:
with Foo() as f3: with Foo() as f3:
f3 f3
with open("a"), open("b") as bfile:
#? ['flush']
bfile.flush
# ----------------- # -----------------
# Avoiding multiple definitions # Avoiding multiple definitions
# ----------------- # -----------------
@@ -420,3 +426,13 @@ with Foo() as f3:
some_array = ['', ''] some_array = ['', '']
#! ['def upper'] #! ['def upper']
some_array[some_not_defined_index].upper some_array[some_not_defined_index].upper
# -----------------
# operator
# -----------------
#? bool()
res = 'f' in 'foo'; res
#? bool()
res = not {}; res
+3 -2
View File
@@ -31,14 +31,15 @@ if x:
#? ['else'] #? ['else']
else else
# python >= 3.11
try: try:
pass pass
#? ['except', 'Exception'] #? ['except', 'Exception', 'ExceptionGroup']
except except
try: try:
pass pass
#? 6 ['except', 'Exception'] #? 6 ['except', 'Exception', 'ExceptionGroup']
except AttributeError: except AttributeError:
pass pass
#? ['finally'] #? ['finally']
+6
View File
@@ -27,3 +27,9 @@ def capsysbinary(capsysbinary):
#? ['close'] #? ['close']
capsysbinary.clos capsysbinary.clos
return capsysbinary return capsysbinary
# used when fixtures are defined in multiple files
pytest_plugins = [
"completion.fixture_module",
]
+2 -4
View File
@@ -21,11 +21,9 @@ class Y(X):
#? [] #? []
def __doc__ def __doc__
# This might or might not be what we wanted, currently properties are also
# used like this. IMO this is not wanted ~dave.
#? ['__class__']
def __class__
#? [] #? []
def __class__
#? ['__class__']
__class__ __class__
+6
View File
@@ -0,0 +1,6 @@
# Exists only for completion/pytest.py
import pytest
@pytest.fixture
def my_module_fixture():
return 1.0
+2 -1
View File
@@ -1,3 +1,4 @@
# python >= 3.11
class Foo: class Foo:
bar = 1 bar = 1
@@ -13,7 +14,7 @@ Fr'{Foo.bar'
Fr'{Foo.bar Fr'{Foo.bar
#? ['bar'] #? ['bar']
Fr'{Foo.bar Fr'{Foo.bar
#? ['Exception'] #? ['Exception', 'ExceptionGroup']
F"{Excepti F"{Excepti
#? 8 Foo #? 8 Foo
+2 -1
View File
@@ -2,7 +2,8 @@
#? ['raise'] #? ['raise']
raise raise
#? ['Exception'] # python >= 3.11
#? ['Exception', 'ExceptionGroup']
except except
#? [] #? []
-1
View File
@@ -1,7 +1,6 @@
# For assignment expressions / named expressions / walrus operators / whatever # For assignment expressions / named expressions / walrus operators / whatever
# they are called. # they are called.
# python >= 3.8
b = (a:=1, a) b = (a:=1, a)
#? int() #? int()
+8 -6
View File
@@ -108,33 +108,35 @@ def z(bam, bar=2, *, bas=1):
#? 7 ['bar=', 'baz='] #? 7 ['bar=', 'baz=']
x(1, ba) x(1, ba)
# python >= 3.11
#? 14 ['baz='] #? 14 ['baz=']
x(1, bar=2, ba) x(1, bar=2, ba)
#? 7 ['bar=', 'baz='] #? 7 ['bar=', 'baz=']
x(1, ba, baz=3) x(1, ba, baz=3)
#? 14 ['baz='] #? 14 ['baz=']
x(1, bar=2, baz=3) x(1, bar=2, baz=3)
#? 7 ['BaseException'] #? 7 ['BaseException', 'BaseExceptionGroup']
x(basee) x(basee)
#? 22 ['bar=', 'baz='] #? 22 ['bar=', 'baz=']
x(1, 2, 3, 4, 5, 6, bar=2) x(1, 2, 3, 4, 5, 6, bar=2)
#? 14 ['baz='] #? 14 ['baz=']
y(1, bar=2, ba) y(1, bar=2, ba)
#? 7 ['bar=', 'BaseException', 'baz='] #? 7 ['bar=', 'BaseException', 'BaseExceptionGroup', 'baz=']
y(1, ba, baz=3) y(1, ba, baz=3)
#? 14 ['baz='] #? 14 ['baz=']
y(1, bar=2, baz=3) y(1, bar=2, baz=3)
#? 7 ['BaseException'] #? 7 ['BaseException', 'BaseExceptionGroup']
y(basee) y(basee)
#? 22 ['bar=', 'BaseException', 'baz='] #? 22 ['bar=', 'BaseException', 'BaseExceptionGroup', 'baz=']
y(1, 2, 3, 4, 5, 6, bar=2) y(1, 2, 3, 4, 5, 6, bar=2)
#? 11 ['bar=', 'bas='] #? 11 ['bar=', 'bas=']
z(bam=1, bar=2, bas=3) z(bam=1, bar=2, bas=3)
#? 8 ['BaseException', 'bas='] #? 8 ['BaseException', 'BaseExceptionGroup', 'bas=']
z(1, bas=2) z(1, bas=2)
#? 12 ['BaseException'] #? 12 ['BaseException', 'BaseExceptionGroup']
z(1, bas=bas) z(1, bas=bas)
#? 19 ['dict'] #? 19 ['dict']
+1 -1
View File
@@ -76,7 +76,7 @@ from import_tree.pkg.mod1 import not_existant,
#? 22 ['mod1', 'base'] #? 22 ['mod1', 'base']
from import_tree.pkg. import mod1 from import_tree.pkg. import mod1
#? 17 ['mod1', 'mod2', 'random', 'pkg', 'references', 'rename1', 'rename2', 'classes', 'globals', 'recurse_class1', 'recurse_class2', 'invisible_pkg', 'flow_import'] #? 17 ['mod1', 'mod2', 'random', 'pkg', 'references', 'rename1', 'rename2', 'classes', 'globals', 'recurse_class1', 'recurse_class2', 'invisible_pkg', 'flow_import']
from import_tree. import pkg from import_tree. import new_pkg
#? 18 ['pkg'] #? 18 ['pkg']
from import_tree.p import pkg from import_tree.p import pkg
+40
View File
@@ -180,6 +180,11 @@ def argskwargs(*args: int, **kwargs: float):
#? float() #? float()
kwargs[''] kwargs['']
class Test:
str: str = 'abc'
#? ['upper']
Test.str.upp
class NotCalledClass: class NotCalledClass:
def __init__(self, x): def __init__(self, x):
@@ -198,3 +203,38 @@ class NotCalledClass:
self.w: float self.w: float
#? float() #? float()
self.w self.w
def tuple_func() -> tuple[int, str]:
return 1, ""
x = tuple_func()
a, b = x
#? int()
a
#? str()
b
#? int()
x[0]
#? str()
x[1]
def check_newstyle_unions(u1: int | str, u2: list[int] | list[str]):
#? int() str()
u1
#? list()
u2
#? int() str()
u2[1]
def use_type_with_annotation() -> type[int]: ...
#? int
use_type_with_annotation()
def union_with_forward_references(x: int | "str", y: "int" | str, z: "int | str"):
#? int() str()
x
#? int() str()
y
#? int() str()
z
@@ -11,6 +11,7 @@ from typing import (
TypeVar, TypeVar,
Union, Union,
Sequence, Sequence,
Self,
) )
K = TypeVar('K') K = TypeVar('K')
@@ -387,3 +388,19 @@ first(custom_partial2_unbound_instance)
#? str() #? str()
values(custom_partial2_unbound_instance)[0] values(custom_partial2_unbound_instance)[0]
def generic_func1(arg: T) -> int | str | T: pass
def generic_func2(arg: T) -> Union[int, str, T]: pass
#? int() str() bytes()
generic_func1(b"hello")
#? int() str() bytes()
generic_func2(b"hello")
class CustomGeneric2(Generic[T_co]):
val: T_co
def __init__(cls, val: T_co) -> Self:
raise NotImplementedError
#? int()
CustomGeneric2(1).val
+57 -9
View File
@@ -3,7 +3,7 @@ Test the typing library, with docstrings and annotations
""" """
import typing import typing
from typing import Sequence, MutableSequence, List, Iterable, Iterator, \ from typing import Sequence, MutableSequence, List, Iterable, Iterator, \
AbstractSet, Tuple, Mapping, Dict, Union, Optional AbstractSet, Tuple, Mapping, Dict, Union, Optional, Final, Self
class B: class B:
pass pass
@@ -49,11 +49,7 @@ def iterators(ps: Iterable[int], qs: Iterator[str], rs:
a, b = ps a, b = ps
#? int() #? int()
a a
##? int() --- TODO fix support for tuple assignment #? int()
# https://github.com/davidhalter/jedi/pull/663#issuecomment-172317854
# test below is just to make sure that in case it gets fixed by accident
# these tests will be fixed as well the way they should be
#?
b b
for q in qs: for q in qs:
@@ -76,7 +72,7 @@ def sets(p: AbstractSet[int], q: typing.MutableSet[float]):
#? ["add"] #? ["add"]
q.a q.a
def tuple(p: Tuple[int], q: Tuple[int, str, float], r: Tuple[B, ...]): def tupletest(p: Tuple[int], q: Tuple[int, str, float], r: Tuple[B, ...]):
#? int() #? int()
p[0] p[0]
#? ['index'] #? ['index']
@@ -476,8 +472,6 @@ dynamic_annotation('')
# TypeDict # TypeDict
# ------------------------- # -------------------------
# python >= 3.8
class Foo(typing.TypedDict): class Foo(typing.TypedDict):
foo: str foo: str
bar: typing.List[float] bar: typing.List[float]
@@ -557,3 +551,57 @@ def typed_dict_test_foo(arg: Bar):
arg['an_int'] arg['an_int']
#? int() #? int()
arg['another_variable'] arg['another_variable']
# -----------------
# Self
# -----------------
import typing_extensions
# From #2023, #2068
class Builder:
def __init__(self):
self.x = 0
self.y = 0
def add_x(self: Self, x: int) -> Self:
self.x = x
return self
def add_y(self: Self, y: int) -> Self:
self.y = y
return self
def add_not_implemented(self: Self, y: int) -> Self:
raise NotImplementedError
def add_not_implemented_typing_extensions(self: Self, y: int) -> typing_extensions.Self:
raise NotImplementedError
b = Builder()
#? Builder()
b.add_x(2)
#? Builder()
b.add_x(2).add_y(5)
# python >= 3.11
#? Builder()
b.add_x(2).add_not_implemented(5)
#? Builder()
b.add_x(2).add_not_implemented_typing_extensions(5)
# -----------------
# TypeAlias (see also #1969)
# -----------------
from typing import TypeAlias
IntX: typing.TypeAlias = int
IntY: TypeAlias = int
#? int
IntX
def f(x: IntX, y: IntY):
#? int()
x
#? int()
y
+44 -3
View File
@@ -59,6 +59,7 @@ class VarClass:
var_class1: typing.ClassVar[str] = 1 var_class1: typing.ClassVar[str] = 1
var_class2: typing.ClassVar[bytes] var_class2: typing.ClassVar[bytes]
var_class3 = None var_class3 = None
var_class4: typing.ClassVar = ""
def __init__(self): def __init__(self):
#? int() #? int()
@@ -71,7 +72,7 @@ class VarClass:
d.var_class2 d.var_class2
#? [] #? []
d.int d.int
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2', 'var_class3'] #? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2', 'var_class3', 'var_class4']
self.var_ self.var_
class VarClass2(VarClass): class VarClass2(VarClass):
@@ -81,7 +82,7 @@ class VarClass2(VarClass):
#? int() #? int()
self.var_class3 self.var_class3
#? ['var_class1', 'var_class2', 'var_instance1', 'var_class3', 'var_instance2'] #? ['var_class1', 'var_class2', 'var_class4', 'var_instance1', 'var_class3', 'var_instance2']
VarClass.var_ VarClass.var_
#? int() #? int()
VarClass.var_instance1 VarClass.var_instance1
@@ -91,11 +92,13 @@ VarClass.var_instance2
VarClass.var_class1 VarClass.var_class1
#? bytes() #? bytes()
VarClass.var_class2 VarClass.var_class2
#? str()
VarClass.var_class4
#? [] #? []
VarClass.int VarClass.int
d = VarClass() d = VarClass()
#? ['var_class1', 'var_class2', 'var_class3', 'var_instance1', 'var_instance2'] #? ['var_class1', 'var_class2', 'var_class3', 'var_class4', 'var_instance1', 'var_instance2']
d.var_ d.var_
#? int() #? int()
d.var_instance1 d.var_instance1
@@ -105,6 +108,8 @@ d.var_instance2
d.var_class1 d.var_class1
#? bytes() #? bytes()
d.var_class2 d.var_class2
#? str()
d.var_class4
#? [] #? []
d.int d.int
@@ -117,3 +122,39 @@ class DC:
#? int() #? int()
DC().name DC().name
# -------------------------
# Final
# -------------------------
# TODO this is wrong, but shouldn't matter that much
#? 0 int()
x: typing.Final[str] = 1
#? 0 int()
y: typing.Final = 1
#? str()
x
#? int()
y
def f(x: typing.Final[str]):
#? str()
x
class C:
x: typing.Final[bytes] = 1
#? 4 str()
y: typing.Final = ""
#? bytes()
x
#? str()
y
#? bytes()
C.x
#? str()
C.y
#? bytes()
C().x
#? str()
C().y
+24
View File
@@ -0,0 +1,24 @@
from typing import Annotated
# This is just a dummy and very meaningless thing to use with to the Annotated
# type hint
class Foo:
pass
class A:
pass
def annotated_function_params(
basic: Annotated[str, Foo()],
obj: A,
annotated_obj: Annotated[A, Foo()],
):
#? str()
basic
#? A()
obj
#? A()
annotated_obj
@@ -0,0 +1,14 @@
# python >= 3.12
# -----------------
# new generic syntax should not fail
# -----------------
class C[T]:
def c(self) -> str: ...
def f[T](x: T, y: T) -> int: ...
#? int()
f()
#? str()
C().c()
@@ -1,5 +1,3 @@
# python >= 3.8
def positional_only_call(a, /, b): def positional_only_call(a, /, b):
#? str() #? str()
a a
+2 -2
View File
@@ -54,7 +54,7 @@ a
#? int() #? int()
(3 ** 3) (3 ** 3)
#? int() #? int() float()
(3 ** 'a') (3 ** 'a')
#? int() #? int()
(3 + 'a') (3 + 'a')
@@ -167,7 +167,7 @@ from datetime import datetime, timedelta
(datetime - timedelta) (datetime - timedelta)
#? datetime() #? datetime()
(datetime() - timedelta()) (datetime() - timedelta())
#? timedelta() #? timedelta() datetime()
(datetime() - datetime()) (datetime() - datetime())
#? timedelta() #? timedelta()
(timedelta() - datetime()) (timedelta() - datetime())
+3
View File
@@ -96,6 +96,9 @@ def test_x(my_con
#? 18 ['my_conftest_fixture'] #? 18 ['my_conftest_fixture']
def test_x(my_conftest_fixture): def test_x(my_conftest_fixture):
return return
#? ['my_module_fixture']
def test_x(my_modu
return
#? [] #? []
def lala(my_con def lala(my_con

Some files were not shown because too many files have changed in this diff Show More