@@ -761,6 +761,102 @@ def test_ignore_preprocessor_in_comments(self):
761761 """ )
762762 self .clinic .parse (raw )
763763
764+ def test_getset_in_ifdef (self ):
765+ block = """
766+ /*[clinic input]
767+ output everything block
768+ class Foo "FooObject *" "&Foo_Type"
769+ [clinic start generated code]*/
770+ #ifdef CONDITION
771+ /*[clinic input]
772+ @getter
773+ Foo.property
774+ [clinic start generated code]*/
775+ /*[clinic input]
776+ @setter
777+ Foo.property
778+ [clinic start generated code]*/
779+ #endif
780+ """
781+ generated = self .clinic .parse (dedent (block ))
782+ self .assertIn ("#if defined(CONDITION)" , generated )
783+ # The getset is undefined if the condition is false.
784+ self .assertIn ("#ifndef FOO_PROPERTY_GETSETDEF\n "
785+ " #define FOO_PROPERTY_GETSETDEF\n "
786+ "#endif /* !defined(FOO_PROPERTY_GETSETDEF) */" ,
787+ generated )
788+
789+ def test_getset_duplicate (self ):
790+ for annotation in "@getter" , "@setter" :
791+ with self .subTest (annotation = annotation ):
792+ self .clinic = _make_clinic (filename = "test.c" )
793+ block = f"""
794+ /*[clinic input]
795+ class Foo "FooObject *" "&Foo_Type"
796+ [clinic start generated code]*/
797+ /*[clinic input]
798+ { annotation }
799+ Foo.property
800+ [clinic start generated code]*/
801+ /*[clinic input]
802+ { annotation }
803+ Foo.property
804+ [clinic start generated code]*/
805+ """
806+ kind = 'setter' if annotation == '@setter' else 'getter'
807+ err = f"Cannot apply @{ kind } to 'Foo.property' twice"
808+ self .expect_failure (block , err , lineno = 10 )
809+
810+ def test_getset_different_c_basename (self ):
811+ block = """
812+ /*[clinic input]
813+ class Foo "FooObject *" "&Foo_Type"
814+ [clinic start generated code]*/
815+ /*[clinic input]
816+ @getter
817+ Foo.property as foo_get
818+ [clinic start generated code]*/
819+ /*[clinic input]
820+ @setter
821+ Foo.property as foo_set
822+ [clinic start generated code]*/
823+ """
824+ err = "The accessors of 'Foo.property' must have the same C basename"
825+ self .expect_failure (block , err , lineno = 10 )
826+
827+ def test_setter_deletion_check (self ):
828+ block = """
829+ /*[clinic input]
830+ output everything block
831+ class Foo "FooObject *" "&Foo_Type"
832+ [clinic start generated code]*/
833+ /*[clinic input]
834+ @setter
835+ Foo.property
836+ [clinic start generated code]*/
837+ """
838+ generated = self .clinic .parse (dedent (block ))
839+ self .assertIn ("if (value == NULL) {" , generated )
840+ self .assertIn ("\" attribute 'property' of '%.100s' objects "
841+ "cannot be deleted\" " , generated )
842+
843+ def test_deleter (self ):
844+ # @deleter means that the setter is called with NULL to delete
845+ # the attribute, so it checks the value itself.
846+ block = """
847+ /*[clinic input]
848+ output everything block
849+ class Foo "FooObject *" "&Foo_Type"
850+ [clinic start generated code]*/
851+ /*[clinic input]
852+ @setter
853+ @deleter
854+ Foo.property
855+ [clinic start generated code]*/
856+ """
857+ generated = self .clinic .parse (dedent (block ))
858+ self .assertNotIn ("if (value == NULL) {" , generated )
859+
764860
765861class ParseFileUnitTest (TestCase ):
766862 def expect_parsing_failure (
@@ -2345,7 +2441,7 @@ class Foo "" ""
23452441 { annotation }
23462442 Foo.property -> int
23472443 """
2348- expected_error = f" { annotation } method cannot define a return type"
2444+ expected_error = "@getter and @setter methods cannot define a return type"
23492445 self .expect_failure (block , expected_error , lineno = 3 )
23502446
23512447 block = f"""
@@ -2356,7 +2452,7 @@ class Foo "" ""
23562452 obj: int
23572453 /
23582454 """
2359- expected_error = f" { annotation } methods cannot define parameters"
2455+ expected_error = "@getter and @setter methods cannot define parameters"
23602456 self .expect_failure (block , expected_error )
23612457
23622458 def test_setter_docstring (self ):
@@ -2399,9 +2495,51 @@ class Foo "" ""
23992495 { dup [1 ]}
24002496 Foo.property -> int
24012497 """
2402- expected_error = "Cannot apply both @getter and @setter to the same function!"
2498+ expected_error = (f"Can't set { dup [1 ]} , "
2499+ f"function is not a normal callable" )
24032500 self .expect_failure (block , expected_error , lineno = 3 )
24042501
2502+ def test_deleter_without_setter (self ):
2503+ block = """
2504+ module foo
2505+ class Foo "" ""
2506+ @deleter
2507+ Foo.property
2508+ """
2509+ expected_error = "Can't set @deleter, @setter is not applied"
2510+ self .expect_failure (block , expected_error , lineno = 2 )
2511+
2512+ block = """
2513+ module foo
2514+ class Foo "" ""
2515+ @deleter
2516+ @setter
2517+ Foo.property
2518+ """
2519+ self .expect_failure (block , expected_error , lineno = 2 )
2520+
2521+ def test_deleter_twice (self ):
2522+ block = """
2523+ module foo
2524+ class Foo "" ""
2525+ @setter
2526+ @deleter
2527+ @deleter
2528+ Foo.property
2529+ """
2530+ expected_error = "Cannot apply @deleter twice to the same function!"
2531+ self .expect_failure (block , expected_error , lineno = 4 )
2532+
2533+ def test_setter_and_deleter (self ):
2534+ function = self .parse_function ("""
2535+ module foo
2536+ class Foo "" ""
2537+ @setter
2538+ @deleter
2539+ Foo.property
2540+ """ , signatures_in_block = 3 , function_index = 2 )
2541+ self .assertEqual (function .kind , FunctionKind .SETTER_AND_DELETER )
2542+
24052543 def test_getset_no_class (self ):
24062544 for annotation in "@getter" , "@setter" :
24072545 with self .subTest (annotation = annotation ):
0 commit comments