@@ -404,8 +404,8 @@ impl Static {
404404 }
405405
406406 /// Populate static definitions from a list of statements.
407- /// Returns the set of implicit captures (names read but not locally defined)
408- /// and a map of Final variable string values.
407+ /// Returns the set of implicit captures (names read but not locally defined),
408+ /// the set of all Final names, and a map of Final variable string values.
409409 fn stmts (
410410 & mut self ,
411411 x : & [ Stmt ] ,
@@ -415,7 +415,7 @@ impl Static {
415415 sys_info : SysInfo ,
416416 get_annotation_idx : & mut impl FnMut ( ShortIdentifier ) -> Idx < KeyAnnotation > ,
417417 scopes : Option < & Scopes > ,
418- ) -> ( SmallSet < Name > , SmallMap < Name , String > ) {
418+ ) -> ( SmallSet < Name > , SmallSet < Name > , SmallMap < Name , String > ) {
419419 let mut d = Definitions :: new (
420420 x,
421421 module_info. name ( ) ,
@@ -464,12 +464,13 @@ impl Static {
464464 self . upsert ( name. cloned ( ) , range, StaticStyle :: MergeableImport , range)
465465 }
466466 }
467+ let final_names = d. final_names . keys ( ) . cloned ( ) . collect ( ) ;
467468 let final_string_values = d
468469 . final_names
469470 . into_iter ( )
470471 . filter_map ( |( name, value) | value. map ( |v| ( name, v) ) )
471472 . collect ( ) ;
472- ( implicit_captures, final_string_values)
473+ ( implicit_captures, final_names , final_string_values)
473474 }
474475
475476 fn expr_lvalue ( & mut self , x : & Expr ) {
@@ -1206,6 +1207,9 @@ pub struct Scope {
12061207 /// from enclosing scopes. Populated during `init_current_static` from the
12071208 /// `Definitions` phase. Used to seed flow entries for captured variables.
12081209 implicit_captures : SmallSet < Name > ,
1210+ /// All names marked `Final` in this scope. Used to prevent literal
1211+ /// promotion so that `Final` variables preserve their literal types.
1212+ final_names : SmallSet < Name > ,
12091213 /// Names marked `Final` with string literal values, e.g. `X: Final = "x"`.
12101214 /// Used to resolve Final variable references in synthesized class field names.
12111215 final_string_values : SmallMap < Name , String > ,
@@ -1227,6 +1231,7 @@ impl Scope {
12271231 finally_depth : 0 ,
12281232 with_depth : 0 ,
12291233 implicit_captures : SmallSet :: new ( ) ,
1234+ final_names : SmallSet :: new ( ) ,
12301235 final_string_values : SmallMap :: new ( ) ,
12311236 }
12321237 }
@@ -1673,7 +1678,7 @@ impl Scopes {
16731678 get_annotation_idx : & mut impl FnMut ( ShortIdentifier ) -> Idx < KeyAnnotation > ,
16741679 ) {
16751680 let mut initialize = |scope : & mut Scope , myself : Option < & Self > | {
1676- let ( implicit_captures, final_string_values) = scope. stat . stmts (
1681+ let ( implicit_captures, final_names , final_string_values) = scope. stat . stmts (
16771682 x,
16781683 module_info,
16791684 top_level,
@@ -1683,6 +1688,7 @@ impl Scopes {
16831688 myself,
16841689 ) ;
16851690 scope. implicit_captures = implicit_captures;
1691+ scope. final_names = final_names;
16861692 scope. final_string_values = final_string_values;
16871693 // Presize the flow, as its likely to need as much space as static
16881694 scope. flow . info . reserve ( scope. stat . 0 . capacity ( ) ) ;
@@ -1701,6 +1707,11 @@ impl Scopes {
17011707 }
17021708 }
17031709
1710+ /// Check if a name is declared as `Final` at module scope.
1711+ pub fn is_final_at_module_scope ( & self , name : & Name ) -> bool {
1712+ self . scopes . first ( ) . scope . final_names . contains ( name)
1713+ }
1714+
17041715 /// Look up a Final variable's string literal value in the current scope stack.
17051716 /// Searches from the innermost scope outward, stopping at the first scope
17061717 /// that binds the name, even if it's not Final.
0 commit comments