tapioca 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/lib/tapioca/cli.rb +9 -10
  4. data/lib/tapioca/commands/annotations.rb +2 -2
  5. data/lib/tapioca/commands/check_shims.rb +2 -2
  6. data/lib/tapioca/commands/command.rb +2 -2
  7. data/lib/tapioca/commands/command_without_tracker.rb +18 -0
  8. data/lib/tapioca/commands/configure.rb +3 -3
  9. data/lib/tapioca/commands/dsl.rb +10 -10
  10. data/lib/tapioca/commands/gem.rb +5 -5
  11. data/lib/tapioca/commands/require.rb +2 -2
  12. data/lib/tapioca/commands/todo.rb +2 -2
  13. data/lib/tapioca/commands.rb +1 -0
  14. data/lib/tapioca/dsl/compiler.rb +3 -3
  15. data/lib/tapioca/dsl/compilers/aasm.rb +4 -4
  16. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
  17. data/lib/tapioca/dsl/compilers/action_mailer.rb +1 -1
  18. data/lib/tapioca/dsl/compilers/active_job.rb +2 -2
  19. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
  20. data/lib/tapioca/dsl/compilers/active_record_associations.rb +13 -13
  21. data/lib/tapioca/dsl/compilers/active_record_columns.rb +22 -22
  22. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +1 -1
  23. data/lib/tapioca/dsl/compilers/active_record_relations.rb +49 -39
  24. data/lib/tapioca/dsl/compilers/active_record_scope.rb +3 -3
  25. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +2 -2
  26. data/lib/tapioca/dsl/compilers/active_storage.rb +2 -2
  27. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +1 -1
  28. data/lib/tapioca/dsl/compilers/config.rb +2 -2
  29. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +1 -0
  30. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +1 -0
  31. data/lib/tapioca/dsl/compilers/identity_cache.rb +12 -12
  32. data/lib/tapioca/dsl/compilers/protobuf.rb +9 -9
  33. data/lib/tapioca/dsl/compilers/rails_generators.rb +2 -2
  34. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
  35. data/lib/tapioca/dsl/compilers/smart_properties.rb +2 -2
  36. data/lib/tapioca/dsl/compilers/state_machines.rb +24 -24
  37. data/lib/tapioca/dsl/compilers/url_helpers.rb +1 -1
  38. data/lib/tapioca/dsl/compilers.rb +1 -1
  39. data/lib/tapioca/dsl/pipeline.rb +5 -4
  40. data/lib/tapioca/executor.rb +2 -2
  41. data/lib/tapioca/gem/events.rb +1 -1
  42. data/lib/tapioca/gem/listeners/foreign_constants.rb +3 -2
  43. data/lib/tapioca/gem/listeners/methods.rb +3 -3
  44. data/lib/tapioca/gem/listeners/mixins.rb +3 -7
  45. data/lib/tapioca/gem/listeners/source_location.rb +1 -1
  46. data/lib/tapioca/gem/listeners/subconstants.rb +1 -1
  47. data/lib/tapioca/gem/listeners/yard_doc.rb +1 -1
  48. data/lib/tapioca/gem/pipeline.rb +7 -3
  49. data/lib/tapioca/gemfile.rb +4 -4
  50. data/lib/tapioca/helpers/config_helper.rb +4 -4
  51. data/lib/tapioca/helpers/env_helper.rb +1 -0
  52. data/lib/tapioca/helpers/gem_helper.rb +17 -5
  53. data/lib/tapioca/helpers/rbi_files_helper.rb +3 -3
  54. data/lib/tapioca/helpers/rbi_helper.rb +1 -1
  55. data/lib/tapioca/helpers/sorbet_helper.rb +2 -2
  56. data/lib/tapioca/helpers/source_uri.rb +1 -1
  57. data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -1
  58. data/lib/tapioca/loaders/dsl.rb +1 -1
  59. data/lib/tapioca/loaders/gem.rb +2 -2
  60. data/lib/tapioca/loaders/loader.rb +1 -1
  61. data/lib/tapioca/rbi_ext/model.rb +3 -3
  62. data/lib/tapioca/rbi_formatter.rb +2 -2
  63. data/lib/tapioca/runtime/generic_type_registry.rb +22 -2
  64. data/lib/tapioca/runtime/reflection.rb +8 -2
  65. data/lib/tapioca/runtime/trackers/autoload.rb +3 -0
  66. data/lib/tapioca/runtime/trackers/constant_definition.rb +13 -5
  67. data/lib/tapioca/runtime/trackers/mixin.rb +37 -36
  68. data/lib/tapioca/runtime/trackers/required_ancestor.rb +17 -4
  69. data/lib/tapioca/runtime/trackers/tracker.rb +45 -0
  70. data/lib/tapioca/runtime/trackers.rb +27 -1
  71. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +17 -6
  72. data/lib/tapioca/version.rb +1 -1
  73. data/lib/tapioca.rb +0 -10
  74. metadata +4 -2
