Hi,

Stefan Behnel wrote:
> That's actually a tricky problem. Py2 does not accept unicode as keyword
> arguments and Py3 requires them, so we have to distinguish between Py2 and Py3
> here. However, keywords are not really identifiers. You could pass any byte
> string or unicode string in Py2 using the **dict syntax.
> 
> To make things worse, Cython stores keyword arguments as a StringNode in a
> generic DictItemNode of a DictNode. So I find it difficult to figure out the
> right place to store the information that the string must behave like an
> identifier.

Here is a patch. It adds a new KeywordNameNode class that makes keyword names
behave just like identifiers and also interns them.

The way this is implemented would allow non-ASCII keyword names in Py3,
although we can't currently enable that in the parser, as the resulting C code
would not work in Py2. You'd end up with UTF-8 encoded byte string names, and
you can't pass these directly from Py2, neither can ParseTupleAndKeywords
handle them.

Stefan
# HG changeset patch
# User Stefan Behnel <[EMAIL PROTECTED]>
# Date 1211093956 -7200
# Node ID 8b35bdb2d0065727567e616a68ba341a5951918a
# Parent  d31d00fe34eeaaf1b1175f9cdbcf8e71d1384262
fix keyword arguments in function calls: must be unicode in Py3 and strings in Py2, just like identifiers

diff -r d31d00fe34ee -r 8b35bdb2d006 Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py	Sun May 18 08:54:06 2008 +0200
+++ b/Cython/Compiler/ExprNodes.py	Sun May 18 08:59:16 2008 +0200
@@ -734,6 +734,18 @@ class StringNode(ConstNode):
             return self.entry.pystring_cname
         else:
             return self.entry.cname
+
+
+class KeywordNameNode(ConstNode):
+    # A keyword in a Python function call: a string that behaves like
+    # an identifier
+    type = PyrexTypes.py_object_type
+
+    def analyse_types(self, env):
+        self.cname = env.intern_identifier(self.value)
+
+    def calculate_result_code(self):
+        return self.cname
 
 
 class LongNode(AtomicExprNode):
