Class: Evilution::Integration::Loading::RedefinitionRecovery
- Inherits:
-
Object
- Object
- Evilution::Integration::Loading::RedefinitionRecovery
- Defined in:
- lib/evilution/integration/loading/redefinition_recovery.rb
Overview
Some DSLs (Rails 8 enum, define_method guards) raise ArgumentError on re-declaration. On such a conflict we strip constants declared in the source and retry the load once against a fresh namespace.
A second class of idempotency violation comes from gem-internal registries (dry-monads ‘register_mixin`, Rails plugins, etc.) which raise when called a second time in the same process. For these we swallow the error: the class body executed up to the raise point — method defs preceding the registry call are already in place — and any state change blocked by the guard was intentional duplicate-prevention from the gem’s side. Mutations that target a def after such a class-body call would not be applied; emit a one-shot warning so that mode is visible.
Constant Summary collapse
- IDEMPOTENCY_PATTERNS =
[ "already registered", "already initialized", "already exists" ].freeze
Instance Method Summary collapse
- #call(source, &block) ⇒ Object
-
#initialize(constant_names: Evilution::AST::ConstantNames.new) ⇒ RedefinitionRecovery
constructor
A new instance of RedefinitionRecovery.
Constructor Details
#initialize(constant_names: Evilution::AST::ConstantNames.new) ⇒ RedefinitionRecovery
Returns a new instance of RedefinitionRecovery.
25 26 27 |
# File 'lib/evilution/integration/loading/redefinition_recovery.rb', line 25 def initialize(constant_names: Evilution::AST::ConstantNames.new) @constant_names = constant_names end |
Instance Method Details
#call(source, &block) ⇒ Object
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/evilution/integration/loading/redefinition_recovery.rb', line 29 def call(source, &block) block.call rescue ArgumentError => e if redefinition_conflict?(e) remove_defined_constants(source) block.call elsif idempotency_violation?(e) warn_once_for(e) nil else raise end rescue TypeError => e raise unless superclass_mismatch?(e) # `class X < Struct.new(...)` (or Data.define / Class.new in the same # position) returns a fresh anonymous parent class on every call, so the # recorded superclass of the existing X differs from the re-eval's. Ruby # raises TypeError. Simply swallowing would leave the *original* class # in place and silently report the mutation as survived — a false # negative. Instead, strip the constants this source declares and retry # exactly once, mirroring the ArgumentError 'already defined' path. If # the retry still mismatches (genuine inheritance conflict the mutation # cannot resolve), propagate so the mutation reports :error rather than # being silently miscounted. remove_defined_constants(source) block.call end |