tapioca 0.6.4 → 0.7.0

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 (108) 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 +84 -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 +10 -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 +11 -11
  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 +10 -8
  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 +10 -8
  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 +16 -14
  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 +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  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/rbi_helper.rb +17 -0
  66. data/lib/tapioca/helpers/shims_helper.rb +87 -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/internal.rb +17 -10
  72. data/lib/tapioca/rbi_ext/model.rb +2 -48
  73. data/lib/tapioca/rbi_formatter.rb +37 -0
  74. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  75. data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
  76. data/lib/tapioca/runtime/loader.rb +123 -0
  77. data/lib/tapioca/runtime/reflection.rb +153 -0
  78. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  79. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  80. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  81. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  82. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  83. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +33 -15
  84. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  85. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  86. data/lib/tapioca/static/symbol_loader.rb +83 -0
  87. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  88. data/lib/tapioca/version.rb +1 -1
  89. data/lib/tapioca.rb +2 -7
  90. metadata +80 -60
  91. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  92. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  93. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  94. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  95. data/lib/tapioca/compilers/sorbet.rb +0 -59
  96. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  97. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  98. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  99. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  100. data/lib/tapioca/generators/todo.rb +0 -76
  101. data/lib/tapioca/generators.rb +0 -9
  102. data/lib/tapioca/generic_type_registry.rb +0 -164
  103. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  104. data/lib/tapioca/loader.rb +0 -119
  105. data/lib/tapioca/reflection.rb +0 -151
  106. data/lib/tapioca/trackers/autoload.rb +0 -70
  107. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  108. 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,153 @@
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 { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
144
+ def descendants_of(klass)
145
+ result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
146
+ T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
147
+ end
148
+
149
+ T.unsafe(result)
150
+ end
151
+ end
152
+ end
153
+ 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"
@@ -17,7 +17,7 @@ module T
17
17
  constant = super
18
18
  # `register_type` method builds and returns an instantiated clone of the generic type
19
19
  # so, we just return that from this method as well.
20
- Tapioca::GenericTypeRegistry.register_type(constant, types)
20
+ Tapioca::Runtime::GenericTypeRegistry.register_type(constant, types)
21
21
  end
22
22
 
23
23
  def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
@@ -31,7 +31,7 @@ module T
31
31
  lower,
32
32
  upper
33
33
  ).tap do |type_variable|
34
- Tapioca::GenericTypeRegistry.register_type_variable(self, type_variable)
34
+ Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
35
35
  end
36
36
  end
37
37
 
@@ -46,7 +46,7 @@ module T
46
46
  lower,
47
47
  upper
48
48
  ).tap do |type_variable|
49
- Tapioca::GenericTypeRegistry.register_type_variable(self, type_variable)
49
+ Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
50
50
  end
51
51
  end
52
52
  end
@@ -57,18 +57,6 @@ module T
57
57
  module Types
58
58
  class Simple
59
59
  module GenericPatch
60
- def valid?(obj)
61
- # Since `Tapioca::TypeVariable` is a `Module`, it will be wrapped by a
62
- # `Simple` type. We want to always make type variable types valid, so we
63
- # need to explicitly check that `raw_type` is a `Tapioca::TypeVariable`
64
- # and return `true`
65
- if defined?(Tapioca::TypeVariableModule) && Tapioca::TypeVariableModule === @raw_type
66
- return true
67
- end
68
-
69
- obj.is_a?(@raw_type)
70
- end
71
-
72
60
  # This method intercepts calls to the `name` method for simple types, so that
73
61
  # it can ask the name to the type if the type is generic, since, by this point,
74
62
  # we've created a clone of that type with the `name` method returning the
@@ -88,9 +76,34 @@ module T
88
76
  prepend GenericPatch
89
77
  end
90
78
  end
79
+
80
+ module Utils
81
+ module CoercePatch
82
+ def coerce(val)
83
+ if val.is_a?(Tapioca::TypeVariableModule)
84
+ val.coerce_to_type_variable
85
+ else
86
+ super
87
+ end
88
+ end
89
+ end
90
+
91
+ class << self
92
+ prepend(CoercePatch)
93
+ end
94
+ end
91
95
  end
92
96
 
93
97
  module Tapioca
98
+ class TypeVariable < ::T::Types::TypeVariable
99
+ def initialize(name, variance)
100
+ @name = name
101
+ super(variance)
102
+ end
103
+
104
+ attr_reader :name
105
+ end
106
+
94
107
  # This is subclassing from `Module` so that instances of this type will be modules.
95
108
  # The reason why we want that is because that means those instances will automatically
96
109
  # get bound to the constant names they are assigned to by Ruby. As a result, we don't
@@ -153,6 +166,11 @@ module Tapioca
153
166
  serialized
154
167
  end
155
168
 
169
+ sig { returns(Tapioca::TypeVariable) }
170
+ def coerce_to_type_variable
171
+ TypeVariable.new(name, @variance)
172
+ end
173
+
156
174
  private
157
175
 
158
176
  sig do
@@ -1,12 +1,18 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ # We need sorbet to compile the signature for `qualified_name_of` before applying
5
+ # the patch to avoid an infinite loop.
6
+ T::Utils.signature_for_method(::Tapioca::Runtime::Reflection.method(:qualified_name_of))
7
+
4
8
  module T
5
9
  module Types
6
10
  class Simple
7
11
  module NamePatch
8
12
  def name
9
- @name ||= ::Tapioca::Reflection.name_of(@raw_type).freeze
13
+ # Sorbet memoizes this method into the `@name` instance variable but
14
+ # doing so means that types get memoized before this patch is applied
15
+ ::Tapioca::Runtime::Reflection.qualified_name_of(@raw_type)
10
16
  end
11
17
  end
12
18
 
@@ -4,7 +4,7 @@
4
4
  require "spoom"
5
5
 
6
6
  module Tapioca
7
- module Compilers
7
+ module Static
8
8
  class RequiresCompiler
9
9
  extend T::Sig
10
10
 
@@ -17,7 +17,7 @@ module Tapioca
17
17
  def compile
18
18
  config = Spoom::Sorbet::Config.parse_file(@sorbet_path)
19
19
  files = collect_files(config)
20
- names_in_project = files.map { |file| [File.basename(file, ".rb"), true] }.to_h
20
+ names_in_project = files.to_h { |file| [File.basename(file, ".rb"), true] }
21
21
  files.flat_map do |file|
22
22
  collect_requires(file).reject { |req| names_in_project[req] }
23
23
  end.sort.uniq.map do |name|