@@ -8,7 +8,7 @@ module Tapioca
8
8
  Spec = T.type_alias do
9
9
  T.any(
10
10
  ::Bundler::StubSpecification,
11
- ::Gem::Specification
11
+ ::Gem::Specification,
12
12
  )
13
13
  end
14
14
 
@@ -147,7 +147,7 @@ module Tapioca
147
147
  .flat_map do |spec|
148
148
  spec.files.filter_map { |file| [file.realpath.to_s, spec] if file.exist? }
149
149
  end.to_h,
150
- T.nilable(T::Hash[String, Gemfile::GemSpec])
150
+ T.nilable(T::Hash[String, Gemfile::GemSpec]),
151
151
  )
152
152
  end
153
153
  end
@@ -157,7 +157,7 @@ module Tapioca
157
157
  "sorbet", "sorbet-static", "sorbet-runtime", "sorbet-static-and-runtime",
158
158
  "debug", "fakefs",
159
159
  ].freeze,
160
- T::Array[String]
160
+ T::Array[String],
161
161
  )
162
162
 
163
163
  sig { returns(String) }
@@ -201,7 +201,7 @@ module Tapioca
201
201
  if default_gem?
202
202
  files.any? { |file| file.to_s == to_realpath(path) }
203
203
  else
204
- to_realpath(path).start_with?(full_gem_path) || has_parent_gemspec?(path)
204
+ path_in_dir?(to_realpath(path), full_gem_path) || has_parent_gemspec?(path)
205
205
  end
206
206
  end
207
207
 
@@ -96,7 +96,7 @@ module Tapioca
96
96
  params(
97
97
  command_options: T::Hash[Symbol, Thor::Option],
98
98
  config_key: String,
99
- config_options: T::Hash[T.untyped, T.untyped]
99
+ config_options: T::Hash[T.untyped, T.untyped],
100
100
  ).returns(T::Array[ConfigError])
101
101
  end
102
102
  def validate_config_options(command_options, config_key, config_options)
@@ -157,18 +157,18 @@ module Tapioca
157
157
  if match
158
158
  ConfigErrorMessagePart.new(
159
159
  message: "#{match[1]}#{match[2]}",
160
- colors: [:bold, :blue]
160
+ colors: [:bold, :blue],
161
161
  )
162
162
  else
163
163
  ConfigErrorMessagePart.new(
164
164
  message: part,
165
- colors: [:yellow]
165
+ colors: [:yellow],
166
166
  )
167
167
  end
168
168
  end
169
169
 
170
170
  ConfigError.new(
171
- message_parts: message_parts
171
+ message_parts: message_parts,
172
172
  )
173
173
  end
174
174
 
@@ -11,6 +11,7 @@ module Tapioca
11
11
  sig { params(options: T::Hash[Symbol, T.untyped]).void }
12
12
  def set_environment(options) # rubocop:disable Naming/AccessorMethodName
13
13
  ENV["RAILS_ENV"] = ENV["RACK_ENV"] = options[:environment]
14
+ ENV["RUBY_DEBUG_ENABLE"] = "0"
14
15
  end
15
16
  end
16
17
  end
@@ -5,15 +5,17 @@ module Tapioca
5
5
  module GemHelper
6
6
  extend T::Sig
7
7
 
