|
15 | 15 | import re |
16 | 16 | import sys |
17 | 17 | import unittest |
| 18 | +from unittest import mock |
18 | 19 | import warnings |
19 | 20 |
|
20 | 21 | test_tools.skip_if_missing('clinic') |
21 | 22 | with test_tools.imports_under_tool('clinic'): |
22 | 23 | import libclinic |
23 | 24 | from libclinic import ClinicError, unspecified, NULL, fail |
24 | 25 | from libclinic.converters import ( |
25 | | - int_converter, object_converter, str_converter, self_converter) |
| 26 | + int_converter, object_converter, str_converter, self_converter, |
| 27 | + Py_ssize_t_converter) |
26 | 28 | from libclinic.function import ( |
27 | 29 | Module, Class, Function, FunctionKind, Parameter, |
28 | 30 | permute_optional_groups, permute_right_option_groups, |
|
34 | 36 | from libclinic.block_parser import Block, BlockParser |
35 | 37 | from libclinic.codegen import BlockPrinter, Destination |
36 | 38 | from libclinic.dsl_parser import DSLParser |
| 39 | + from libclinic.slots import SlotSpec, SLOTS |
37 | 40 | from libclinic.cli import parse_file, Clinic |
38 | 41 |
|
39 | 42 |
|
@@ -807,6 +810,112 @@ def test_var_keyword_non_dict(self): |
807 | 810 | """ |
808 | 811 | self.expect_failure(block, err, lineno=4) |
809 | 812 |
|
| 813 | + def test_slot_next(self): |
| 814 | + block = """ |
| 815 | + /*[clinic input] |
| 816 | + output everything block |
| 817 | + class Foo "FooObject *" "&Foo_Type" |
| 818 | + [clinic start generated code]*/ |
| 819 | + /*[clinic input] |
| 820 | + Foo.__next__ |
| 821 | + [clinic start generated code]*/ |
| 822 | + /*[clinic input] |
| 823 | + @critical_section |
| 824 | + Foo.__next__ as foo_iternext |
| 825 | + [clinic start generated code]*/ |
| 826 | + #ifdef CONDITION |
| 827 | + /*[clinic input] |
| 828 | + Foo.__next__ as foo_iternext_cond |
| 829 | + [clinic start generated code]*/ |
| 830 | + #endif |
| 831 | + """ |
| 832 | + generated = self.clinic.parse(dedent(block)) |
| 833 | + self.assertIn(dedent(""" |
| 834 | + static PyObject * |
| 835 | + Foo_next(PyObject *self) |
| 836 | + { |
| 837 | + return Foo_next_impl((FooObject *)self); |
| 838 | + } |
| 839 | + """), generated) |
| 840 | + self.assertIn(dedent(""" |
| 841 | + static PyObject * |
| 842 | + foo_iternext(PyObject *self) |
| 843 | + { |
| 844 | + PyObject *return_value = NULL; |
| 845 | +
|
| 846 | + Py_BEGIN_CRITICAL_SECTION(self); |
| 847 | + return_value = foo_iternext_impl((FooObject *)self); |
| 848 | + Py_END_CRITICAL_SECTION(); |
| 849 | +
|
| 850 | + return return_value; |
| 851 | + } |
| 852 | + """), generated) |
| 853 | + self.assertIn("#if defined(CONDITION)\n\nstatic PyObject *\n" |
| 854 | + "foo_iternext_cond_impl(FooObject *self);", generated) |
| 855 | + # A slot function is not stored in a PyMethodDef. |
| 856 | + for text in "METHODDEF", "PyDoc_", "#ifndef": |
| 857 | + self.assertNotIn(text, generated) |
| 858 | + |
| 859 | + def test_slot_with_parameters_and_return_type(self): |
| 860 | + # Slots which take arguments or return something else than an |
| 861 | + # object are not supported yet; check the machinery with fake ones. |
| 862 | + slots = { |
| 863 | + '__contains__': SlotSpec('__contains__', 'int', 'int', |
| 864 | + {(object_converter,): 'sq_contains'}), |
| 865 | + '__bool__': SlotSpec('__bool__', 'int', 'int', {(): 'nb_bool'}), |
| 866 | + } |
| 867 | + block = """ |
| 868 | + /*[clinic input] |
| 869 | + output everything block |
| 870 | + class Foo "FooObject *" "&Foo_Type" |
| 871 | + [clinic start generated code]*/ |
| 872 | + /*[clinic input] |
| 873 | + Foo.__contains__ |
| 874 | + value: object |
| 875 | + / |
| 876 | + [clinic start generated code]*/ |
| 877 | + /*[clinic input] |
| 878 | + @critical_section |
| 879 | + Foo.__bool__ |
| 880 | + [clinic start generated code]*/ |
| 881 | + """ |
| 882 | + with mock.patch.dict(SLOTS, slots): |
| 883 | + generated = self.clinic.parse(dedent(block)) |
| 884 | + self.assertIn(dedent(""" |
| 885 | + static int |
| 886 | + Foo_contains(PyObject *self, PyObject *value) |
| 887 | + { |
| 888 | + return Foo_contains_impl((FooObject *)self, value); |
| 889 | + } |
| 890 | + """), generated) |
| 891 | + self.assertIn(dedent(""" |
| 892 | + static int |
| 893 | + Foo_bool(PyObject *self) |
| 894 | + { |
| 895 | + int return_value = -1; |
| 896 | +
|
| 897 | + Py_BEGIN_CRITICAL_SECTION(self); |
| 898 | + return_value = Foo_bool_impl((FooObject *)self); |
| 899 | + Py_END_CRITICAL_SECTION(); |
| 900 | +
|
| 901 | + return return_value; |
| 902 | + } |
| 903 | + """), generated) |
| 904 | + |
| 905 | + def test_slot_cannot_be_cloned(self): |
| 906 | + block = """ |
| 907 | + /*[clinic input] |
| 908 | + class Foo "FooObject *" "&Foo_Type" |
| 909 | + [clinic start generated code]*/ |
| 910 | + /*[clinic input] |
| 911 | + Foo.__next__ |
| 912 | + [clinic start generated code]*/ |
| 913 | + /*[clinic input] |
| 914 | + Foo.other = Foo.__next__ |
| 915 | + [clinic start generated code]*/ |
| 916 | + """ |
| 917 | + self.expect_failure(block, "Type slot methods cannot be cloned", lineno=8) |
| 918 | + |
810 | 919 | def test_getset_in_ifdef(self): |
811 | 920 | block = """ |
812 | 921 | /*[clinic input] |
@@ -3237,6 +3346,78 @@ class Foo "" "" |
3237 | 3346 | f"a normal callable") |
3238 | 3347 | self.expect_failure(block, expected_error, lineno=3) |
3239 | 3348 |
|
| 3349 | + def test_slot_next(self): |
| 3350 | + function = self.parse_function(""" |
| 3351 | + module foo |
| 3352 | + class Foo "FooObject *" "" |
| 3353 | + Foo.__next__ |
| 3354 | + """, signatures_in_block=3, function_index=2) |
| 3355 | + self.assertIs(function.kind, FunctionKind.SLOT) |
| 3356 | + self.assertEqual(function.slot.name, "__next__") |
| 3357 | + self.assertEqual(function.c_basename, "Foo_next") |
| 3358 | + self.assertEqual(function.return_converter.type, "PyObject *") |
| 3359 | + self.assertEqual(list(function.parameters), ["self"]) |
| 3360 | + |
| 3361 | + def test_slot_parameters(self): |
| 3362 | + slot = SlotSpec('__contains__', 'int', 'int', { |
| 3363 | + (Py_ssize_t_converter,): 'sq_fake', |
| 3364 | + (object_converter,): 'sq_contains'}) |
| 3365 | + block = """ |
| 3366 | + module foo |
| 3367 | + class Foo "FooObject *" "" |
| 3368 | + Foo.__contains__ |
| 3369 | + value: {converter} |
| 3370 | + / |
| 3371 | + """ |
| 3372 | + errors = [ |
| 3373 | + ("Foo.__contains__", |
| 3374 | + "'__contains__' must declare (Py_ssize_t) for sq_fake or " |
| 3375 | + "(object) for sq_contains; got ()"), |
| 3376 | + ("Foo.__contains__\n value: int\n /", |
| 3377 | + "'__contains__' must declare"), |
| 3378 | + ("Foo.__contains__\n value: object", |
| 3379 | + "The parameters of '__contains__' must be positional-only"), |
| 3380 | + ("Foo.__contains__\n value: object = None\n /", |
| 3381 | + "The parameters of '__contains__' cannot have a default value"), |
| 3382 | + ] |
| 3383 | + with mock.patch.dict(SLOTS, {'__contains__': slot}): |
| 3384 | + for converter in "Py_ssize_t", "object": |
| 3385 | + with self.subTest(converter=converter): |
| 3386 | + function = self.parse_function( |
| 3387 | + block.format(converter=converter), |
| 3388 | + signatures_in_block=3, function_index=2) |
| 3389 | + self.assertIs(function.kind, FunctionKind.SLOT) |
| 3390 | + self.assertEqual(function.return_converter.type, "int") |
| 3391 | + self.assertEqual(list(function.parameters), ["self", "value"]) |
| 3392 | + for body, err in errors: |
| 3393 | + with self.subTest(block=body): |
| 3394 | + body = 'class Foo "FooObject *" "&Foo_Type"\n' + body |
| 3395 | + self.expect_failure(body, err) |
| 3396 | + |
| 3397 | + def test_slot_errors(self): |
| 3398 | + cases = [ |
| 3399 | + ("module foo\nfoo.__next__", |
| 3400 | + "'__next__' must be a method!"), |
| 3401 | + ("@classmethod\nFoo.__next__", |
| 3402 | + "'__next__' must be a normal method"), |
| 3403 | + ("@getter\nFoo.__next__", |
| 3404 | + "'__next__' must be a normal method"), |
| 3405 | + ("@coexist\nFoo.__next__", |
| 3406 | + "@coexist cannot be applied to '__next__'"), |
| 3407 | + ("@vectorcall\nFoo.__next__", |
| 3408 | + "@vectorcall can only be used with __init__ and __new__"), |
| 3409 | + ("Foo.__next__\n a: int", |
| 3410 | + "'__next__' methods cannot define parameters"), |
| 3411 | + ("Foo.__next__ -> int", |
| 3412 | + "'Foo.__next__' cannot define a return type"), |
| 3413 | + ("Foo.__next__\n\nReturn the next item.", |
| 3414 | + "'__next__' methods cannot have a docstring"), |
| 3415 | + ] |
| 3416 | + for block, err in cases: |
| 3417 | + with self.subTest(block=block): |
| 3418 | + block = 'class Foo "FooObject *" "&Foo_Type"\n' + block |
| 3419 | + self.expect_failure(block, err) |
| 3420 | + |
3240 | 3421 | def test_deleter_without_setter(self): |
3241 | 3422 | block = """ |
3242 | 3423 | module foo |
|
0 commit comments