diff -r d31d00fe34ee -r 8b35bdb2d006 Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py	Sun May 18 08:54:06 2008 +0200
+++ b/Cython/Compiler/Nodes.py	Sun May 18 08:59:16 2008 +0200
@@ -4347,7 +4347,7 @@ static int __Pyx_InitStrings(__Pyx_Strin
 static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
     while (t->p) {
         #if PY_MAJOR_VERSION < 3
-        if (t->is_unicode) {
+        if (t->is_unicode && (!t->is_identifier)) {
             *t->p = PyUnicode_DecodeUTF8(t->s, t->n - 1, NULL);
         } else if (t->intern) {
             *t->p = PyString_InternFromString(t->s);
diff -r d31d00fe34ee -r 8b35bdb2d006 Cython/Compiler/Parsing.py
--- a/Cython/Compiler/Parsing.py	Sun May 18 08:54:06 2008 +0200
+++ b/Cython/Compiler/Parsing.py	Sun May 18 08:59:16 2008 +0200
@@ -284,8 +284,7 @@ def p_call(s, function):
                 s.error("Expected an identifier before '='",
                     pos = arg.pos)
             encoded_name = Utils.EncodedString(arg.name)
-            encoded_name.encoding = s.source_encoding
-            keyword = ExprNodes.StringNode(arg.pos, 
+            keyword = ExprNodes.KeywordNameNode(arg.pos, 
                 value = encoded_name)
             arg = p_simple_expr(s)
             keyword_args.append((keyword, arg))
diff -r d31d00fe34ee -r 8b35bdb2d006 tests/run/kwonlyargscall.pyx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run/kwonlyargscall.pyx	Sun May 18 08:59:16 2008 +0200
@@ -0,0 +1,165 @@
+__doc__ = u"""
+    >>> call3(b)
+    >>> call4(b)
+    Traceback (most recent call last):
+    TypeError: function takes exactly 3 arguments (4 given)
+
+    >>> call2(c)
+    >>> call3(c)
+    >>> call4(c)
+    Traceback (most recent call last):
+    TypeError: function takes at most 3 arguments (4 given)
+
+    >>> call2(d)
+    >>> call2c(d)
+
+    >>> call3(d)
+    Traceback (most recent call last):
+    TypeError: function takes at most 2 positional arguments (3 given)
+    >>> call2d(d)
+    Traceback (most recent call last):
+    TypeError: 'd' is an invalid keyword argument for this function
+
+    >>> call2(e)
+    >>> call2c(e)
+    >>> call2d(e)
+    >>> call2cde(e)
+    >>> call3(e)
+    >>> call4(e)
+    Traceback (most recent call last):
+    TypeError: function takes at most 3 positional arguments (4 given)
+
+    >>> call2c(f)
+    >>> call2cd(f)
+
+    >>> call3(f)
+    Traceback (most recent call last):
+    TypeError: function takes at most 2 positional arguments (3 given)
+    >>> call2(f)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'c' is missing
+    >>> call2ce(f)
+    Traceback (most recent call last):
+    TypeError: 'e' is an invalid keyword argument for this function
+
+    >>> call2cf(g)
+    >>> call2cefd(g)
+    >>> call2cfex(g)
+
+    >>> call3(g)
+    Traceback (most recent call last):
+    TypeError: function takes at most 2 positional arguments (3 given)
+    >>> call2(g)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'c' is missing
+    >>> call2c(g)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'f' is missing
+
+    >>> call2cf(h)
+    >>> call2cfe(h)
+    >>> call6cf(h)
+    >>> call6cfexy(h)
+
+    >>> call3(h)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'c' is missing
+    >>> call3d(h)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'c' is missing
+
+    >>> call2cf(k)
+    >>> call2cfe(k)
+    >>> call6df(k)
+    >>> call6dfexy(k)
+
+    >>> call3(k)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'f' is missing
+    >>> call2d(k)
+    Traceback (most recent call last):
+    TypeError: required keyword argument 'f' is missing
+"""
+
+import sys, re
+if sys.version_info[0] >= 3:
+    __doc__ = re.sub(u"Error: (.*)exactly(.*)", u"Error: \\1at most\\2", __doc__)
+
+# the calls:
+
+def call2(f):
+    f(1,2)
+
+def call3(f):
+    f(1,2,3)
+
+def call4(f):
+    f(1,2,3,4)
+
+def call2c(f):
+    f(1,2, c=1)
+
+def call2d(f):
+    f(1,2, d=1)
+
+def call3d(f):
+    f(1,2,3, d=1)
+
+def call2cd(f):
+    f(1,2, c=1, d=2)
+
+def call2ce(f):
+    f(1,2, c=1, e=2)
+
+def call2cde(f):
+    f(1,2, c=1, d=2, e=3)
+
+def call2cf(f):
+    f(1,2, c=1, f=2)
+
+def call6cf(f):
+    f(1,2,3,4,5,6, c=1, f=2)
+
+def call6df(f):
+    f(1,2,3,4,5,6, d=1, f=2)
+
+def call2cfe(f):
+    f(1,2, c=1, f=2, e=3)
+
+def call2cefd(f):
+    f(1,2, c=1, e=0, f=2, d=11)
+
+def call2cfex(f):
+    f(1,2, c=1, f=2, e=0, x=25)
+
+def call6cfexy(f):
+    f(1,2,3,4,5,6, c=1, f=2, e=3, x=25, y=11)
+
+def call6dfexy(f):
+    f(1,2,3,4,5,6, d=1, f=2, e=3, x=25, y=11)
+
+# the called functions:
+
+def b(a, b, c):
+    pass
+
+def c(a, b, c=1):
+    pass
+
+def d(a, b, *, c = 88):
+    pass
+
+def e(a, b, c = 88, **kwds):
+    pass
+
+def f(a, b, *, c, d = 42):
+    pass
+
+def g(a, b, *, c, d = 42, e = 17, f, **kwds):
+    pass
+
+def h(a, b, *args, c, d = 42, e = 17, f, **kwds):
+    pass
+
+def k(a, b, c=1, *args, d = 42, e = 17, f, **kwds):
+    pass
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to