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.
- checksums.yaml +4 -4
- data/Gemfile +5 -1
- data/README.md +9 -7
- data/lib/tapioca/cli.rb +8 -1
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +38 -39
- data/lib/tapioca/compilers/dsl/action_mailer.rb +4 -4
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +52 -51
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +60 -65
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +27 -23
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +18 -17
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +8 -7
- data/lib/tapioca/compilers/dsl/active_resource.rb +5 -4
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +6 -7
- data/lib/tapioca/compilers/dsl/base.rb +14 -10
- data/lib/tapioca/compilers/dsl/frozen_record.rb +25 -25
- data/lib/tapioca/compilers/dsl/{active_record_identity_cache.rb → identity_cache.rb} +11 -10
- data/lib/tapioca/compilers/dsl/protobuf.rb +1 -1
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +83 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +1 -1
- data/lib/tapioca/compilers/dsl/state_machines.rb +75 -73
- data/lib/tapioca/compilers/dsl/url_helpers.rb +9 -5
- data/lib/tapioca/compilers/dsl_compiler.rb +5 -5
- data/lib/tapioca/compilers/requires_compiler.rb +34 -6
- data/lib/tapioca/compilers/sorbet.rb +1 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +69 -33
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -1
- data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/core_ext/class.rb +8 -3
- data/lib/tapioca/gemfile.rb +1 -1
- data/lib/tapioca/generator.rb +1 -1
- data/lib/tapioca/loader.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- 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`
|
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
|
-
# `
|
19
|
-
# (
|
20
|
-
# methods generated by
|
21
|
-
#
|
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
|
49
|
-
# extend
|
50
|
-
# end
|
50
|
+
# include StateMachineInstanceHelperModule
|
51
|
+
# extend StateMachineClassHelperModule
|
51
52
|
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
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
|
-
#
|
57
|
-
#
|
58
|
-
#
|
57
|
+
# sig { params(state: T.any(String, Symbol)).returns(String) }
|
58
|
+
# def human_alarm_state_name(state); end
|
59
|
+
# end
|
59
60
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
61
|
+
# module StateMachineInstanceHelperModule
|
62
|
+
# sig { returns(T::Boolean) }
|
63
|
+
# def alarm_active?; end
|
63
64
|
#
|
64
|
-
#
|
65
|
-
#
|
65
|
+
# sig { returns(T::Boolean) }
|
66
|
+
# def alarm_off?; end
|
66
67
|
#
|
67
|
-
#
|
68
|
-
#
|
68
|
+
# sig { returns(Integer) }
|
69
|
+
# def alarm_state; end
|
69
70
|
#
|
70
|
-
#
|
71
|
-
#
|
71
|
+
# sig { params(value: Integer).returns(Integer) }
|
72
|
+
# def alarm_state=(value); end
|
72
73
|
#
|
73
|
-
#
|
74
|
-
#
|
74
|
+
# sig { params(state: T.any(String, Symbol)).returns(T::Boolean) }
|
75
|
+
# def alarm_state?(state); end
|
75
76
|
#
|
76
|
-
#
|
77
|
-
#
|
77
|
+
# sig { params(args: T.untyped).returns(T::Array[T.any(String, Symbol)]) }
|
78
|
+
# def alarm_state_events(*args); end
|
78
79
|
#
|
79
|
-
#
|
80
|
-
#
|
80
|
+
# sig { returns(T.any(String, Symbol)) }
|
81
|
+
# def alarm_state_name; end
|
81
82
|
#
|
82
|
-
#
|
83
|
-
#
|
83
|
+
# sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
|
84
|
+
# def alarm_state_paths(*args); end
|
84
85
|
#
|
85
|
-
#
|
86
|
-
#
|
86
|
+
# sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
|
87
|
+
# def alarm_state_transitions(*args); end
|
87
88
|
#
|
88
|
-
#
|
89
|
-
#
|
89
|
+
# sig { returns(T::Boolean) }
|
90
|
+
# def can_disable_alarm?; end
|
90
91
|
#
|
91
|
-
#
|
92
|
-
#
|
92
|
+
# sig { returns(T::Boolean) }
|
93
|
+
# def can_enable_alarm?; end
|
93
94
|
#
|
94
|
-
#
|
95
|
-
#
|
95
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
96
|
+
# def disable_alarm(*args); end
|
96
97
|
#
|
97
|
-
#
|
98
|
-
#
|
98
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
99
|
+
# def disable_alarm!(*args); end
|
99
100
|
#
|
100
|
-
#
|
101
|
-
#
|
101
|
+
# sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
|
102
|
+
# def disable_alarm_transition(*args); end
|
102
103
|
#
|
103
|
-
#
|
104
|
-
#
|
104
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
105
|
+
# def enable_alarm(*args); end
|
105
106
|
#
|
106
|
-
#
|
107
|
-
#
|
107
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
108
|
+
# def enable_alarm!(*args); end
|
108
109
|
#
|
109
|
-
#
|
110
|
-
#
|
110
|
+
# sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
|
111
|
+
# def enable_alarm_transition(*args); end
|
111
112
|
#
|
112
|
-
#
|
113
|
-
#
|
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
|
-
#
|
116
|
-
#
|
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
|
-
|
127
|
-
|
128
|
+
root.path(constant) do |klass|
|
129
|
+
instance_module_name = "StateMachineInstanceHelperModule"
|
130
|
+
class_module_name = "StateMachineClassHelperModule"
|
128
131
|
|
129
|
-
|
130
|
-
|
132
|
+
instance_module = klass.create_module(instance_module_name)
|
133
|
+
class_module = klass.create_module(class_module_name)
|
131
134
|
|
132
|
-
|
133
|
-
|
135
|
+
constant.state_machines.each_value do |machine|
|
136
|
+
state_type = state_type_for(machine)
|
134
137
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
+
define_state_methods(instance_module, machine)
|
146
|
+
define_event_methods(instance_module, machine)
|
147
|
+
end
|
145
148
|
|
146
|
-
|
149
|
+
matching_integration_name = ::StateMachines::Integrations.match(constant)&.integration_name
|
147
150
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
#
|
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(
|
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
|
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
|
-
|
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,
|
53
|
-
def file_ignored_by_sorbet?(config,
|
54
|
-
|
55
|
-
|
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
|
|
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
-
|
300
|
-
|
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 =
|
312
|
-
|
313
|
-
|
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:
|
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)
|