tapioca 0.4.6 → 0.4.11

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/README.md +9 -7
  4. data/lib/tapioca/cli.rb +8 -1
  5. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +38 -39
  6. data/lib/tapioca/compilers/dsl/action_mailer.rb +4 -4
  7. data/lib/tapioca/compilers/dsl/active_record_associations.rb +52 -51
  8. data/lib/tapioca/compilers/dsl/active_record_columns.rb +60 -65
  9. data/lib/tapioca/compilers/dsl/active_record_enum.rb +27 -23
  10. data/lib/tapioca/compilers/dsl/active_record_scope.rb +18 -17
  11. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +8 -7
  12. data/lib/tapioca/compilers/dsl/active_resource.rb +5 -4
  13. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +6 -7
  14. data/lib/tapioca/compilers/dsl/base.rb +14 -10
  15. data/lib/tapioca/compilers/dsl/frozen_record.rb +25 -25
  16. data/lib/tapioca/compilers/dsl/{active_record_identity_cache.rb → identity_cache.rb} +11 -10
  17. data/lib/tapioca/compilers/dsl/protobuf.rb +1 -1
  18. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +83 -0
  19. data/lib/tapioca/compilers/dsl/smart_properties.rb +1 -1
  20. data/lib/tapioca/compilers/dsl/state_machines.rb +75 -73
  21. data/lib/tapioca/compilers/dsl/url_helpers.rb +9 -5
  22. data/lib/tapioca/compilers/dsl_compiler.rb +5 -5
  23. data/lib/tapioca/compilers/requires_compiler.rb +34 -6
  24. data/lib/tapioca/compilers/sorbet.rb +1 -1
  25. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +69 -33
  26. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -1
  27. data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
  28. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  29. data/lib/tapioca/core_ext/class.rb +8 -3
  30. data/lib/tapioca/gemfile.rb +1 -1
  31. data/lib/tapioca/generator.rb +1 -1
  32. data/lib/tapioca/loader.rb +1 -1
  33. data/lib/tapioca/version.rb +1 -1
  34. metadata +4 -3
@@ -15,7 +15,7 @@ module Tapioca
15
15
  module Compilers
16
16
  module Dsl
17
17
  # `Tapioca::Compilers::Dsl::SmartProperties` generates RBI files for classes that include
18
- # `SmartProperties` (see https://github.com/t6d/smart_properties).
18
+ # [`SmartProperties`](https://github.com/t6d/smart_properties).
19
19
  #
20
20
  # For example, with the following class that includes `SmartProperties`:
21
21
  #
@@ -15,10 +15,12 @@ end
15
15
  module Tapioca
16
16
  module Compilers
17
17
  module Dsl
18
- # `RbiGenerator::StateMachines` generates RBI files for classes that setup a `state_machine`
19
- # (see https://github.com/state-machines/state_machines). The generator also processes the extra
20
- # methods generated by [StateMachines Active Record](https://github.com/state-machines/state_machines-activerecord)
21
- # and [StateMachines ActiveModel](https://github.com/state-machines/state_machines-activemodel) integrations.
18
+ # `Tapioca::Compilers::Dsl::StateMachines` generates RBI files for classes that setup a
19
+ # [`state_machine`](https://github.com/state-machines/state_machines). The generator also
20
+ # processes the extra methods generated by
21
+ # [StateMachines Active Record](https://github.com/state-machines/state_machines-activerecord)
22
+ # and [StateMachines Active Model](https://github.com/state-machines/state_machines-activemodel)
23
+ # integrations.
22
24
  #
23
25
  # For example, with the following `Vehicle` class:
24
26
  #
@@ -45,75 +47,75 @@ module Tapioca
45
47
  # # vehicle.rbi
46
48
  # # typed: true
47
49
  # class Vehicle
48
- # include Vehicle::StateMachineInstanceHelperModule
49
- # extend Vehicle::StateMachineClassHelperModule
50
- # end
50
+ # include StateMachineInstanceHelperModule
51
+ # extend StateMachineClassHelperModule
51
52
  #
