tapioca 0.6.4 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +86 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
  46. data/lib/tapioca/dsl/pipeline.rb +169 -0
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  66. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  72. data/lib/tapioca/internal.rb +18 -10
  73. data/lib/tapioca/rbi_ext/model.rb +14 -50
  74. data/lib/tapioca/rbi_formatter.rb +37 -0
  75. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  76. data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +157 -0
  79. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  80. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  81. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  82. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  83. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  84. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  87. data/lib/tapioca/static/symbol_loader.rb +83 -0
  88. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  89. data/lib/tapioca/version.rb +1 -1
  90. data/lib/tapioca.rb +2 -7
  91. metadata +83 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
  96. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  97. data/lib/tapioca/compilers/sorbet.rb +0 -59
  98. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  99. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  100. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  101. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  102. data/lib/tapioca/generators/todo.rb +0 -76
  103. data/lib/tapioca/generators.rb +0 -9
  104. data/lib/tapioca/generic_type_registry.rb +0 -164
  105. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  106. data/lib/tapioca/loader.rb +0 -119
  107. data/lib/tapioca/reflection.rb +0 -151
  108. data/lib/tapioca/trackers/autoload.rb +0 -70
  109. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  110. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -0,0 +1,123 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ class Loader
7
+ extend(T::Sig)
8
+
9
+ sig do
10
+ params(gemfile: Tapioca::Gemfile, initialize_file: T.nilable(String), require_file: T.nilable(String)).void
11
+ end
12
+ def load_bundle(gemfile, initialize_file, require_file)
13
+ require_helper(initialize_file)
14
+
15
+ load_rails_application
16
+
17
+ gemfile.require_bundle
18
+
19
+ require_helper(require_file)
20
+
21
+ load_rails_engines
22
+ end
23
+
24
+ sig { params(environment_load: T::Boolean, eager_load: T::Boolean).void }
25
+ def load_rails_application(environment_load: false, eager_load: false)
26
+ return unless File.exist?("config/application.rb")
27
+
28
+ silence_deprecations
29
+
30
+ if environment_load
31
+ safe_require("./config/environment")
32
+ else
33
+ safe_require("./config/application")
34
+ end
35
+
36
+ eager_load_rails_app if eager_load
37
+ end
38
+
39
+ private
40
+
41
+ sig { params(file: T.nilable(String)).void }
42
+ def require_helper(file)
43
+ return unless file
44
+ file = File.absolute_path(file)
45
+ return unless File.exist?(file)
46
+
47
+ require(file)
48
+ end
49
+
50
+ sig { returns(T::Array[T.untyped]) }
51
+ def rails_engines
52
+ return [] unless Object.const_defined?("Rails::Engine")
53
+
54
+ # We can use `Class#descendants` here, since we know Rails is loaded
55
+ Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
56
+ end
57
+
58
+ sig { params(path: String).void }
59
+ def safe_require(path)
60
+ require path
61
+ rescue LoadError
62
+ nil
63
+ end
64
+
65
+ sig { void }
66
+ def silence_deprecations
67
+ # Stop any ActiveSupport Deprecations from being reported
68
+ Object.const_get("ActiveSupport::Deprecation").silenced = true
69
+ rescue NameError
70
+ nil
71
+ end
72
+
73
+ sig { void }
74
+ def eager_load_rails_app
75
+ rails = Object.const_get("Rails")
76
+ application = rails.application
77
+
78
+ if Object.const_defined?("ActiveSupport")
79
+ Object.const_get("ActiveSupport").run_load_hooks(
80
+ :before_eager_load,
81
+ application
82
+ )
83
+ end
84
+
85
+ if Object.const_defined?("Zeitwerk::Loader")
86
+ zeitwerk_loader = Object.const_get("Zeitwerk::Loader")
87
+ zeitwerk_loader.eager_load_all
88
+ end
89
+
90
+ if rails.respond_to?(:autoloaders) && rails.autoloaders.zeitwerk_enabled?
91
+ rails.autoloaders.each(&:eager_load)
92
+ end
93
+
94
+ if application.config.respond_to?(:eager_load_namespaces)
95
+ application.config.eager_load_namespaces.each(&:eager_load!)
96
+ end
97
+ end
98
+
99
+ sig { void }
100
+ def load_rails_engines
101
+ rails_engines.each do |engine|
102
+ errored_files = []
103
+
104
+ engine.config.eager_load_paths.each do |load_path|
105
+ Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
106
+ require(file)
107
+ rescue LoadError, StandardError
108
+ errored_files << file
109
+ end
110
+ end
111
+
112
+ # Try files that have errored one more time
113
+ # It might have been a load order problem
114
+ errored_files.each do |file|
115
+ require(file)
116
+ rescue LoadError, StandardError
117
+ nil
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,157 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ module Reflection
7
+ extend T::Sig
8
+ extend self
9
+
10
+ CLASS_METHOD = T.let(Kernel.instance_method(:class), UnboundMethod)
11
+ CONSTANTS_METHOD = T.let(Module.instance_method(:constants), UnboundMethod)
12
+ NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
13
+ SINGLETON_CLASS_METHOD = T.let(Object.instance_method(:singleton_class), UnboundMethod)
14
+ ANCESTORS_METHOD = T.let(Module.instance_method(:ancestors), UnboundMethod)
15
+ SUPERCLASS_METHOD = T.let(Class.instance_method(:superclass), UnboundMethod)
16
+ OBJECT_ID_METHOD = T.let(BasicObject.instance_method(:__id__), UnboundMethod)
17
+ EQUAL_METHOD = T.let(BasicObject.instance_method(:equal?), UnboundMethod)
18
+ PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
19
+ PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
20
+ PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
21
+ METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
22
+
23
+ sig do
24
+ params(
25
+ symbol: String,
26
+ inherit: T::Boolean,
27
+ namespace: Module
28
+ ).returns(BasicObject).checked(:never)
29
+ end
30
+ def constantize(symbol, inherit: false, namespace: Object)
31
+ namespace.const_get(symbol, inherit)
32
+ rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
33
+ nil
34
+ end
35
+
36
+ sig { params(object: BasicObject).returns(Class).checked(:never) }
37
+ def class_of(object)
38
+ CLASS_METHOD.bind(object).call
39
+ end
40
+
41
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
42
+ def constants_of(constant)
43
+ CONSTANTS_METHOD.bind(constant).call(false)
44
+ end
45
+
46
+ sig { params(constant: Module).returns(T.nilable(String)) }
47
+ def name_of(constant)
48
+ name = NAME_METHOD.bind(constant).call
49
+ name&.start_with?("#<") ? nil : name
50
+ end
51
+
52
+ sig { params(constant: Module).returns(Class) }
53
+ def singleton_class_of(constant)
54
+ SINGLETON_CLASS_METHOD.bind(constant).call
55
+ end
56
+
57
+ sig { params(constant: Module).returns(T::Array[Module]) }
58
+ def ancestors_of(constant)
59
+ ANCESTORS_METHOD.bind(constant).call
60
+ end
61
+
62
+ sig { params(constant: Class).returns(T.nilable(Class)) }
63
+ def superclass_of(constant)
64
+ SUPERCLASS_METHOD.bind(constant).call
65
+ end
66
+
67
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
68
+ def object_id_of(object)
69
+ OBJECT_ID_METHOD.bind(object).call
70
+ end
71
+
72
+ sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
73
+ def are_equal?(object, other)
74
+ EQUAL_METHOD.bind(object).call(other)
75
+ end
76
+
77
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
78
+ def public_instance_methods_of(constant)
79
+ PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
80
+ end
81
+
82
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
83
+ def protected_instance_methods_of(constant)
84
+ PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
85
+ end
86
+
87
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
88
+ def private_instance_methods_of(constant)
89
+ PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
90
+ end
91
+
92
+ sig { params(constant: Module).returns(T::Array[Module]) }
93
+ def inherited_ancestors_of(constant)
94
+ if Class === constant
95
+ ancestors_of(superclass_of(constant) || Object)
96
+ else
97
+ Module.ancestors
98
+ end
99
+ end
100
+
101
+ sig { params(constant: Module).returns(T.nilable(String)) }
102
+ def qualified_name_of(constant)
103
+ name = name_of(constant)
104
+ return if name.nil?
105
+
106
+ if name.start_with?("::")
107
+ name
108
+ else
109
+ "::#{name}"
110
+ end
111
+ end
112
+
113
+ sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
114
+ def signature_of(method)
115
+ T::Utils.signature_for_method(method)
116
+ rescue LoadError, StandardError
117
+ nil
118
+ end
119
+
120
+ sig { params(type: T::Types::Base).returns(String) }
121
+ def name_of_type(type)
122
+ type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
123
+ end
124
+
125
+ sig { params(constant: Module, method: Symbol).returns(Method) }
126
+ def method_of(constant, method)
127
+ METHOD_METHOD.bind(constant).call(method)
128
+ end
129
+
130
+ # Returns an array with all classes that are < than the supplied class.
131
+ #
132
+ # class C; end
133
+ # descendants_of(C) # => []
134
+ #
135
+ # class B < C; end
136
+ # descendants_of(C) # => [B]
137
+ #
138
+ # class A < B; end
139
+ # descendants_of(C) # => [B, A]
140
+ #
141
+ # class D < C; end
142
+ # descendants_of(C) # => [B, A, D]
143
+ sig do
144
+ type_parameters(:U)
145
+ .params(klass: T.all(Class, T.type_parameter(:U)))
146
+ .returns(T::Array[T.type_parameter(:U)])
147
+ end
148
+ def descendants_of(klass)
149
+ result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
150
+ T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
151
+ end
152
+
153
+ T.unsafe(result)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,72 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ module Trackers
7
+ module Autoload
8
+ extend T::Sig
9
+
10
+ NOOP_METHOD = ->(*_args, **_kwargs, &_block) {}
11
+
12
+ @constant_names_registered_for_autoload = T.let([], T::Array[String])
13
+
14
+ class << self
15
+ extend T::Sig
16
+
17
+ sig { void }
18
+ def eager_load_all!
19
+ with_disabled_exits do
20
+ until @constant_names_registered_for_autoload.empty?
21
+ # Grab the next constant name
22
+ constant_name = T.must(@constant_names_registered_for_autoload.shift)
23
+ # Trigger autoload by constantizing the registered name
24
+ Reflection.constantize(constant_name, inherit: true)
25
+ end
26
+ end
27
+ end
28
+
29
+ sig { params(constant_name: String).void }
30
+ def register(constant_name)
31
+ @constant_names_registered_for_autoload << constant_name
32
+ end
33
+
34
+ sig do
35
+ type_parameters(:Result)
36
+ .params(block: T.proc.returns(T.type_parameter(:Result)))
37
+ .returns(T.type_parameter(:Result))
38
+ end
39
+ def with_disabled_exits(&block)
40
+ original_abort = Kernel.instance_method(:abort)
41
+ original_exit = Kernel.instance_method(:exit)
42
+
43
+ begin
44
+ Kernel.define_method(:abort, NOOP_METHOD)
45
+ Kernel.define_method(:exit, NOOP_METHOD)
46
+
47
+ block.call
48
+ ensure
49
+ Kernel.define_method(:exit, original_exit)
50
+ Kernel.define_method(:abort, original_abort)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # We need to do the alias-method-chain dance since Bootsnap does the same,
60
+ # and prepended modules and alias-method-chain don't play well together.
61
+ #
62
+ # So, why does Bootsnap do alias-method-chain and not prepend? Glad you asked!
63
+ # That's because RubyGems does alias-method-chain for Kernel#require and such,
64
+ # so, if Bootsnap were to do prepend, it might end up breaking RubyGems.
65
+ class Module
66
+ alias_method(:autoload_without_tapioca, :autoload)
67
+
68
+ def autoload(const_name, path)
69
+ Tapioca::Runtime::Trackers::Autoload.register("#{self}::#{const_name}")
70
+ autoload_without_tapioca(const_name, path)
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "set"
5
+
6
+ module Tapioca
7
+ module Runtime
8
+ module Trackers
9
+ # Registers a TracePoint immediately upon load to track points at which
10
+ # classes and modules are opened for definition. This is used to track
11
+ # correspondence between classes/modules and files, as this information isn't
12
+ # available in the ruby runtime without extra accounting.
13
+ module ConstantDefinition
14
+ extend Reflection
15
+
16
+ @class_files = {}
17
+
18
+ # Immediately activated upon load. Observes class/module definition.
19
+ TracePoint.trace(:class) do |tp|
20
+ unless tp.self.singleton_class?
21
+ key = name_of(tp.self)
22
+ file = tp.path
23
+ if file == "(eval)"
24
+ file = T.must(caller_locations)
25
+ .drop_while { |loc| loc.path == "(eval)" }
26
+ .first&.path
27
+ end
28
+ @class_files[key] ||= Set.new
29
+ @class_files[key] << file
30
+ end
31
+ end
32
+
33
+ # Returns the files in which this class or module was opened. Doesn't know
34
+ # about situations where the class was opened prior to +require+ing,
35
+ # or where metaprogramming was used via +eval+, etc.
36
+ def self.files_for(klass)
37
+ name = String === klass ? klass : name_of(klass)
38
+ files = @class_files[name]
39
+ files || Set.new
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,80 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ module Trackers
7
+ module Mixin
8
+ extend T::Sig
9
+
10
+ @mixin_map = {}.compare_by_identity
11
+
12
+ class Type < T::Enum
13
+ enums do
14
+ Prepend = new
15
+ Include = new
16
+ Extend = new
17
+ end
18
+ end
19
+
20
+ sig do
21
+ params(
22
+ constant: Module,
23
+ mod: Module,
24
+ mixin_type: Type,
25
+ locations: T.nilable(T::Array[Thread::Backtrace::Location])
26
+ ).void
27
+ end
28
+ def self.register(constant, mod, mixin_type, locations)
29
+ locations ||= []
30
+ locations.map!(&:absolute_path).uniq!
31
+ locs = mixin_locations_for(constant)
32
+ locs.fetch(mixin_type).store(mod, T.cast(locations, T::Array[String]))
33
+ end
34
+
35
+ sig { params(constant: Module).returns(T::Hash[Type, T::Hash[Module, T::Array[String]]]) }
36
+ def self.mixin_locations_for(constant)
37
+ @mixin_map[constant] ||= {
38
+ Type::Prepend => {}.compare_by_identity,
39
+ Type::Include => {}.compare_by_identity,
40
+ Type::Extend => {}.compare_by_identity,
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ class Module
49
+ prepend(Module.new do
50
+ def prepend_features(constant)
51
+ Tapioca::Runtime::Trackers::Mixin.register(
52
+ constant,
53
+ self,
54
+ Tapioca::Runtime::Trackers::Mixin::Type::Prepend,
55
+ caller_locations
56
+ )
57
+ super
58
+ end
59
+
60
+ def append_features(constant)
61
+ Tapioca::Runtime::Trackers::Mixin.register(
62
+ constant,
63
+ self,
64
+ Tapioca::Runtime::Trackers::Mixin::Type::Include,
65
+ caller_locations
66
+ )
67
+ super
68
+ end
69
+
70
+ def extend_object(obj)
71
+ Tapioca::Runtime::Trackers::Mixin.register(
72
+ obj,
73
+ self,
74
+ Tapioca::Runtime::Trackers::Mixin::Type::Extend,
75
+ caller_locations
76
+ ) if Module === obj
77
+ super
78
+ end
79
+ end)
80
+ end
@@ -0,0 +1,50 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ module Trackers
7
+ module RequiredAncestor
8
+ extend T::Sig
9
+
10
+ @required_ancestors_map = {}.compare_by_identity
11
+
12
+ sig { params(requiring: T::Helpers, block: T.proc.returns(Module)).void }
13
+ def self.register(requiring, block)
14
+ ancestors = @required_ancestors_map[requiring] ||= []
15
+ ancestors << block
16
+ end
17
+
18
+ sig { params(mod: Module).returns(T::Array[T.proc.returns(Module)]) }
19
+ def self.required_ancestors_blocks_by(mod)
20
+ @required_ancestors_map[mod] || []
21
+ end
22
+
23
+ sig { params(mod: Module).returns(T::Array[T.nilable(Module)]) }
24
+ def self.required_ancestors_by(mod)
25
+ blocks = required_ancestors_blocks_by(mod)
26
+ blocks.map do |block|
27
+ block.call
28
+ rescue NameError
29
+ # The ancestor required doesn't exist, let's return nil and let the compiler decide what to do.
30
+ nil
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module T
39
+ module Helpers
40
+ prepend(Module.new do
41
+ def requires_ancestor(&block)
42
+ # We can't directly call the block since the ancestor might not be loaded yet.
43
+ # We save the block in the map and will resolve it later.
44
+ Tapioca::Runtime::Trackers::RequiredAncestor.register(self, block)
45
+
46
+ super
47
+ end
48
+ end)
49
+ end
50
+ end
@@ -9,6 +9,7 @@
9
9
  # catch and filter those mixins as coming from Tapioca, we need
10
10
  # the mixin tracker to be in place, before any mixin operations
11
11
  # are performed.
12
- require "tapioca/trackers/mixin"
13
- require "tapioca/trackers/constant_definition"
14
- require "tapioca/trackers/autoload"
12
+ require "tapioca/runtime/trackers/mixin"
13
+ require "tapioca/runtime/trackers/constant_definition"
14
+ require "tapioca/runtime/trackers/autoload"
15
+ require "tapioca/runtime/trackers/required_ancestor"