@@ -355,6 +355,8 @@ impl Executor {
355355 } else {
356356 current_func. lexical_class ( globals)
357357 } ;
358+ // Ancestor-phase lookup starts at the *runtime* cref too.
359+ let lexical_class = self . runtime_innermost_cref ( globals, lexical_class, current_func) ;
358360 let module = globals[ lexical_class] . get_module ( ) ;
359361 match self . get_constant_superclass ( globals, module, name) ? {
360362 Some ( v) => return Ok ( Some ( v) ) ,
@@ -482,7 +484,14 @@ impl Executor {
482484 Some ( iseq) => iseq,
483485 None => return false ,
484486 } ;
487+ let mut first = true ;
485488 for module in globals. store [ iseq] . lexical_context . iter ( ) . rev ( ) . copied ( ) {
489+ let module = if first {
490+ first = false ;
491+ self . runtime_innermost_cref ( globals, module, current_func)
492+ } else {
493+ module
494+ } ;
486495 if Self :: probe_constant_at ( globals, module, name) {
487496 return true ;
488497 }
@@ -585,6 +594,119 @@ impl Executor {
585594 Self :: probe_constant_superclass_qualified ( globals, globals. store [ parent] . get_module ( ) , name)
586595 }
587596
597+ /// Recover the *runtime* innermost cref when the statically stamped
598+ /// one is stale. A `def` inside a re-executed class body (most
599+ /// notably inside `class << obj`, or a `class X` nested in one)
600+ /// shares its iseq across executions while `enter_classdef`
601+ /// re-stamps the static `lexical_context` per execution (last write
602+ /// wins) — so the recorded innermost class may belong to a
603+ /// *different* execution. Detection: the stamped class is no longer
604+ /// on the receiver's ancestor chain. Recovery: the runtime cref is
605+ /// the ancestor that owns the executing method (for a plain `def`
606+ /// in a class body, the innermost cref IS the defining class).
607+ pub ( crate ) fn runtime_innermost_cref (
608+ & self ,
609+ globals : & Globals ,
610+ statically : ClassId ,
611+ current_func : FuncId ,
612+ ) -> ClassId {
613+ // Staleness is only possible when the def is lexically nested
614+ // (at any depth) inside a `class << obj` body — every other
615+ // classdef form re-opens the same class on re-execution, so
616+ // the stamp cannot go stale. Never substitute otherwise: a
617+ // singleton def (`def a.meth`) legitimately has a cref that is
618+ // unrelated to the receiver's ancestry.
619+ if !globals. store [ current_func]
620+ . is_iseq ( )
621+ . is_some_and ( |iseq| globals. store [ iseq] . in_singleton_lexical )
622+ {
623+ return statically;
624+ }
625+ let self_val = self . cfp ( ) . lfp ( ) . self_val ( ) ;
626+ if Self :: static_cref_plausible ( globals, self_val, statically) {
627+ return statically;
628+ }
629+ let self_class = self_val. class ( ) ;
630+ let mut module = Some ( globals. store [ self_class] . get_module ( ) ) ;
631+ while let Some ( m) = module {
632+ if globals. store . class_owns_func ( m. id ( ) , current_func) {
633+ // For a plain `def` the owner IS the cref. For a
634+ // singleton def (`def self.f` in a class body nested
635+ // under `class << obj`) the owner is the metaclass
636+ // while the cref is the attached class itself — the
637+ // stamped entry being a non-singleton class tells the
638+ // two forms apart.
639+ if globals. store [ statically] . get_module ( ) . is_singleton ( ) . is_none ( ) {
640+ if let Some ( attached) = globals. store [ m. id ( ) ]
641+ . get_module ( )
642+ . is_singleton ( )
643+ . and_then ( |v| v. is_class_or_module ( ) )
644+ {
645+ return attached. id ( ) ;
646+ }
647+ }
648+ return m. id ( ) ;
649+ }
650+ module = m. superclass ( ) ;
651+ }
652+ statically
653+ }
654+
655+ /// Is `statically` a plausible innermost cref for this receiver?
656+ /// True when it lies on the receiver's class ancestry (a plain
657+ /// `def` in a class body), or — for a class/module receiver — on
658+ /// the receiver's *own* ancestry: a singleton method (`def M.f`,
659+ /// `class << self` inside `module M`) runs with self == M while
660+ /// its cref is M or an enclosing module, neither of which appears
661+ /// among the metaclass's ancestors.
662+ fn static_cref_plausible ( globals : & Globals , self_val : Value , statically : ClassId ) -> bool {
663+ if globals
664+ . store
665+ . static_cref_plausible_for_class ( self_val. class ( ) , statically)
666+ {
667+ return true ;
668+ }
669+ // Checked directly on the value as well: a class/module
670+ // receiver whose metaclass has not been materialized has a
671+ // plain class (`Class`/`Module`) that carries no attachment.
672+ if let Some ( m) = self_val. is_class_or_module ( ) {
673+ if globals. store . class_in_ancestors ( m. id ( ) , statically) {
674+ return true ;
675+ }
676+ }
677+ false
678+ }
679+
680+ /// The self-dependence key for the constant cache: `Some(self's
681+ /// class)` for any method lexically nested under `class << obj`.
682+ /// Resolution there goes through [`Self::runtime_innermost_cref`],
683+ /// which depends only on self's class, so the cached result is
684+ /// valid exactly for the same self class. The key must NOT be
685+ /// weakened to `None` when the static stamp happens to be
686+ /// plausible for the current receiver: plausibility is relative
687+ /// to the *current* stamp, and a later re-execution of the
688+ /// `class << obj` body re-stamps it, silently invalidating what a
689+ /// `None`-keyed entry resolved against.
690+ pub ( crate ) fn const_lexical_self_key (
691+ & self ,
692+ globals : & Globals ,
693+ current_func : FuncId ,
694+ ) -> Option < ClassId > {
695+ let iseq = globals. store [ current_func] . is_iseq ( ) ?;
696+ if !globals. store [ iseq] . in_singleton_lexical {
697+ return None ;
698+ }
699+ // For a class/module receiver (a classdef body, or `def
700+ // self.f`), key by the module's own id: its metaclass may be
701+ // unmaterialized, in which case `.class()` is the shared
702+ // `Class`/`Module` and would alias distinct receivers.
703+ let self_val = self . cfp ( ) . lfp ( ) . self_val ( ) ;
704+ Some ( match self_val. is_class_or_module ( ) {
705+ Some ( m) => m. id ( ) ,
706+ None => self_val. class ( ) ,
707+ } )
708+ }
709+
588710 fn search_lexical_stack (
589711 & mut self ,
590712 globals : & mut Globals ,
@@ -602,12 +724,15 @@ impl Executor {
602724 Some ( iseq) => iseq,
603725 None => return Ok ( None ) ,
604726 } ;
605- let stack = globals. store [ iseq]
727+ let mut stack = globals. store [ iseq]
606728 . lexical_context
607729 . iter ( )
608730 . rev ( )
609731 . cloned ( )
610732 . collect :: < Vec < _ > > ( ) ;
733+ if let Some ( first) = stack. first_mut ( ) {
734+ * first = self . runtime_innermost_cref ( globals, * first, current_func) ;
735+ }
611736 for module in stack {
612737 if globals. store . get_constant ( module, name) . is_some ( ) {
613738 // Trigger autoload / read the value. If the autoload
0 commit comments