@@ -155,6 +155,67 @@ test_frozenset_add_in_capi(PyObject *self, PyObject *Py_UNUSED(obj))
155155 return NULL ;
156156}
157157
158+ static PyObject *
159+ test_set_contains_does_not_convert_unhashable_key (PyObject * self , PyObject * Py_UNUSED (obj ))
160+ {
161+ // The documentation of PySet_Contains state:
162+ //
163+ // int PySet_Contains(PyObject *anyset, PyObject *key)
164+ //
165+ // Part of the Stable ABI.
166+ //
167+ // ... Unlike the Python __contains__() method, this function does not
168+ // automatically convert unhashable sets [key] into temporary frozensets.
169+ // Raise a TypeError if the key is unhashable.
170+ //
171+ // That is to say {2,3} in {1, 2, frozenset({2,3})}
172+ // ^_ will be converted in a frozenset in Python code.
173+ // But not if using PySet_Contains(..., key)
174+ //
175+ // We test that this behavior is unchanged as this is a stable API.
176+
177+ PyObject * outer_set = PySet_New (NULL );
178+
179+ PyObject * needle = PySet_New (NULL );
180+ if (needle == NULL ) {
181+ Py_DECREF (outer_set );
182+ return NULL ;
183+ }
184+
185+ PyObject * num = PyLong_FromLong (42 );
186+ if (num == NULL ) {
187+ Py_DECREF (outer_set );
188+ Py_DECREF (needle );
189+ return NULL ;
190+ }
191+
192+ // Add an element to needle to make it {42}
193+ if (PySet_Add (needle , num ) < 0 ) {
194+ Py_DECREF (outer_set );
195+ Py_DECREF (needle );
196+ Py_DECREF (num );
197+ return NULL ;
198+ }
199+
200+ int result = PySet_Contains (outer_set , needle );
201+
202+ Py_DECREF (num );
203+ Py_DECREF (needle );
204+ Py_DECREF (outer_set );
205+
206+ if (result < 0 ) {
207+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
208+ PyErr_Clear ();
209+ Py_RETURN_NONE ;
210+ }
211+ return NULL ;
212+ }
213+
214+ PyErr_SetString (PyExc_AssertionError ,
215+ "PySet_Contains should have raised TypeError for unhashable key" );
216+ return NULL ;
217+ }
218+
158219static PyMethodDef test_methods [] = {
159220 {"set_check" , set_check , METH_O },
160221 {"set_checkexact" , set_checkexact , METH_O },
@@ -174,6 +235,8 @@ static PyMethodDef test_methods[] = {
174235 {"set_clear" , set_clear , METH_O },
175236
176237 {"test_frozenset_add_in_capi" , test_frozenset_add_in_capi , METH_NOARGS },
238+ {"test_set_contains_does_not_convert_unhashable_key" ,
239+ test_set_contains_does_not_convert_unhashable_key , METH_NOARGS },
177240
178241 {NULL },
179242};
0 commit comments