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
3ee57173
Commit
3ee57173
authored
May 12, 2015
by
Stefan Behnel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
partially implement PEP 448 for function calls only
parent
1ba47438
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
910 additions
and
203 deletions
+910
-203
CHANGES.rst
CHANGES.rst
+3
-0
Cython/Compiler/ExprNodes.py
Cython/Compiler/ExprNodes.py
+180
-131
Cython/Compiler/Parsing.pxd
Cython/Compiler/Parsing.pxd
+1
-1
Cython/Compiler/Parsing.py
Cython/Compiler/Parsing.py
+82
-65
Cython/Parser/Grammar
Cython/Parser/Grammar
+18
-6
Cython/Utility/FunctionArguments.c
Cython/Utility/FunctionArguments.c
+51
-0
tests/run/pep448_test_extcall.pyx
tests/run/pep448_test_extcall.pyx
+575
-0
No files found.
CHANGES.rst
View file @
3ee57173
...
...
@@ -15,6 +15,9 @@ Features added
* Tracing is supported in ``nogil`` functions/sections and module init code.
* PEP 448 (Additional Unpacking Generalizations) was implemented for function
calls.
* When generators are used in a Cython module and the module imports the
modules "inspect" and/or "asyncio", Cython enables interoperability by
patching these modules to recognise Cython's internal generator type.
...
...
Cython/Compiler/ExprNodes.py
View file @
3ee57173
...
...
@@ -5450,8 +5450,9 @@ class AsTupleNode(ExprNode):
self
.
compile_time_value_error
(
e
)
def
analyse_types
(
self
,
env
):
self
.
arg
=
self
.
arg
.
analyse_types
(
env
)
self
.
arg
=
self
.
arg
.
coerce_to_pyobject
(
env
)
self
.
arg
=
self
.
arg
.
analyse_types
(
env
).
coerce_to_pyobject
(
env
)
if
self
.
arg
.
type
is
tuple_type
:
return
self
.
arg
.
as_none_safe_node
(
"'NoneType' object is not iterable"
)
self
.
type
=
tuple_type
self
.
is_temp
=
1
return
self
...
...
@@ -5471,6 +5472,159 @@ class AsTupleNode(ExprNode):
code
.
put_gotref
(
self
.
py_result
())
class
KeywordArgsNode
(
ExprNode
):
# Helper class for keyword arguments.
#
# keyword_args [DictNode or other ExprNode]
subexprs
=
[
'keyword_args'
]
is_temp
=
1
type
=
dict_type
reject_duplicates
=
True
def
calculate_constant_result
(
self
):
result
=
{}
reject_duplicates
=
self
.
reject_duplicates
for
item
in
self
.
keyword_args
:
if
item
.
is_dict_literal
:
# process items in order
items
=
((
key
.
constant_result
,
value
.
constant_result
)
for
key
,
value
in
item
.
key_value_pairs
)
else
:
items
=
item
.
constant_result
.
iteritems
()
for
key
,
value
in
items
:
if
reject_duplicates
and
key
in
result
:
raise
ValueError
(
"duplicate keyword argument found: %s"
%
key
)
result
[
key
]
=
value
self
.
constant_result
=
result
def
compile_time_value
(
self
,
denv
):
result
=
{}
reject_duplicates
=
self
.
reject_duplicates
for
item
in
self
.
keyword_args
:
if
item
.
is_dict_literal
:
# process items in order
items
=
[(
key
.
compile_time_value
(
denv
),
value
.
compile_time_value
(
denv
))
for
key
,
value
in
item
.
key_value_pairs
]
else
:
items
=
item
.
compile_time_value
(
denv
).
iteritems
()
try
:
for
key
,
value
in
items
:
if
reject_duplicates
and
key
in
result
:
raise
ValueError
(
"duplicate keyword argument found: %s"
%
key
)
result
[
key
]
=
value
except
Exception
,
e
:
self
.
compile_time_value_error
(
e
)
return
result
def
type_dependencies
(
self
,
env
):
return
()
def
infer_type
(
self
,
env
):
return
dict_type
def
analyse_types
(
self
,
env
):
args
=
[
arg
.
analyse_types
(
env
).
coerce_to_pyobject
(
env
).
as_none_safe_node
(
# FIXME: CPython's error message starts with the runtime function name
'argument after ** must be a mapping, not NoneType'
)
for
arg
in
self
.
keyword_args
]
if
len
(
args
)
==
1
and
args
[
0
].
type
is
dict_type
:
# strip this intermediate node and use the bare dict
arg
=
args
[
0
]
if
arg
.
is_name
and
arg
.
entry
.
is_arg
and
len
(
arg
.
entry
.
cf_assignments
)
==
1
:
# passing **kwargs through to function call => allow NULL
arg
.
allow_null
=
True
return
arg
self
.
keyword_args
=
args
return
self
def
may_be_none
(
self
):
return
False
gil_message
=
"Constructing Python dict"
def
generate_evaluation_code
(
self
,
code
):
code
.
mark_pos
(
self
.
pos
)
self
.
allocate_temp_result
(
code
)
if
self
.
reject_duplicates
and
len
(
self
.
keyword_args
)
>
1
:
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"RaiseDoubleKeywords"
,
"FunctionArguments.c"
))
args
=
iter
(
self
.
keyword_args
)
item
=
next
(
args
)
item
.
generate_evaluation_code
(
code
)
if
item
.
type
is
not
dict_type
:
# CPython supports calling functions with non-dicts, so do we
code
.
putln
(
'if (likely(PyDict_Check(%s))) {'
%
item
.
py_result
())
code
.
putln
(
"%s = %s;"
%
(
self
.
result
(),
item
.
py_result
()))
item
.
generate_post_assignment_code
(
code
)
if
item
.
type
is
not
dict_type
:
code
.
putln
(
'} else {'
)
code
.
putln
(
"%s = PyObject_CallFunctionObjArgs((PyObject*)&PyDict_Type, %s, NULL); %s"
%
(
self
.
result
(),
item
.
py_result
(),
code
.
error_goto_if_null
(
self
.
result
(),
self
.
pos
)))
code
.
put_gotref
(
self
.
py_result
())
item
.
generate_disposal_code
(
code
)
code
.
putln
(
'}'
)
item
.
free_temps
(
code
)
for
item
in
args
:
if
item
.
is_dict_literal
:
for
arg
in
item
.
keyword_args
:
arg
.
generate_evaluation_code
(
code
)
if
self
.
reject_duplicates
:
code
.
putln
(
"if (unlikely(PyDict_Contains(%s, %s))) {"
%
(
self
.
result
(),
arg
.
key
.
py_result
()))
# FIXME: find out function name at runtime!
code
.
putln
(
'__Pyx_RaiseDoubleKeywordsError("function", %s); %s'
%
(
arg
.
key
.
py_result
(),
code
.
error_goto
(
self
.
pos
)))
code
.
putln
(
"}"
)
code
.
put_error_if_neg
(
arg
.
key
.
pos
,
"PyDict_SetItem(%s, %s, %s)"
%
(
self
.
result
(),
arg
.
key
.
py_result
(),
arg
.
value
.
py_result
()))
arg
.
generate_disposal_code
(
code
)
arg
.
free_temps
(
code
)
continue
item
.
generate_evaluation_code
(
code
)
if
self
.
reject_duplicates
:
# merge mapping into kwdict one by one as we need to check for duplicates
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"MergeKeywords"
,
"FunctionArguments.c"
))
code
.
put_error_if_neg
(
item
.
pos
,
"__Pyx_MergeKeywords(%s, %s)"
%
(
self
.
result
(),
item
.
py_result
()))
else
:
# simple case, just add all entries
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"RaiseMappingExpected"
,
"FunctionArguments.c"
))
code
.
putln
(
"if (unlikely(PyDict_Update(%s, %s) < 0)) {"
%
(
self
.
result
(),
item
.
py_result
()))
code
.
putln
(
"if (PyErr_ExceptionMatches(PyExc_AttributeError)) __Pyx_RaiseMappingExpected(%s);"
%
(
item
.
py_result
()))
code
.
putln
(
code
.
error_goto
(
item
.
pos
))
code
.
putln
(
"}"
)
code
.
putln
(
"}"
)
item
.
generate_disposal_code
(
code
)
item
.
free_temps
(
code
)
def
annotate
(
self
,
code
):
for
item
in
self
.
keyword_args
:
item
.
annotate
(
code
)
class
AttributeNode
(
ExprNode
):
# obj.attribute
#
...
...
@@ -7134,6 +7288,7 @@ class DictNode(ExprNode):
exclude_null_values
=
False
type
=
dict_type
is_dict_literal
=
True
reject_duplicates
=
False
obj_conversion_errors
=
[]
...
...
@@ -7223,23 +7378,38 @@ class DictNode(ExprNode):
# pairs are evaluated and used one at a time.
code
.
mark_pos
(
self
.
pos
)
self
.
allocate_temp_result
(
code
)
if
self
.
type
.
is_pyobject
:
is_dict
=
self
.
type
.
is_pyobject
if
is_dict
:
self
.
release_errors
()
code
.
putln
(
"%s = PyDict_New(); %s"
%
(
self
.
result
(),
code
.
error_goto_if_null
(
self
.
result
(),
self
.
pos
)))
code
.
put_gotref
(
self
.
py_result
())
for
item
in
self
.
key_value_pairs
:
if
self
.
reject_duplicates
and
len
(
self
.
key_value_pairs
)
>
1
:
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"RaiseDoubleKeywords"
,
"FunctionArguments.c"
))
for
i
,
item
in
enumerate
(
self
.
key_value_pairs
):
item
.
generate_evaluation_code
(
code
)
if
self
.
type
.
is_pyobje
ct
:
if
is_di
ct
:
if
self
.
exclude_null_values
:
code
.
putln
(
'if (%s) {'
%
item
.
value
.
py_result
())
code
.
put_error_if_neg
(
self
.
pos
,
"PyDict_SetItem(%s, %s, %s)"
%
(
if
self
.
reject_duplicates
and
i
>=
1
:
code
.
putln
(
'if (unlikely(PyDict_Contains(%s, %s))) {'
%
(
self
.
result
(),
item
.
key
.
py_result
()))
# currently only used in function calls
code
.
putln
(
'__Pyx_RaiseDoubleKeywordsError("function", %s); %s'
%
(
item
.
key
.
py_result
(),
code
.
error_goto
(
item
.
pos
)))
code
.
putln
(
"} else {"
)
code
.
put_error_if_neg
(
self
.
pos
,
"PyDict_SetItem(%s, %s, %s)"
%
(
self
.
result
(),
item
.
key
.
py_result
(),
item
.
value
.
py_result
()))
if
self
.
reject_duplicates
and
i
>=
1
:
code
.
putln
(
'}'
)
if
self
.
exclude_null_values
:
code
.
putln
(
'}'
)
else
:
...
...
@@ -7254,6 +7424,7 @@ class DictNode(ExprNode):
for
item
in
self
.
key_value_pairs
:
item
.
annotate
(
code
)
class
DictItemNode
(
ExprNode
):
# Represents a single item in a DictNode
#
...
...
@@ -7454,128 +7625,6 @@ class Py3ClassNode(ExprNode):
code
.
put_gotref
(
self
.
py_result
())
class
KeywordArgsNode
(
ExprNode
):
# Helper class for keyword arguments.
#
# starstar_arg DictNode
# keyword_args [DictItemNode]
subexprs
=
[
'starstar_arg'
,
'keyword_args'
]
is_temp
=
1
type
=
dict_type
def
calculate_constant_result
(
self
):
result
=
dict
(
self
.
starstar_arg
.
constant_result
)
for
item
in
self
.
keyword_args
:
key
,
value
=
item
.
constant_result
if
key
in
result
:
raise
ValueError
(
"duplicate keyword argument found: %s"
%
key
)
result
[
key
]
=
value
self
.
constant_result
=
result
def
compile_time_value
(
self
,
denv
):
result
=
self
.
starstar_arg
.
compile_time_value
(
denv
)
pairs
=
[
(
item
.
key
.
compile_time_value
(
denv
),
item
.
value
.
compile_time_value
(
denv
))
for
item
in
self
.
keyword_args
]
try
:
result
=
dict
(
result
)
for
key
,
value
in
pairs
:
if
key
in
result
:
raise
ValueError
(
"duplicate keyword argument found: %s"
%
key
)
result
[
key
]
=
value
except
Exception
,
e
:
self
.
compile_time_value_error
(
e
)
return
result
def
type_dependencies
(
self
,
env
):
return
()
def
infer_type
(
self
,
env
):
return
dict_type
def
analyse_types
(
self
,
env
):
arg
=
self
.
starstar_arg
.
analyse_types
(
env
)
arg
=
arg
.
coerce_to_pyobject
(
env
)
arg
=
arg
.
as_none_safe_node
(
# FIXME: CPython's error message starts with the runtime function name
'argument after ** must be a mapping, not NoneType'
)
self
.
starstar_arg
=
arg
if
not
self
.
keyword_args
and
arg
.
type
is
dict_type
:
# strip this intermediate node and use the bare dict
if
arg
.
is_name
and
arg
.
entry
.
is_arg
and
len
(
arg
.
entry
.
cf_assignments
)
==
1
:
# passing **kwargs through to function call => allow NULL
arg
.
allow_null
=
True
return
arg
self
.
keyword_args
=
[
item
.
analyse_types
(
env
)
for
item
in
self
.
keyword_args
]
return
self
def
may_be_none
(
self
):
return
False
gil_message
=
"Constructing Python dict"
def
generate_evaluation_code
(
self
,
code
):
code
.
mark_pos
(
self
.
pos
)
self
.
allocate_temp_result
(
code
)
self
.
starstar_arg
.
generate_evaluation_code
(
code
)
if
self
.
starstar_arg
.
type
is
not
dict_type
:
# CPython supports calling functions with non-dicts, so do we
code
.
putln
(
'if (likely(PyDict_Check(%s))) {'
%
self
.
starstar_arg
.
py_result
())
if
self
.
keyword_args
:
code
.
putln
(
"%s = PyDict_Copy(%s); %s"
%
(
self
.
result
(),
self
.
starstar_arg
.
py_result
(),
code
.
error_goto_if_null
(
self
.
result
(),
self
.
pos
)))
code
.
put_gotref
(
self
.
py_result
())
else
:
code
.
putln
(
"%s = %s;"
%
(
self
.
result
(),
self
.
starstar_arg
.
py_result
()))
code
.
put_incref
(
self
.
result
(),
py_object_type
)
if
self
.
starstar_arg
.
type
is
not
dict_type
:
code
.
putln
(
'} else {'
)
code
.
putln
(
"%s = PyObject_CallFunctionObjArgs("
"(PyObject*)&PyDict_Type, %s, NULL); %s"
%
(
self
.
result
(),
self
.
starstar_arg
.
py_result
(),
code
.
error_goto_if_null
(
self
.
result
(),
self
.
pos
)))
code
.
put_gotref
(
self
.
py_result
())
code
.
putln
(
'}'
)
self
.
starstar_arg
.
generate_disposal_code
(
code
)
self
.
starstar_arg
.
free_temps
(
code
)
if
not
self
.
keyword_args
:
return
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"RaiseDoubleKeywords"
,
"FunctionArguments.c"
))
for
item
in
self
.
keyword_args
:
item
.
generate_evaluation_code
(
code
)
code
.
putln
(
"if (unlikely(PyDict_GetItem(%s, %s))) {"
%
(
self
.
result
(),
item
.
key
.
py_result
()))
# FIXME: find out function name at runtime!
code
.
putln
(
'__Pyx_RaiseDoubleKeywordsError("function", %s); %s'
%
(
item
.
key
.
py_result
(),
code
.
error_goto
(
self
.
pos
)))
code
.
putln
(
"}"
)
code
.
put_error_if_neg
(
self
.
pos
,
"PyDict_SetItem(%s, %s, %s)"
%
(
self
.
result
(),
item
.
key
.
py_result
(),
item
.
value
.
py_result
()))
item
.
generate_disposal_code
(
code
)
item
.
free_temps
(
code
)
def
annotate
(
self
,
code
):
self
.
starstar_arg
.
annotate
(
code
)
for
item
in
self
.
keyword_args
:
item
.
annotate
(
code
)
class
PyClassMetaclassNode
(
ExprNode
):
# Helper class holds Python3 metaclass object
#
...
...
Cython/Compiler/Parsing.pxd
View file @
3ee57173
...
...
@@ -48,7 +48,7 @@ cdef p_power(PyrexScanner s)
cdef
p_new_expr
(
PyrexScanner
s
)
cdef
p_trailer
(
PyrexScanner
s
,
node1
)
cdef
p_call_parse_args
(
PyrexScanner
s
,
bint
allow_genexp
=
*
)
cdef
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
)
cdef
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
)
cdef
p_call
(
PyrexScanner
s
,
function
)
cdef
p_index
(
PyrexScanner
s
,
base
)
cdef
tuple
p_subscript_list
(
PyrexScanner
s
)
...
...
Cython/Compiler/Parsing.py
View file @
3ee57173
...
...
@@ -12,10 +12,11 @@ cython.declare(Nodes=object, ExprNodes=object, EncodedString=object,
FileSourceDescriptor
=
object
,
lookup_unicodechar
=
object
,
Future
=
object
,
Options
=
object
,
error
=
object
,
warning
=
object
,
Builtin
=
object
,
ModuleNode
=
object
,
Utils
=
object
,
re
=
object
,
_unicode
=
object
,
_bytes
=
object
)
re
=
object
,
_unicode
=
object
,
_bytes
=
object
,
partial
=
object
)
import
re
from
unicodedata
import
lookup
as
lookup_unicodechar
from
functools
import
partial
from
.Scanning
import
PyrexScanner
,
FileSourceDescriptor
from
.
import
Nodes
...
...
@@ -409,24 +410,35 @@ def p_trailer(s, node1):
return
ExprNodes
.
AttributeNode
(
pos
,
obj
=
node1
,
attribute
=
name
)
# arglist: argument (',' argument)* [',']
# argument: [test '='] test # Really [keyword '='] test
def
p_call_parse_args
(
s
,
allow_genexp
=
True
):
# since PEP 448:
# argument: ( test [comp_for] |
# test '=' test |
# '**' expr |
# star_expr )
def
p_call_parse_args
(
s
,
allow_genexp
=
True
):
# s.sy == '('
pos
=
s
.
position
()
s
.
next
()
positional_args
=
[]
keyword_args
=
[]
star
_arg
=
Non
e
starstar_arg
=
Non
e
while
s
.
sy
not
in
(
'**'
,
')'
)
:
star
star_seen
=
Fals
e
last_was_tuple_unpack
=
Fals
e
while
s
.
sy
!=
')'
:
if
s
.
sy
==
'*'
:
if
star_arg
:
s
.
error
(
"only one star-arg parameter allowed"
,
pos
=
s
.
position
())
if
starstar_seen
:
s
.
error
(
"Non-keyword arg following keyword arg"
,
pos
=
s
.
position
())
s
.
next
()
star_arg
=
p_test
(
s
)
positional_args
.
append
(
p_test
(
s
))
last_was_tuple_unpack
=
True
elif
s
.
sy
==
'**'
:
s
.
next
()
keyword_args
.
append
(
p_test
(
s
))
starstar_seen
=
True
else
:
arg
=
p_test
(
s
)
if
s
.
sy
==
'='
:
...
...
@@ -441,73 +453,78 @@ def p_call_parse_args(s, allow_genexp = True):
keyword_args
.
append
((
keyword
,
arg
))
else
:
if
keyword_args
:
s
.
error
(
"Non-keyword arg following keyword arg"
,
pos
=
arg
.
pos
)
if
star_arg
:
s
.
error
(
"Non-keyword arg following star-arg"
,
pos
=
arg
.
pos
)
positional_args
.
append
(
arg
)
s
.
error
(
"Non-keyword arg following keyword arg"
,
pos
=
arg
.
pos
)
if
positional_args
and
not
last_was_tuple_unpack
:
positional_args
[
-
1
].
append
(
arg
)
else
:
positional_args
.
append
([
arg
]
)
last_was_tuple_unpack
=
False
if
s
.
sy
!=
','
:
break
s
.
next
()
if
s
.
sy
==
'for'
:
if
len
(
positional_args
)
==
1
and
not
star_arg
:
positional_args
=
[
p_genexp
(
s
,
positional_args
[
0
])
]
elif
s
.
sy
==
'**'
:
s
.
next
()
starstar_arg
=
p_test
(
s
)
if
s
.
sy
==
','
:
s
.
next
()
# FIXME: this is actually not valid Python syntax
if
not
keyword_args
and
not
last_was_tuple_unpack
:
if
len
(
positional_args
)
==
1
and
len
(
positional_args
[
0
])
==
1
:
positional_args
=
[
p_genexp
(
s
,
positional_args
[
0
][
0
])]
s
.
expect
(
')'
)
return
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
return
positional_args
or
[[]],
keyword_args
def
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
):
arg_tuple
=
None
def
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
):
keyword_dict
=
None
if
positional_args
or
not
star_arg
:
arg_tuple
=
ExprNodes
.
TupleNode
(
pos
,
args
=
positional_args
)
if
star_arg
:
star_arg_tuple
=
ExprNodes
.
AsTupleNode
(
pos
,
arg
=
star_arg
)
if
arg_tuple
:
arg_tuple
=
ExprNodes
.
binop_node
(
pos
,
operator
=
'+'
,
operand1
=
arg_tuple
,
operand2
=
star_arg_tuple
)
else
:
arg_tuple
=
star_arg_tuple
if
keyword_args
or
starstar_arg
:
keyword_args
=
[
ExprNodes
.
DictItemNode
(
pos
=
key
.
pos
,
key
=
key
,
value
=
value
)
for
key
,
value
in
keyword_args
]
if
starstar_arg
:
keyword_dict
=
ExprNodes
.
KeywordArgsNode
(
pos
,
starstar_arg
=
starstar_arg
,
keyword_args
=
keyword_args
)
else
:
keyword_dict
=
ExprNodes
.
DictNode
(
pos
,
key_value_pairs
=
keyword_args
)
subtuples
=
[
ExprNodes
.
TupleNode
(
pos
,
args
=
arg
)
if
isinstance
(
arg
,
list
)
else
ExprNodes
.
AsTupleNode
(
pos
,
arg
=
arg
)
for
arg
in
positional_args
]
# TODO: implement a faster way to join tuples than creating each one and adding them
arg_tuple
=
reduce
(
partial
(
ExprNodes
.
binop_node
,
pos
,
'+'
),
subtuples
)
if
keyword_args
:
kwargs
=
[]
dict_items
=
[]
for
item
in
keyword_args
:
if
isinstance
(
item
,
tuple
):
key
,
value
=
item
dict_items
.
append
(
ExprNodes
.
DictItemNode
(
pos
=
key
.
pos
,
key
=
key
,
value
=
value
))
elif
item
.
is_dict_literal
:
# unpack "**{a:b}" directly
dict_items
.
extend
(
item
.
key_value_pairs
)
else
:
if
dict_items
:
kwargs
.
append
(
ExprNodes
.
DictNode
(
dict_items
[
0
].
pos
,
key_value_pairs
=
dict_items
,
reject_duplicates
=
True
))
dict_items
=
[]
kwargs
.
append
(
item
)
if
dict_items
:
kwargs
.
append
(
ExprNodes
.
DictNode
(
dict_items
[
0
].
pos
,
key_value_pairs
=
dict_items
,
reject_duplicates
=
True
))
if
kwargs
:
if
len
(
kwargs
)
==
1
and
kwargs
[
0
].
is_dict_literal
:
# only simple keyword arguments found -> one dict
keyword_dict
=
kwargs
[
0
]
else
:
# at least one **kwargs
keyword_dict
=
ExprNodes
.
KeywordArgsNode
(
pos
,
keyword_args
=
kwargs
)
return
arg_tuple
,
keyword_dict
def
p_call
(
s
,
function
):
# s.sy == '('
pos
=
s
.
position
()
positional_args
,
keyword_args
=
p_call_parse_args
(
s
)
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
=
\
p_call_parse_args
(
s
)
if
not
(
keyword_args
or
star_arg
or
starstar_arg
):
return
ExprNodes
.
SimpleCallNode
(
pos
,
function
=
function
,
args
=
positional_args
)
if
not
keyword_args
and
len
(
positional_args
)
==
1
and
isinstance
(
positional_args
[
0
],
list
):
return
ExprNodes
.
SimpleCallNode
(
pos
,
function
=
function
,
args
=
positional_args
[
0
])
else
:
arg_tuple
,
keyword_dict
=
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
)
return
ExprNodes
.
GeneralCallNode
(
pos
,
function
=
function
,
positional_args
=
arg_tuple
,
keyword_args
=
keyword_dict
)
arg_tuple
,
keyword_dict
=
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
)
return
ExprNodes
.
GeneralCallNode
(
pos
,
function
=
function
,
positional_args
=
arg_tuple
,
keyword_args
=
keyword_dict
)
#lambdef: 'lambda' [varargslist] ':' test
...
...
@@ -2969,6 +2986,7 @@ def p_py_arg_decl(s, annotated = 1):
annotation
=
p_test
(
s
)
return
Nodes
.
PyArgDeclNode
(
pos
,
name
=
name
,
annotation
=
annotation
)
def
p_class_statement
(
s
,
decorators
):
# s.sy == 'class'
pos
=
s
.
position
()
...
...
@@ -2979,10 +2997,8 @@ def p_class_statement(s, decorators):
keyword_dict
=
None
starstar_arg
=
None
if
s
.
sy
==
'('
:
positional_args
,
keyword_args
,
star_arg
,
starstar_arg
=
\
p_call_parse_args
(
s
,
allow_genexp
=
False
)
arg_tuple
,
keyword_dict
=
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
,
star_arg
,
None
)
positional_args
,
keyword_args
=
p_call_parse_args
(
s
,
allow_genexp
=
False
)
arg_tuple
,
keyword_dict
=
p_call_build_packed_args
(
pos
,
positional_args
,
keyword_args
)
if
arg_tuple
is
None
:
# XXX: empty arg_tuple
arg_tuple
=
ExprNodes
.
TupleNode
(
pos
,
args
=
[])
...
...
@@ -2995,6 +3011,7 @@ def p_class_statement(s, decorators):
doc
=
doc
,
body
=
body
,
decorators
=
decorators
,
force_py3_semantics
=
s
.
context
.
language_level
>=
3
)
def
p_c_class_definition
(
s
,
pos
,
ctx
):
# s.sy == 'class'
s
.
next
()
...
...
Cython/Parser/Grammar
View file @
3ee57173
...
...
@@ -109,17 +109,29 @@ subscript: test | [test] ':' [test] [sliceop]
sliceop: ':' [test]
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
testlist: test (',' test)* [',']
dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) |
(test (comp_for | (',' test)* [','])) )
dictorsetmaker: ( ((test ':' test | '**' expr)
(comp_for | (',' (test ':' test | '**' expr))* [','])) |
((test | star_expr)
(comp_for | (',' (test | star_expr))* [','])) )
classdef: 'class' PY_NAME ['(' [arglist] ')'] ':' suite
arglist: (argument ',')* (argument [',']
|'*' test (',' argument)* [',' '**' test]
|'**' test)
arglist: argument (',' argument)* [',']
# The reason that keywords are test nodes instead of NAME is that using NAME
# results in an ambiguity. ast.c makes sure it's a NAME.
argument: test [comp_for] | test '=' test # Really [keyword '='] test
# "test '=' test" is really "keyword '=' test", but we have no such token.
# These need to be in a single rule to avoid grammar that is ambiguous
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
# we explicitly match '*' here, too, to give it proper precedence.
# Illegal combinations and orderings are blocked in ast.c:
# multiple (test comp_for) arguements are blocked; keyword unpackings
# that precede iterable unpackings are blocked; etc.
argument: ( test [comp_for] |
test '=' test |
'**' expr |
star_expr )
comp_iter: comp_for | comp_if
comp_for: 'for' exprlist ('in' or_test | for_from_clause) [comp_iter]
comp_if: 'if' test_nocond [comp_iter]
...
...
Cython/Utility/FunctionArguments.c
View file @
3ee57173
...
...
@@ -110,6 +110,17 @@ static void __Pyx_RaiseDoubleKeywordsError(
}
//////////////////// RaiseMappingExpected.proto ////////////////////
static
void
__Pyx_RaiseMappingExpectedError
(
PyObject
*
arg
);
/*proto*/
//////////////////// RaiseMappingExpected ////////////////////
static
void
__Pyx_RaiseMappingExpectedError
(
PyObject
*
arg
)
{
PyErr_Format
(
PyExc_TypeError
,
"'%.200s' object is not a mapping"
,
Py_TYPE
(
arg
)
->
tp_name
);
}
//////////////////// KeywordStringCheck.proto ////////////////////
static
CYTHON_INLINE
int
__Pyx_CheckKeywordStrings
(
PyObject
*
kwdict
,
const
char
*
function_name
,
int
kw_allowed
);
/*proto*/
...
...
@@ -290,3 +301,43 @@ invalid_keyword:
bad:
return
-
1
;
}
//////////////////// MergeKeywords.proto ////////////////////
static
int
__Pyx_MergeKeywords
(
PyObject
*
kwdict
,
PyObject
*
source_mapping
);
/*proto*/
//////////////////// MergeKeywords ////////////////////
//@requires: RaiseDoubleKeywords
//@requires: Optimize.c::dict_iter
static
int
__Pyx_MergeKeywords
(
PyObject
*
kwdict
,
PyObject
*
source_mapping
)
{
PyObject
*
iter
,
*
key
,
*
value
;
int
source_is_dict
,
result
;
Py_ssize_t
orig_length
,
ppos
=
0
;
iter
=
__Pyx_dict_iterator
(
source_mapping
,
0
,
PYIDENT
(
"items"
),
&
orig_length
,
&
source_is_dict
);
if
(
unlikely
(
!
iter
))
goto
bad
;
while
(
1
)
{
result
=
__Pyx_dict_iter_next
(
iter
,
orig_length
,
&
ppos
,
&
key
,
&
value
,
NULL
,
source_is_dict
);
if
(
unlikely
(
result
<
0
))
goto
bad
;
if
(
!
result
)
break
;
if
(
unlikely
(
PyDict_Contains
(
kwdict
,
key
)))
{
__Pyx_RaiseDoubleKeywordsError
(
"function"
,
key
);
result
=
-
1
;
}
else
{
result
=
PyDict_SetItem
(
kwdict
,
key
,
value
);
}
Py_DECREF
(
key
);
Py_DECREF
(
value
);
if
(
unlikely
(
result
<
0
))
goto
bad
;
}
Py_XDECREF
(
iter
);
return
0
;
bad:
Py_XDECREF
(
iter
);
return
-
1
;
}
tests/run/pep448_test_extcall.pyx
0 → 100644
View file @
3ee57173
# mode: run
# tag: pep448
from
__future__
import
print_function
import
sys
IS_PY3
=
sys
.
version_info
[
0
]
>=
3
if
IS_PY3
:
__doc__
=
"""
>>> def f(*, w): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, a, b, c, d, e): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, kw, b): pass
>>> errors_call_3args_2kwargs(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b=2, *, kw): pass
>>> try: errors_call_3args_1kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, kw): pass
>>> try: errors_call_1arg_1kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
# test for method/function calls. adapted from CPython's "test_extcall.py".
def
sortdict
(
d
):
return
'{%s}'
%
', '
.
join
([
'%r: %r'
%
item
for
item
in
sorted
(
d
.
items
())])
# We're going the use these types for extra testing
try
:
from
collections
import
UserList
,
UserDict
except
ImportError
:
from
UserList
import
UserList
from
UserDict
import
UserDict
# We're defining four helper functions
def
e
(
a
,
b
):
print
(
a
,
b
)
def
f
(
*
a
,
**
k
):
print
(
a
,
sortdict
(
k
))
def
g
(
x
,
*
y
,
**
z
):
print
(
x
,
y
,
sortdict
(
z
))
def
h
(
j
=
1
,
a
=
2
,
h
=
3
):
print
(
j
,
a
,
h
)
# Argument list examples
def
call_f_positional
():
"""
>>> call_f_positional()
() {}
(1,) {}
(1, 2) {}
(1, 2, 3) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5, 6, 7) {}
(1, 2, 3, 4, 5, 6, 7) {}
(1, 2, 3, 4, 5, 6, 7) {}
"""
f
()
f
(
1
)
f
(
1
,
2
)
f
(
1
,
2
,
3
)
f
(
1
,
2
,
3
,
*
(
4
,
5
))
f
(
1
,
2
,
3
,
*
[
4
,
5
])
f
(
*
[
1
,
2
,
3
],
4
,
5
)
f
(
1
,
2
,
3
,
*
UserList
([
4
,
5
]))
f
(
1
,
2
,
3
,
*
[
4
,
5
],
*
[
6
,
7
])
f
(
1
,
*
[
2
,
3
],
4
,
*
[
5
,
6
],
7
)
f
(
*
UserList
([
1
,
2
]),
*
UserList
([
3
,
4
]),
5
,
*
UserList
([
6
,
7
]))
# Here we add keyword arguments
def
call_f_kwargs
():
"""
>>> call_f_kwargs()
(1, 2, 3) {'a': 4, 'b': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7}
(1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7, 'c': 8}
(1, 2, 3, 4, 5) {'a': 8, 'b': 9, 'x': 6, 'y': 7}
(1, 2, 3) {'a': 4, 'b': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7}
(1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5}
(1, 2, 3, 4, 5) {'a': 8, 'b': 9, 'x': 6, 'y': 7}
"""
f
(
1
,
2
,
3
,
**
{
'a'
:
4
,
'b'
:
5
})
f
(
1
,
2
,
3
,
*
[
4
,
5
],
**
{
'a'
:
6
,
'b'
:
7
})
f
(
1
,
2
,
3
,
x
=
4
,
y
=
5
,
*
(
6
,
7
),
**
{
'a'
:
8
,
'b'
:
9
})
f
(
1
,
2
,
3
,
*
[
4
,
5
],
**
{
'c'
:
8
},
**
{
'a'
:
6
,
'b'
:
7
})
f
(
1
,
2
,
3
,
*
(
4
,
5
),
x
=
6
,
y
=
7
,
**
{
'a'
:
8
,
'b'
:
9
})
f
(
1
,
2
,
3
,
**
UserDict
(
a
=
4
,
b
=
5
))
f
(
1
,
2
,
3
,
*
(
4
,
5
),
**
UserDict
(
a
=
6
,
b
=
7
))
f
(
1
,
2
,
3
,
x
=
4
,
y
=
5
,
*
(
6
,
7
),
**
UserDict
(
a
=
8
,
b
=
9
))
f
(
1
,
2
,
3
,
*
(
4
,
5
),
x
=
6
,
y
=
7
,
**
UserDict
(
a
=
8
,
b
=
9
))
# Examples with invalid arguments (TypeErrors). We're also testing the function
# names in the exception messages.
#
# Verify clearing of SF bug #733667
def
errors_f1
():
"""
>>> errors_f1() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for keyword argument 'a'
"""
f
(
1
,
2
,
**
{
'a'
:
-
1
,
'b'
:
5
},
**
{
'a'
:
4
,
'c'
:
6
})
def
errors_f2
():
"""
>>> errors_f2() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...multiple values for keyword argument 'a'
"""
f
(
1
,
2
,
**
{
'a'
:
-
1
,
'b'
:
5
},
a
=
4
,
c
=
6
)
def
errors_e1
():
"""
>>> try: errors_e1()
... except TypeError: pass
... else: print("FAILED!")
"""
e
(
c
=
4
)
def
errors_e2
():
"""
>>> try: errors_e2()
... except TypeError: pass
... else: print("FAILED!")
"""
e
(
a
=
1
,
b
=
2
,
c
=
4
)
def
errors_g1
():
"""
>>> errors_g1()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g
()
def
errors_g2
():
"""
>>> errors_g2()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g
(
*
())
def
errors_g3
():
"""
>>> errors_g3()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g
(
*
(),
**
{})
def
call_g_positional
():
"""
>>> call_g_positional()
1 () {}
1 (2,) {}
1 (2, 3) {}
1 (2, 3, 4, 5) {}
"""
g
(
1
)
g
(
1
,
2
)
g
(
1
,
2
,
3
)
g
(
1
,
2
,
3
,
*
(
4
,
5
))
def
call_nonseq_positional1
():
"""
>>> call_nonseq_positional1()
Traceback (most recent call last):
...
TypeError: 'Nothing' object is not iterable
# TypeError: g() argument after * must be a sequence, not Nothing
"""
class
Nothing
(
object
):
pass
g
(
*
Nothing
())
def
call_nonseq_positional2
():
"""
>>> call_nonseq_positional2()
Traceback (most recent call last):
...
TypeError: 'Nothing' object is not iterable
# TypeError: g() argument after * must be a sequence, not Nothing
"""
class
Nothing
(
object
):
def
__len__
(
self
):
return
5
g
(
*
Nothing
())
def
call_seqlike_positional1
():
"""
>>> call_seqlike_positional1()
0 (1, 2) {}
"""
class
Nothing
(
object
):
def
__len__
(
self
):
return
5
def
__getitem__
(
self
,
i
):
if
i
<
3
:
return
i
else
:
raise
IndexError
(
i
)
g
(
*
Nothing
())
def
call_seqlike_positional2
():
"""
>>> call_seqlike_positional2()
0 (1, 2, 3) {}
"""
class
Nothing
:
def
__init__
(
self
):
self
.
c
=
0
def
__iter__
(
self
):
return
self
def
__next__
(
self
):
if
self
.
c
==
4
:
raise
StopIteration
c
=
self
.
c
self
.
c
+=
1
return
c
next
=
__next__
g
(
*
Nothing
())
# Make sure that the function doesn't stomp the dictionary
def
call_kwargs_unmodified1
():
"""
>>> call_kwargs_unmodified1()
1 () {'a': 1, 'b': 2, 'c': 3, 'd': 4}
True
"""
d
=
{
'a'
:
1
,
'b'
:
2
,
'c'
:
3
}
d2
=
d
.
copy
()
g
(
1
,
d
=
4
,
**
d
)
return
d
==
d2
# What about willful misconduct?
def
call_kwargs_unmodified2
():
"""
>>> call_kwargs_unmodified2()
{}
"""
def
saboteur
(
**
kw
):
kw
[
'x'
]
=
'm'
return
kw
d
=
{}
kw
=
saboteur
(
a
=
1
,
**
d
)
return
d
def
errors_args_kwargs_overlap
():
"""
>>> errors_args_kwargs_overlap() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for... argument 'x'
"""
g
(
1
,
2
,
3
,
**
{
'x'
:
4
,
'y'
:
5
})
def
errors_non_string_kwarg
():
"""
>>> errors_non_string_kwarg()
Traceback (most recent call last):
...
TypeError: f() keywords must be strings
"""
f
(
**
{
1
:
2
})
def
errors_unexpected_kwarg
():
"""
>>> errors_unexpected_kwarg()
Traceback (most recent call last):
...
TypeError: h() got an unexpected keyword argument 'e'
"""
h
(
**
{
'e'
:
2
})
def
errors_call_nonseq
():
"""
>>> try: errors_call_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
h
(
*
h
)
def
errors_call_builtin_nonseq
():
"""
>>> try: errors_call_builtin_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
dir
(
*
h
)
def
errors_call_none_nonseq
():
"""
>>> try: errors_call_none_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
None
(
*
h
)
def
errors_call_nonmapping_kwargs
():
"""
>>> try: errors_call_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
h
(
**
h
)
def
errors_call_builtin_nonmapping_kwargs
():
"""
>>> try: errors_call_builtin_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
dir
(
**
h
)
def
errors_call_none_nonmapping_kwargs
():
"""
>>> try: errors_call_none_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
None
(
**
h
)
''' # compile time error in Cython
def errors_call_builtin_duplicate_kwarg():
"""
>>> errors_call_builtin_duplicate_kwarg() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for keyword argument 'b'
"""
dir(b=1, **{'b': 1})
'''
# Another helper function
def
f2
(
*
a
,
**
b
):
return
a
,
b
def
call_many_kwargs
():
"""
call_many_kwargs()
(3, 512, True)
"""
d
=
{}
for
i
in
range
(
512
):
key
=
'k%d'
%
i
d
[
key
]
=
i
a
,
b
=
f2
(
1
,
*
(
2
,
3
),
**
d
)
return
len
(
a
),
len
(
b
),
b
==
d
def
call_method
(
Foo
):
"""
>>> class Foo(object):
... def method(self, arg1, arg2):
... print(arg1+arg2)
>>> call_method(Foo)
3
3
5
5
"""
x
=
Foo
()
Foo
.
method
(
*
(
x
,
1
,
2
))
Foo
.
method
(
x
,
*
(
1
,
2
))
if
sys
.
version_info
[
0
]
>=
3
:
Foo
.
method
(
*
(
1
,
2
,
3
))
Foo
.
method
(
1
,
*
[
2
,
3
])
else
:
print
(
5
)
print
(
5
)
# A PyCFunction that takes only positional parameters should allow an
# empty keyword dictionary to pass without a complaint, but raise a
# TypeError if te dictionary is not empty
def
call_builtin_empty_dict
():
"""
>>> call_builtin_empty_dict()
"""
silence
=
id
(
1
,
*
{})
silence
=
id
(
1
,
**
{})
def
call_builtin_nonempty_dict
():
"""
>>> call_builtin_nonempty_dict()
Traceback (most recent call last):
...
TypeError: id() takes no keyword arguments
"""
return
id
(
1
,
**
{
'foo'
:
1
})
''' Cython: currently just passes empty kwargs into f() while CPython keeps the content
# A corner case of keyword dictionary items being deleted during
# the function call setup. See <http://bugs.python.org/issue2016>.
def call_kwargs_modified_while_building():
"""
>>> call_kwargs_modified_while_building()
1 2
"""
class Name(str):
def __eq__(self, other):
try:
del x[self]
except KeyError:
pass
return str.__eq__(self, other)
def __hash__(self):
return str.__hash__(self)
x = {Name("a"):1, Name("b"):2}
def f(a, b):
print(a,b)
f(**x)
'''
# Too many arguments:
def
errors_call_one_arg
(
f
):
"""
>>> def f(): pass
>>> try: errors_call_one_arg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f
(
1
)
def
errors_call_2args
(
f
):
"""
>>> def f(a): pass
>>> try: errors_call_2args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f
(
1
,
2
)
def
errors_call_3args
(
f
):
"""
>>> def f(a, b=1): pass
>>> try: errors_call_3args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f
(
1
,
2
,
3
)
def
errors_call_1arg_1kwarg
(
f
):
# Py3 only
f
(
1
,
kw
=
3
)
def
errors_call_3args_2kwargs
(
f
):
# Py3 only
f
(
1
,
2
,
3
,
b
=
3
,
kw
=
3
)
def
errors_call_3args_1kwarg
(
f
):
# Py3 only
f
(
2
,
3
,
4
,
kw
=
4
)
# Too few and missing arguments:
def
errors_call_no_args
(
f
):
"""
>>> def f(a): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b, c): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b, c, d, e): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f
()
def
errors_call_one_missing_kwarg
(
f
):
"""
>>> def f(a, b=4, c=5, d=5): pass
>>> try: errors_call_one_missing_kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f
(
c
=
12
,
b
=
9
)
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