52
- # module Vehicle::StateMachineClassHelperModule
53
- # sig { params(event: T.any(String, Symbol)).returns(String) }
54
- # def human_alarm_state_event_name(event); end
53
+ # module StateMachineClassHelperModule
54
+ # sig { params(event: T.any(String, Symbol)).returns(String) }
55
+ # def human_alarm_state_event_name(event); end
55
56
  #
56
- # sig { params(state: T.any(String, Symbol)).returns(String) }
57
- # def human_alarm_state_name(state); end
58
- # end
57
+ # sig { params(state: T.any(String, Symbol)).returns(String) }
58
+ # def human_alarm_state_name(state); end
59
+ # end
59
60
  #
60
- # module Vehicle::StateMachineInstanceHelperModule
61
- # sig { returns(T::Boolean) }
62
- # def alarm_active?; end
61
+ # module StateMachineInstanceHelperModule
62
+ # sig { returns(T::Boolean) }
63
+ # def alarm_active?; end
63
64
  #
64
- # sig { returns(T::Boolean) }
65
- # def alarm_off?; end
65
+ # sig { returns(T::Boolean) }
66
+ # def alarm_off?; end
66
67
  #
67
- # sig { returns(Integer) }
68
- # def alarm_state; end
68
+ # sig { returns(Integer) }
69
+ # def alarm_state; end
69
70
  #
70
- # sig { params(value: Integer).returns(Integer) }
71
- # def alarm_state=(value); end
71
+ # sig { params(value: Integer).returns(Integer) }
72
+ # def alarm_state=(value); end
72
73
  #
73
- # sig { params(state: T.any(String, Symbol)).returns(T::Boolean) }
74
- # def alarm_state?(state); end
74
+ # sig { params(state: T.any(String, Symbol)).returns(T::Boolean) }
75
+ # def alarm_state?(state); end
75
76
  #
76
- # sig { params(args: T.untyped).returns(T::Array[T.any(String, Symbol)]) }
77
- # def alarm_state_events(*args); end
77
+ # sig { params(args: T.untyped).returns(T::Array[T.any(String, Symbol)]) }
78
+ # def alarm_state_events(*args); end
78
79
  #
79
- # sig { returns(T.any(String, Symbol)) }
80
- # def alarm_state_name; end
80
+ # sig { returns(T.any(String, Symbol)) }
81
+ # def alarm_state_name; end
81
82
  #
82
- # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
83
- # def alarm_state_paths(*args); end
83
+ # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
84
+ # def alarm_state_paths(*args); end
84
85
  #
85
- # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
86
- # def alarm_state_transitions(*args); end
86
+ # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
87
+ # def alarm_state_transitions(*args); end
87
88
  #
88
- # sig { returns(T::Boolean) }
89
- # def can_disable_alarm?; end
89
+ # sig { returns(T::Boolean) }
90
+ # def can_disable_alarm?; end
90
91
  #
91
- # sig { returns(T::Boolean) }
92
- # def can_enable_alarm?; end
92
+ # sig { returns(T::Boolean) }
93
+ # def can_enable_alarm?; end
93
94
  #
94
- # sig { params(args: T.untyped).returns(T::Boolean) }
95
- # def disable_alarm(*args); end
95
+ # sig { params(args: T.untyped).returns(T::Boolean) }
96
+ # def disable_alarm(*args); end
96
97
  #
97
- # sig { params(args: T.untyped).returns(T::Boolean) }
98
- # def disable_alarm!(*args); end
98
+ # sig { params(args: T.untyped).returns(T::Boolean) }
99
+ # def disable_alarm!(*args); end
99
100
  #
100
- # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
101
- # def disable_alarm_transition(*args); end
101
+ # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
102
+ # def disable_alarm_transition(*args); end
102
103
  #
103
- # sig { params(args: T.untyped).returns(T::Boolean) }
104
- # def enable_alarm(*args); end
104
+ # sig { params(args: T.untyped).returns(T::Boolean) }
105
+ # def enable_alarm(*args); end
105
106
  #
106
- # sig { params(args: T.untyped).returns(T::Boolean) }
107
- # def enable_alarm!(*args); end
107
+ # sig { params(args: T.untyped).returns(T::Boolean) }
108
+ # def enable_alarm!(*args); end
108
109
  #
