19 Commits

Author SHA1 Message Date
Dave Halter bfe3058441 Prepare the release 0.8.7 2026-05-02 01:09:55 +02:00
Dave Halter a36a4216a4 Merge pull request #237 from darki73/feat/pep695-type-params
PEP 695 type parameter syntax for Python 3.12+
2026-04-03 20:09:17 +00:00
darki73 5b63e63911 feat: PEP 695 type parameter syntax for Python 3.12+ 2026-04-03 21:29:31 +04:00
Dave Halter 28005a5cf6 Upgrade to 0.8.6 2026-02-09 16:40:11 +01:00
Dave Halter 0e774b0fd3 Add a changelog 2026-02-09 16:36:49 +01:00
Dave Halter cc2c562500 Merge pull request #235 from davidhalter/typing
Typing changes
2026-02-09 15:34:33 +00:00
Dave Halter 341a60b115 Upgrade zuban 2026-02-09 16:32:59 +01:00
Dave Halter c0f6b96b0c Add a way to debug github CI 2026-02-09 16:31:46 +01:00
Dave Halter 59541564e5 Better dependencies 2026-02-08 00:36:52 +01:00
Dave Halter a0fa3ae95d Pass older Python versions 2026-02-08 00:33:06 +01:00
Dave Halter d13000a74c Fix a flake8 issue 2026-02-08 00:31:37 +01:00
Dave Halter 0b6800c7a9 Use zuban instead of Mypy 2026-02-08 00:28:41 +01:00
Dave Halter 59f4282d21 Fix a typing issue 2026-02-04 16:14:13 +01:00
Dave Halter 9eb6675f00 Make the whole code base type checkable 2026-02-04 16:06:59 +01:00
Dave Halter 22da0526b3 Change a few small typing related things 2026-02-04 16:05:26 +01:00
Dave Halter 07fb4584a3 Help the type checker a bit with recursive type definitions 2026-02-04 15:58:38 +01:00
Dave Halter 6fbeec9e2f Use Zuban and therefore check untyped code 2026-02-04 02:55:06 +01:00
Dave Halter aecfc0e0c4 Avoid duplication of code 2026-02-04 02:29:05 +01:00
Dave Halter 3158571e46 Change a small typing issue 2026-02-04 02:28:45 +01:00
23 changed files with 225 additions and 102 deletions
+17 -4
View File
@@ -1,6 +1,14 @@
name: Build name: Build
on: [push, pull_request] on:
push:
pull_request:
workflow_call:
inputs:
debug_ssh_session:
required: false
type: boolean
env: env:
PYTEST_ADDOPTS: --color=yes PYTEST_ADDOPTS: --color=yes
@@ -17,12 +25,17 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade pip setuptools wheel
pip install .[qa] pip install .[qa] .[testing]
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ inputs.debug_ssh_session }}
with:
limit-access-to-actor: true
- name: Run Flake8 - name: Run Flake8
# Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. # Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong.
run: flake8 --extend-ignore F401 parso test/*.py setup.py scripts/ run: flake8 --extend-ignore F401 parso test/*.py setup.py scripts/
- name: Run Mypy - name: Run Zuban
run: mypy parso setup.py run: zuban check
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }} continue-on-error: ${{ matrix.experimental }}
+12
View File
@@ -0,0 +1,12 @@
name: Debug CI
on:
workflow_dispatch:
jobs:
tests:
uses: ./.github/workflows/tests.yml
with:
all_tests: true
debug_ssh_session: true
secrets: inherit
+11
View File
@@ -6,6 +6,17 @@ Changelog
Unreleased Unreleased
++++++++++ ++++++++++
0.8.7 (2026-05-02)
++++++++++++++++++
- Add PEP 695 type parameter syntax
0.8.6 (2026-02-09)
++++++++++++++++++
- Switch the type checker to Zuban. It's faster and now also checks untyped
code.
0.8.5 (2025-08-23) 0.8.5 (2025-08-23)
++++++++++++++++++ ++++++++++++++++++
+12
View File
@@ -145,3 +145,15 @@ def works_ge_py38(each_version):
def works_ge_py39(each_version): def works_ge_py39(each_version):
version_info = parse_version_string(each_version) version_info = parse_version_string(each_version)
return Checker(each_version, version_info >= (3, 9)) return Checker(each_version, version_info >= (3, 9))
@pytest.fixture
def works_ge_py312(each_version):
version_info = parse_version_string(each_version)
return Checker(each_version, version_info >= (3, 12))
@pytest.fixture
def works_ge_py313(each_version):
version_info = parse_version_string(each_version)
return Checker(each_version, version_info >= (3, 13))
+1 -1
View File
@@ -43,7 +43,7 @@ from parso.grammar import Grammar, load_grammar
from parso.utils import split_lines, python_bytes_to_unicode from parso.utils import split_lines, python_bytes_to_unicode
__version__ = '0.8.5' __version__ = '0.8.7'
def parse(code=None, **kwargs): def parse(code=None, **kwargs):
+3 -4
View File
@@ -1,6 +1,6 @@
import hashlib import hashlib
import os import os
from typing import Generic, TypeVar, Union, Dict, Optional, Any from typing import Generic, TypeVar, Union, Dict, Optional, Any, Iterator
from pathlib import Path from pathlib import Path
from parso._compatibility import is_pypy from parso._compatibility import is_pypy
@@ -8,7 +8,7 @@ from parso.pgen2 import generate_grammar
from parso.utils import split_lines, python_bytes_to_unicode, \ from parso.utils import split_lines, python_bytes_to_unicode, \
PythonVersionInfo, parse_version_string PythonVersionInfo, parse_version_string
from parso.python.diff import DiffParser from parso.python.diff import DiffParser
from parso.python.tokenize import tokenize_lines, tokenize from parso.python.tokenize import tokenize_lines, tokenize, PythonToken
from parso.python.token import PythonTokenTypes from parso.python.token import PythonTokenTypes
from parso.cache import parser_cache, load_module, try_to_save_module from parso.cache import parser_cache, load_module, try_to_save_module
from parso.parser import BaseParser from parso.parser import BaseParser
@@ -223,7 +223,7 @@ class PythonGrammar(Grammar):
) )
self.version_info = version_info self.version_info = version_info
def _tokenize_lines(self, lines, **kwargs): def _tokenize_lines(self, lines, **kwargs) -> Iterator[PythonToken]:
return tokenize_lines(lines, version_info=self.version_info, **kwargs) return tokenize_lines(lines, version_info=self.version_info, **kwargs)
def _tokenize(self, code): def _tokenize(self, code):
@@ -255,7 +255,6 @@ def load_grammar(*, version: str = None, path: str = None):
'grammar%s%s.txt' % (version_info.major, version_info.minor) 'grammar%s%s.txt' % (version_info.major, version_info.minor)
) )
global _loaded_grammars
path = os.path.join(os.path.dirname(__file__), file) path = os.path.join(os.path.dirname(__file__), file)
try: try:
return _loaded_grammars[path] return _loaded_grammars[path]
+4 -4
View File
@@ -1,8 +1,11 @@
from contextlib import contextmanager from contextlib import contextmanager
from typing import Dict, List from typing import Dict, List, Any
class _NormalizerMeta(type): class _NormalizerMeta(type):
rule_value_classes: Any
rule_type_classes: Any
def __new__(cls, name, bases, dct): def __new__(cls, name, bases, dct):
new_cls = type.__new__(cls, name, bases, dct) new_cls = type.__new__(cls, name, bases, dct)
new_cls.rule_value_classes = {} new_cls.rule_value_classes = {}
@@ -109,9 +112,6 @@ class NormalizerConfig:
normalizer_class = Normalizer normalizer_class = Normalizer
def create_normalizer(self, grammar): def create_normalizer(self, grammar):
if self.normalizer_class is None:
return None
return self.normalizer_class(grammar, self) return self.normalizer_class(grammar, self)
+4 -4
View File
@@ -83,14 +83,14 @@ class DFAState(Generic[_TokenTypeT]):
self.from_rule = from_rule self.from_rule = from_rule
self.nfa_set = nfa_set self.nfa_set = nfa_set
# map from terminals/nonterminals to DFAState # map from terminals/nonterminals to DFAState
self.arcs: Mapping[str, DFAState] = {} self.arcs: dict[str, DFAState] = {}
# In an intermediary step we set these nonterminal arcs (which has the # In an intermediary step we set these nonterminal arcs (which has the
# same structure as arcs). These don't contain terminals anymore. # same structure as arcs). These don't contain terminals anymore.
self.nonterminal_arcs: Mapping[str, DFAState] = {} self.nonterminal_arcs: dict[str, DFAState] = {}
# Transitions are basically the only thing that the parser is using # Transitions are basically the only thing that the parser is using
# with is_final. Everyting else is purely here to create a parser. # with is_final. Everyting else is purely here to create a parser.
self.transitions: Mapping[Union[_TokenTypeT, ReservedString], DFAPlan] = {} self.transitions: dict[Union[_TokenTypeT, ReservedString], DFAPlan] = {}
self.is_final = final in nfa_set self.is_final = final in nfa_set
def add_arc(self, next_, label): def add_arc(self, next_, label):
@@ -261,7 +261,7 @@ def generate_grammar(bnf_grammar: str, token_namespace) -> Grammar:
if start_nonterminal is None: if start_nonterminal is None:
start_nonterminal = nfa_a.from_rule start_nonterminal = nfa_a.from_rule
reserved_strings: Mapping[str, ReservedString] = {} reserved_strings: dict[str, ReservedString] = {}
for nonterminal, dfas in rule_to_dfas.items(): for nonterminal, dfas in rule_to_dfas.items():
for dfa_state in dfas: for dfa_state in dfas:
for terminal_or_nonterminal, next_dfa in dfa_state.arcs.items(): for terminal_or_nonterminal, next_dfa in dfa_state.arcs.items():
+1 -1
View File
@@ -881,6 +881,6 @@ class _NodesTree:
end_pos[0] += len(lines) - 1 end_pos[0] += len(lines) - 1
end_pos[1] = len(lines[-1]) end_pos[1] = len(lines[-1])
endmarker = EndMarker('', tuple(end_pos), self.prefix + self._prefix_remainder) endmarker = EndMarker('', (end_pos[0], end_pos[1]), self.prefix + self._prefix_remainder)
endmarker.parent = self._module endmarker.parent = self._module
self._module.children.append(endmarker) self._module.children.append(endmarker)
+6 -2
View File
@@ -17,7 +17,11 @@ decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef) decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: 'async' funcdef async_funcdef: 'async' funcdef
funcdef: 'def' NAME parameters ['->' test] ':' suite funcdef: 'def' NAME [type_params] parameters ['->' test] ':' suite
type_params: '[' type_param (',' type_param)* [','] ']'
type_param: NAME [type_param_bound] | '*' NAME | '**' NAME
type_param_bound: ':' test
parameters: '(' [typedargslist] ')' parameters: '(' [typedargslist] ')'
typedargslist: ( typedargslist: (
@@ -131,7 +135,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((test [':=' test] | star_expr) ((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) ) (comp_for | (',' (test [':=' test] | star_expr))* [','])) )
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite classdef: 'class' NAME [type_params] ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [','] arglist: argument (',' argument)* [',']
+7 -2
View File
@@ -17,7 +17,12 @@ decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef) decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: 'async' funcdef async_funcdef: 'async' funcdef
funcdef: 'def' NAME parameters ['->' test] ':' suite funcdef: 'def' NAME [type_params] parameters ['->' test] ':' suite
type_params: '[' type_param (',' type_param)* [','] ']'
type_param: NAME [type_param_bound] [type_param_default] | '*' NAME [type_param_default] | '**' NAME [type_param_default]
type_param_bound: ':' test
type_param_default: '=' test
parameters: '(' [typedargslist] ')' parameters: '(' [typedargslist] ')'
typedargslist: ( typedargslist: (
@@ -131,7 +136,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((test [':=' test] | star_expr) ((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) ) (comp_for | (',' (test [':=' test] | star_expr))* [','])) )
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite classdef: 'class' NAME [type_params] ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [','] arglist: argument (',' argument)* [',']
+7 -2
View File
@@ -17,7 +17,12 @@ decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef) decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: 'async' funcdef async_funcdef: 'async' funcdef
funcdef: 'def' NAME parameters ['->' test] ':' suite funcdef: 'def' NAME [type_params] parameters ['->' test] ':' suite
type_params: '[' type_param (',' type_param)* [','] ']'
type_param: NAME [type_param_bound] [type_param_default] | '*' NAME [type_param_default] | '**' NAME [type_param_default]
type_param_bound: ':' test
type_param_default: '=' test
parameters: '(' [typedargslist] ')' parameters: '(' [typedargslist] ')'
typedargslist: ( typedargslist: (
@@ -131,7 +136,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((test [':=' test] | star_expr) ((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) ) (comp_for | (',' (test [':=' test] | star_expr))* [','])) )
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite classdef: 'class' NAME [type_params] ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [','] arglist: argument (',' argument)* [',']
+2 -2
View File
@@ -676,7 +676,7 @@ class PEP8Normalizer(ErrorFinder):
elif leaf.parent.type == 'function' and leaf.parent.name == leaf: elif leaf.parent.type == 'function' and leaf.parent.name == leaf:
self.add_issue(leaf, 743, message % 'function') self.add_issue(leaf, 743, message % 'function')
else: else:
self.add_issuadd_issue(741, message % 'variables', leaf) self.add_issue(741, message % 'variables', leaf)
elif leaf.value == ':': elif leaf.value == ':':
if isinstance(leaf.parent, (Flow, Scope)) and leaf.parent.type != 'lambdef': if isinstance(leaf.parent, (Flow, Scope)) and leaf.parent.type != 'lambdef':
next_leaf = leaf.get_next_leaf() next_leaf = leaf.get_next_leaf()
@@ -764,4 +764,4 @@ class BlankLineAtEnd(Rule):
message = 'Blank line at end of file' message = 'Blank line at end of file'
def is_issue(self, leaf): def is_issue(self, leaf):
return self._newline_count >= 2 return False # TODO return self._newline_count >= 2
+14 -14
View File
@@ -16,7 +16,7 @@ import re
import itertools as _itertools import itertools as _itertools
from codecs import BOM_UTF8 from codecs import BOM_UTF8
from typing import NamedTuple, Tuple, Iterator, Iterable, List, Dict, \ from typing import NamedTuple, Tuple, Iterator, Iterable, List, Dict, \
Pattern, Set Pattern, Set, Any
from parso.python.token import PythonTokenTypes from parso.python.token import PythonTokenTypes
from parso.utils import split_lines, PythonVersionInfo, parse_version_string from parso.utils import split_lines, PythonVersionInfo, parse_version_string
@@ -47,12 +47,12 @@ class TokenCollection(NamedTuple):
endpats: Dict[str, Pattern] endpats: Dict[str, Pattern]
whitespace: Pattern whitespace: Pattern
fstring_pattern_map: Dict[str, str] fstring_pattern_map: Dict[str, str]
always_break_tokens: Tuple[str] always_break_tokens: Set[str]
BOM_UTF8_STRING = BOM_UTF8.decode('utf-8') BOM_UTF8_STRING = BOM_UTF8.decode('utf-8')
_token_collection_cache: Dict[PythonVersionInfo, TokenCollection] = {} _token_collection_cache: Dict[Tuple[int, int], TokenCollection] = {}
def group(*choices, capture=False, **kwargs): def group(*choices, capture=False, **kwargs):
@@ -249,7 +249,7 @@ class Token(NamedTuple):
class PythonToken(Token): class PythonToken(Token):
def __repr__(self): def __repr__(self):
return ('TokenInfo(type=%s, string=%r, start_pos=%r, prefix=%r)' % return ('TokenInfo(type=%s, string=%r, start_pos=%r, prefix=%r)' %
self._replace(type=self.type.name)) self._replace(type=self.type.name)) # type: ignore[arg-type]
class FStringNode: class FStringNode:
@@ -257,7 +257,7 @@ class FStringNode:
self.quote = quote self.quote = quote
self.parentheses_count = 0 self.parentheses_count = 0
self.previous_lines = '' self.previous_lines = ''
self.last_string_start_pos = None self.last_string_start_pos: Any = None
# In the syntax there can be multiple format_spec's nested: # In the syntax there can be multiple format_spec's nested:
# {x:{y:3}} # {x:{y:3}}
self.format_spec_count = 0 self.format_spec_count = 0
@@ -340,7 +340,7 @@ def _find_fstring_string(endpats, fstring_stack, line, lnum, pos):
def tokenize( def tokenize(
code: str, *, version_info: PythonVersionInfo, start_pos: Tuple[int, int] = (1, 0) code: str, *, version_info: Tuple[int, int], start_pos: Tuple[int, int] = (1, 0)
) -> Iterator[PythonToken]: ) -> Iterator[PythonToken]:
"""Generate tokens from a the source code (string).""" """Generate tokens from a the source code (string)."""
lines = split_lines(code, keepends=True) lines = split_lines(code, keepends=True)
@@ -363,7 +363,7 @@ def _print_tokens(func):
def tokenize_lines( def tokenize_lines(
lines: Iterable[str], lines: Iterable[str],
*, *,
version_info: PythonVersionInfo, version_info: Tuple[int, int],
indents: List[int] = None, indents: List[int] = None,
start_pos: Tuple[int, int] = (1, 0), start_pos: Tuple[int, int] = (1, 0),
is_first_token=True, is_first_token=True,
@@ -444,7 +444,7 @@ def tokenize_lines(
if string: if string:
yield PythonToken( yield PythonToken(
FSTRING_STRING, string, FSTRING_STRING, string,
tos.last_string_start_pos, tos.last_string_start_pos, # type: ignore[arg-type]
# Never has a prefix because it can start anywhere and # Never has a prefix because it can start anywhere and
# include whitespace. # include whitespace.
prefix='' prefix=''
@@ -496,8 +496,8 @@ def tokenize_lines(
initial = token[0] initial = token[0]
else: else:
match = whitespace.match(line, pos) match = whitespace.match(line, pos)
initial = line[match.end()] initial = line[match.end()] # type: ignore[union-attr]
start = match.end() start = match.end() # type: ignore[union-attr]
spos = (lnum, start) spos = (lnum, start)
if new_line and initial not in '\r\n#' and (initial != '\\' or pseudomatch is None): if new_line and initial not in '\r\n#' and (initial != '\\' or pseudomatch is None):
@@ -512,12 +512,12 @@ def tokenize_lines(
if not pseudomatch: # scan for tokens if not pseudomatch: # scan for tokens
match = whitespace.match(line, pos) match = whitespace.match(line, pos)
if new_line and paren_level == 0 and not fstring_stack: if new_line and paren_level == 0 and not fstring_stack:
yield from dedent_if_necessary(match.end()) yield from dedent_if_necessary(match.end()) # type: ignore[union-attr]
pos = match.end() pos = match.end() # type: ignore[union-attr]
new_line = False new_line = False
yield PythonToken( yield PythonToken(
ERRORTOKEN, line[pos], (lnum, pos), ERRORTOKEN, line[pos], (lnum, pos),
additional_prefix + match.group(0) additional_prefix + match.group(0) # type: ignore[union-attr]
) )
additional_prefix = '' additional_prefix = ''
pos += 1 pos += 1
@@ -586,7 +586,7 @@ def tokenize_lines(
# backslash and is continued. # backslash and is continued.
contstr_start = lnum, start contstr_start = lnum, start
endprog = (endpats.get(initial) or endpats.get(token[1]) endprog = (endpats.get(initial) or endpats.get(token[1])
or endpats.get(token[2])) or endpats.get(token[2])) # type: ignore[assignment]
contstr = line[start:] contstr = line[start:]
contline = line contline = line
break break
+33 -23
View File
@@ -43,11 +43,8 @@ Parser Tree Classes
""" """
import re import re
try:
from collections.abc import Mapping from collections.abc import Mapping
except ImportError: from typing import Tuple, Any
from collections import Mapping
from typing import Tuple
from parso.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, search_ancestor # noqa from parso.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, search_ancestor # noqa
from parso.python.prefix import split_prefix from parso.python.prefix import split_prefix
@@ -70,6 +67,9 @@ _IMPORTS = set(['import_name', 'import_from'])
class DocstringMixin: class DocstringMixin:
__slots__ = () __slots__ = ()
type: str
children: "list[Any]"
parent: Any
def get_doc_node(self): def get_doc_node(self):
""" """
@@ -101,6 +101,7 @@ class PythonMixin:
Some Python specific utilities. Some Python specific utilities.
""" """
__slots__ = () __slots__ = ()
children: "list[Any]"
def get_name_of_position(self, position): def get_name_of_position(self, position):
""" """
@@ -219,7 +220,7 @@ class Name(_LeafWithoutNewlines):
type_ = node.type type_ = node.type
if type_ in ('funcdef', 'classdef'): if type_ in ('funcdef', 'classdef'):
if self == node.name: if self == node.name: # type: ignore[union-attr]
return node return node
return None return None
@@ -232,7 +233,7 @@ class Name(_LeafWithoutNewlines):
if node.type == 'suite': if node.type == 'suite':
return None return None
if node.type in _GET_DEFINITION_TYPES: if node.type in _GET_DEFINITION_TYPES:
if self in node.get_defined_names(include_setitem): if self in node.get_defined_names(include_setitem): # type: ignore[attr-defined]
return node return node
if import_name_always and node.type in _IMPORTS: if import_name_always and node.type in _IMPORTS:
return node return node
@@ -296,6 +297,7 @@ class FStringEnd(PythonLeaf):
class _StringComparisonMixin: class _StringComparisonMixin:
__slots__ = () __slots__ = ()
value: Any
def __eq__(self, other): def __eq__(self, other):
""" """
@@ -368,7 +370,7 @@ class Scope(PythonBaseNode, DocstringMixin):
def __repr__(self): def __repr__(self):
try: try:
name = self.name.value name = self.name.value # type: ignore[attr-defined]
except AttributeError: except AttributeError:
name = '' name = ''
@@ -477,13 +479,13 @@ class Class(ClassOrFunc):
Returns the `arglist` node that defines the super classes. It returns Returns the `arglist` node that defines the super classes. It returns
None if there are no arguments. None if there are no arguments.
""" """
if self.children[2] != '(': # Has no parentheses for i, child in enumerate(self.children):
if child == '(':
next_child = self.children[i + 1]
if next_child == ')':
return None return None
else: return next_child
if self.children[3] == ')': # Empty parentheses
return None return None
else:
return self.children[3]
def _create_params(parent, argslist_list): def _create_params(parent, argslist_list):
@@ -550,15 +552,21 @@ class Function(ClassOrFunc):
def __init__(self, children): def __init__(self, children):
super().__init__(children) super().__init__(children)
parameters = self.children[2] # After `def foo` parameters = self._find_parameters()
parameters_children = parameters.children[1:-1] parameters_children = parameters.children[1:-1]
# If input parameters list already has Param objects, keep it as is;
# otherwise, convert it to a list of Param objects.
if not any(isinstance(child, Param) for child in parameters_children): if not any(isinstance(child, Param) for child in parameters_children):
parameters.children[1:-1] = _create_params(parameters, parameters_children) parameters.children[1:-1] = _create_params(
parameters, parameters_children
)
def _find_parameters(self):
for child in self.children:
if child.type == 'parameters':
return child
raise Exception("A function should always have parameters")
def _get_param_nodes(self): def _get_param_nodes(self):
return self.children[2].children return self._find_parameters().children
def get_params(self): def get_params(self):
""" """
@@ -631,12 +639,9 @@ class Function(ClassOrFunc):
""" """
Returns the test node after `->` or `None` if there is no annotation. Returns the test node after `->` or `None` if there is no annotation.
""" """
try: for i, child in enumerate(self.children):
if self.children[3] == "->": if child == '->':
return self.children[4] return self.children[i + 1]
assert self.children[3] == ":"
return None
except IndexError:
return None return None
@@ -794,6 +799,8 @@ class WithStmt(Flow):
class Import(PythonBaseNode): class Import(PythonBaseNode):
__slots__ = () __slots__ = ()
get_paths: Any
_aliases: Any
def get_path_for_name(self, name): def get_path_for_name(self, name):
""" """
@@ -818,6 +825,9 @@ class Import(PythonBaseNode):
def is_star_import(self): def is_star_import(self):
return self.children[-1] == '*' return self.children[-1] == '*'
def get_defined_names(self):
raise NotImplementedError("Use ImportFrom or ImportName")
class ImportFrom(Import): class ImportFrom(Import):
type = 'import_from' type = 'import_from'
+2 -7
View File
@@ -14,12 +14,7 @@ def search_ancestor(node: 'NodeOrLeaf', *node_types: str) -> 'Optional[BaseNode]
:param node: The ancestors of this node will be checked. :param node: The ancestors of this node will be checked.
:param node_types: type names that are searched for. :param node_types: type names that are searched for.
""" """
n = node.parent return node.search_ancestor(*node_types)
while n is not None:
if n.type in node_types:
return n
n = n.parent
return None
class NodeOrLeaf: class NodeOrLeaf:
@@ -371,7 +366,7 @@ class BaseNode(NodeOrLeaf):
""" """
__slots__ = ('children',) __slots__ = ('children',)
def __init__(self, children: List[NodeOrLeaf]) -> None: def __init__(self, children) -> None:
self.children = children self.children = children
""" """
A list of :class:`NodeOrLeaf` child nodes. A list of :class:`NodeOrLeaf` child nodes.
+2 -2
View File
@@ -2,7 +2,7 @@ import re
import sys import sys
from ast import literal_eval from ast import literal_eval
from functools import total_ordering from functools import total_ordering
from typing import NamedTuple, Sequence, Union from typing import NamedTuple, Union
# The following is a list in Python that are line breaks in str.splitlines, but # The following is a list in Python that are line breaks in str.splitlines, but
# not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed, # not in Python. In Python only \r (Carriage Return, 0xD) and \n (Line Feed,
@@ -26,7 +26,7 @@ class Version(NamedTuple):
micro: int micro: int
def split_lines(string: str, keepends: bool = False) -> Sequence[str]: def split_lines(string: str, keepends: bool = False) -> "list[str]":
r""" r"""
Intended for Python code. In contrast to Python's :py:meth:`str.splitlines`, Intended for Python code. In contrast to Python's :py:meth:`str.splitlines`,
looks at form feeds and other special characters as normal text. Just looks at form feeds and other special characters as normal text. Just
+14
View File
@@ -0,0 +1,14 @@
[tool.zuban]
enable_error_code = ["ignore-without-code"]
disallow_subclassing_any = true
# Avoid creating future gotchas emerging from bad typing
warn_redundant_casts = true
warn_unused_ignores = true
warn_unused_configs = true
warn_unreachable = true
strict_equality = true
implicit_optional = true
exclude = "^test/normalizer_issue_files"
+5 -5
View File
@@ -15,11 +15,11 @@ Options:
import cProfile import cProfile
from docopt import docopt from docopt import docopt
from jedi.parser.python import load_grammar from jedi.parser.python import load_grammar # type: ignore[import-not-found]
from jedi.parser.diff import DiffParser from jedi.parser.diff import DiffParser # type: ignore[import-not-found]
from jedi.parser.python import ParserWithRecovery from jedi.parser.python import ParserWithRecovery # type: ignore[import-not-found]
from jedi.common import splitlines from jedi.common import splitlines # type: ignore[import-not-found]
import jedi import jedi # type: ignore[import-not-found]
def run(parser, lines): def run(parser, lines):
-17
View File
@@ -10,20 +10,3 @@ ignore =
E226, E226,
# line break before binary operator # line break before binary operator
W503, W503,
[mypy]
show_error_codes = true
enable_error_code = ignore-without-code
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
strict_equality = True
no_implicit_optional = False
+1 -2
View File
@@ -58,9 +58,8 @@ setup(
'qa': [ 'qa': [
# Latest version which supports Python 3.6 # Latest version which supports Python 3.6
'flake8==5.0.4', 'flake8==5.0.4',
# Latest version which supports Python 3.6
'mypy==0.971',
# Arbitrary pins, latest at the time of pinning # Arbitrary pins, latest at the time of pinning
'zuban==0.5.1',
'types-setuptools==67.2.0.1', 'types-setuptools==67.2.0.1',
], ],
}, },
+1 -1
View File
@@ -33,4 +33,4 @@ def test_invalid_grammar_version(string):
def test_grammar_int_version(): def test_grammar_int_version():
with pytest.raises(TypeError): with pytest.raises(TypeError):
load_grammar(version=3.8) load_grammar(version=3.8) # type: ignore
+62 -1
View File
@@ -117,7 +117,7 @@ def test_param_splitting(each_version):
def test_unicode_string(): def test_unicode_string():
s = tree.String(None, '', (0, 0)) s = tree.String('', (0, 0))
assert repr(s) # Should not raise an Error! assert repr(s) # Should not raise an Error!
@@ -206,3 +206,64 @@ def test_positional_only_arguments(works_ge_py38, param_code):
) )
def test_decorator_expression(works_ge_py39, expression): def test_decorator_expression(works_ge_py39, expression):
works_ge_py39.parse("@%s\ndef x(): pass" % expression) works_ge_py39.parse("@%s\ndef x(): pass" % expression)
@pytest.mark.parametrize(
'code', [
'class Foo[T]: pass',
'class Foo[T: str]: pass',
'class Foo[T, U]: pass',
'class Foo[T: str, U: int]: pass',
'class Foo[T](Base): pass',
'class Foo[T: str](Base, Mixin): pass',
'class Foo[*Ts]: pass',
'class Foo[**P]: pass',
]
)
def test_pep695_generic_class(works_ge_py312, code):
works_ge_py312.parse(code)
@pytest.mark.parametrize(
'code', [
'def foo[T](x: T) -> T: pass',
'def foo[T: int](x: T) -> T: pass',
'def foo[T, U](x: T, y: U): pass',
'def foo[*Ts](*args): pass',
'def foo[**P](*args): pass',
]
)
def test_pep695_generic_function(works_ge_py312, code):
works_ge_py312.parse(code)
def test_pep695_class_get_super_arglist(works_ge_py312):
module = works_ge_py312.parse('class Foo[T](Bar, Baz): pass')
if module is None:
return
classdef = module.children[0]
arglist = classdef.get_super_arglist()
assert arglist is not None
assert 'Bar' in arglist.get_code()
assert 'Baz' in arglist.get_code()
def test_pep695_class_no_bases(works_ge_py312):
module = works_ge_py312.parse('class Foo[T]: pass')
if module is None:
return
classdef = module.children[0]
assert classdef.get_super_arglist() is None
@pytest.mark.parametrize(
'code', [
'class Foo[T = int]: pass',
'class Foo[T: str = "default"]: pass',
'class Foo[*Ts = tuple[int, ...]]: pass',
'class Foo[**P = None]: pass',
'def foo[T = int](x: T) -> T: pass',
]
)
def test_pep696_type_param_defaults(works_ge_py313, code):
works_ge_py313.parse(code)