@@ -632,6 +632,11 @@ fn accessor_key_expr(key: MethodKeyKind) -> Expr {
632632}
633633
634634pub ( super ) fn lower_object ( ctx : & mut LoweringContext , obj : & ast:: ObjectLit ) -> Result < Expr > {
635+ // A directly exported object is the producer boundary for #8775. Consume
636+ // the marker here so nested literals continue through their ordinary
637+ // lowering paths.
638+ let prefer_exported_method_shape_seed =
639+ std:: mem:: take ( & mut ctx. prefer_exported_method_shape_seed ) ;
635640 // Phase 3: closed-shape object literals lower to `new __AnonShape_N()`
636641 // so downstream field access hits the direct-GEP fast path. The
637642 // anon class is synthesized as a shape-only class with constructor
@@ -1074,7 +1079,8 @@ pub(super) fn lower_object(ctx: &mut LoweringContext, obj: &ast::ObjectLit) -> R
10741079 collect_local_refs_expr ( value, & mut refs, & mut visited_closures) ;
10751080 !refs. contains ( & param_id)
10761081 } ;
1077- let can_emit_static_object = has_method
1082+ let can_emit_static_object = !prefer_exported_method_shape_seed
1083+ && has_method
10781084 && !has_spread
10791085 && !has_accessor
10801086 && !has_computed
@@ -1105,8 +1111,58 @@ pub(super) fn lower_object(ctx: &mut LoweringContext, obj: &ast::ObjectLit) -> R
11051111 return Ok ( Expr :: Object ( props) ) ;
11061112 }
11071113
1108- // Pass 2: build the IIFE wrapper. `__o` starts as an empty object
1109- // and each op mutates it in source order.
1114+ // Imported-object capabilities need a producer-authoritative non-zero
1115+ // class and stable slot order. Keep directly exported, static-key
1116+ // method literals in the source-ordered IIFE, but seed it with a
1117+ // shape-only anonymous class instead of `{}`. This composes with the
1118+ // direct-object optimization above: non-exported literals still skip
1119+ // the IIFE, while exported literals retain the metadata required by a
1120+ // consumer's guarded direct call.
1121+ let static_shape_seed = if prefer_exported_method_shape_seed
1122+ && !has_spread
1123+ && !has_accessor
1124+ && !has_computed
1125+ && !has_proto_setter
1126+ {
1127+ let mut names = Vec :: new ( ) ;
1128+ let mut seen = std:: collections:: HashSet :: new ( ) ;
1129+ let mut eligible = true ;
1130+ for op in & ops {
1131+ let name = match op {
1132+ SpreadOp :: Set {
1133+ key : Expr :: String ( name) ,
1134+ infer_name : false ,
1135+ ..
1136+ }
1137+ | SpreadOp :: MethodByName { key : name, .. } => name,
1138+ _ => {
1139+ eligible = false ;
1140+ break ;
1141+ }
1142+ } ;
1143+ if seen. insert ( name. clone ( ) ) {
1144+ names. push ( name. clone ( ) ) ;
1145+ }
1146+ }
1147+ eligible. then ( || {
1148+ let fields: Vec < ( String , Type ) > =
1149+ names. iter ( ) . map ( |name| ( name. clone ( ) , Type :: Any ) ) . collect ( ) ;
1150+ let class_name = ctx. synthesize_anon_shape_class ( & fields) ;
1151+ Expr :: New {
1152+ class_name,
1153+ args : names. iter ( ) . map ( |_| Expr :: Undefined ) . collect ( ) ,
1154+ type_args : Vec :: new ( ) ,
1155+ byte_offset : 0 ,
1156+ cap_args_appended : 0 ,
1157+ }
1158+ } )
1159+ } else {
1160+ None
1161+ } ;
1162+
1163+ // Pass 2: build the IIFE wrapper. `__o` starts as the exported stable
1164+ // shape seed when eligible, otherwise as an empty object, and each op
1165+ // mutates it in source order.
11101166 let extern_call = |name : & str , args : Vec < Expr > | Expr :: Call {
11111167 callee : Box :: new ( Expr :: ExternFuncRef {
11121168 name : name. to_string ( ) ,
@@ -1250,7 +1306,7 @@ pub(super) fn lower_object(ctx: &mut LoweringContext, obj: &ast::ObjectLit) -> R
12501306 } ;
12511307 return Ok ( Expr :: Call {
12521308 callee : Box :: new ( closure) ,
1253- args : vec ! [ Expr :: Object ( Vec :: new( ) ) ] ,
1309+ args : vec ! [ static_shape_seed . unwrap_or_else ( || Expr :: Object ( Vec :: new( ) ) ) ] ,
12541310 type_args : vec ! [ ] ,
12551311 byte_offset : 0 ,
12561312 } ) ;
0 commit comments