8
- sig { params(gemfile_dir: String, full_gem_path: String).returns(T::Boolean) }
9
- def gem_in_app_dir?(gemfile_dir, full_gem_path)
10
- !gem_in_bundle_path?(to_realpath(full_gem_path)) &&
11
- full_gem_path.start_with?(to_realpath(gemfile_dir))
8
+ sig { params(app_dir: String, full_gem_path: String).returns(T::Boolean) }
9
+ def gem_in_app_dir?(app_dir, full_gem_path)
10
+ app_dir = to_realpath(app_dir)
11
+ full_gem_path = to_realpath(full_gem_path)
12
+
13
+ !gem_in_bundle_path?(full_gem_path) && path_in_dir?(full_gem_path, app_dir)
12
14
  end
13
15
 
14
16
  sig { params(full_gem_path: String).returns(T::Boolean) }
15
17
  def gem_in_bundle_path?(full_gem_path)
16
- full_gem_path.start_with?(Bundler.bundle_path.to_s, Bundler.app_cache.to_s)
18
+ path_in_dir?(full_gem_path, Bundler.bundle_path) || path_in_dir?(full_gem_path, Bundler.app_cache)
17
19
  end
18
20
 
19
21
  sig { params(path: T.any(String, Pathname)).returns(String) }
@@ -22,5 +24,15 @@ module Tapioca
22
24
  path_string = File.realpath(path_string) if File.exist?(path_string)
23
25
  path_string
24
26
  end
27
+
28
+ private
29
+
30
+ sig { params(path: T.any(Pathname, String), dir: T.any(Pathname, String)).returns(T::Boolean) }
31
+ def path_in_dir?(path, dir)
32
+ dir = Pathname.new(dir)
33
+ path = Pathname.new(path)
34
+
35
+ path.ascend.any?(dir)
36
+ end
25
37
  end
26
38
  end
@@ -42,7 +42,7 @@ module Tapioca
42
42
  params(
43
43
  index: RBI::Index,
44
44
  shim_rbi_dir: String,
45
- todo_rbi_file: String
45
+ todo_rbi_file: String,
46
46
  ).returns(T::Hash[String, T::Array[RBI::Node]])
47
47
  end
48
48
  def duplicated_nodes_from_index(index, shim_rbi_dir:, todo_rbi_file:)
@@ -80,7 +80,7 @@ module Tapioca
80
80
  dsl_dir: String,
81
81
  auto_strictness: T::Boolean,
82
82
  gems: T::Array[Gemfile::GemSpec],
83
- compilers: T::Enumerable[Class]
83
+ compilers: T::Enumerable[Class],
84
84
  ).void
85
85
  end
86
86
  def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
@@ -92,7 +92,7 @@ module Tapioca
92
92
  "--error-url-base=#{error_url_base}",
93
93
  "--stop-after namer",
94
94
  dsl_dir,
95
- gem_dir
95
+ gem_dir,
96
96
  )
97
97
  say(" Done", :green)
98
98
 
@@ -17,7 +17,7 @@ module Tapioca
17
17
  variance: Symbol,
18
18
  fixed: T.nilable(String),
19
19
  upper: T.nilable(String),
20
- lower: T.nilable(String)
20
+ lower: T.nilable(String),
21
21
  ).returns(String)
22
22
  end
23
23
  def serialize_type_variable(type, variance, fixed, upper, lower)
@@ -7,12 +7,12 @@ module Tapioca
7
7
 
8
8
  SORBET_GEM_SPEC = T.let(
9
9
  ::Gem::Specification.find_by_name("sorbet-static"),
10
- ::Gem::Specification
10
+ ::Gem::Specification,
11
11
  )
12
12
 
13
13
  SORBET_BIN = T.let(
14
14
  Pathname.new(SORBET_GEM_SPEC.full_gem_path) / "libexec" / "sorbet",
15
- Pathname
15
+ Pathname,
16
16
  )
17
17
 
18
18
  SORBET_EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
@@ -29,7 +29,7 @@ module URI
29
29
  gem_name: String,
30
30
  gem_version: T.nilable(String),
31
31
  path: String,
32
- line_number: T.nilable(String)
32
+ line_number: T.nilable(String),
33
33
  ).returns(URI::Source)
34
34
  end
35
35
  def build(gem_name:, gem_version:, path:, line_number:)
@@ -109,7 +109,7 @@ module Tapioca
109
109
  def pipeline
