Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cython
Commits
b3cfe42f
Commit
b3cfe42f
authored
Apr 14, 2019
by
Josh Tobin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adds positional only args support (PEP 570)
parent
d5da2dbc
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
400 additions
and
11 deletions
+400
-11
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+45
-10
Cython/Compiler/Parsing.py
Cython/Compiler/Parsing.py
+15
-1
tests/compile/posonly.pyx
tests/compile/posonly.pyx
+17
-0
tests/run/posonly.pyx
tests/run/posonly.pyx
+323
-0
No files found.
Cython/Compiler/Nodes.py
View file @
b3cfe42f
...
@@ -861,8 +861,9 @@ class CArgDeclNode(Node):
...
@@ -861,8 +861,9 @@ class CArgDeclNode(Node):
# annotation ExprNode or None Py3 function arg annotation
# annotation ExprNode or None Py3 function arg annotation
# is_self_arg boolean Is the "self" arg of an extension type method
# is_self_arg boolean Is the "self" arg of an extension type method
# is_type_arg boolean Is the "class" arg of an extension type classmethod
# is_type_arg boolean Is the "class" arg of an extension type classmethod
#
is_kw_only
boolean Is a keyword-only argument
#
kw_only
boolean Is a keyword-only argument
# is_dynamic boolean Non-literal arg stored inside CyFunction
# is_dynamic boolean Non-literal arg stored inside CyFunction
# pos_only boolean Is a positional-only argument
child_attrs
=
[
"base_type"
,
"declarator"
,
"default"
,
"annotation"
]
child_attrs
=
[
"base_type"
,
"declarator"
,
"default"
,
"annotation"
]
outer_attrs
=
[
"default"
,
"annotation"
]
outer_attrs
=
[
"default"
,
"annotation"
]
...
@@ -871,6 +872,7 @@ class CArgDeclNode(Node):
...
@@ -871,6 +872,7 @@ class CArgDeclNode(Node):
is_type_arg
=
0
is_type_arg
=
0
is_generic
=
1
is_generic
=
1
kw_only
=
0
kw_only
=
0
pos_only
=
0
not_none
=
0
not_none
=
0
or_none
=
0
or_none
=
0
type
=
None
type
=
None
...
@@ -3655,6 +3657,7 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3655,6 +3657,7 @@ class DefNodeWrapper(FuncDefNode):
positional_args
=
[]
positional_args
=
[]
required_kw_only_args
=
[]
required_kw_only_args
=
[]
optional_kw_only_args
=
[]
optional_kw_only_args
=
[]
num_pos_only_args
=
0
for
arg
in
args
:
for
arg
in
args
:
if
arg
.
is_generic
:
if
arg
.
is_generic
:
if
arg
.
default
:
if
arg
.
default
:
...
@@ -3668,6 +3671,9 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3668,6 +3671,9 @@ class DefNodeWrapper(FuncDefNode):
elif
not
arg
.
is_self_arg
and
not
arg
.
is_type_arg
:
elif
not
arg
.
is_self_arg
and
not
arg
.
is_type_arg
:
positional_args
.
append
(
arg
)
positional_args
.
append
(
arg
)
if
arg
.
pos_only
:
num_pos_only_args
+=
1
# sort required kw-only args before optional ones to avoid special
# sort required kw-only args before optional ones to avoid special
# cases in the unpacking code
# cases in the unpacking code
kw_only_args
=
required_kw_only_args
+
optional_kw_only_args
kw_only_args
=
required_kw_only_args
+
optional_kw_only_args
...
@@ -3685,10 +3691,10 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3685,10 +3691,10 @@ class DefNodeWrapper(FuncDefNode):
code
.
putln
(
'{'
)
code
.
putln
(
'{'
)
all_args
=
tuple
(
positional_args
)
+
tuple
(
kw_only_args
)
all_args
=
tuple
(
positional_args
)
+
tuple
(
kw_only_args
)
code
.
putln
(
"static PyObject **%s[] = {%s
,0
};"
%
(
code
.
putln
(
"static PyObject **%s[] = {%s};"
%
(
Naming
.
pykwdlist_cname
,
Naming
.
pykwdlist_cname
,
','
.
join
([
'&%s'
%
code
.
intern_identifier
(
arg
.
name
)
','
.
join
([
'&%s'
%
code
.
intern_identifier
(
arg
.
name
)
for
arg
in
all_args
])))
for
arg
in
all_args
if
not
arg
.
pos_only
]
+
[
'0'
])))
# Before being converted and assigned to the target variables,
# Before being converted and assigned to the target variables,
# borrowed references to all unpacked argument values are
# borrowed references to all unpacked argument values are
...
@@ -3706,8 +3712,8 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3706,8 +3712,8 @@ class DefNodeWrapper(FuncDefNode):
Naming
.
kwds_cname
))
Naming
.
kwds_cname
))
self
.
generate_keyword_unpacking_code
(
self
.
generate_keyword_unpacking_code
(
min_positional_args
,
max_positional_args
,
min_positional_args
,
max_positional_args
,
has_fixed_positional_count
,
has_kw_only_args
,
num_pos_only_args
,
has_fixed_positional_count
,
all_args
,
argtuple_error_label
,
code
)
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
)
# --- optimised code when we do not receive any keyword arguments
# --- optimised code when we do not receive any keyword arguments
if
(
self
.
num_required_kw_args
and
min_positional_args
>
0
)
or
min_positional_args
==
max_positional_args
:
if
(
self
.
num_required_kw_args
and
min_positional_args
>
0
)
or
min_positional_args
==
max_positional_args
:
...
@@ -3870,8 +3876,8 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3870,8 +3876,8 @@ class DefNodeWrapper(FuncDefNode):
code
.
putln
(
'values[%d] = %s;'
%
(
i
,
arg
.
type
.
as_pyobject
(
default_value
)))
code
.
putln
(
'values[%d] = %s;'
%
(
i
,
arg
.
type
.
as_pyobject
(
default_value
)))
def
generate_keyword_unpacking_code
(
self
,
min_positional_args
,
max_positional_args
,
def
generate_keyword_unpacking_code
(
self
,
min_positional_args
,
max_positional_args
,
has_fixed_positional_count
,
has_kw_only_args
,
num_pos_only_args
,
has_fixed_positional_count
,
all_args
,
argtuple_error_label
,
code
):
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
):
code
.
putln
(
'Py_ssize_t kw_args;'
)
code
.
putln
(
'Py_ssize_t kw_args;'
)
code
.
putln
(
'const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);'
%
Naming
.
args_cname
)
code
.
putln
(
'const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);'
%
Naming
.
args_cname
)
# copy the values from the args tuple and check that it's not too long
# copy the values from the args tuple and check that it's not too long
...
@@ -3901,9 +3907,12 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3901,9 +3907,12 @@ class DefNodeWrapper(FuncDefNode):
code
.
putln
(
'kw_args = PyDict_Size(%s);'
%
Naming
.
kwds_cname
)
code
.
putln
(
'kw_args = PyDict_Size(%s);'
%
Naming
.
kwds_cname
)
if
self
.
num_required_args
or
max_positional_args
>
0
:
if
self
.
num_required_args
or
max_positional_args
>
0
:
last_required_arg
=
-
1
last_required_arg
=
-
1
last_required_posonly_arg
=
-
1
for
i
,
arg
in
enumerate
(
all_args
):
for
i
,
arg
in
enumerate
(
all_args
):
if
not
arg
.
default
:
if
not
arg
.
default
:
last_required_arg
=
i
last_required_arg
=
i
if
arg
.
pos_only
and
not
arg
.
default
:
last_required_posonly_arg
=
i
if
last_required_arg
<
max_positional_args
:
if
last_required_arg
<
max_positional_args
:
last_required_arg
=
max_positional_args
-
1
last_required_arg
=
max_positional_args
-
1
if
max_positional_args
>
0
:
if
max_positional_args
>
0
:
...
@@ -3917,6 +3926,12 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3917,6 +3926,12 @@ class DefNodeWrapper(FuncDefNode):
else
:
else
:
code
.
putln
(
'case %2d:'
%
i
)
code
.
putln
(
'case %2d:'
%
i
)
pystring_cname
=
code
.
intern_identifier
(
arg
.
name
)
pystring_cname
=
code
.
intern_identifier
(
arg
.
name
)
if
arg
.
pos_only
:
if
i
==
last_required_posonly_arg
:
code
.
put_goto
(
argtuple_error_label
)
if
i
==
last_required_arg
:
code
.
putln
(
'break;'
)
continue
if
arg
.
default
:
if
arg
.
default
:
if
arg
.
kw_only
:
if
arg
.
kw_only
:
# optional kw-only args are handled separately below
# optional kw-only args are handled separately below
...
@@ -3971,14 +3986,34 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3971,14 +3986,34 @@ class DefNodeWrapper(FuncDefNode):
# arguments, this will always do the right thing for unpacking
# arguments, this will always do the right thing for unpacking
# keyword arguments, so that we can concentrate on optimising
# keyword arguments, so that we can concentrate on optimising
# common cases above.
# common cases above.
#
# ParseOptionalKeywords() needs to know how many of the arguments
# that could be passed as keywords have in fact been passed as
# positional args.
if
num_pos_only_args
>
0
:
# There are positional-only arguments which we don't want to count,
# since they cannot be keyword arguments. Subtract the number of
# pos-only arguments from the number of positional arguments we got.
# If we get a negative number then none of the keyword arguments were
# passed as positional args.
code
.
putln
(
'const Py_ssize_t kwd_pos_args = (pos_args < %d) ? 0 : (pos_args - %d);'
%
(
num_pos_only_args
,
num_pos_only_args
))
elif
max_positional_args
>
0
:
code
.
putln
(
'const Py_ssize_t kwd_pos_args = pos_args;'
)
if
max_positional_args
==
0
:
if
max_positional_args
==
0
:
pos_arg_count
=
"0"
pos_arg_count
=
"0"
elif
self
.
star_arg
:
elif
self
.
star_arg
:
code
.
putln
(
"const Py_ssize_t used_pos_args = (pos_args < %d) ? pos_args : %d;"
%
(
# If there is a *arg, the number of used positional args could be larger than
max_positional_args
,
max_positional_args
))
# the number of possible keyword arguments. But ParseOptionalKeywords() uses the
# number of positional args as an index into the keyword argument name array,
# if this is larger than the number of kwd args we get a segfault. So round
# this down to max_positional_args - num_pos_only_args (= num possible kwd args).
code
.
putln
(
"const Py_ssize_t used_pos_args = (kwd_pos_args < %d) ? kwd_pos_args : %d;"
%
(
max_positional_args
-
num_pos_only_args
,
max_positional_args
-
num_pos_only_args
))
pos_arg_count
=
"used_pos_args"
pos_arg_count
=
"used_pos_args"
else
:
else
:
pos_arg_count
=
"pos_args"
pos_arg_count
=
"
kwd_
pos_args"
code
.
globalstate
.
use_utility_code
(
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"ParseKeywords"
,
"FunctionArguments.c"
))
UtilityCode
.
load_cached
(
"ParseKeywords"
,
"FunctionArguments.c"
))
code
.
putln
(
'if (unlikely(__Pyx_ParseOptionalKeywords(%s, %s, %s, values, %s, "%s") < 0)) %s'
%
(
code
.
putln
(
'if (unlikely(__Pyx_ParseOptionalKeywords(%s, %s, %s, values, %s, "%s") < 0)) %s'
%
(
...
...
Cython/Compiler/Parsing.py
View file @
b3cfe42f
...
@@ -2965,7 +2965,7 @@ def p_exception_value_clause(s):
...
@@ -2965,7 +2965,7 @@ def p_exception_value_clause(s):
exc_val
=
p_test
(
s
)
exc_val
=
p_test
(
s
)
return
exc_val
,
exc_check
return
exc_val
,
exc_check
c_arg_list_terminators
=
cython
.
declare
(
set
,
set
([
'*'
,
'**'
,
'.'
,
')'
,
':'
]))
c_arg_list_terminators
=
cython
.
declare
(
set
,
set
([
'*'
,
'**'
,
'.'
,
')'
,
':'
,
'/'
]))
def
p_c_arg_list
(
s
,
ctx
=
Ctx
(),
in_pyfunc
=
0
,
cmethod_flag
=
0
,
def
p_c_arg_list
(
s
,
ctx
=
Ctx
(),
in_pyfunc
=
0
,
cmethod_flag
=
0
,
nonempty_declarators
=
0
,
kw_only
=
0
,
annotated
=
1
):
nonempty_declarators
=
0
,
kw_only
=
0
,
annotated
=
1
):
...
@@ -3424,6 +3424,20 @@ def p_varargslist(s, terminator=')', annotated=1):
...
@@ -3424,6 +3424,20 @@ def p_varargslist(s, terminator=')', annotated=1):
annotated
=
annotated
)
annotated
=
annotated
)
star_arg
=
None
star_arg
=
None
starstar_arg
=
None
starstar_arg
=
None
if
s
.
sy
==
'/'
:
if
len
(
args
)
==
0
:
s
.
error
(
"Got zero positional-only arguments despite presence of "
"positional-only specifier '/'"
)
s
.
next
()
# Mark all args to the left as pos only
for
arg
in
args
:
arg
.
pos_only
=
1
if
s
.
sy
==
','
:
s
.
next
()
args
.
extend
(
p_c_arg_list
(
s
,
in_pyfunc
=
1
,
nonempty_declarators
=
1
,
annotated
=
annotated
))
elif
s
.
sy
!=
terminator
:
s
.
error
(
"Syntax error in Python function argument list"
)
if
s
.
sy
==
'*'
:
if
s
.
sy
==
'*'
:
s
.
next
()
s
.
next
()
if
s
.
sy
==
'IDENT'
:
if
s
.
sy
==
'IDENT'
:
...
...
tests/compile/posonly.pyx
0 → 100644
View file @
b3cfe42f
# mode: compile
# tag: posonly
# TODO: remove posonly tag before merge (and maybe remove this test,
# since it seems covered by the runs/ test)
def
test
(
x
,
y
,
z
=
42
,
/
,
w
=
43
):
pass
def
test2
(
x
,
y
,
/
):
pass
def
test3
(
x
,
/
,
z
):
pass
def
test4
(
x
,
/
,
z
,
*
,
w
):
pass
tests/run/posonly.pyx
0 → 100644
View file @
b3cfe42f
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment