You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Class-level memoization fixes recognize both idioms (@x ||=
and return @x if defined?(@x)) and apply freeze-on-memoize,
the Rails-core pattern: the memoization stays exactly as
written and only the memoized value becomes shareable
(.freeze, or Ractor.make_shareable for containers). Ractor.store_if_absent remains the fallback for initializers
with blocks and invalidated caches, emitted as an indented do..end block when the value spans lines.
Frozen memoization is recognized as a pattern, not punished:
the class-level-state check and the runtime sweep downgrade
class state that holds only shareable values to an info note
telling you to warm the cache at boot. Info notes no longer
taint the verdict; a target with only info findings is ready.
--dry-run previews render touching edits as a single hunk
instead of repeating a line in two half-applied states.
Battle-tested against i18n; its full suite passes after --fix-unsafe. Five fixer bugs found and fixed in the process:
autoload conversion keeps the registration and appends the
eager require at the end of the file (converting in
registration order broke mutually referencing files); the
conversion is withheld for files guarded by rescue LoadError
or resolving outside the target (optional dependencies);
require hoisting moved to the unsafe tier (eager loading is not
behavior-preserving for context-sensitive files); shareable_constant_value is only inserted when every constant
is a literal all the way down (Racc parser tables are array
literals full of locals and raise at load otherwise), and the
insertion ignores doc comments that merely look like magic
comments; caches with nil invalidation convert to Ractor.current[key] ||=, preserving reset semantics where store_if_absent would cache the nil forever.
Battle-tested against five more gems (multi_json, jwt, tzinfo,
addressable, public_suffix); every suite passes after --fix-unsafe except two public_suffix assertions that inspect
the moved ivar itself. Six more fixer bugs found and fixed:
edits are spliced by byte offset (multibyte sources, such as
addressable's Unicode tables, were corrupted by
character-indexed splicing); containers holding sync primitives
classify as sync primitives and get no wrap
(Ractor.make_shareable on multi_json's Hash of Mutexes raised
at load); memoized values that are not provably strings get
Ractor.make_shareable instead of .freeze (freezing a
memoized adapter Class froze the class object); ternaries of
string literals classify as strings so they keep a plain
parenthesized .freeze; shareable_constant_value requires
bare literals (a frozen literal is a method call and raises at
assignment, as jwt's NAMED_CURVES showed); empty-container
memo accumulators (@x ||= {} registries) and rescue-guarded
requires (tzinfo's optional tzinfo-data) are left for humans.
New checks trained on the Rails core ractorization effort
(documented in docs/rails_core_best_practices.md): Hash.new
with a default proc (the block survives .freeze), in-place
mutation of screaming-case constants (RENDERERS << key), and define_method with a literal block (the method carries an
unshareable Proc). Class-level state advice now teaches the
Rails copy-on-write idiom: rebuild and refreeze on write, and
compute per-subclass values in the inherited hook.