@@ -794,6 +794,102 @@ def test_ignore_preprocessor_in_comments(self):
794794 """ )
795795 self .clinic .parse (raw )
796796
797+ def test_getset_in_ifdef (self ):
798+ block = """
799+ /*[clinic input]
800+ output everything block
801+ class Foo "FooObject *" "&Foo_Type"
802+ [clinic start generated code]*/
803+ #ifdef CONDITION
804+ /*[clinic input]
805+ @getter
806+ Foo.property
807+ [clinic start generated code]*/
808+ /*[clinic input]
809+ @setter
810+ Foo.property
811+ [clinic start generated code]*/
812+ #endif
813+ """
814+ generated = self .clinic .parse (dedent (block ))
815+ self .assertIn ("#if defined(CONDITION)" , generated )
816+ # The getset is undefined if the condition is false.
817+ self .assertIn ("#ifndef FOO_PROPERTY_GETSETDEF\n "
818+ " #define FOO_PROPERTY_GETSETDEF\n "
819+ "#endif /* !defined(FOO_PROPERTY_GETSETDEF) */" ,
820+ generated )
821+
822+ def test_getset_duplicate (self ):
823+ for annotation in "@getter" , "@setter" :
824+ with self .subTest (annotation = annotation ):
825+ self .clinic = _make_clinic (filename = "test.c" )
826+ block = f"""
827+ /*[clinic input]
828+ class Foo "FooObject *" "&Foo_Type"
829+ [clinic start generated code]*/
830+ /*[clinic input]
831+ { annotation }
832+ Foo.property
833+ [clinic start generated code]*/
834+ /*[clinic input]
835+ { annotation }
836+ Foo.property
837+ [clinic start generated code]*/
838+ """
839+ kind = 'setter' if annotation == '@setter' else 'getter'
840+ err = f"Cannot apply @{ kind } to 'Foo.property' twice"
841+ self .expect_failure (block , err , lineno = 10 )
842+
843+ def test_getset_different_c_basename (self ):
844+ block = """
845+ /*[clinic input]
846+ class Foo "FooObject *" "&Foo_Type"
847+ [clinic start generated code]*/
848+ /*[clinic input]
849+ @getter
850+ Foo.property as foo_get
851+ [clinic start generated code]*/
852+ /*[clinic input]
853+ @setter
854+ Foo.property as foo_set
855+ [clinic start generated code]*/
856+ """
857+ err = "The accessors of 'Foo.property' must have the same C basename"
858+ self .expect_failure (block , err , lineno = 10 )
859+
860+ def test_setter_deletion_check (self ):
861+ block = """
862+ /*[clinic input]
863+ output everything block
864+ class Foo "FooObject *" "&Foo_Type"
865+ [clinic start generated code]*/
866+ /*[clinic input]
867+ @setter
868+ Foo.property
869+ [clinic start generated code]*/
870+ """
871+ generated = self .clinic .parse (dedent (block ))
872+ self .assertIn ("if (value == NULL) {" , generated )
873+ self .assertIn ("\" attribute 'property' of '%.100s' objects "
874+ "cannot be deleted\" " , generated )
875+
876+ def test_deleter (self ):
877+ # @deleter means that the setter is called with NULL to delete
878+ # the attribute, so it checks the value itself.
879+ block = """
880+ /*[clinic input]
881+ output everything block
882+ class Foo "FooObject *" "&Foo_Type"
883+ [clinic start generated code]*/
884+ /*[clinic input]
885+ @setter
886+ @deleter
887+ Foo.property
888+ [clinic start generated code]*/
889+ """
890+ generated = self .clinic .parse (dedent (block ))
891+ self .assertNotIn ("if (value == NULL) {" , generated )
892+
797893 def test_var_keyword_non_dict (self ):
798894 err = "'var_keyword_object' is not a valid converter"
799895 block = """
@@ -2671,7 +2767,7 @@ class Foo "" ""
26712767 { annotation }
26722768 Foo.property -> int
26732769 """
2674- expected_error = f" { annotation } method cannot define a return type"
2770+ expected_error = "@getter and @setter methods cannot define a return type"
26752771 self .expect_failure (block , expected_error , lineno = 3 )
26762772
26772773 block = f"""
@@ -2682,7 +2778,7 @@ class Foo "" ""
26822778 obj: int
26832779 /
26842780 """
2685- expected_error = f" { annotation } methods cannot define parameters"
2781+ expected_error = "@getter and @setter methods cannot define parameters"
26862782 self .expect_failure (block , expected_error )
26872783
26882784 def test_setter_docstring (self ):
@@ -2725,9 +2821,51 @@ class Foo "" ""
27252821 { dup [1 ]}
27262822 Foo.property -> int
27272823 """
2728- expected_error = "Cannot apply both @getter and @setter to the same function!"
2824+ expected_error = (f"Can't set { dup [1 ]} , "
2825+ f"function is not a normal callable" )
27292826 self .expect_failure (block , expected_error , lineno = 3 )
27302827
2828+ def test_deleter_without_setter (self ):
2829+ block = """
2830+ module foo
2831+ class Foo "" ""
2832+ @deleter
2833+ Foo.property
2834+ """
2835+ expected_error = "Can't set @deleter, @setter is not applied"
2836+ self .expect_failure (block , expected_error , lineno = 2 )
2837+
2838+ block = """
2839+ module foo
2840+ class Foo "" ""
2841+ @deleter
2842+ @setter
2843+ Foo.property
2844+ """
2845+ self .expect_failure (block , expected_error , lineno = 2 )
2846+
2847+ def test_deleter_twice (self ):
2848+ block = """
2849+ module foo
2850+ class Foo "" ""
2851+ @setter
2852+ @deleter
2853+ @deleter
2854+ Foo.property
2855+ """
2856+ expected_error = "Cannot apply @deleter twice to the same function!"
2857+ self .expect_failure (block , expected_error , lineno = 4 )
2858+
2859+ def test_setter_and_deleter (self ):
2860+ function = self .parse_function ("""
2861+ module foo
2862+ class Foo "" ""
2863+ @setter
2864+ @deleter
2865+ Foo.property
2866+ """ , signatures_in_block = 3 , function_index = 2 )
2867+ self .assertEqual (function .kind , FunctionKind .SETTER_AND_DELETER )
2868+
27312869 def test_getset_no_class (self ):
27322870 for annotation in "@getter" , "@setter" :
27332871 with self .subTest (annotation = annotation ):
0 commit comments