@@ -775,6 +775,102 @@ def test_ignore_preprocessor_in_comments(self):
775775 """ )
776776 self .clinic .parse (raw )
777777
778+ def test_getset_in_ifdef (self ):
779+ block = """
780+ /*[clinic input]
781+ output everything block
782+ class Foo "FooObject *" "&Foo_Type"
783+ [clinic start generated code]*/
784+ #ifdef CONDITION
785+ /*[clinic input]
786+ @getter
787+ Foo.property
788+ [clinic start generated code]*/
789+ /*[clinic input]
790+ @setter
791+ Foo.property
792+ [clinic start generated code]*/
793+ #endif
794+ """
795+ generated = self .clinic .parse (dedent (block ))
796+ self .assertIn ("#if defined(CONDITION)" , generated )
797+ # The getset is undefined if the condition is false.
798+ self .assertIn ("#ifndef FOO_PROPERTY_GETSETDEF\n "
799+ " #define FOO_PROPERTY_GETSETDEF\n "
800+ "#endif /* !defined(FOO_PROPERTY_GETSETDEF) */" ,
801+ generated )
802+
803+ def test_getset_duplicate (self ):
804+ for annotation in "@getter" , "@setter" :
805+ with self .subTest (annotation = annotation ):
806+ self .clinic = _make_clinic (filename = "test.c" )
807+ block = f"""
808+ /*[clinic input]
809+ class Foo "FooObject *" "&Foo_Type"
810+ [clinic start generated code]*/
811+ /*[clinic input]
812+ { annotation }
813+ Foo.property
814+ [clinic start generated code]*/
815+ /*[clinic input]
816+ { annotation }
817+ Foo.property
818+ [clinic start generated code]*/
819+ """
820+ kind = 'setter' if annotation == '@setter' else 'getter'
821+ err = f"Cannot apply @{ kind } to 'Foo.property' twice"
822+ self .expect_failure (block , err , lineno = 10 )
823+
824+ def test_getset_different_c_basename (self ):
825+ block = """
826+ /*[clinic input]
827+ class Foo "FooObject *" "&Foo_Type"
828+ [clinic start generated code]*/
829+ /*[clinic input]
830+ @getter
831+ Foo.property as foo_get
832+ [clinic start generated code]*/
833+ /*[clinic input]
834+ @setter
835+ Foo.property as foo_set
836+ [clinic start generated code]*/
837+ """
838+ err = "The accessors of 'Foo.property' must have the same C basename"
839+ self .expect_failure (block , err , lineno = 10 )
840+
841+ def test_setter_deletion_check (self ):
842+ block = """
843+ /*[clinic input]
844+ output everything block
845+ class Foo "FooObject *" "&Foo_Type"
846+ [clinic start generated code]*/
847+ /*[clinic input]
848+ @setter
849+ Foo.property
850+ [clinic start generated code]*/
851+ """
852+ generated = self .clinic .parse (dedent (block ))
853+ self .assertIn ("if (value == NULL) {" , generated )
854+ self .assertIn ("\" attribute 'property' of '%.100s' objects "
855+ "cannot be deleted\" " , generated )
856+
857+ def test_deleter (self ):
858+ # @deleter means that the setter is called with NULL to delete
859+ # the attribute, so it checks the value itself.
860+ block = """
861+ /*[clinic input]
862+ output everything block
863+ class Foo "FooObject *" "&Foo_Type"
864+ [clinic start generated code]*/
865+ /*[clinic input]
866+ @setter
867+ @deleter
868+ Foo.property
869+ [clinic start generated code]*/
870+ """
871+ generated = self .clinic .parse (dedent (block ))
872+ self .assertNotIn ("if (value == NULL) {" , generated )
873+
778874 def test_var_keyword_non_dict (self ):
779875 err = "'var_keyword_object' is not a valid converter"
780876 block = """
@@ -2599,7 +2695,7 @@ class Foo "" ""
25992695 { annotation }
26002696 Foo.property -> int
26012697 """
2602- expected_error = f" { annotation } method cannot define a return type"
2698+ expected_error = "@getter and @setter methods cannot define a return type"
26032699 self .expect_failure (block , expected_error , lineno = 3 )
26042700
26052701 block = f"""
@@ -2610,7 +2706,7 @@ class Foo "" ""
26102706 obj: int
26112707 /
26122708 """
2613- expected_error = f" { annotation } methods cannot define parameters"
2709+ expected_error = "@getter and @setter methods cannot define parameters"
26142710 self .expect_failure (block , expected_error )
26152711
26162712 def test_setter_docstring (self ):
@@ -2653,9 +2749,51 @@ class Foo "" ""
26532749 { dup [1 ]}
26542750 Foo.property -> int
26552751 """
2656- expected_error = "Cannot apply both @getter and @setter to the same function!"
2752+ expected_error = (f"Can't set { dup [1 ]} , "
2753+ f"function is not a normal callable" )
26572754 self .expect_failure (block , expected_error , lineno = 3 )
26582755
2756+ def test_deleter_without_setter (self ):
2757+ block = """
2758+ module foo
2759+ class Foo "" ""
2760+ @deleter
2761+ Foo.property
2762+ """
2763+ expected_error = "Can't set @deleter, @setter is not applied"
2764+ self .expect_failure (block , expected_error , lineno = 2 )
2765+
2766+ block = """
2767+ module foo
2768+ class Foo "" ""
2769+ @deleter
2770+ @setter
2771+ Foo.property
2772+ """
2773+ self .expect_failure (block , expected_error , lineno = 2 )
2774+
2775+ def test_deleter_twice (self ):
2776+ block = """
2777+ module foo
2778+ class Foo "" ""
2779+ @setter
2780+ @deleter
2781+ @deleter
2782+ Foo.property
2783+ """
2784+ expected_error = "Cannot apply @deleter twice to the same function!"
2785+ self .expect_failure (block , expected_error , lineno = 4 )
2786+
2787+ def test_setter_and_deleter (self ):
2788+ function = self .parse_function ("""
2789+ module foo
2790+ class Foo "" ""
2791+ @setter
2792+ @deleter
2793+ Foo.property
2794+ """ , signatures_in_block = 3 , function_index = 2 )
2795+ self .assertEqual (function .kind , FunctionKind .SETTER_AND_DELETER )
2796+
26592797 def test_getset_no_class (self ):
26602798 for annotation in "@getter" , "@setter" :
26612799 with self .subTest (annotation = annotation ):
0 commit comments