-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
descendants_tracker.rb
112 lines (95 loc) · 2.95 KB
/
descendants_tracker.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# frozen_string_literal: true
require "weakref"
module ActiveSupport
# = Active Support Descendants Tracker
#
# This module provides an internal implementation to track descendants
# which is faster than iterating through +ObjectSpace+.
#
# However Ruby 3.1 provide a fast native +Class#subclasses+ method,
# so if you know your code won't be executed on older rubies, including
# +ActiveSupport::DescendantsTracker+ does not provide any benefit.
module DescendantsTracker
@clear_disabled = false
if RUBY_ENGINE == "ruby"
# On MRI `ObjectSpace::WeakMap` keys are weak references.
# So we can simply use WeakMap as a `Set`.
class WeakSet < ObjectSpace::WeakMap # :nodoc:
alias_method :to_a, :keys
def <<(object)
self[object] = true
end
end
else
# On TruffleRuby `ObjectSpace::WeakMap` keys are strong references.
# So we use `object_id` as a key and the actual object as a value.
#
# JRuby for now doesn't have Class#descendant, but when it will, it will likely
# have the same WeakMap semantic than Truffle so we future proof this as much as possible.
class WeakSet # :nodoc:
def initialize
@map = ObjectSpace::WeakMap.new
end
def [](object)
@map.key?(object.object_id)
end
alias_method :include?, :[]
def []=(object, _present)
@map[object.object_id] = object
end
def to_a
@map.values
end
def <<(object)
self[object] = true
end
end
end
@excluded_descendants = WeakSet.new
module ReloadedClassesFiltering # :nodoc:
def subclasses
DescendantsTracker.reject!(super)
end
def descendants
DescendantsTracker.reject!(super)
end
end
class << self
def disable_clear! # :nodoc:
unless @clear_disabled
@clear_disabled = true
ReloadedClassesFiltering.remove_method(:subclasses)
ReloadedClassesFiltering.remove_method(:descendants)
@excluded_descendants = nil
end
end
def clear(classes) # :nodoc:
raise "DescendantsTracker.clear was disabled because config.enable_reloading is false" if @clear_disabled
classes.each do |klass|
@excluded_descendants << klass
klass.descendants.each do |descendant|
@excluded_descendants << descendant
end
end
end
def reject!(classes) # :nodoc:
if @excluded_descendants
classes.reject! { |d| @excluded_descendants.include?(d) }
end
classes
end
end
class << self
def subclasses(klass)
klass.subclasses
end
def descendants(klass)
klass.descendants
end
end
def descendants
subclasses = DescendantsTracker.reject!(self.subclasses)
subclasses.concat(subclasses.flat_map(&:descendants))
end
end
end