110
110
  @pipeline ||= Tapioca::Dsl::Pipeline.new(
111
111
  requested_constants: [],
112
- requested_compilers: activated_compiler_classes
112
+ requested_compilers: activated_compiler_classes,
113
113
  )
114
114
  end
115
115
  end
@@ -63,7 +63,7 @@ module Tapioca
63
63
 
64
64
  load_rails_application(
65
65
  environment_load: true,
66
- eager_load: @eager_load
66
+ eager_load: @eager_load,
67
67
  )
68
68
 
69
69
  say("Done", :green)
@@ -14,7 +14,7 @@ module Tapioca
14
14
  bundle: Gemfile,
15
15
  prerequire: T.nilable(String),
16
16
  postrequire: String,
17
- default_command: String
17
+ default_command: String,
18
18
  ).void
19
19
  end
20
20
  def load_application(bundle:, prerequire:, postrequire:, default_command:)
@@ -38,7 +38,7 @@ module Tapioca
38
38
  bundle: Gemfile,
39
39
  prerequire: T.nilable(String),
40
40
  postrequire: String,
41
- default_command: String
41
+ default_command: String,
42
42
  ).void
43
43
  end
44
44
  def initialize(bundle:, prerequire:, postrequire:, default_command:)
@@ -113,7 +113,7 @@ module Tapioca
113
113
  if Object.const_defined?("ActiveSupport")
114
114
  Object.const_get("ActiveSupport").run_load_hooks(
115
115
  :before_eager_load,
116
- application
116
+ application,
117
117
  )
118
118
  end
119
119
 
@@ -32,7 +32,7 @@ module RBI
32
32
  params(
33
33
  name: String,
34
34
  superclass_name: T.nilable(String),
35
- block: T.nilable(T.proc.params(scope: RBI::Scope).void)
35
+ block: T.nilable(T.proc.params(scope: RBI::Scope).void),
36
36
  ).returns(Scope)
37
37
  end
38
38
  def create_class(name, superclass_name: nil, &block)
@@ -68,7 +68,7 @@ module RBI
68
68
  variance: Symbol,
69
69
  fixed: T.nilable(String),
70
70
  upper: T.nilable(String),
71
- lower: T.nilable(String)
71
+ lower: T.nilable(String),
72
72
  ).void
73
73
  end
74
74
  def create_type_variable(name, type:, variance: :invariant, fixed: nil, upper: nil, lower: nil)
@@ -82,7 +82,7 @@ module RBI
82
82
  parameters: T::Array[TypedParam],
83
83
  return_type: String,
84
84
  class_method: T::Boolean,
85
- visibility: RBI::Visibility
85
+ visibility: RBI::Visibility,
86
86
  ).void
87
87
  end
88
88
  def create_method(name, parameters: [], return_type: "T.untyped", class_method: false, visibility: RBI::Public.new)
@@ -9,7 +9,7 @@ module Tapioca
9
9
  params(
10
10
  file: RBI::File,
11
11
  command: String,
12
- reason: T.nilable(String)
12
+ reason: T.nilable(String),
13
13
  ).void
14
14
  end
15
15
  def write_header!(file, command, reason: nil)
@@ -32,6 +32,6 @@ module Tapioca
32
32
  max_line_length: nil,
33
33
  nest_singleton_methods: true,
34
34
  nest_non_public_methods: true,
35
- sort_nodes: true
35
+ sort_nodes: true,
36
36
  ), RBIFormatter)
37
37
  end
@@ -23,14 +23,30 @@ module Tapioca
23
23
  module GenericTypeRegistry
24
24
  @generic_instances = T.let(
25
25
  {},
26
- T::Hash[String, Module]
26
+ T::Hash[String, Module],
27
27
  )
28
28
 
29
29
  @type_variables = T.let(
30
30
  {}.compare_by_identity,
31
- T::Hash[Module, T::Array[TypeVariableModule]]
31
+ T::Hash[Module, T::Array[TypeVariableModule]],
32
32
  )
33
33
 
34
+ class GenericType < T::Types::Simple
35
+ extend T::Sig
36
+
37
+ sig { params(raw_type: Module, underlying_type: Module).void }
38
+ def initialize(raw_type, underlying_type)
39
+ super(raw_type)
40
+
41
+ @underlying_type = T.let(underlying_type, Module)
42
+ end
43
+
44
+ sig { params(obj: T.untyped).returns(T::Boolean) }
45
+ def valid?(obj)
46
+ obj.is_a?(@underlying_type)
47
+ end
48
+ end
49
+
34
50
  class << self
