tapioca 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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|