@@ -39,8 +39,9 @@ use super::{
3939 float_const, force_unwrap, generic_call, generics, identity_swap, implicit_typing, init_method,
4040 intersection, just_float, kw_subscript, literal_types, main_function, modifiers,
4141 mutable_defaults, none_chain, not_type, optional_type, overload, postfix_await, propagate,
42- repeated_underscore, sentinel, some_ctor, super_keyword, top_star, tuple_index, type_is,
43- typed_dict_literal, typed_lambda, typeof_keyword, unpack, use_site_variance,
42+ repeated_underscore, sentinel, some_ctor, super_keyword, symbolic_type_op, top_star,
43+ tuple_index, type_is, typed_dict_literal, typed_lambda, typeof_keyword, unpack,
44+ use_site_variance,
4445} ;
4546use crate :: Config ;
4647use crate :: type_info:: TypeInfo ;
@@ -84,6 +85,11 @@ pub(crate) struct PassContext {
8485 /// Lines to append AFTER the spliced body (e.g. modifiers' auto-
8586 /// generated `__all__ = [...]`). Driver emits each as its own line
8687 pub ( crate ) epilogue : Vec < String > ,
88+ /// Source ranges of operations that `symbolic_type_op` resolved up front
89+ /// (e.g. `1 + 1` → `Literal[2]`). Type-aware passes skip these via
90+ /// [`walk_type_positions_skipping`](super::type_expr_walker::walk_type_positions_skipping)
91+ /// so they don't re-process an operation that no longer appears in the output
92+ pub ( crate ) claimed_type_op_ranges : Vec < TextRange > ,
8793}
8894
8995/// A single AST-level rewrite pass.
@@ -286,6 +292,17 @@ pub(crate) fn run_against_source<'a>(
286292 text_edits : RefCell :: new ( vec ! [ ] ) ,
287293 } ;
288294
295+ // resolve symbolic operations in type positions (`1 + 1` → `Literal[2]`)
296+ // up front, from the original parse where `typeof` operands are still
297+ // intact for ty to read. the pass replaces each operation node and must run
298+ // before `typeof` lowering so a `typeof` operand is consumed here
299+ let symbolic_folds =
300+ symbolic_type_op:: collect_symbolic_folds ( parsed_handle. suite ( ) , & semantic_model) ;
301+ let symbolic_needs_literal_import = symbolic_folds. needs_literal_import ;
302+ let symbolic_needs_any_import = symbolic_folds. needs_any_import ;
303+ ctx. claimed_type_op_ranges = symbolic_folds. claimed_ranges ( ) ;
304+ let symbolic_pass = symbolic_type_op:: SymbolicTypeOp :: new ( symbolic_folds) ;
305+
289306 let tuple_index_inner = tuple_index:: TupleIndex :: new ( ) ;
290307 let tuple_index_pass = VisitorPass {
291308 inner : & tuple_index_inner,
@@ -395,7 +412,12 @@ pub(crate) fn run_against_source<'a>(
395412 & decorator_keyword_pass,
396413 & unpack_pass,
397414 & typed_dict_literal_pass,
398- // AST-mutation passes second (may zero node ranges)
415+ // AST-mutation passes second (may zero node ranges).
416+ // symbolic_type_op replaces whole operation nodes (consuming any
417+ // `typeof` operand) and reads original source ranges, so it must run
418+ // first among the mutation passes — before `typeof` and before any
419+ // pass that zeroes ranges
420+ & symbolic_pass,
399421 & coalesce_pass,
400422 & cast_pass,
401423 & typeof_pass,
@@ -463,6 +485,18 @@ pub(crate) fn run_against_source<'a>(
463485 ctx. required_imports
464486 . push ( "from ty_extensions import TypeOf" . to_owned ( ) ) ;
465487 }
488+ // symbolic folds that produced a `Literal[..]` need the import, unless the
489+ // source already binds `Literal`
490+ if symbolic_needs_literal_import && !literal_types:: literal_already_imported ( & semantic_model) {
491+ ctx. required_imports
492+ . push ( "from typing import Literal" . to_owned ( ) ) ;
493+ }
494+ // symbolic folds that produced `Any` (e.g. `dynamic + 1`) need the import,
495+ // unless the source already binds `Any`
496+ if symbolic_needs_any_import && !semantic_model. is_bound_globally ( "Any" ) {
497+ ctx. required_imports
498+ . push ( "from typing import Any" . to_owned ( ) ) ;
499+ }
466500 if sentinel_inner. ever_changed ( ) {
467501 ctx. required_imports
468502 . push ( "from typing_extensions import Sentinel" . to_owned ( ) ) ;
0 commit comments