35
51
  extend T::Sig
36
52
 
@@ -116,6 +132,10 @@ module Tapioca
116
132
  generic_type.define_singleton_method(:name, name_proc)
117
133
  generic_type.define_singleton_method(:to_s, name_proc)
118
134
 
135
+ override_type = GenericType.new(generic_type, constant)
136
+ override_type_proc = -> { override_type }
137
+ generic_type.define_singleton_method(:__tapioca_override_type, override_type_proc)
138
+
119
139
  # We need to define a `<=` method on the cloned constant, so that Sorbet
120
140
  # can do covariance/contravariance checks on the type variables.
121
141
  #
@@ -19,20 +19,26 @@ module Tapioca
19
19
  PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
20
20
  PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
21
21
  METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
22
+ UNDEFINED_CONSTANT = T.let(Module.new.freeze, Module)
22
23
 
23
24
  REQUIRED_FROM_LABELS = T.let(["<top (required)>", "<main>"].freeze, T::Array[String])
24
25
 
26
+ T::Sig::WithoutRuntime.sig { params(constant: BasicObject).returns(T::Boolean) }
27
+ def constant_defined?(constant)
28
+ !UNDEFINED_CONSTANT.eql?(constant)
29
+ end
30
+
25
31
  sig do
26
32
  params(
27
33
  symbol: String,
28
34
  inherit: T::Boolean,
29
- namespace: Module
35
+ namespace: Module,
30
36
  ).returns(BasicObject).checked(:never)
31
37
  end
32
38
  def constantize(symbol, inherit: false, namespace: Object)
33
39
  namespace.const_get(symbol, inherit)
34
40
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
35
- nil
41
+ UNDEFINED_CONSTANT
36
42
  end
37
43
 
38
44
  sig { params(object: BasicObject).returns(Class).checked(:never) }
@@ -5,6 +5,7 @@ module Tapioca
5
5
  module Runtime
6
6
  module Trackers
7
7
  module Autoload
8
+ extend Tracker
8
9
  extend T::Sig
9
10
 
10
11
  NOOP_METHOD = ->(*_args, **_kwargs, &_block) {}
@@ -28,6 +29,8 @@ module Tapioca
28
29
 
29
30
  sig { params(constant_name: String).void }
30
31
  def register(constant_name)
32
+ return unless enabled?
33
+
31
34
  @constant_names_registered_for_autoload << constant_name
32
35
  end
33
36
 
@@ -9,6 +9,7 @@ module Tapioca
9
9
  # correspondence between classes/modules and files, as this information isn't
10
10
  # available in the ruby runtime without extra accounting.
11
11
  module ConstantDefinition
12
+ extend Tracker
12
13
  extend Reflection
13
14
  extend T::Sig
14
15
 
@@ -20,7 +21,7 @@ module Tapioca
20
21
  @class_files = {}.compare_by_identity
21
22
 
22
23
  # Immediately activated upon load. Observes class/module definition.
23
- Tapioca.register_trace(:class) do |tp|
24
+ @class_tracepoint = TracePoint.trace(:class) do |tp|
24
25
  next if tp.self.singleton_class?
25
26
 
26
27
  key = tp.self
@@ -40,19 +41,26 @@ module Tapioca
40
41
  (@class_files[key] ||= Set.new) << loc
41
42
  end
42
43
 
43
- Tapioca.register_trace(:c_return) do |tp|
44
+ @creturn_tracepoint = TracePoint.trace(:c_return) do |tp|
44
45
  next unless tp.method_id == :new
45
- next unless Module === tp.return_value
46
46
 
47
47
  key = tp.return_value
48
+ next unless Module === key
49
+
48
50
  loc = build_constant_location(tp, caller_locations)
49
51
  (@class_files[key] ||= Set.new) << loc
50
52
  end
51
53
 
52
54
  class << self
55
+ def disable!
56
+ @class_tracepoint.disable
57
+ @creturn_tracepoint.disable
58
+ super
59
+ end
60
+
53
61
  def build_constant_location(tp, locations)
