@@ -45,16 +45,16 @@ impl<'src> AutoQuote<'src> {
4545
4646impl AstPass for AutoQuote < ' _ > {
4747 fn run ( & self , module : & mut ModModule , ctx : & mut PassContext ) {
48- // skip quoting whenever annotations won't be eagerly evaluated:
49- // native deferral on 3.14+, or a future import that defers them all
50- if self . min_version . defers_annotations ( )
48+ // annotation positions need no quoting when they won't be eagerly
49+ // evaluated: native deferral on 3.14+ (PEP 649), or a future import
50+ // that defers them all. class *bases* and value-position subscripts
51+ // (`class A(list[A])`, `list[A]()`) evaluate eagerly regardless, so
52+ // their self-references are always quoted
53+ let quote_annotations = !( self . min_version . defers_annotations ( )
5154 || self . inject_future
52- || has_future_annotations ( & module. body )
53- {
54- return ;
55- }
55+ || has_future_annotations ( & module. body ) ) ;
5656 let mut edits: Vec < ( TextRange , String ) > = Vec :: new ( ) ;
57- process_stmts ( & module. body , self . source , & mut edits) ;
57+ process_stmts ( & module. body , self . source , & mut edits, quote_annotations ) ;
5858 ctx. text_edits . extend ( edits) ;
5959 }
6060}
@@ -67,51 +67,66 @@ fn has_future_annotations(stmts: &[Stmt]) -> bool {
6767 } )
6868}
6969
70- fn process_stmts ( stmts : & [ Stmt ] , source : & str , edits : & mut Vec < ( TextRange , String ) > ) {
70+ fn process_stmts (
71+ stmts : & [ Stmt ] ,
72+ source : & str ,
73+ edits : & mut Vec < ( TextRange , String ) > ,
74+ quote_annotations : bool ,
75+ ) {
7176 for stmt in stmts {
7277 if let Stmt :: ClassDef ( c) = stmt {
73- process_class ( c, source, edits) ;
78+ process_class ( c, source, edits, quote_annotations ) ;
7479 // nested classes inside this one's body are recursed into by
7580 // process_class so the inner ClassDef walks with its own name
7681 } else {
7782 // top-level non-class statements may contain nested classes via
7883 // function bodies — descend
79- walk_for_nested_classes ( stmt, source, edits) ;
84+ walk_for_nested_classes ( stmt, source, edits, quote_annotations ) ;
8085 }
8186 }
8287}
8388
84- fn walk_for_nested_classes ( stmt : & Stmt , source : & str , edits : & mut Vec < ( TextRange , String ) > ) {
89+ fn walk_for_nested_classes (
90+ stmt : & Stmt ,
91+ source : & str ,
92+ edits : & mut Vec < ( TextRange , String ) > ,
93+ quote_annotations : bool ,
94+ ) {
8595 // only walk into structures that may contain nested class defs.
8696 // function bodies, if/while/try blocks, etc.
8797 match stmt {
88- Stmt :: FunctionDef ( f) => process_stmts ( & f. body , source, edits) ,
98+ Stmt :: FunctionDef ( f) => process_stmts ( & f. body , source, edits, quote_annotations ) ,
8999 Stmt :: If ( i) => {
90- process_stmts ( & i. body , source, edits) ;
100+ process_stmts ( & i. body , source, edits, quote_annotations ) ;
91101 for clause in & i. elif_else_clauses {
92- process_stmts ( & clause. body , source, edits) ;
102+ process_stmts ( & clause. body , source, edits, quote_annotations ) ;
93103 }
94104 }
95- Stmt :: While ( w) => process_stmts ( & w. body , source, edits) ,
105+ Stmt :: While ( w) => process_stmts ( & w. body , source, edits, quote_annotations ) ,
96106 Stmt :: For ( f) => {
97- process_stmts ( & f. body , source, edits) ;
98- process_stmts ( & f. orelse , source, edits) ;
107+ process_stmts ( & f. body , source, edits, quote_annotations ) ;
108+ process_stmts ( & f. orelse , source, edits, quote_annotations ) ;
99109 }
100- Stmt :: With ( w) => process_stmts ( & w. body , source, edits) ,
110+ Stmt :: With ( w) => process_stmts ( & w. body , source, edits, quote_annotations ) ,
101111 Stmt :: Try ( t) => {
102- process_stmts ( & t. body , source, edits) ;
112+ process_stmts ( & t. body , source, edits, quote_annotations ) ;
103113 for h in & t. handlers {
104114 let ruff_python_ast:: ExceptHandler :: ExceptHandler ( eh) = h;
105- process_stmts ( & eh. body , source, edits) ;
115+ process_stmts ( & eh. body , source, edits, quote_annotations ) ;
106116 }
107- process_stmts ( & t. orelse , source, edits) ;
108- process_stmts ( & t. finalbody , source, edits) ;
117+ process_stmts ( & t. orelse , source, edits, quote_annotations ) ;
118+ process_stmts ( & t. finalbody , source, edits, quote_annotations ) ;
109119 }
110120 _ => { }
111121 }
112122}
113123
114- fn process_class ( class : & StmtClassDef , source : & str , edits : & mut Vec < ( TextRange , String ) > ) {
124+ fn process_class (
125+ class : & StmtClassDef ,
126+ source : & str ,
127+ edits : & mut Vec < ( TextRange , String ) > ,
128+ quote_annotations : bool ,
129+ ) {
115130 let class_name = class. name . id . as_str ( ) ;
116131 let typevar_names: Vec < String > = class
117132 . type_params
@@ -148,25 +163,30 @@ fn process_class(class: &StmtClassDef, source: &str, edits: &mut Vec<(TextRange,
148163 // (handled by walker), method bodies need separate descent for
149164 // `list[A]()` patterns
150165 for stmt in & class. body {
151- process_class_body_stmt ( stmt, & mut visitor) ;
166+ process_class_body_stmt ( stmt, & mut visitor, quote_annotations ) ;
152167 }
153168
154169 // recurse into nested classes inside the body so they get their own
155170 // class-context walk (the `visitor` borrow of `edits` ends above)
156- process_stmts ( & class. body , source, edits) ;
171+ process_stmts ( & class. body , source, edits, quote_annotations ) ;
157172}
158173
159- fn process_class_body_stmt ( stmt : & Stmt , visitor : & mut Visitor < ' _ > ) {
174+ fn process_class_body_stmt ( stmt : & Stmt , visitor : & mut Visitor < ' _ > , quote_annotations : bool ) {
160175 match stmt {
161176 Stmt :: Expr ( e) => walk_value_subscripts ( e. value . as_ref ( ) , visitor) ,
162177 Stmt :: Assign ( a) => walk_value_subscripts ( a. value . as_ref ( ) , visitor) ,
163178 Stmt :: AnnAssign ( a) => {
164- walk_one_type_expr ( a. annotation . as_ref ( ) , visitor) ;
179+ if quote_annotations {
180+ walk_one_type_expr ( a. annotation . as_ref ( ) , visitor) ;
181+ }
165182 if let Some ( value) = & a. value {
166183 walk_value_subscripts ( value. as_ref ( ) , visitor) ;
167184 }
168185 }
169186 Stmt :: FunctionDef ( f) => {
187+ if !quote_annotations {
188+ return ;
189+ }
170190 for param in f. parameters . iter_non_variadic_params ( ) {
171191 if let Some ( ann) = & param. parameter . annotation {
172192 walk_one_type_expr ( ann, visitor) ;
@@ -516,6 +536,21 @@ mod tests {
516536 ) ;
517537 }
518538
539+ #[ test]
540+ fn class_base_still_quoted_when_version_defers_annotations ( ) {
541+ // pep 649 defers annotations only — a class *base* evaluates eagerly,
542+ // so its self-reference still needs the quote
543+ let config = Config {
544+ min_version : PythonVersion :: from ( ( 3 , 14 ) ) ,
545+ ..Config :: test_default ( )
546+ } ;
547+ let out = transpile_with ( "class A(list[A]):\n pass\n " , & config) ;
548+ assert ! (
549+ out. contains( "class A(list[\" A\" ]):" ) ,
550+ "base self-ref must stay quoted on 3.14+, got: {out}"
551+ ) ;
552+ }
553+
519554 #[ test]
520555 fn not_quoted_when_source_has_future ( ) {
521556 // a user-written future import already defers every annotation
0 commit comments