109
- # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
110
- # def enable_alarm_transition(*args); end
110
+ # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
111
+ # def enable_alarm_transition(*args); end
111
112
  #
112
- # sig { params(event: T.any(String, Symbol), args: T.untyped).returns(T::Boolean) }
113
- # def fire_alarm_state_event(event, *args); end
113
+ # sig { params(event: T.any(String, Symbol), args: T.untyped).returns(T::Boolean) }
114
+ # def fire_alarm_state_event(event, *args); end
114
115
  #
115
- # sig { returns(String) }
116
- # def human_alarm_state_name; end
116
+ # sig { returns(String) }
117
+ # def human_alarm_state_name; end
118
+ # end
117
119
  # end
118
120
  # ~~~
119
121
  class StateMachines < Base
@@ -123,34 +125,34 @@ module Tapioca
123
125
  def decorate(root, constant)
124
126
  return if constant.state_machines.empty?
125
127
 
126
- instance_module_name = "#{constant}::StateMachineInstanceHelperModule"
127
- class_module_name = "#{constant}::StateMachineClassHelperModule"
128
+ root.path(constant) do |klass|
129
+ instance_module_name = "StateMachineInstanceHelperModule"
130
+ class_module_name = "StateMachineClassHelperModule"
128
131
 
129
- instance_module = root.create_module(instance_module_name)
130
- class_module = root.create_module(class_module_name)
132
+ instance_module = klass.create_module(instance_module_name)
133
+ class_module = klass.create_module(class_module_name)
131
134
 
132
- constant.state_machines.each_value do |machine|
133
- state_type = state_type_for(machine)
135
+ constant.state_machines.each_value do |machine|
136
+ state_type = state_type_for(machine)
134
137
 
135
- define_state_accessor(instance_module, machine, state_type)
136
- define_state_predicate(instance_module, machine)
137
- define_event_helpers(instance_module, machine)
138
- define_path_helpers(instance_module, machine)
139
- define_name_helpers(instance_module, class_module, machine)
140
- define_scopes(class_module, machine)
138
+ define_state_accessor(instance_module, machine, state_type)
139
+ define_state_predicate(instance_module, machine)
140
+ define_event_helpers(instance_module, machine)
141
+ define_path_helpers(instance_module, machine)
142
+ define_name_helpers(instance_module, class_module, machine)
143
+ define_scopes(class_module, machine)
141
144
 
142
- define_state_methods(instance_module, machine)
143
- define_event_methods(instance_module, machine)
144
- end
145
+ define_state_methods(instance_module, machine)
146
+ define_event_methods(instance_module, machine)
147
+ end
145
148
 
146
- matching_integration_name = ::StateMachines::Integrations.match(constant)&.integration_name
149
+ matching_integration_name = ::StateMachines::Integrations.match(constant)&.integration_name
147
150
 
148
- case matching_integration_name
149
- when :active_record
150
- define_activerecord_methods(instance_module)
151
- end
151
+ case matching_integration_name
152
+ when :active_record
153
+ define_activerecord_methods(instance_module)
154
+ end
152
155
 
153
- root.path(constant) do |klass|
154
156
  klass.create_include(instance_module_name)
155
157
  klass.create_extend(class_module_name)
156
158
  end
@@ -15,8 +15,7 @@ module Tapioca
15
15
  module Compilers
16
16
  module Dsl
17
17
  # `Tapioca::Compilers::Dsl::UrlHelpers` generates RBI files for classes that include or extend
18
- # `Rails.application.routes.url_helpers`
19
- # (see https://api.rubyonrails.org/v5.1.7/classes/ActionDispatch/Routing/UrlFor.html#module-ActionDispatch::Routing::UrlFor-label-URL+generation+for+named+routes).
18
+ # [`Rails.application.routes.url_helpers`](https://api.rubyonrails.org/v5.1.7/classes/ActionDispatch/Routing/UrlFor.html#module-ActionDispatch::Routing::UrlFor-label-URL+generation+for+named+routes).
20
19
  #
21
20
  # For example, with the following setup:
22
21
  #
