summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Behnel <stefan_ml@behnel.de>2014-02-14 16:13:17 +0100
committerStefan Behnel <stefan_ml@behnel.de>2014-02-14 16:13:17 +0100
commit3b5438eb91a8829fef662717a04f704ebfcbb68e (patch)
tree89be6e7defef27bab08b5b7d88b09a6c9413cd83
parentd195f25a159e0b405269e8a86a515e082d96331a (diff)
downloadcython-3b5438eb91a8829fef662717a04f704ebfcbb68e.tar.gz
restore singleton property of empty frozenset
-rw-r--r--Cython/Compiler/Optimize.py8
-rw-r--r--Cython/Utility/Builtins.c25
-rw-r--r--tests/run/set.pyx33
3 files changed, 63 insertions, 3 deletions
diff --git a/Cython/Compiler/Optimize.py b/Cython/Compiler/Optimize.py
index e11778963..089bd91a6 100644
--- a/Cython/Compiler/Optimize.py
+++ b/Cython/Compiler/Optimize.py
@@ -2027,15 +2027,17 @@ class OptimizeBuiltinCalls(Visitor.MethodDispatcherTransform):
])
def _handle_simple_function_frozenset(self, node, function, pos_args):
- if len(pos_args) != 1:
+ if not pos_args:
+ pos_args = [ExprNodes.NullNode(node.pos)]
+ elif len(pos_args) > 1:
return node
# PyFrozenSet_New(it) is better than a generic Python call to frozenset(it)
return ExprNodes.PythonCapiCallNode(
- node.pos, "PyFrozenSet_New",
+ node.pos, "__Pyx_PyFrozenSet_New",
self.PyFrozenSet_New_func_type,
args=pos_args,
is_temp=node.is_temp,
- utility_code=UtilityCode.load_cached('pyset_compat', 'Builtins.c'),
+ utility_code=UtilityCode.load_cached('pyfrozenset_new', 'Builtins.c'),
py_name="frozenset")
PyObject_AsDouble_func_type = PyrexTypes.CFuncType(
diff --git a/Cython/Utility/Builtins.c b/Cython/Utility/Builtins.c
index cb46cf1e8..244ca42e9 100644
--- a/Cython/Utility/Builtins.c
+++ b/Cython/Utility/Builtins.c
@@ -388,6 +388,9 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d) {
#define PySet_Size(anyset) \
PyObject_Size((anyset))
+#define PySet_GET_SIZE(anyset) \
+ PyObject_Size((anyset))
+
#define PySet_Contains(anyset, key) \
PySequence_Contains((anyset), (key))
@@ -414,3 +417,25 @@ static CYTHON_INLINE int PySet_Add(PyObject *set, PyObject *key) {
#endif /* PyAnySet_CheckExact (<= Py2.4) */
#endif /* < Py2.5 */
+
+//////////////////// pyfrozenset_new.proto ////////////////////
+//@substitute: naming
+//@requires: pyset_compat
+
+static CYTHON_INLINE PyObject* __Pyx_PyFrozenSet_New(PyObject* it) {
+ if (it) {
+ PyObject* result = PyFrozenSet_New(it);
+ if (unlikely(!result))
+ return NULL;
+ if (likely(PySet_GET_SIZE(result)))
+ return result;
+ // empty frozenset is a singleton
+ // seems wasteful, but CPython does the same
+ Py_DECREF(result);
+ }
+ #if CYTHON_COMPILING_IN_CPYTHON
+ return PyFrozenSet_Type.tp_new(&PyFrozenSet_Type, $empty_tuple, NULL);
+ #else
+ return PyObject_Call((PyObject*)&PyFrozenSet_Type, $empty_tuple, NULL);
+ #endif
+}
diff --git a/tests/run/set.pyx b/tests/run/set.pyx
index 25e39d315..c7d9376ef 100644
--- a/tests/run/set.pyx
+++ b/tests/run/set.pyx
@@ -5,6 +5,8 @@ _frozenset = frozenset
cimport cython
+import sys
+
def cython_set():
"""
@@ -284,6 +286,37 @@ def test_frozenset_of_iterable(x):
return frozenset(x)
+@cython.test_assert_path_exists("//PythonCapiCallNode")
+@cython.test_fail_if_path_exists(
+ "//SimpleCallNode",
+ "//SetNode"
+)
+def test_empty_frozenset():
+ """
+ >>> s = test_empty_frozenset()
+ >>> isinstance(s, _frozenset)
+ True
+ >>> len(s)
+ 0
+ >>> sys.version_info < (2,5) or s is frozenset() # singleton!
+ True
+ """
+ return frozenset()
+
+
+def test_singleton_empty_frozenset():
+ """
+ >>> test_singleton_empty_frozenset() # from CPython's test_set.py
+ 1
+ """
+ f = frozenset()
+ efs = [frozenset(), frozenset([]), frozenset(()), frozenset(''),
+ frozenset(), frozenset([]), frozenset(()), frozenset(''),
+ frozenset(range(0)), frozenset(frozenset()),
+ frozenset(f), f]
+ return len(set(map(id, efs))) if sys.version_info >= (2,5) else 1
+
+
def sorted(it):
# Py3 can't compare different types
chars = []