Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,15 @@ def works_ge_py38(each_version):
def works_ge_py39(each_version):
version_info = parse_version_string(each_version)
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))
8 changes: 6 additions & 2 deletions parso/python/grammar312.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ decorators: decorator+
decorated: decorators (classdef | 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] ')'
typedargslist: (
Expand Down Expand Up @@ -131,7 +135,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((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)* [',']

Expand Down
9 changes: 7 additions & 2 deletions parso/python/grammar313.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ decorators: decorator+
decorated: decorators (classdef | 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] ')'
typedargslist: (
Expand Down Expand Up @@ -131,7 +136,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((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)* [',']

Expand Down
9 changes: 7 additions & 2 deletions parso/python/grammar314.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ decorators: decorator+
decorated: decorators (classdef | 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] ')'
typedargslist: (
Expand Down Expand Up @@ -131,7 +136,7 @@ dictorsetmaker: ( ((test ':' test | '**' expr)
((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)* [',']

Expand Down
41 changes: 22 additions & 19 deletions parso/python/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,13 @@ def get_super_arglist(self):
Returns the `arglist` node that defines the super classes. It returns
None if there are no arguments.
"""
if self.children[2] != '(': # Has no parentheses
return None
else:
if self.children[3] == ')': # Empty parentheses
return None
else:
return self.children[3]
for i, child in enumerate(self.children):
if child == '(':
next_child = self.children[i + 1]
if next_child == ')':
return None
return next_child
return None


def _create_params(parent, argslist_list):
Expand Down Expand Up @@ -552,15 +552,21 @@ class Function(ClassOrFunc):

def __init__(self, children):
super().__init__(children)
parameters = self.children[2] # After `def foo`
parameters = self._find_parameters()
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):
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):
return self.children[2].children
return self._find_parameters().children

def get_params(self):
"""
Expand Down Expand Up @@ -633,13 +639,10 @@ def annotation(self):
"""
Returns the test node after `->` or `None` if there is no annotation.
"""
try:
if self.children[3] == "->":
return self.children[4]
assert self.children[3] == ":"
return None
except IndexError:
return None
for i, child in enumerate(self.children):
if child == '->':
return self.children[i + 1]
return None


class Lambda(Function):
Expand Down
61 changes: 61 additions & 0 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,64 @@ def test_positional_only_arguments(works_ge_py38, param_code):
)
def test_decorator_expression(works_ge_py39, 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)
Loading