54
- file = resolve_loc(caller_locations)
55
- lineno = file == File.realpath(tp.path) ? tp.lineno : 0
62
+ file = resolve_loc(locations)
63
+ lineno = File.identical?(file, tp.path) ? tp.lineno : 0
56
64
 
57
65
  ConstantLocation.new(path: file, lineno: lineno)
58
66
  end
@@ -5,11 +5,11 @@ module Tapioca
5
5
  module Runtime
6
6
  module Trackers
7
7
  module Mixin
8
+ extend Tracker
8
9
  extend T::Sig
9
10
 
10
11
  @constants_to_mixin_locations = {}.compare_by_identity
11
12
  @mixins_to_constants = {}.compare_by_identity
12
- @enabled = true
13
13
 
14
14
  class Type < T::Enum
15
15
  enums do
@@ -28,31 +28,52 @@ module Tapioca
28
28
  .returns(T.type_parameter(:Result))
29
29
  end
30
30
  def with_disabled_registration(&block)
31
- @enabled = false
32
-
33
- block.call
34
- ensure
35
- @enabled = true
31
+ with_disabled_tracker(&block)
36
32
  end
37
33
 
38
- sig do
39
- params(
40
- constant: Module,
41
- mixin: Module,
42
- mixin_type: Type,
43
- ).void
44
- end
34
+ sig { params(constant: Module, mixin: Module, mixin_type: Type).void }
45
35
  def register(constant, mixin, mixin_type)
46
- return unless @enabled
36
+ return unless enabled?
47
37
 
48
38
  location = Reflection.resolve_loc(caller_locations)
49
39
 
50
- constants = constants_with_mixin(mixin)
51
- constants.fetch(mixin_type).store(constant, location)
40
+ register_with_location(constant, mixin, mixin_type, location)
41
+ end
42
+
43
+ def resolve_to_attached_class(constant, mixin, mixin_type)
44
+ attached_class = Reflection.attached_class_of(constant)
45
+ return unless attached_class
46
+
47
+ if mixin_type == Type::Include || mixin_type == Type::Prepend
48
+ location = mixin_location(mixin, mixin_type, constant)
49
+ register_with_location(constant, mixin, Type::Extend, T.must(location))
50
+ end
51
+
52
+ attached_class
52
53
  end
53
54
 
54
55
  sig { params(mixin: Module).returns(T::Hash[Type, T::Hash[Module, String]]) }
55
56
  def constants_with_mixin(mixin)
57
+ find_or_initialize_mixin_lookup(mixin)
58
+ end
59
+
60
+ sig { params(mixin: Module, mixin_type: Type, constant: Module).returns(T.nilable(String)) }
61
+ def mixin_location(mixin, mixin_type, constant)
62
+ find_or_initialize_mixin_lookup(mixin).dig(mixin_type, constant)
63
+ end
64
+
65
+ private
66
+
67
+ sig { params(constant: Module, mixin: Module, mixin_type: Type, location: String).void }
68
+ def register_with_location(constant, mixin, mixin_type, location)
69
+ return unless @enabled
70
+
71
+ constants = find_or_initialize_mixin_lookup(mixin)
72
+ constants.fetch(mixin_type).store(constant, location)
73
+ end
74
+
75
+ sig { params(mixin: Module).returns(T::Hash[Type, T::Hash[Module, String]]) }
76
+ def find_or_initialize_mixin_lookup(mixin)
56
77
  @mixins_to_constants[mixin] ||= {
57
78
  Type::Prepend => {}.compare_by_identity,
58
79
  Type::Include => {}.compare_by_identity,
@@ -74,8 +95,6 @@ class Module
74
95
  Tapioca::Runtime::Trackers::Mixin::Type::Prepend,
75
96
  )
76
97
 
77
- register_extend_on_attached_class(constant) if constant.singleton_class?
78
-
79
98
  super
80
99
  end
81
100
 
@@ -86,8 +105,6 @@ class Module
86
105
  Tapioca::Runtime::Trackers::Mixin::Type::Include,
87
106
  )
88
107
 
89
- register_extend_on_attached_class(constant) if constant.singleton_class?
90
-
91
108
  super
92
109
  end
93
110
 
@@ -99,21 +116,5 @@ class Module
99
116
  ) if Module === obj
