Class: Tapioca::Runtime::DynamicMixinCompiler
- Inherits:
-
Object
- Object
- Tapioca::Runtime::DynamicMixinCompiler
- Extended by:
- T::Sig
- Includes:
- Reflection
- Defined in:
- lib/tapioca/runtime/dynamic_mixin_compiler.rb
Constant Summary
Constants included from Reflection
Reflection::ANCESTORS_METHOD, Reflection::CLASS_METHOD, Reflection::CONSTANTS_METHOD, Reflection::EQUAL_METHOD, Reflection::METHOD_METHOD, Reflection::NAME_METHOD, Reflection::OBJECT_ID_METHOD, Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Reflection::REQUIRED_FROM_LABELS, Reflection::SINGLETON_CLASS_METHOD, Reflection::SUPERCLASS_METHOD, Reflection::SignatureBlockError, Reflection::UNDEFINED_CONSTANT
Instance Attribute Summary collapse
-
#class_attribute_predicates ⇒ Object
readonly
: Array.
-
#class_attribute_readers ⇒ Object
readonly
: Array.
-
#class_attribute_writers ⇒ Object
readonly
: Array.
-
#dynamic_extends ⇒ Object
readonly
: Array.
-
#dynamic_includes ⇒ Object
readonly
: Array.
-
#instance_attribute_predicates ⇒ Object
readonly
: Array.
-
#instance_attribute_readers ⇒ Object
readonly
: Array.
-
#instance_attribute_writers ⇒ Object
readonly
: Array.
Instance Method Summary collapse
-
#compile_class_attributes(tree) ⇒ Object
: (RBI::Tree tree) -> void.
- #compile_mixes_in_class_methods(tree) ⇒ Object
-
#empty_attributes? ⇒ Boolean
: -> bool.
-
#filtered_mixin?(qualified_mixin_name) ⇒ Boolean
: (String qualified_mixin_name) -> bool.
-
#initialize(constant) ⇒ DynamicMixinCompiler
constructor
: (Module constant) -> void.
-
#module_included_by_another_dynamic_extend?(mod, dynamic_extends) ⇒ Boolean
: (Module mod, Array dynamic_extends) -> bool.
Methods included from Reflection
#abstract_type_of, #ancestors_of, #are_equal?, #class_of, #const_source_location, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #final_module?, #inherited_ancestors_of, #method_of, #name_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #sealed_module?, #signature_of, #signature_of!, #singleton_class_of, #superclass_of
Methods included from AttachedClassOf
Constructor Details
#initialize(constant) ⇒ DynamicMixinCompiler
: (Module constant) -> void
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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 20 def initialize(constant) @constant = constant mixins_from_modules = {}.compare_by_identity class_attribute_readers = [] #: Array[Symbol] class_attribute_writers = [] #: Array[Symbol] class_attribute_predicates = [] #: Array[Symbol] instance_attribute_readers = [] #: Array[Symbol] instance_attribute_writers = [] #: Array[Symbol] instance_attribute_predicates = [] #: Array[Symbol] Class.new do # Override the `self.include` method define_singleton_method(:include) do |mod| # Take a snapshot of the list of singleton class ancestors # before the actual include before = singleton_class.ancestors # Call the actual `include` method with the supplied module ::Tapioca::Runtime::Trackers::Mixin.with_disabled_registration do super(mod).tap do # Take a snapshot of the list of singleton class ancestors # after the actual include after = singleton_class.ancestors # The difference is the modules that are added to the list # of ancestors of the singleton class. Those are all the # modules that were `extend`ed due to the `include` call. # # We record those modules on our lookup table keyed by # the included module with the values being all the modules # that that module pulls into the singleton class. # # We need to reverse the order, since the extend order should # be the inverse of the ancestor order. That is, earlier # extended modules would be later in the ancestor chain. mixins_from_modules[mod] = (after - before).reverse! end end rescue Exception # rubocop:disable Lint/RescueException # this is a best effort, bail if we can't perform this end define_singleton_method(:class_attribute) do |*attrs, **kwargs| class_attribute_readers.concat(attrs) class_attribute_writers.concat(attrs) instance_predicate = kwargs.fetch(:instance_predicate, true) instance_accessor = kwargs.fetch(:instance_accessor, true) instance_reader = kwargs.fetch(:instance_reader, instance_accessor) instance_writer = kwargs.fetch(:instance_writer, instance_accessor) if instance_reader instance_attribute_readers.concat(attrs) end if instance_writer instance_attribute_writers.concat(attrs) end if instance_predicate class_attribute_predicates.concat(attrs) if instance_reader instance_attribute_predicates.concat(attrs) end end super(*attrs, **kwargs) if defined?(super) end # rubocop:disable Style/MissingRespondToMissing T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) } def method_missing(symbol, *args) # We need this here so that we can handle any random instance # method calls on the fake including class that may be done by # the included module during the `self.included` hook. end class << self extend T::Sig T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) } def method_missing(symbol, *args) # Similarly, we need this here so that we can handle any # random class method calls on the fake including class # that may be done by the included module during the # `self.included` hook. end end # rubocop:enable Style/MissingRespondToMissing end.include(constant) # The value that corresponds to the original included constant # is the list of all dynamically extended modules because of that # constant. We grab that value by deleting the key for the original # constant. @dynamic_extends = mixins_from_modules.delete(constant) || [] #: Array[Module] # Since we deleted the original constant from the list of keys, all # the keys that remain are the ones that are dynamically included modules # during the include of the original constant. @dynamic_includes = mixins_from_modules.keys #: Array[Module] @class_attribute_readers = class_attribute_readers #: Array[Symbol] @class_attribute_writers = class_attribute_writers #: Array[Symbol] @class_attribute_predicates = class_attribute_predicates #: Array[Symbol] @instance_attribute_readers = instance_attribute_readers #: Array[Symbol] @instance_attribute_writers = instance_attribute_writers #: Array[Symbol] @instance_attribute_predicates = instance_attribute_predicates #: Array[Symbol] end |
Instance Attribute Details
#class_attribute_predicates ⇒ Object (readonly)
: Array
14 15 16 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 14 def class_attribute_predicates @class_attribute_predicates end |
#class_attribute_readers ⇒ Object (readonly)
: Array
14 15 16 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 14 def class_attribute_readers @class_attribute_readers end |
#class_attribute_writers ⇒ Object (readonly)
: Array
14 15 16 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 14 def class_attribute_writers @class_attribute_writers end |
#dynamic_extends ⇒ Object (readonly)
: Array
11 12 13 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 11 def dynamic_extends @dynamic_extends end |
#dynamic_includes ⇒ Object (readonly)
: Array
11 12 13 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 11 def dynamic_includes @dynamic_includes end |
#instance_attribute_predicates ⇒ Object (readonly)
: Array
17 18 19 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 17 def instance_attribute_predicates @instance_attribute_predicates end |
#instance_attribute_readers ⇒ Object (readonly)
: Array
17 18 19 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 17 def instance_attribute_readers @instance_attribute_readers end |
#instance_attribute_writers ⇒ Object (readonly)
: Array
17 18 19 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 17 def instance_attribute_writers @instance_attribute_writers end |
Instance Method Details
#compile_class_attributes(tree) ⇒ Object
: (RBI::Tree tree) -> void
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 137 def compile_class_attributes(tree) return if empty_attributes? # Create a synthetic module to hold the generated class methods tree << RBI::Module.new("GeneratedClassMethods") do |mod| class_attribute_readers.each do |attribute| mod << RBI::Method.new(attribute.to_s) end class_attribute_writers.each do |attribute| mod << RBI::Method.new("#{attribute}=") do |method| method << RBI::ReqParam.new("value") end end class_attribute_predicates.each do |attribute| mod << RBI::Method.new("#{attribute}?") end end # Create a synthetic module to hold the generated instance methods tree << RBI::Module.new("GeneratedInstanceMethods") do |mod| instance_attribute_readers.each do |attribute| mod << RBI::Method.new(attribute.to_s) end instance_attribute_writers.each do |attribute| mod << RBI::Method.new("#{attribute}=") do |method| method << RBI::ReqParam.new("value") end end instance_attribute_predicates.each do |attribute| mod << RBI::Method.new("#{attribute}?") end end # Add a mixes_in_class_methods and include for the generated modules tree << RBI::MixesInClassMethods.new("GeneratedClassMethods") tree << RBI::Include.new("GeneratedInstanceMethods") end |
#compile_mixes_in_class_methods(tree) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 180 def compile_mixes_in_class_methods(tree) includes = dynamic_includes.filter_map do |mod| qname = qualified_name_of(mod) next if qname.nil? || qname.empty? next if filtered_mixin?(qname) tree << RBI::Include.new(qname) mod end # If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the # constant itself mixed_in_class_methods = dynamic_extends.select do |mod| mod != @constant && !module_included_by_another_dynamic_extend?(mod, dynamic_extends) end return [[], []] if mixed_in_class_methods.empty? mixed_in_class_methods.each do |mod| qualified_name = qualified_name_of(mod) next if qualified_name.nil? || qualified_name.empty? next if filtered_mixin?(qualified_name) tree << RBI::MixesInClassMethods.new(qualified_name) end [mixed_in_class_methods, includes] rescue [[], []] # silence errors end |
#empty_attributes? ⇒ Boolean
: -> bool
132 133 134 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 132 def empty_attributes? @class_attribute_readers.empty? && @class_attribute_writers.empty? end |
#filtered_mixin?(qualified_mixin_name) ⇒ Boolean
: (String qualified_mixin_name) -> bool
222 223 224 225 226 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 222 def filtered_mixin?(qualified_mixin_name) # filter T:: namespace mixins that aren't T::Props # T::Props and subconstants have semantic value qualified_mixin_name.start_with?("::T::") && !qualified_mixin_name.start_with?("::T::Props") end |
#module_included_by_another_dynamic_extend?(mod, dynamic_extends) ⇒ Boolean
: (Module mod, Array dynamic_extends) -> bool
215 216 217 218 219 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 215 def module_included_by_another_dynamic_extend?(mod, dynamic_extends) dynamic_extends.any? do |dynamic_extend| mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod) end end |