@@ -32,7 +31,12 @@ module Tapioca
32
31
  # ~~~rb
33
32
  # app/models/post.rb
34
33
  # class Post
35
- # include Rails.application.routes.url_helpers
34
+ # # Use `T.unsafe` so that Sorbet does not complain about a dynamic
35
+ # # module being included. This allows the `include` to happen properly
36
+ # # at runtime but Sorbet won't see the include. However, since this
37
+ # # generator will generate the proper RBI files for the include,
38
+ # # static type checking will work as expected.
39
+ # T.unsafe(self).include Rails.application.routes.url_helpers
36
40
  # end
37
41
  # ~~~
38
42
  #
@@ -126,8 +130,8 @@ module Tapioca
126
130
  sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
127
131
  def generate_module_for(root, constant)
128
132
  root.create_module(T.must(constant.name)) do |mod|
129
- mod.create_include("ActionDispatch::Routing::UrlFor")
130
- mod.create_include("ActionDispatch::Routing::PolymorphicRoutes")
133
+ mod.create_include("::ActionDispatch::Routing::UrlFor")
134
+ mod.create_include("::ActionDispatch::Routing::PolymorphicRoutes")
131
135
 
132
136
  constant.instance_methods(false).each do |method|
133
137
  mod.create_method(
@@ -1,5 +1,5 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
- # typed: true
3
3
 
4
4
  require "tapioca/compilers/dsl/base"
5
5
 
@@ -30,7 +30,7 @@ module Tapioca
30
30
  T::Enumerable[Dsl::Base]
31
31
  )
32
32
  @requested_constants = requested_constants
33
- @error_handler = error_handler || $stderr.method(:puts)
33
+ @error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
34
34
  end
35
35
 
36
36
  sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
@@ -54,9 +54,9 @@ module Tapioca
54
54
 
55
55
  private
56
56
 
57
- sig { params(requested_generators: T::Array[String]).returns(Proc) }
57
+ sig { params(requested_generators: T::Array[String]).returns(T.proc.params(klass: Class).returns(T::Boolean)) }
58
58
  def generator_filter(requested_generators)
59
- return proc { true } if requested_generators.empty?
59
+ return ->(_klass) { true } if requested_generators.empty?
60
60
 
61
61
  generators = requested_generators.map(&:downcase)
62
62
 
@@ -70,7 +70,7 @@ module Tapioca
70
70
  def gather_generators(requested_generators)
71
71
  generator_filter = generator_filter(requested_generators)
72
72
 
73
- Dsl::Base.descendants.select(&generator_filter).map(&:new)
73
+ T.cast(Dsl::Base.descendants.select(&generator_filter).map(&:new), T::Enumerable[Dsl::Base])
74
74
  end
75
75
 
76
76
  sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: strict
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'spoom'
5
5
 
@@ -34,7 +34,8 @@ module Tapioca
34
34
  path = (Pathname.new(@sorbet_path) / "../.." / path).cleanpath
35
35
  if path.directory?
36
36
  Dir.glob("#{path}/**/*.rb", File::FNM_EXTGLOB).reject do |file|
37
- file_ignored_by_sorbet?(config, file)
37
+ relative_file_path = Pathname.new(file).relative_path_from(path)
38
+ file_ignored_by_sorbet?(config, relative_file_path)
38
39
  end
39
40
  else
40
41
  [path.to_s]
@@ -49,13 +50,40 @@ module Tapioca
49
50
  end.compact
50
51
  end
51
52
 
