tapioca 0.10.1 → 0.10.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 (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