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::UNDEFINED_CONSTANT
Instance Attribute Summary collapse
-
#class_attribute_predicates ⇒ Object
readonly
Returns the value of attribute class_attribute_predicates.
-
#class_attribute_readers ⇒ Object
readonly
Returns the value of attribute class_attribute_readers.
-
#class_attribute_writers ⇒ Object
readonly
Returns the value of attribute class_attribute_writers.
-
#dynamic_extends ⇒ Object
readonly
Returns the value of attribute dynamic_extends.
-
#dynamic_includes ⇒ Object
readonly
Returns the value of attribute dynamic_includes.
-
#instance_attribute_predicates ⇒ Object
readonly
Returns the value of attribute instance_attribute_predicates.
-
#instance_attribute_readers ⇒ Object
readonly
Returns the value of attribute instance_attribute_readers.
-
#instance_attribute_writers ⇒ Object
readonly
Returns the value of attribute instance_attribute_writers.
Instance Method Summary collapse
- #compile_class_attributes(tree) ⇒ Object
- #compile_mixes_in_class_methods(tree) ⇒ Object
- #empty_attributes? ⇒ Boolean
- #filtered_mixin?(qualified_mixin_name) ⇒ Boolean
-
#initialize(constant) ⇒ DynamicMixinCompiler
constructor
A new instance of DynamicMixinCompiler.
- #module_included_by_another_dynamic_extend?(mod, dynamic_extends) ⇒ Boolean
Methods included from Reflection
#abstract_type_of, #ancestors_of, #are_equal?, #class_of, #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
Returns a new instance of DynamicMixinCompiler.
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 = T.let([], T::Array[Symbol]) class_attribute_writers = T.let([], T::Array[Symbol]) class_attribute_predicates = T.let([], T::Array[Symbol]) instance_attribute_readers = T.let([], T::Array[Symbol]) instance_attribute_writers = T.let([], T::Array[Symbol]) instance_attribute_predicates = T.let([], T::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 = T.let(mixins_from_modules.delete(constant) || [], T::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 = T.let(mixins_from_modules.keys, T::Array[Module]) @class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol]) @class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol]) @class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol]) @instance_attribute_readers = T.let(instance_attribute_readers, T::Array[Symbol]) @instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol]) @instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol]) end |
Instance Attribute Details
#class_attribute_predicates ⇒ Object (readonly)
Returns the value of attribute class_attribute_predicates.
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)
Returns the value of attribute class_attribute_readers.
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)
Returns the value of attribute class_attribute_writers.
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)
Returns the value of attribute dynamic_extends.
11 12 13 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 11 def dynamic_extends @dynamic_extends end |
#dynamic_includes ⇒ Object (readonly)
Returns the value of attribute dynamic_includes.
11 12 13 |
# File 'lib/tapioca/runtime/dynamic_mixin_compiler.rb', line 11 def dynamic_includes @dynamic_includes end |
#instance_attribute_predicates ⇒ Object (readonly)
Returns the value of attribute instance_attribute_predicates.
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)
Returns the value of attribute instance_attribute_readers.
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)
Returns the value of attribute instance_attribute_writers.
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
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
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
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
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 |