52
- sig { params(config: Spoom::Sorbet::Config, file: String).returns(T::Boolean) }
53
- def file_ignored_by_sorbet?(config, file)
54
- config.ignore.any? do |path|
55
- Regexp.new(Regexp.escape(path)) =~ file
53
+ sig { params(config: Spoom::Sorbet::Config, file_path: Pathname).returns(T::Boolean) }
54
+ def file_ignored_by_sorbet?(config, file_path)
55
+ file_path_parts = path_parts(file_path)
56
+
57
+ config.ignore.any? do |ignore|
58
+ # Sorbet --ignore matching method:
59
+ # ---
60
+ # Ignores input files that contain the given
61
+ # string in their paths (relative to the input
62
+ # path passed to Sorbet).
63
+ #
64
+ # Strings beginning with / match against the
65
+ # prefix of these relative paths; others are
66
+ # substring matchs.
67
+
68
+ # Matches must be against whole folder and file
69
+ # names, so `foo` matches `/foo/bar.rb` and
70
+ # `/bar/foo/baz.rb` but not `/foo.rb` or
71
+ # `/foo2/bar.rb`.
72
+ ignore_parts = path_parts(Pathname.new(ignore))
73
+ file_path_part_sequences = file_path_parts.each_cons(ignore_parts.size)
74
+ # if ignore string begins with /, we only want the first sequence to match
75
+ file_path_part_sequences = [file_path_part_sequences.first].to_enum if ignore.start_with?("/")
76
+
77
+ # we need to match whole segments
78
+ file_path_part_sequences.include?(ignore_parts)
56
79
  end
57
80
  end
58
81
 
82
+ sig { params(path: Pathname).returns(T::Array[String]) }
83
+ def path_parts(path)
84
+ T.unsafe(path).descend.map { |part| part.basename.to_s }
85
+ end
86
+
59
87
  sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
60
88
  def name_in_project?(files, name)
61
89
  files.any? do |file|
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pathname'
5
5
  require 'shellwords'
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
1
  # typed: true
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'pathname'
5
5
 
@@ -74,9 +74,9 @@ module Tapioca
74
74
  compile(symbol, constant)
75
75
  end
76
76
 
77
- sig { params(symbol: String).returns(BasicObject).checked(:never) }
78
- def resolve_constant(symbol)
79
- Object.const_get(symbol, false)
77
+ sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) }
78
+ def resolve_constant(symbol, inherit: false)
79
+ Object.const_get(symbol, inherit)
80
80
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
81
81
  nil
82
82
  end
@@ -209,7 +209,11 @@ module Tapioca
209
209
  method = "const" if prop.fetch(:immutable, false)
210
210
  type = prop.fetch(:type_object, "T.untyped")
211
211
 
212
- indented("#{method} :#{name}, #{type}")
212
+ if prop.key?(:default)
213
+ indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
214
+ else
215
+ indented("#{method} :#{name}, #{type}")
216
+ end
213
217
  end.join("\n")
214
218
  end
215
219
 
@@ -288,33 +292,16 @@ module Tapioca
288
292
 
289
293
  sig { params(constant: Module).returns(String) }
290
294
  def compile_mixins(constant)
291
- ignorable_ancestors =
292
- if constant.is_a?(Class)
293
- ancestors = constant.superclass&.ancestors || Object.ancestors
294
- Set.new(ancestors)
295
- else
296
- Module.ancestors
297
- end
295
+ singleton_class = singleton_class_of(constant)
298
296
 
299
- inherited_singleton_class_ancestors =
300
- if constant.is_a?(Class)
301
- Set.new(singleton_class_of(constant.superclass).ancestors)
302
- else
303
- Module.ancestors
304
- end
305
-
306
- interesting_ancestors =
307
- constant.ancestors.reject { |mod| ignorable_ancestors.include?(mod) }
297
+ interesting_ancestors = interesting_ancestors_of(constant)
298
+ interesting_singleton_class_ancestors = interesting_ancestors_of(singleton_class)
308
299
 
309
300
  prepend = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
310
301
  include = interesting_ancestors.drop(prepend.size + 1)
311
- extend = singleton_class_of(constant).ancestors
312
- .reject do |mod|
313
- mod == singleton_class_of(constant) ||
314
- inherited_singleton_class_ancestors.include?(mod) ||
315
- !public_module?(mod) ||
316
- Module != class_of(mod)
317
- end
302
+ extend = interesting_singleton_class_ancestors.reject do |mod|
303
+ !public_module?(mod) || Module != class_of(mod) || are_equal?(mod, singleton_class)
304
+ end
318
305
 
319
306
  prepends = prepend
320
307
  .reverse
@@ -513,7 +500,9 @@ module Tapioca
513
500
 
514
501
  parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
515
502
 