100
117
  super
101
118
  end
102
-
103
- private
104
-
105
- # Including or prepending on a singleton class is functionally equivalent to extending the
106
- # attached class. Registering the mixin as an extend on the attached class ensures that
107
- # this mixin can be found whether searching for an include/prepend on the singleton class
108
- # or an extend on the attached class.
109
- def register_extend_on_attached_class(constant)
110
- attached_class = Tapioca::Runtime::Reflection.attached_class_of(constant)
111
-
112
- Tapioca::Runtime::Trackers::Mixin.register(
113
- T.cast(attached_class, Module),
114
- self,
115
- Tapioca::Runtime::Trackers::Mixin::Type::Extend,
116
- ) if attached_class
117
- end
118
119
  end)
119
120
  end
@@ -5,27 +5,40 @@ module Tapioca
5
5
  module Runtime
6
6
  module Trackers
7
7
  module RequiredAncestor
8
+ extend Tracker
8
9
  @required_ancestors_map = {}.compare_by_identity
9
10
 
10
11
  class << self
11
12
  extend T::Sig
12
13
 
13
- sig { params(requiring: T::Helpers, block: T.proc.returns(Module)).void }
14
+ sig { params(requiring: T::Helpers, block: T.proc.void).void }
14
15
  def register(requiring, block)
16
+ return unless enabled?
17
+
15
18
  ancestors = @required_ancestors_map[requiring] ||= []
16
19
  ancestors << block
17
20
  end
18
21
 
19
- sig { params(mod: Module).returns(T::Array[T.proc.returns(Module)]) }
22
+ sig { params(mod: Module).returns(T::Array[T.proc.void]) }
20
23
  def required_ancestors_blocks_by(mod)
21
24
  @required_ancestors_map[mod] || []
22
25
  end
23
26
 
24
- sig { params(mod: Module).returns(T::Array[T.nilable(Module)]) }
27
+ sig { params(mod: Module).returns(T::Array[T.untyped]) }
25
28
  def required_ancestors_by(mod)
26
29
  blocks = required_ancestors_blocks_by(mod)
27
30
  blocks.map do |block|
28
- block.call
31
+ # Common return values of `block.call` here could be a Module or a Sorbet's runtime value for T.class.
32
+ # But in reality it could be whatever the block has that can pass Sorbet's static check. Like
33
+ #
34
+ # ```
35
+ # requires_ancestor { T.class_of(Foo); nil }
36
+ # ```
37
+ #
38
+ # So it's not designed to be used at runtime and it's accidental that just calling `to_s` on the above
39
+ # common values can get us the correct value to generate type signatures. (See SorbetRequiredAncestors)
40
+ # Therefore, the return value `block.call` should be considered unreliable and treated with caution.
41
+ T.unsafe(block.call)
29
42
  rescue NameError
30
43
  # The ancestor required doesn't exist, let's return nil and let the compiler decide what to do.
31
44
  nil
@@ -0,0 +1,45 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Runtime
6
+ module Trackers
7
+ module Tracker
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { params(base: T.all(Tracker, Module)).void }
17
+ def extended(base)
18
+ Trackers.register_tracker(base)
19
+ base.instance_exec do
20
+ @enabled = true
21
+ end
22
+ end
23
+ end
24
+
25
+ sig { void }
26
+ def disable!
27
+ @enabled = false
28
+ end
29
+
30
+ def enabled?
31
+ @enabled
32
+ end
33
+
34
+ def with_disabled_tracker(&block)
35
+ original_state = @enabled
36
+ @enabled = false
37
+
38
+ block.call
39
+ ensure
40
+ @enabled = original_state
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,6 +1,32 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "tapioca/runtime/trackers/tracker"
5
+
6
+ module Tapioca
7
+ module Runtime
8
+ module Trackers
9
+ extend T::Sig
10
+
11
+ @trackers = T.let([], T::Array[Tracker])
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { void }
17
+ def disable_all!
18
+ @trackers.each(&:disable!)
19
+ end
20
+
21
+ sig { params(tracker: Tracker).void }
22
+ def register_tracker(tracker)
23
+ @trackers << tracker
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
4
30
  # The load order below is important:
5
31
  # ----------------------------------
6
32
  # We want the mixin tracker to be the first thing that is