Class: Evilution::Integration::Loading::RedefinitionRecovery Private

Inherits:
Object
  • Object
show all
Defined in:
lib/evilution/integration/loading/redefinition_recovery.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

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 =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

[
  "already registered",
  "already initialized",
  "already exists"
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(constant_names: Evilution::AST::ConstantNames.new) ⇒ RedefinitionRecovery

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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