516
- sanitized_parameters = parameters.map do |type, name|
503
+ sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
504
+ fallback_arg_name = "_arg#{index}"
505
+
517
506
  unless name
518
507
  # For attr_writer methods, Sorbet signatures have the name
519
508
  # of the method (without the trailing = sign) as the name of
@@ -533,12 +522,12 @@ module Tapioca
533
522
  name = if writer_method_with_sig
534
523
  T.must(method_name[0...-1]).to_sym
535
524
  else
536
- :_
525
+ fallback_arg_name
537
526
  end
538
527
  end
539
528
 
540
529
  # Sanitize param names
541
- name = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
530
+ name = name.to_s.gsub(/[^a-zA-Z0-9_]/, fallback_arg_name)
542
531
 
543
532
  [type, name]
544
533
  end
@@ -612,6 +601,7 @@ module Tapioca
612
601
  signature_body = signature_body
613
602
  .gsub(".returns(<VOID>)", ".void")
614
603
  .gsub("<NOT-TYPED>", "T.untyped")
604
+ .gsub(".params()", "")
615
605
  .gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
616
606
 
617
607
  "sig { #{signature_body} }"
@@ -750,18 +740,59 @@ module Tapioca
750
740
  Module.instance_method(:name).bind(constant).call
751
741
  end
752
742
 
753
- sig { params(constant: BasicObject).returns(Class).checked(:never) }
743
+ sig { params(constant: Module).returns(Class) }
754
744
  def singleton_class_of(constant)
755
745
  Object.instance_method(:singleton_class).bind(constant).call
756
746
  end
757
747
 
748
+ sig { params(constant: Module).returns(T::Array[Module]) }
749
+ def ancestors_of(constant)
750
+ Module.instance_method(:ancestors).bind(constant).call
751
+ end
752
+
753
+ sig { params(constant: Module).returns(T::Array[Module]) }
754
+ def inherited_ancestors_of(constant)
755
+ if Class === constant
756
+ ancestors_of(superclass_of(constant) || Object)
757
+ else
758
+ Module.ancestors
759
+ end
760
+ end
761
+
762
+ sig { params(constant: Module).returns(T::Array[Module]) }
763
+ def interesting_ancestors_of(constant)
764
+ inherited_ancestors_ids = Set.new(
765
+ inherited_ancestors_of(constant).map { |mod| object_id_of(mod) }
766
+ )
767
+ # TODO: There is actually a bug here where this will drop modules that
768
+ # may be included twice. For example:
769
+ #
770
+ # ```ruby
771
+ # class Foo
772
+ # prepend Kernel
773
+ # end
774
+ # ````
775
+ # would give:
776
+ # ```ruby
777
+ # Foo.ancestors #=> [Kernel, Foo, Object, Kernel, BasicObject]
778
+ # ````
779
+ # but since we drop `Kernel` whenever we match it, we would miss
780
+ # the `prepend Kernel` in the output.
781
+ #
782
+ # Instead, we should only drop the tail matches of the ancestors and
783
+ # inherited ancestors, past the location of the constant itself.
784
+ constant.ancestors.reject do |mod|
785
+ inherited_ancestors_ids.include?(object_id_of(mod))
786
+ end
787
+ end
788
+
758
789
  sig { params(constant: Module).returns(T.nilable(String)) }
759
790
  def name_of(constant)
760
791
  name = name_of_proxy_target(constant)
761
792
  return name if name
762
793
  name = raw_name_of(constant)
763
794
  return if name.nil?
764
- return unless are_equal?(constant, resolve_constant(name))
795
+ return unless are_equal?(constant, resolve_constant(name, inherit: true))
765
796
  name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
766
797
  name
767
798
  end
@@ -810,6 +841,11 @@ module Tapioca
810
841
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
811
842
  end
812
843
 
844
+ sig { params(object: Object).returns(T::Boolean).checked(:never) }
845
+ def object_id_of(object)
846
+ Object.instance_method(:object_id).bind(object).call
847
+ end
848
+
813
849
  sig { params(constant: Module, other: BasicObject).returns(T::Boolean).checked(:never) }
814
850
  def are_equal?(constant, other)
815
851
  BasicObject.instance_method(:equal?).bind(constant).call(other)