@@ -1169,13 +1169,16 @@ fn find_func_type_comment(
11691169 return ( arg_types, ret_type) ;
11701170 }
11711171 let first_stmt = func. body [ 0 ] . start ( ) ;
1172+ if ser. tokens . is_none ( ) {
1173+ return ( arg_types, ret_type) ;
1174+ }
11721175 let tokens = ser. tokens . unwrap ( ) ;
11731176 let tokens_before = tokens. before ( first_stmt) ;
11741177 let mut idx = tokens_before. len ( ) - 1 ;
11751178 loop {
11761179 // Look for function type comments between colon in `def foo(...):` anr first statement.
11771180 let token = tokens_before[ idx] . kind ( ) ;
1178- if token == TokenKind :: Colon {
1181+ if idx == 0 || token == TokenKind :: Colon {
11791182 break ;
11801183 }
11811184 if token == TokenKind :: Comment {
@@ -3579,6 +3582,58 @@ mod tests {
35793582 assert ! ( ser. lines_with_non_ascii. is_empty( ) ) ; // No per-line tracking
35803583 }
35813584
3585+ #[ test]
3586+ fn test_syntax_error_empty_func ( ) {
3587+ // Test that we do not panic on invalid function definition.
3588+ let text = "def hello()\n pass\n " ;
3589+ let mut ser = make_ser ( text) ;
3590+ let opt = ParseOptions :: from ( PySourceType :: Python ) ;
3591+ let parsed = parse_unchecked ( text, opt) ;
3592+ let ast = parsed. syntax ( ) ;
3593+
3594+ // Should not panic
3595+ ast. serialize ( & mut ser) ;
3596+
3597+ // Syntax error is reported normally
3598+ assert ! ( ser. extra_errors. is_empty( ) ) ;
3599+ }
3600+
3601+ #[ test]
3602+ fn test_syntax_error_empty_func_2 ( ) {
3603+ // Same as above, but after a valid function.
3604+ let text = "def ok():\n pass\n def hello()\n pass\n " ;
3605+ let mut ser = make_ser ( text) ;
3606+ let opt = ParseOptions :: from ( PySourceType :: Python ) ;
3607+ let parsed = parse_unchecked ( text, opt) ;
3608+ let ast = parsed. syntax ( ) ;
3609+
3610+ // Should not panic
3611+ ast. serialize ( & mut ser) ;
3612+
3613+ // Syntax error is reported normally
3614+ assert ! ( ser. extra_errors. is_empty( ) ) ;
3615+ }
3616+
3617+ #[ test]
3618+ fn test_syntax_error_broken_args ( ) {
3619+ // Test that we do not panic on invalid function arguments.
3620+ let source = "def f(x, (y, z)): pass\n " ;
3621+ let path = write_temp_py ( "test_source" , source) ;
3622+ // Simply check that we do not panic
3623+ let _ = serialize_python_file ( & path, None , false , Options :: default ( ) ) . unwrap ( ) ;
3624+ let _ = std:: fs:: remove_file ( & path) ;
3625+ }
3626+
3627+ #[ test]
3628+ fn test_syntax_error_broken_args_2 ( ) {
3629+ // Same as above, but after a valid function.
3630+ let source = "def ok(x): pass\n def f(x, (y, z)): pass\n " ;
3631+ let path = write_temp_py ( "test_source" , source) ;
3632+ // Simply check that we do not panic
3633+ let _ = serialize_python_file ( & path, None , false , Options :: default ( ) ) . unwrap ( ) ;
3634+ let _ = std:: fs:: remove_file ( & path) ;
3635+ }
3636+
35823637 #[ test]
35833638 fn test_unicode_with_crlf_line_endings ( ) {
35843639 // Test that Unicode handling works correctly with Windows (CRLF) line endings
0 commit comments