@@ -37,6 +37,11 @@ use crate::type_info::TypeInfo;
3737pub ( crate ) struct CallableSyntax < ' src > {
3838 source : & ' src str ,
3939 types : Option < & ' src ( dyn TypeInfo + ' src ) > ,
40+ /// ranges already folded by `symbolic_type_op` (e.g. `1 + typeof d` →
41+ /// `Literal[3]`). a claimed sub-expression is opaque here: descending into
42+ /// it would re-render its `typeof`/operator surface and the wider edit
43+ /// would clobber the fold, so `rewrite` leaves it for the fold's own edit
44+ claimed_ranges : & ' src [ TextRange ] ,
4045 pub ( crate ) edits : Vec < Fix > ,
4146 pub ( crate ) needs_import : bool ,
4247 pub ( crate ) needs_protocol_import : bool ,
@@ -64,6 +69,7 @@ impl<'src> CallableSyntax<'src> {
6469 Self {
6570 source,
6671 types : None ,
72+ claimed_ranges : & [ ] ,
6773 edits : Vec :: new ( ) ,
6874 needs_import : false ,
6975 needs_protocol_import : false ,
@@ -81,6 +87,11 @@ impl<'src> CallableSyntax<'src> {
8187 self
8288 }
8389
90+ pub ( crate ) fn with_claimed_ranges ( mut self , claimed : & ' src [ TextRange ] ) -> Self {
91+ self . claimed_ranges = claimed;
92+ self
93+ }
94+
8495 pub ( crate ) fn class_defs ( & self ) -> & str {
8596 & self . protocol_class_defs
8697 }
@@ -228,6 +239,12 @@ impl<'src> CallableSyntax<'src> {
228239 }
229240
230241 pub ( crate ) fn rewrite ( & mut self , expr : & Expr ) -> Option < String > {
242+ // a symbolic-fold-claimed sub-expression is opaque: the fold emits its
243+ // own edit over this exact range, so re-rendering here (and clobbering
244+ // it with a wider edit) must not happen
245+ if self . claimed_ranges . contains ( & expr. range ( ) ) {
246+ return None ;
247+ }
231248 match expr {
232249 Expr :: CallableType ( ct) if self . is_non_denotable ( ct) => {
233250 self . needs_protocol_import = true ;
@@ -517,7 +534,12 @@ impl<'ast> ruff_python_ast::visitor::Visitor<'ast> for ValueCallableWalker<'_, '
517534
518535impl TypeAwarePass for CallableSyntaxPass < ' _ > {
519536 fn run ( & self , stmts : & [ Stmt ] , types : & dyn TypeInfo , ctx : & mut PassContext ) {
520- let mut inner = CallableSyntax :: new ( self . source ) . with_types ( types) ;
537+ // owned copy so `inner`'s borrow doesn't pin `ctx` against the mutable
538+ // `required_imports` / `edits` uses below
539+ let claimed = ctx. claimed_type_op_ranges . clone ( ) ;
540+ let mut inner = CallableSyntax :: new ( self . source )
541+ . with_types ( types)
542+ . with_claimed_ranges ( & claimed) ;
521543 crate :: transforms:: type_expr_walker:: walk_type_positions_skipping (
522544 stmts,
523545 Some ( types) ,
0 commit comments