tapioca 0.16.9 → 0.17.7
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/README.md +21 -0
- data/exe/tapioca +6 -1
- data/lib/ruby_lsp/tapioca/addon.rb +73 -43
- data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +43 -43
- data/lib/ruby_lsp/tapioca/server_addon.rb +13 -10
- data/lib/tapioca/bundler_ext/auto_require_hook.rb +6 -14
- data/lib/tapioca/cli.rb +16 -8
- data/lib/tapioca/commands/abstract_dsl.rb +39 -66
- data/lib/tapioca/commands/abstract_gem.rb +25 -46
- data/lib/tapioca/commands/annotations.rb +28 -34
- data/lib/tapioca/commands/check_shims.rb +6 -15
- data/lib/tapioca/commands/command.rb +12 -26
- data/lib/tapioca/commands/command_without_tracker.rb +2 -5
- data/lib/tapioca/commands/configure.rb +11 -16
- data/lib/tapioca/commands/dsl_compiler_list.rb +2 -1
- data/lib/tapioca/commands/dsl_generate.rb +2 -1
- data/lib/tapioca/commands/dsl_verify.rb +2 -1
- data/lib/tapioca/commands/gem_generate.rb +5 -9
- data/lib/tapioca/commands/gem_sync.rb +2 -1
- data/lib/tapioca/commands/gem_verify.rb +3 -2
- data/lib/tapioca/commands/require.rb +3 -7
- data/lib/tapioca/commands/todo.rb +6 -10
- data/lib/tapioca/dsl/compiler.rb +36 -63
- data/lib/tapioca/dsl/compilers/aasm.rb +33 -44
- data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +8 -7
- data/lib/tapioca/dsl/compilers/action_mailer.rb +6 -5
- data/lib/tapioca/dsl/compilers/action_text.rb +6 -5
- data/lib/tapioca/dsl/compilers/active_job.rb +6 -10
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +10 -11
- data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +5 -6
- data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +5 -12
- data/lib/tapioca/dsl/compilers/active_record_associations.rb +17 -44
- data/lib/tapioca/dsl/compilers/active_record_columns.rb +20 -26
- data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +9 -8
- data/lib/tapioca/dsl/compilers/active_record_enum.rb +7 -6
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +54 -62
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +148 -209
- data/lib/tapioca/dsl/compilers/active_record_scope.rb +8 -13
- data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +5 -4
- data/lib/tapioca/dsl/compilers/active_record_store.rb +5 -4
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +19 -28
- data/lib/tapioca/dsl/compilers/active_resource.rb +19 -21
- data/lib/tapioca/dsl/compilers/active_storage.rb +6 -14
- data/lib/tapioca/dsl/compilers/active_support_concern.rb +9 -8
- data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +8 -7
- data/lib/tapioca/dsl/compilers/active_support_time_ext.rb +5 -4
- data/lib/tapioca/dsl/compilers/config.rb +5 -4
- data/lib/tapioca/dsl/compilers/frozen_record.rb +7 -11
- data/lib/tapioca/dsl/compilers/graphql_input_object.rb +9 -10
- data/lib/tapioca/dsl/compilers/graphql_mutation.rb +6 -10
- data/lib/tapioca/dsl/compilers/identity_cache.rb +11 -39
- data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +9 -18
- data/lib/tapioca/dsl/compilers/kredis.rb +7 -8
- data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +5 -4
- data/lib/tapioca/dsl/compilers/protobuf.rb +13 -26
- data/lib/tapioca/dsl/compilers/rails_generators.rb +9 -11
- data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +23 -13
- data/lib/tapioca/dsl/compilers/smart_properties.rb +32 -38
- data/lib/tapioca/dsl/compilers/state_machines.rb +15 -26
- data/lib/tapioca/dsl/compilers/url_helpers.rb +10 -9
- data/lib/tapioca/dsl/compilers.rb +4 -7
- data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +13 -16
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +13 -28
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +19 -15
- data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +5 -24
- data/lib/tapioca/dsl/pipeline.rb +30 -58
- data/lib/tapioca/executor.rb +6 -12
- data/lib/tapioca/gem/events.rb +24 -34
- data/lib/tapioca/gem/listeners/base.rb +7 -10
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +4 -2
- data/lib/tapioca/gem/listeners/foreign_constants.rb +5 -7
- data/lib/tapioca/gem/listeners/methods.rb +36 -47
- data/lib/tapioca/gem/listeners/mixins.rb +6 -18
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +4 -2
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +4 -2
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +4 -2
- data/lib/tapioca/gem/listeners/sorbet_props.rb +4 -2
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +4 -2
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +7 -5
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +6 -4
- data/lib/tapioca/gem/listeners/source_location.rb +15 -8
- data/lib/tapioca/gem/listeners/subconstants.rb +5 -4
- data/lib/tapioca/gem/listeners/yard_doc.rb +30 -23
- data/lib/tapioca/gem/pipeline.rb +107 -91
- data/lib/tapioca/gem_info.rb +1 -1
- data/lib/tapioca/gemfile.rb +64 -73
- data/lib/tapioca/helpers/cli_helper.rb +4 -7
- data/lib/tapioca/helpers/config_helper.rb +17 -29
- data/lib/tapioca/helpers/env_helper.rb +2 -5
- data/lib/tapioca/helpers/gem_helper.rb +5 -5
- data/lib/tapioca/helpers/git_attributes.rb +3 -3
- data/lib/tapioca/helpers/rbi_files_helper.rb +76 -73
- data/lib/tapioca/helpers/rbi_helper.rb +14 -22
- data/lib/tapioca/helpers/sorbet_helper.rb +9 -18
- data/lib/tapioca/helpers/source_uri.rb +15 -25
- data/lib/tapioca/helpers/test/content.rb +7 -10
- data/lib/tapioca/helpers/test/dsl_compiler.rb +20 -33
- data/lib/tapioca/helpers/test/isolation.rb +10 -14
- data/lib/tapioca/helpers/test/template.rb +6 -11
- data/lib/tapioca/internal.rb +18 -8
- data/lib/tapioca/loaders/dsl.rb +11 -19
- data/lib/tapioca/loaders/gem.rb +6 -21
- data/lib/tapioca/loaders/loader.rb +21 -39
- data/lib/tapioca/rbi_ext/model.rb +12 -37
- data/lib/tapioca/rbi_formatter.rb +10 -19
- data/lib/tapioca/rbs/rewriter.rb +55 -0
- data/lib/tapioca/repo_index.rb +7 -9
- data/lib/tapioca/runtime/attached_class_of_32.rb +1 -1
- data/lib/tapioca/runtime/attached_class_of_legacy.rb +2 -5
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +23 -23
- data/lib/tapioca/runtime/generic_type_registry.rb +13 -23
- data/lib/tapioca/runtime/reflection.rb +81 -60
- data/lib/tapioca/runtime/source_location.rb +44 -0
- data/lib/tapioca/runtime/trackers/autoload.rb +7 -9
- data/lib/tapioca/runtime/trackers/constant_definition.rb +18 -14
- data/lib/tapioca/runtime/trackers/method_definition.rb +65 -0
- data/lib/tapioca/runtime/trackers/mixin.rb +8 -11
- data/lib/tapioca/runtime/trackers/required_ancestor.rb +3 -3
- data/lib/tapioca/runtime/trackers/tracker.rb +3 -6
- data/lib/tapioca/runtime/trackers.rb +5 -8
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +9 -15
- data/lib/tapioca/sorbet_ext/name_patch.rb +2 -2
- data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +1 -1
- data/lib/tapioca/static/requires_compiler.rb +6 -6
- data/lib/tapioca/static/symbol_loader.rb +14 -16
- data/lib/tapioca/static/symbol_table_parser.rb +8 -8
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +22 -29
- metadata +27 -10
data/lib/tapioca/repo_index.rb
CHANGED
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
module Tapioca
|
|
5
5
|
class RepoIndex
|
|
6
6
|
extend T::Sig
|
|
7
|
-
extend T::Generic
|
|
8
|
-
|
|
9
7
|
class << self
|
|
10
8
|
extend T::Sig
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
#: (String json) -> RepoIndex
|
|
13
11
|
def from_json(json)
|
|
14
12
|
RepoIndex.from_hash(JSON.parse(json))
|
|
15
13
|
end
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
#: (Hash[String, Hash[untyped, untyped]] hash) -> RepoIndex
|
|
18
16
|
def from_hash(hash)
|
|
19
17
|
hash.each_with_object(RepoIndex.new) do |(name, _), index|
|
|
20
18
|
index << name
|
|
@@ -22,22 +20,22 @@ module Tapioca
|
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
#: -> void
|
|
26
24
|
def initialize
|
|
27
|
-
@entries =
|
|
25
|
+
@entries = Set.new #: Set[String]
|
|
28
26
|
end
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
#: (String gem_name) -> void
|
|
31
29
|
def <<(gem_name)
|
|
32
30
|
@entries.add(gem_name)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
#: -> T::Enumerable[String]
|
|
36
34
|
def gems
|
|
37
35
|
@entries.sort
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
#: (String gem_name) -> bool
|
|
41
39
|
def has_gem?(gem_name)
|
|
42
40
|
@entries.include?(gem_name)
|
|
43
41
|
end
|
|
@@ -10,7 +10,7 @@ module Tapioca
|
|
|
10
10
|
module AttachedClassOf
|
|
11
11
|
extend T::Sig
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
#: (Class singleton_class) -> Module?
|
|
14
14
|
def attached_class_of(singleton_class)
|
|
15
15
|
result = singleton_class.attached_object
|
|
16
16
|
Module === result ? result : nil
|
|
@@ -7,13 +7,10 @@ module Tapioca
|
|
|
7
7
|
# older than 3.2. Because the Class#attached_object method is not
|
|
8
8
|
# available, it implements finding the attached class of a singleton
|
|
9
9
|
# class by iterating through ObjectSpace.
|
|
10
|
+
# @requires_ancestor: Tapioca::Runtime::Reflection
|
|
10
11
|
module AttachedClassOf
|
|
11
12
|
extend T::Sig
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
requires_ancestor { Tapioca::Runtime::Reflection }
|
|
15
|
-
|
|
16
|
-
sig { params(singleton_class: Class).returns(T.nilable(Module)) }
|
|
13
|
+
#: (Class singleton_class) -> Module?
|
|
17
14
|
def attached_class_of(singleton_class)
|
|
18
15
|
# https://stackoverflow.com/a/36622320/98634
|
|
19
16
|
result = ObjectSpace.each_object(singleton_class).find do |klass|
|
|
@@ -7,26 +7,26 @@ module Tapioca
|
|
|
7
7
|
extend T::Sig
|
|
8
8
|
include Runtime::Reflection
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
#: Array[Module]
|
|
11
11
|
attr_reader :dynamic_extends, :dynamic_includes
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
#: Array[Symbol]
|
|
14
14
|
attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
#: Array[Symbol]
|
|
17
17
|
attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
#: (Module constant) -> void
|
|
20
20
|
def initialize(constant)
|
|
21
21
|
@constant = constant
|
|
22
22
|
mixins_from_modules = {}.compare_by_identity
|
|
23
|
-
class_attribute_readers =
|
|
24
|
-
class_attribute_writers =
|
|
25
|
-
class_attribute_predicates =
|
|
23
|
+
class_attribute_readers = [] #: Array[Symbol]
|
|
24
|
+
class_attribute_writers = [] #: Array[Symbol]
|
|
25
|
+
class_attribute_predicates = [] #: Array[Symbol]
|
|
26
26
|
|
|
27
|
-
instance_attribute_readers =
|
|
28
|
-
instance_attribute_writers =
|
|
29
|
-
instance_attribute_predicates =
|
|
27
|
+
instance_attribute_readers = [] #: Array[Symbol]
|
|
28
|
+
instance_attribute_writers = [] #: Array[Symbol]
|
|
29
|
+
instance_attribute_predicates = [] #: Array[Symbol]
|
|
30
30
|
|
|
31
31
|
Class.new do
|
|
32
32
|
# Override the `self.include` method
|
|
@@ -112,28 +112,28 @@ module Tapioca
|
|
|
112
112
|
# is the list of all dynamically extended modules because of that
|
|
113
113
|
# constant. We grab that value by deleting the key for the original
|
|
114
114
|
# constant.
|
|
115
|
-
@dynamic_extends =
|
|
115
|
+
@dynamic_extends = mixins_from_modules.delete(constant) || [] #: Array[Module]
|
|
116
116
|
|
|
117
117
|
# Since we deleted the original constant from the list of keys, all
|
|
118
118
|
# the keys that remain are the ones that are dynamically included modules
|
|
119
119
|
# during the include of the original constant.
|
|
120
|
-
@dynamic_includes =
|
|
120
|
+
@dynamic_includes = mixins_from_modules.keys #: Array[Module]
|
|
121
121
|
|
|
122
|
-
@class_attribute_readers =
|
|
123
|
-
@class_attribute_writers =
|
|
124
|
-
@class_attribute_predicates =
|
|
122
|
+
@class_attribute_readers = class_attribute_readers #: Array[Symbol]
|
|
123
|
+
@class_attribute_writers = class_attribute_writers #: Array[Symbol]
|
|
124
|
+
@class_attribute_predicates = class_attribute_predicates #: Array[Symbol]
|
|
125
125
|
|
|
126
|
-
@instance_attribute_readers =
|
|
127
|
-
@instance_attribute_writers =
|
|
128
|
-
@instance_attribute_predicates =
|
|
126
|
+
@instance_attribute_readers = instance_attribute_readers #: Array[Symbol]
|
|
127
|
+
@instance_attribute_writers = instance_attribute_writers #: Array[Symbol]
|
|
128
|
+
@instance_attribute_predicates = instance_attribute_predicates #: Array[Symbol]
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
#: -> bool
|
|
132
132
|
def empty_attributes?
|
|
133
133
|
@class_attribute_readers.empty? && @class_attribute_writers.empty?
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
#: (RBI::Tree tree) -> void
|
|
137
137
|
def compile_class_attributes(tree)
|
|
138
138
|
return if empty_attributes?
|
|
139
139
|
|
|
@@ -176,7 +176,7 @@ module Tapioca
|
|
|
176
176
|
tree << RBI::Include.new("GeneratedInstanceMethods")
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
#: (RBI::Tree tree) -> [Array[Module], Array[Module]]
|
|
180
180
|
def compile_mixes_in_class_methods(tree)
|
|
181
181
|
includes = dynamic_includes.filter_map do |mod|
|
|
182
182
|
qname = qualified_name_of(mod)
|
|
@@ -211,14 +211,14 @@ module Tapioca
|
|
|
211
211
|
[[], []] # silence errors
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
#: (Module mod, Array[Module] dynamic_extends) -> bool
|
|
215
215
|
def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
|
216
216
|
dynamic_extends.any? do |dynamic_extend|
|
|
217
217
|
mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
|
|
218
218
|
end
|
|
219
219
|
end
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
#: (String qualified_mixin_name) -> bool
|
|
222
222
|
def filtered_mixin?(qualified_mixin_name)
|
|
223
223
|
# filter T:: namespace mixins that aren't T::Props
|
|
224
224
|
# T::Props and subconstants have semantic value
|
|
@@ -21,27 +21,22 @@ module Tapioca
|
|
|
21
21
|
# variable to type variable serializers. This allows us to associate type variables
|
|
22
22
|
# to the constant names that represent them, easily.
|
|
23
23
|
module GenericTypeRegistry
|
|
24
|
-
@generic_instances =
|
|
25
|
-
{},
|
|
26
|
-
T::Hash[String, Module],
|
|
27
|
-
)
|
|
24
|
+
@generic_instances = {} #: Hash[String, Module]
|
|
28
25
|
|
|
29
|
-
@type_variables =
|
|
30
|
-
{}.compare_by_identity,
|
|
31
|
-
T::Hash[Module, T::Array[TypeVariableModule]],
|
|
32
|
-
)
|
|
26
|
+
@type_variables = {}.compare_by_identity #: Hash[Module, Array[TypeVariableModule]]
|
|
33
27
|
|
|
34
28
|
class GenericType < T::Types::Simple
|
|
35
29
|
extend T::Sig
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
#: (Module raw_type, Module underlying_type) -> void
|
|
38
32
|
def initialize(raw_type, underlying_type)
|
|
39
33
|
super(raw_type)
|
|
40
34
|
|
|
41
|
-
@underlying_type =
|
|
35
|
+
@underlying_type = underlying_type #: Module
|
|
42
36
|
end
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
# @override
|
|
39
|
+
#: (untyped obj) -> bool
|
|
45
40
|
def valid?(obj)
|
|
46
41
|
obj.is_a?(@underlying_type)
|
|
47
42
|
end
|
|
@@ -61,7 +56,7 @@ module Tapioca
|
|
|
61
56
|
# 2 hash lookups (for the other two `Foo[Integer]`s).
|
|
62
57
|
#
|
|
63
58
|
# This method returns the created or cached clone of the constant.
|
|
64
|
-
|
|
59
|
+
#: (untyped constant, untyped types) -> Module
|
|
65
60
|
def register_type(constant, types)
|
|
66
61
|
# Build the name of the instantiated generic type,
|
|
67
62
|
# something like `"Foo[X, Y, Z]"`
|
|
@@ -76,12 +71,12 @@ module Tapioca
|
|
|
76
71
|
@generic_instances[name] ||= create_generic_type(constant, name)
|
|
77
72
|
end
|
|
78
73
|
|
|
79
|
-
|
|
74
|
+
#: (Object instance) -> bool
|
|
80
75
|
def generic_type_instance?(instance)
|
|
81
76
|
@generic_instances.values.any? { |generic_type| generic_type === instance }
|
|
82
77
|
end
|
|
83
78
|
|
|
84
|
-
|
|
79
|
+
#: (Module constant) -> Array[TypeVariableModule]?
|
|
85
80
|
def lookup_type_variables(constant)
|
|
86
81
|
@type_variables[constant]
|
|
87
82
|
end
|
|
@@ -95,12 +90,7 @@ module Tapioca
|
|
|
95
90
|
#
|
|
96
91
|
# Finally, the original `type_variable` is returned from this method, so that the caller
|
|
97
92
|
# can return it from the original methods as well.
|
|
98
|
-
|
|
99
|
-
params(
|
|
100
|
-
constant: T.untyped,
|
|
101
|
-
type_variable: TypeVariableModule,
|
|
102
|
-
).void
|
|
103
|
-
end
|
|
93
|
+
#: (untyped constant, TypeVariableModule type_variable) -> void
|
|
104
94
|
def register_type_variable(constant, type_variable)
|
|
105
95
|
type_variables = lookup_or_initialize_type_variables(constant)
|
|
106
96
|
|
|
@@ -109,7 +99,7 @@ module Tapioca
|
|
|
109
99
|
|
|
110
100
|
private
|
|
111
101
|
|
|
112
|
-
|
|
102
|
+
#: (Module constant, String name) -> Module
|
|
113
103
|
def create_generic_type(constant, name)
|
|
114
104
|
generic_type = case constant
|
|
115
105
|
when Class
|
|
@@ -151,7 +141,7 @@ module Tapioca
|
|
|
151
141
|
generic_type
|
|
152
142
|
end
|
|
153
143
|
|
|
154
|
-
|
|
144
|
+
#: (Class[top] constant) -> Class[top]
|
|
155
145
|
def create_safe_subclass(constant)
|
|
156
146
|
# Lookup the "inherited" class method
|
|
157
147
|
inherited_method = constant.method(:inherited)
|
|
@@ -178,7 +168,7 @@ module Tapioca
|
|
|
178
168
|
end
|
|
179
169
|
end
|
|
180
170
|
|
|
181
|
-
|
|
171
|
+
#: (Module constant) -> Array[TypeVariableModule]
|
|
182
172
|
def lookup_or_initialize_type_variables(constant)
|
|
183
173
|
@type_variables[constant] ||= []
|
|
184
174
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "tapioca/runtime/source_location"
|
|
5
|
+
|
|
4
6
|
# On Ruby 3.2 or newer, Class defines an attached_object method that returns the
|
|
5
7
|
# attached class of a singleton class without iterating ObjectSpace. On older
|
|
6
8
|
# versions of Ruby, we fall back to iterating ObjectSpace.
|
|
@@ -18,97 +20,93 @@ module Tapioca
|
|
|
18
20
|
extend T::Sig
|
|
19
21
|
extend self
|
|
20
22
|
|
|
21
|
-
CLASS_METHOD =
|
|
22
|
-
CONSTANTS_METHOD =
|
|
23
|
-
NAME_METHOD =
|
|
24
|
-
SINGLETON_CLASS_METHOD =
|
|
25
|
-
ANCESTORS_METHOD =
|
|
26
|
-
SUPERCLASS_METHOD =
|
|
27
|
-
OBJECT_ID_METHOD =
|
|
28
|
-
EQUAL_METHOD =
|
|
29
|
-
PUBLIC_INSTANCE_METHODS_METHOD =
|
|
30
|
-
PROTECTED_INSTANCE_METHODS_METHOD =
|
|
31
|
-
PRIVATE_INSTANCE_METHODS_METHOD =
|
|
32
|
-
METHOD_METHOD =
|
|
33
|
-
UNDEFINED_CONSTANT =
|
|
34
|
-
|
|
35
|
-
REQUIRED_FROM_LABELS =
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
CLASS_METHOD = Kernel.instance_method(:class) #: UnboundMethod
|
|
24
|
+
CONSTANTS_METHOD = Module.instance_method(:constants) #: UnboundMethod
|
|
25
|
+
NAME_METHOD = Module.instance_method(:name) #: UnboundMethod
|
|
26
|
+
SINGLETON_CLASS_METHOD = Object.instance_method(:singleton_class) #: UnboundMethod
|
|
27
|
+
ANCESTORS_METHOD = Module.instance_method(:ancestors) #: UnboundMethod
|
|
28
|
+
SUPERCLASS_METHOD = Class.instance_method(:superclass) #: UnboundMethod
|
|
29
|
+
OBJECT_ID_METHOD = BasicObject.instance_method(:__id__) #: UnboundMethod
|
|
30
|
+
EQUAL_METHOD = BasicObject.instance_method(:equal?) #: UnboundMethod
|
|
31
|
+
PUBLIC_INSTANCE_METHODS_METHOD = Module.instance_method(:public_instance_methods) #: UnboundMethod
|
|
32
|
+
PROTECTED_INSTANCE_METHODS_METHOD = Module.instance_method(:protected_instance_methods) #: UnboundMethod
|
|
33
|
+
PRIVATE_INSTANCE_METHODS_METHOD = Module.instance_method(:private_instance_methods) #: UnboundMethod
|
|
34
|
+
METHOD_METHOD = Kernel.instance_method(:method) #: UnboundMethod
|
|
35
|
+
UNDEFINED_CONSTANT = Module.new.freeze #: Module
|
|
36
|
+
|
|
37
|
+
REQUIRED_FROM_LABELS = ["<top (required)>", "<main>", "<compiled>"].freeze #: Array[String]
|
|
38
|
+
|
|
39
|
+
# @without_runtime
|
|
40
|
+
#: (BasicObject constant) -> bool
|
|
38
41
|
def constant_defined?(constant)
|
|
39
42
|
!UNDEFINED_CONSTANT.eql?(constant)
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
symbol: String,
|
|
45
|
-
inherit: T::Boolean,
|
|
46
|
-
namespace: Module,
|
|
47
|
-
).returns(BasicObject).checked(:never)
|
|
48
|
-
end
|
|
45
|
+
# @without_runtime
|
|
46
|
+
#: (String symbol, ?inherit: bool, ?namespace: Module) -> BasicObject
|
|
49
47
|
def constantize(symbol, inherit: false, namespace: Object)
|
|
50
48
|
namespace.const_get(symbol, inherit)
|
|
51
49
|
rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
|
|
52
50
|
UNDEFINED_CONSTANT
|
|
53
51
|
end
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
#: (BasicObject object) -> Class[top]
|
|
56
54
|
def class_of(object)
|
|
57
55
|
CLASS_METHOD.bind_call(object)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
#: (Module constant) -> Array[Symbol]
|
|
61
59
|
def constants_of(constant)
|
|
62
60
|
CONSTANTS_METHOD.bind_call(constant, false)
|
|
63
61
|
end
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
#: (Module constant) -> String?
|
|
66
64
|
def name_of(constant)
|
|
67
65
|
name = NAME_METHOD.bind_call(constant)
|
|
68
66
|
name&.start_with?("#<") ? nil : name
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
#: (Module constant) -> Class[top]
|
|
72
70
|
def singleton_class_of(constant)
|
|
73
71
|
SINGLETON_CLASS_METHOD.bind_call(constant)
|
|
74
72
|
end
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
#: (Module constant) -> Array[Module]
|
|
77
75
|
def ancestors_of(constant)
|
|
78
76
|
ANCESTORS_METHOD.bind_call(constant)
|
|
79
77
|
end
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
#: (Class[top] constant) -> Class[top]?
|
|
82
80
|
def superclass_of(constant)
|
|
83
81
|
SUPERCLASS_METHOD.bind_call(constant)
|
|
84
82
|
end
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
#: (BasicObject object) -> Integer
|
|
87
85
|
def object_id_of(object)
|
|
88
86
|
OBJECT_ID_METHOD.bind_call(object)
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
#: (BasicObject object, BasicObject other) -> bool
|
|
92
90
|
def are_equal?(object, other)
|
|
93
91
|
EQUAL_METHOD.bind_call(object, other)
|
|
94
92
|
end
|
|
95
93
|
|
|
96
|
-
|
|
94
|
+
#: (Module constant) -> Array[Symbol]
|
|
97
95
|
def public_instance_methods_of(constant)
|
|
98
96
|
PUBLIC_INSTANCE_METHODS_METHOD.bind_call(constant)
|
|
99
97
|
end
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
#: (Module constant) -> Array[Symbol]
|
|
102
100
|
def protected_instance_methods_of(constant)
|
|
103
101
|
PROTECTED_INSTANCE_METHODS_METHOD.bind_call(constant)
|
|
104
102
|
end
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
#: (Module constant) -> Array[Symbol]
|
|
107
105
|
def private_instance_methods_of(constant)
|
|
108
106
|
PRIVATE_INSTANCE_METHODS_METHOD.bind_call(constant)
|
|
109
107
|
end
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
#: (Module constant) -> Array[Module]
|
|
112
110
|
def inherited_ancestors_of(constant)
|
|
113
111
|
if Class === constant
|
|
114
112
|
ancestors_of(superclass_of(constant) || Object)
|
|
@@ -117,7 +115,7 @@ module Tapioca
|
|
|
117
115
|
end
|
|
118
116
|
end
|
|
119
117
|
|
|
120
|
-
|
|
118
|
+
#: (Module constant) -> String?
|
|
121
119
|
def qualified_name_of(constant)
|
|
122
120
|
name = name_of(constant)
|
|
123
121
|
return if name.nil?
|
|
@@ -129,24 +127,28 @@ module Tapioca
|
|
|
129
127
|
end
|
|
130
128
|
end
|
|
131
129
|
|
|
132
|
-
|
|
130
|
+
SignatureBlockError = Class.new(Tapioca::Error)
|
|
131
|
+
|
|
132
|
+
#: ((UnboundMethod | Method) method) -> untyped
|
|
133
133
|
def signature_of!(method)
|
|
134
134
|
T::Utils.signature_for_method(method)
|
|
135
|
+
rescue LoadError, StandardError
|
|
136
|
+
Kernel.raise SignatureBlockError
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
#: ((UnboundMethod | Method) method) -> untyped
|
|
138
140
|
def signature_of(method)
|
|
139
141
|
signature_of!(method)
|
|
140
|
-
rescue
|
|
142
|
+
rescue SignatureBlockError
|
|
141
143
|
nil
|
|
142
144
|
end
|
|
143
145
|
|
|
144
|
-
|
|
146
|
+
#: (T::Types::Base type) -> String
|
|
145
147
|
def name_of_type(type)
|
|
146
148
|
type.to_s
|
|
147
149
|
end
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
#: (Module constant, Symbol method) -> Method
|
|
150
152
|
def method_of(constant, method)
|
|
151
153
|
METHOD_METHOD.bind_call(constant, method)
|
|
152
154
|
end
|
|
@@ -164,11 +166,7 @@ module Tapioca
|
|
|
164
166
|
#
|
|
165
167
|
# class D < C; end
|
|
166
168
|
# descendants_of(C) # => [B, A, D]
|
|
167
|
-
|
|
168
|
-
type_parameters(:U)
|
|
169
|
-
.params(klass: T.all(T::Class[T.anything], T.type_parameter(:U)))
|
|
170
|
-
.returns(T::Array[T.type_parameter(:U)])
|
|
171
|
-
end
|
|
169
|
+
#: [U] ((Class[top] & U) klass) -> Array[U]
|
|
172
170
|
def descendants_of(klass)
|
|
173
171
|
result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
|
|
174
172
|
k.singleton_class? || k == klass
|
|
@@ -177,51 +175,74 @@ module Tapioca
|
|
|
177
175
|
T.unsafe(result)
|
|
178
176
|
end
|
|
179
177
|
|
|
178
|
+
#: ((String | Symbol) constant_name) -> SourceLocation?
|
|
179
|
+
def const_source_location(constant_name)
|
|
180
|
+
return unless Object.respond_to?(:const_source_location)
|
|
181
|
+
|
|
182
|
+
file, line = Object.const_source_location(constant_name)
|
|
183
|
+
|
|
184
|
+
SourceLocation.from_loc([file, line]) if file && line
|
|
185
|
+
end
|
|
186
|
+
|
|
180
187
|
# Examines the call stack to identify the closest location where a "require" is performed
|
|
181
188
|
# by searching for the label "<top (required)>" or "block in <class:...>" in the
|
|
182
189
|
# case of an ActiveSupport.on_load hook. If none is found, it returns the location
|
|
183
190
|
# labeled "<main>", which is the original call site.
|
|
184
|
-
|
|
191
|
+
#: (Array[Thread::Backtrace::Location]? locations) -> SourceLocation?
|
|
185
192
|
def resolve_loc(locations)
|
|
186
|
-
return
|
|
193
|
+
return unless locations
|
|
187
194
|
|
|
195
|
+
# Find the location of the closest file load, which should give us the location of the file that
|
|
196
|
+
# triggered the definition.
|
|
188
197
|
resolved_loc = locations.find do |loc|
|
|
189
198
|
label = loc.label
|
|
190
199
|
next unless label
|
|
191
200
|
|
|
192
201
|
REQUIRED_FROM_LABELS.include?(label) || label.start_with?("block in <class:")
|
|
193
202
|
end
|
|
194
|
-
return
|
|
203
|
+
return unless resolved_loc
|
|
204
|
+
|
|
205
|
+
resolved_loc_path = resolved_loc.absolute_path || resolved_loc.path
|
|
206
|
+
|
|
207
|
+
# Find the location of the last frame in this file to get the most accurate line number.
|
|
208
|
+
resolved_loc = locations.find { |loc| loc.absolute_path == resolved_loc_path }
|
|
209
|
+
return unless resolved_loc
|
|
210
|
+
|
|
211
|
+
# If the last operation was a `require`, and we have no more frames,
|
|
212
|
+
# we are probably dealing with a C-method.
|
|
213
|
+
return if locations.first&.label == "require"
|
|
214
|
+
|
|
215
|
+
file = resolved_loc.absolute_path || resolved_loc.path || ""
|
|
195
216
|
|
|
196
|
-
resolved_loc.
|
|
217
|
+
SourceLocation.from_loc([file, resolved_loc.lineno])
|
|
197
218
|
end
|
|
198
219
|
|
|
199
|
-
|
|
220
|
+
#: (Module constant) -> Set[String]
|
|
200
221
|
def file_candidates_for(constant)
|
|
201
222
|
relevant_methods_for(constant).filter_map do |method|
|
|
202
223
|
method.source_location&.first
|
|
203
224
|
end.to_set
|
|
204
225
|
end
|
|
205
226
|
|
|
206
|
-
|
|
227
|
+
#: (Module constant) -> untyped
|
|
207
228
|
def abstract_type_of(constant)
|
|
208
229
|
T::Private::Abstract::Data.get(constant, :abstract_type) ||
|
|
209
230
|
T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
|
|
210
231
|
end
|
|
211
232
|
|
|
212
|
-
|
|
233
|
+
#: (Module constant) -> bool
|
|
213
234
|
def final_module?(constant)
|
|
214
235
|
T::Private::Final.final_module?(constant)
|
|
215
236
|
end
|
|
216
237
|
|
|
217
|
-
|
|
238
|
+
#: (Module constant) -> bool
|
|
218
239
|
def sealed_module?(constant)
|
|
219
240
|
T::Private::Sealed.sealed_module?(constant)
|
|
220
241
|
end
|
|
221
242
|
|
|
222
243
|
private
|
|
223
244
|
|
|
224
|
-
|
|
245
|
+
#: (Module constant) -> Array[UnboundMethod]
|
|
225
246
|
def relevant_methods_for(constant)
|
|
226
247
|
methods = methods_for(constant).select(&:source_location)
|
|
227
248
|
.reject { |x| method_defined_by_forwardable_module?(x) }
|
|
@@ -237,7 +258,7 @@ module Tapioca
|
|
|
237
258
|
end
|
|
238
259
|
end
|
|
239
260
|
|
|
240
|
-
|
|
261
|
+
#: (Module constant) -> Array[UnboundMethod]
|
|
241
262
|
def methods_for(constant)
|
|
242
263
|
modules = [constant, singleton_class_of(constant)]
|
|
243
264
|
method_list_methods = [
|
|
@@ -251,7 +272,7 @@ module Tapioca
|
|
|
251
272
|
end
|
|
252
273
|
end
|
|
253
274
|
|
|
254
|
-
|
|
275
|
+
#: (Module parent, String name) -> Module?
|
|
255
276
|
def child_module_for_parent_with_name(parent, name)
|
|
256
277
|
return if parent.autoload?(name)
|
|
257
278
|
|
|
@@ -262,12 +283,12 @@ module Tapioca
|
|
|
262
283
|
child
|
|
263
284
|
end
|
|
264
285
|
|
|
265
|
-
|
|
286
|
+
#: (UnboundMethod method) -> bool
|
|
266
287
|
def method_defined_by_forwardable_module?(method)
|
|
267
288
|
method.source_location&.first == Object.const_source_location(:Forwardable)&.first
|
|
268
289
|
end
|
|
269
290
|
|
|
270
|
-
|
|
291
|
+
#: (String name) -> bool
|
|
271
292
|
def has_aliased_namespace?(name)
|
|
272
293
|
name_parts = name.split("::")
|
|
273
294
|
name_parts.pop # drop the constant name, leaving just the namespace
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module Runtime
|
|
6
|
+
class SourceLocation
|
|
7
|
+
# this looks something like:
|
|
8
|
+
# "(eval at /path/to/file.rb:123)"
|
|
9
|
+
# and we are interested in the "/path/to/file.rb" and "123" parts
|
|
10
|
+
EVAL_SOURCE_FILE_PATTERN = /^\(eval at (?<file>.+):(?<line>\d+)\)/ #: Regexp
|
|
11
|
+
|
|
12
|
+
#: String
|
|
13
|
+
attr_reader :file
|
|
14
|
+
|
|
15
|
+
#: Integer
|
|
16
|
+
attr_reader :line
|
|
17
|
+
|
|
18
|
+
def initialize(file:, line:)
|
|
19
|
+
# Ruby 3.3 adds automatic definition of source location for evals if
|
|
20
|
+
# `file` and `line` arguments are not provided. This results in the source
|
|
21
|
+
# file being something like `(eval at /path/to/file.rb:123)`. We try to parse
|
|
22
|
+
# this string to get the actual source file.
|
|
23
|
+
eval_pattern_match = EVAL_SOURCE_FILE_PATTERN.match(file)
|
|
24
|
+
if eval_pattern_match
|
|
25
|
+
file = eval_pattern_match[:file]
|
|
26
|
+
line = eval_pattern_match[:line].to_i
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@file = file
|
|
30
|
+
@line = line
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# force all callers to use the from_loc method
|
|
34
|
+
private_class_method :new
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
#: ([String?, Integer?]? loc) -> SourceLocation?
|
|
38
|
+
def from_loc(loc)
|
|
39
|
+
new(file: loc.first, line: loc.last) if loc&.first && loc.last
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|