Support PEP-570 (positional only arguments) (#946)
Code using positional only arguments is considered >= 3.8
This commit is contained in:
parent
d8fa8df052
commit
2848e2e1d6
18
black.py
18
black.py
@ -143,6 +143,7 @@ class Feature(Enum):
|
|||||||
ASYNC_IDENTIFIERS = 6
|
ASYNC_IDENTIFIERS = 6
|
||||||
ASYNC_KEYWORDS = 7
|
ASYNC_KEYWORDS = 7
|
||||||
ASSIGNMENT_EXPRESSIONS = 8
|
ASSIGNMENT_EXPRESSIONS = 8
|
||||||
|
POS_ONLY_ARGUMENTS = 9
|
||||||
|
|
||||||
|
|
||||||
VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
|
VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
|
||||||
@ -178,6 +179,7 @@ class Feature(Enum):
|
|||||||
Feature.TRAILING_COMMA_IN_DEF,
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
Feature.ASYNC_KEYWORDS,
|
Feature.ASYNC_KEYWORDS,
|
||||||
Feature.ASSIGNMENT_EXPRESSIONS,
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,6 +937,7 @@ def show(cls, code: Union[str, Leaf, Node]) -> None:
|
|||||||
token.DOUBLESTAR,
|
token.DOUBLESTAR,
|
||||||
}
|
}
|
||||||
STARS = {token.STAR, token.DOUBLESTAR}
|
STARS = {token.STAR, token.DOUBLESTAR}
|
||||||
|
VARARGS_SPECIALS = STARS | {token.SLASH}
|
||||||
VARARGS_PARENTS = {
|
VARARGS_PARENTS = {
|
||||||
syms.arglist,
|
syms.arglist,
|
||||||
syms.argument, # double star in arglist
|
syms.argument, # double star in arglist
|
||||||
@ -1847,7 +1850,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
|
|||||||
# that, too.
|
# that, too.
|
||||||
return prevp.prefix
|
return prevp.prefix
|
||||||
|
|
||||||
elif prevp.type in STARS:
|
elif prevp.type in VARARGS_SPECIALS:
|
||||||
if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
|
if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
|
||||||
return NO
|
return NO
|
||||||
|
|
||||||
@ -1937,7 +1940,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
|
|||||||
if not prevp or prevp.type == token.LPAR:
|
if not prevp or prevp.type == token.LPAR:
|
||||||
return NO
|
return NO
|
||||||
|
|
||||||
elif prev.type in {token.EQUAL} | STARS:
|
elif prev.type in {token.EQUAL} | VARARGS_SPECIALS:
|
||||||
return NO
|
return NO
|
||||||
|
|
||||||
elif p.type == syms.decorator:
|
elif p.type == syms.decorator:
|
||||||
@ -3086,7 +3089,7 @@ def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
|
|||||||
extended iterable unpacking (PEP 3132) and additional unpacking
|
extended iterable unpacking (PEP 3132) and additional unpacking
|
||||||
generalizations (PEP 448).
|
generalizations (PEP 448).
|
||||||
"""
|
"""
|
||||||
if leaf.type not in STARS or not leaf.parent:
|
if leaf.type not in VARARGS_SPECIALS or not leaf.parent:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
p = leaf.parent
|
p = leaf.parent
|
||||||
@ -3201,8 +3204,9 @@ def get_features_used(node: Node) -> Set[Feature]:
|
|||||||
|
|
||||||
Currently looking for:
|
Currently looking for:
|
||||||
- f-strings;
|
- f-strings;
|
||||||
- underscores in numeric literals; and
|
- underscores in numeric literals;
|
||||||
- trailing commas after * or ** in function signatures and calls.
|
- trailing commas after * or ** in function signatures and calls;
|
||||||
|
- positional only arguments in function signatures and lambdas;
|
||||||
"""
|
"""
|
||||||
features: Set[Feature] = set()
|
features: Set[Feature] = set()
|
||||||
for n in node.pre_order():
|
for n in node.pre_order():
|
||||||
@ -3215,6 +3219,10 @@ def get_features_used(node: Node) -> Set[Feature]:
|
|||||||
if "_" in n.value: # type: ignore
|
if "_" in n.value: # type: ignore
|
||||||
features.add(Feature.NUMERIC_UNDERSCORES)
|
features.add(Feature.NUMERIC_UNDERSCORES)
|
||||||
|
|
||||||
|
elif n.type == token.SLASH:
|
||||||
|
if n.parent and n.parent.type in {syms.typedargslist, syms.arglist}:
|
||||||
|
features.add(Feature.POS_ONLY_ARGUMENTS)
|
||||||
|
|
||||||
elif n.type == token.COLONEQUAL:
|
elif n.type == token.COLONEQUAL:
|
||||||
features.add(Feature.ASSIGNMENT_EXPRESSIONS)
|
features.add(Feature.ASSIGNMENT_EXPRESSIONS)
|
||||||
|
|
||||||
|
@ -18,15 +18,55 @@ decorated: decorators (classdef | funcdef | async_funcdef)
|
|||||||
async_funcdef: ASYNC funcdef
|
async_funcdef: ASYNC funcdef
|
||||||
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
||||||
parameters: '(' [typedargslist] ')'
|
parameters: '(' [typedargslist] ')'
|
||||||
typedargslist: ((tfpdef ['=' test] ',')*
|
|
||||||
('*' [tname] (',' tname ['=' test])* [',' ['**' tname [',']]] | '**' tname [','])
|
# The following definition for typedarglist is equivalent to this set of rules:
|
||||||
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
|
#
|
||||||
|
# arguments = argument (',' argument)*
|
||||||
|
# argument = tfpdef ['=' test]
|
||||||
|
# kwargs = '**' tname [',']
|
||||||
|
# args = '*' [tname]
|
||||||
|
# kwonly_kwargs = (',' argument)* [',' [kwargs]]
|
||||||
|
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
|
||||||
|
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
|
||||||
|
# typedargslist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
|
||||||
|
# typedarglist = arguments ',' '/' [',' [typedargslist_no_posonly]])|(typedargslist_no_posonly)"
|
||||||
|
#
|
||||||
|
# It needs to be fully expanded to allow our LL(1) parser to work on it.
|
||||||
|
|
||||||
|
typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [
|
||||||
|
',' [((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
|
||||||
|
[',' ['**' tname [',']]] | '**' tname [','])
|
||||||
|
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])]
|
||||||
|
] | ((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
|
||||||
|
[',' ['**' tname [',']]] | '**' tname [','])
|
||||||
|
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
|
||||||
|
|
||||||
tname: NAME [':' test]
|
tname: NAME [':' test]
|
||||||
tfpdef: tname | '(' tfplist ')'
|
tfpdef: tname | '(' tfplist ')'
|
||||||
tfplist: tfpdef (',' tfpdef)* [',']
|
tfplist: tfpdef (',' tfpdef)* [',']
|
||||||
varargslist: ((vfpdef ['=' test] ',')*
|
|
||||||
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]] | '**' vname [','])
|
# The following definition for varargslist is equivalent to this set of rules:
|
||||||
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
#
|
||||||
|
# arguments = argument (',' argument )*
|
||||||
|
# argument = vfpdef ['=' test]
|
||||||
|
# kwargs = '**' vname [',']
|
||||||
|
# args = '*' [vname]
|
||||||
|
# kwonly_kwargs = (',' argument )* [',' [kwargs]]
|
||||||
|
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
|
||||||
|
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
|
||||||
|
# vararglist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
|
||||||
|
# varargslist = arguments ',' '/' [','[(vararglist_no_posonly)]] | (vararglist_no_posonly)
|
||||||
|
#
|
||||||
|
# It needs to be fully expanded to allow our LL(1) parser to work on it.
|
||||||
|
|
||||||
|
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [
|
||||||
|
((vfpdef ['=' test] ',')* ('*' [vname] (',' vname ['=' test])*
|
||||||
|
[',' ['**' vname [',']]] | '**' vname [','])
|
||||||
|
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
||||||
|
]] | ((vfpdef ['=' test] ',')*
|
||||||
|
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]]| '**' vname [','])
|
||||||
|
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
||||||
|
|
||||||
vname: NAME
|
vname: NAME
|
||||||
vfpdef: vname | '(' vfplist ')'
|
vfpdef: vname | '(' vfplist ')'
|
||||||
vfplist: vfpdef (',' vfpdef)* [',']
|
vfplist: vfpdef (',' vfpdef)* [',']
|
||||||
|
44
tests/data/pep_570.py
Normal file
44
tests/data/pep_570.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
def positional_only_arg(a, /):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def all_markers(a, b, /, c, d, *, e, f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def all_markers_with_args_and_kwargs(
|
||||||
|
a_long_one,
|
||||||
|
b_long_one,
|
||||||
|
/,
|
||||||
|
c_long_one,
|
||||||
|
d_long_one,
|
||||||
|
*args,
|
||||||
|
e_long_one,
|
||||||
|
f_long_one,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def all_markers_with_defaults(a, b=1, /, c=2, d=3, *, e=4, f=5):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def long_one_with_long_parameter_names(
|
||||||
|
but_all_of_them,
|
||||||
|
are_positional_only,
|
||||||
|
arguments_mmmmkay,
|
||||||
|
so_this_is_only_valid_after,
|
||||||
|
three_point_eight,
|
||||||
|
/,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
lambda a, /: a
|
||||||
|
|
||||||
|
lambda a, b, /, c, d, *, e, f: a
|
||||||
|
|
||||||
|
lambda a, b, /, c, d, *args, e, f, **kwargs: args
|
||||||
|
|
||||||
|
lambda a, b=1, /, c=2, d=3, *, e=4, f=5: 1
|
@ -344,6 +344,23 @@ def test_fstring(self) -> None:
|
|||||||
black.assert_equivalent(source, actual)
|
black.assert_equivalent(source, actual)
|
||||||
black.assert_stable(source, actual, black.FileMode())
|
black.assert_stable(source, actual, black.FileMode())
|
||||||
|
|
||||||
|
@patch("black.dump_to_file", dump_to_stderr)
|
||||||
|
def test_pep_570(self) -> None:
|
||||||
|
source, expected = read_data("pep_570")
|
||||||
|
actual = fs(source)
|
||||||
|
self.assertFormatEqual(expected, actual)
|
||||||
|
black.assert_stable(source, actual, black.FileMode())
|
||||||
|
if sys.version_info >= (3, 8):
|
||||||
|
black.assert_equivalent(source, actual)
|
||||||
|
|
||||||
|
def test_detect_pos_only_arguments(self) -> None:
|
||||||
|
source, _ = read_data("pep_570")
|
||||||
|
root = black.lib2to3_parse(source)
|
||||||
|
features = black.get_features_used(root)
|
||||||
|
self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
|
||||||
|
versions = black.detect_target_versions(root)
|
||||||
|
self.assertIn(black.TargetVersion.PY38, versions)
|
||||||
|
|
||||||
@patch("black.dump_to_file", dump_to_stderr)
|
@patch("black.dump_to_file", dump_to_stderr)
|
||||||
def test_string_quotes(self) -> None:
|
def test_string_quotes(self) -> None:
|
||||||
source, expected = read_data("string_quotes")
|
source, expected = read_data("string_quotes")
|
||||||
|
Loading…
Reference in New Issue
Block a user