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_KEYWORDS = 7
|
||||
ASSIGNMENT_EXPRESSIONS = 8
|
||||
POS_ONLY_ARGUMENTS = 9
|
||||
|
||||
|
||||
VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
|
||||
@ -178,6 +179,7 @@ class Feature(Enum):
|
||||
Feature.TRAILING_COMMA_IN_DEF,
|
||||
Feature.ASYNC_KEYWORDS,
|
||||
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||
Feature.POS_ONLY_ARGUMENTS,
|
||||
},
|
||||
}
|
||||
|
||||
@ -935,6 +937,7 @@ def show(cls, code: Union[str, Leaf, Node]) -> None:
|
||||
token.DOUBLESTAR,
|
||||
}
|
||||
STARS = {token.STAR, token.DOUBLESTAR}
|
||||
VARARGS_SPECIALS = STARS | {token.SLASH}
|
||||
VARARGS_PARENTS = {
|
||||
syms.arglist,
|
||||
syms.argument, # double star in arglist
|
||||
@ -1847,7 +1850,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
|
||||
# that, too.
|
||||
return prevp.prefix
|
||||
|
||||
elif prevp.type in STARS:
|
||||
elif prevp.type in VARARGS_SPECIALS:
|
||||
if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
|
||||
return NO
|
||||
|
||||
@ -1937,7 +1940,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
|
||||
if not prevp or prevp.type == token.LPAR:
|
||||
return NO
|
||||
|
||||
elif prev.type in {token.EQUAL} | STARS:
|
||||
elif prev.type in {token.EQUAL} | VARARGS_SPECIALS:
|
||||
return NO
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
p = leaf.parent
|
||||
@ -3201,8 +3204,9 @@ def get_features_used(node: Node) -> Set[Feature]:
|
||||
|
||||
Currently looking for:
|
||||
- f-strings;
|
||||
- underscores in numeric literals; and
|
||||
- trailing commas after * or ** in function signatures and calls.
|
||||
- underscores in numeric literals;
|
||||
- trailing commas after * or ** in function signatures and calls;
|
||||
- positional only arguments in function signatures and lambdas;
|
||||
"""
|
||||
features: Set[Feature] = set()
|
||||
for n in node.pre_order():
|
||||
@ -3215,6 +3219,10 @@ def get_features_used(node: Node) -> Set[Feature]:
|
||||
if "_" in n.value: # type: ignore
|
||||
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:
|
||||
features.add(Feature.ASSIGNMENT_EXPRESSIONS)
|
||||
|
||||
|
@ -18,15 +18,55 @@ decorated: decorators (classdef | funcdef | async_funcdef)
|
||||
async_funcdef: ASYNC funcdef
|
||||
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
||||
parameters: '(' [typedargslist] ')'
|
||||
typedargslist: ((tfpdef ['=' test] ',')*
|
||||
('*' [tname] (',' tname ['=' test])* [',' ['**' tname [',']]] | '**' tname [','])
|
||||
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
|
||||
|
||||
# The following definition for typedarglist is equivalent to this set of rules:
|
||||
#
|
||||
# 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]
|
||||
tfpdef: tname | '(' tfplist ')'
|
||||
tfplist: tfpdef (',' tfpdef)* [',']
|
||||
varargslist: ((vfpdef ['=' test] ',')*
|
||||
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]] | '**' vname [','])
|
||||
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
||||
|
||||
# The following definition for varargslist is equivalent to this set of rules:
|
||||
#
|
||||
# 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
|
||||
vfpdef: vname | '(' vfplist ')'
|
||||
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_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)
|
||||
def test_string_quotes(self) -> None:
|
||||
source, expected = read_data("string_quotes")
|
||||
|
Loading…
Reference in New Issue
Block a user