Skip to content

PEP 695 type parameter syntax for Python 3.12+#237

Merged
davidhalter merged 1 commit intodavidhalter:masterfrom
darki73:feat/pep695-type-params
Apr 3, 2026
Merged

PEP 695 type parameter syntax for Python 3.12+#237
davidhalter merged 1 commit intodavidhalter:masterfrom
darki73:feat/pep695-type-params

Conversation

@darki73
Copy link
Copy Markdown
Contributor

@darki73 darki73 commented Mar 31, 2026

Closes #221

I ran into this while building a code intelligence tool that uses jedi for cross-module resolution. My ORM uses class QueryBuilder[T: "Model"](Mixin1, Mixin2) and jedi couldn't resolve any of the inherited methods. Traced it back to parso parsing the class as an error node.

Two things were broken:

  1. The 3.12/3.13/3.14 grammars don't have type_params rules, so class Foo[T] and def bar[T]() parse as error nodes.

  2. Class.get_super_arglist() hardcodes children[2] and children[3] to find the arglist. With type params present, ( shifts to a later position and the method returns None. This means jedi can't find base classes at all, so the entire MRO is invisible.

Fix: added type_params, type_param, type_param_bound rules to the grammars. Added type_param_default for 3.13+ (PEP 696). Replaced the hardcoded index in get_super_arglist with a search for (.

Worth noting - in the jedi issue (davidhalter/jedi#2025) there was concern that jedi would need major changes to support this. Turns out it doesn't. Once parso parses the class correctly and get_super_arglist returns the right node, jedi's existing MRO resolution handles everything. Completions, goto, inference on inherited methods - all work without touching jedi at all. The get_super_arglist fix was the missing piece.

The type soft keyword statement is not included here since that needs tokenizer changes.

@darki73 darki73 force-pushed the feat/pep695-type-params branch from c202a83 to 463a86f Compare March 31, 2026 13:46
@davidhalter
Copy link
Copy Markdown
Owner

davidhalter commented Mar 31, 2026

Did you run the tests in Jedi with this changed parso version? And did they all pass?

Thanks a lot for trying to work on this!

@darki73
Copy link
Copy Markdown
Contributor Author

darki73 commented Mar 31, 2026

I am using jedi 0.19.2 as a direct dependency in my project, before submitting PR for parso, I "patched" the loading mechanism of parso in my projects code because it basically allowed me to submit the version I wanted to release without the need to wait for the PR to be merged.

So far, on 62 cases without the patch, this patch provides 62/62 mappings (i am, like parso, also run my app on itself for checks) - https://github.com/darki73/sylvan/blob/899de480992eb79f1ba8f3c71ac8552b68e647fc/src/sylvan/analysis/structure/jedi_setup.py

This is pretty much the same "fix", just monkey patched.

I have not though ran any tests in jedi, i can try to do it later today / tomorrow when I have time to do so.

Thank you for taking time to review the PR!

@darki73 darki73 force-pushed the feat/pep695-type-params branch from 463a86f to fde9909 Compare April 1, 2026 07:55
@darki73
Copy link
Copy Markdown
Contributor Author

darki73 commented Apr 1, 2026

@davidhalter

Ran the tests against the main of jedi, found some failing tests that required more changes on the parso side (which are already added to this PR as of now. Function alongside with Class changes initially proposed).

As of now, 3743 tests in jedi have passed (had some issues with windows, but turns out just flaky tests on this specific platform).

Comment thread parso/python/tree.py Outdated
else:
return self.children[3]
for i, child in enumerate(self.children):
if hasattr(child, 'value') and child.value == '(':
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use

try:
    value = child.value
except AttributeError:
    continue
if value == "(":
    ...

here and in the places below? hasattr should not be used like that because it uses multiple lookups and is harder to refactor.

Copy link
Copy Markdown
Contributor Author

@darki73 darki73 Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, looked at the existing patterns in this file, we might even go with the direct string comparison.

(had to edit the next line, was confusing as hell to "unprepared" person)
Basically, the original code used the != operator to exit early, and since we are in the loop now, we are checking with == for a ( instead of its absence.
So just like before, instead of self.children[2] != '(' we can just do child == '('.

I will push the updated version for both places, but ultimately, it is for you to decide as you are the author and if you prefer the try/except - just let me know and i will update it.

With this, 1984 parso and 3743 jedi tests pass no issue.

@darki73 darki73 force-pushed the feat/pep695-type-params branch from fde9909 to ea2a2b9 Compare April 3, 2026 11:21
Copy link
Copy Markdown
Owner

@davidhalter davidhalter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for all the comments, I like the direction and probably prefer that to the AttributeError stuff, I just think that while it's hard to understand the code, it gets much easier if you know/look at the grammar. Does that help with understanding it for you? I don't think it's my goal that everybody understands it without looking at the grammar files.

Comment thread parso/python/tree.py Outdated
return None
for i, child in enumerate(self.children):
if child == '->':
if i + 1 < len(self.children):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should always be true, since the grammar file specifies this. I think this line can just be removed.

Comment thread parso/python/tree.py Outdated
if not any(isinstance(child, Param) for child in parameters_children):
parameters.children[1:-1] = _create_params(parameters, parameters_children)
parameters = self._find_parameters()
if parameters is not None:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also never be reachable and can be removed. A function always has parameters, see grammar file

Comment thread parso/python/tree.py Outdated
for child in self.children:
if child.type == 'parameters':
return child
return None
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just raise Exception("A function should always has parameters").

Comment thread parso/python/tree.py Outdated
return self.children[3]
for i, child in enumerate(self.children):
if child == '(':
if i + 1 >= len(self.children):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I guess this should always be true.

@darki73 darki73 force-pushed the feat/pep695-type-params branch from ea2a2b9 to 5b63e63 Compare April 3, 2026 17:29
@darki73
Copy link
Copy Markdown
Contributor Author

darki73 commented Apr 3, 2026

No problem!
I just went a bit defensive on the python side instead of just "trusting" the grammar parser.
But you are right, no need for those check specifically because grammar itself ensures that there is just no possible way for the parameters not to be there.

@davidhalter
Copy link
Copy Markdown
Owner

davidhalter commented Apr 3, 2026

Thanks a lot again for working on this! I think it's especially meaningful, because it fixes things without causing too much work in other places.

And feel free to checkout Zuban, I'm currently in the process of bringing some Jedi goodness to it and there should probably not be a lot of reasons anymore in a few months to use Jedi instead of Zuban.

@davidhalter davidhalter merged commit a36a421 into davidhalter:master Apr 3, 2026
8 checks passed
@delfick
Copy link
Copy Markdown

delfick commented Apr 3, 2026

@darki73 @davidhalter excuse me for the noise, but thank you so much for fixing this!!!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parso can't parse PEP 695: Type Parameter Syntax

3 participants