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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -0
  3. data/exe/tapioca +6 -1
  4. data/lib/ruby_lsp/tapioca/addon.rb +73 -43
  5. data/lib/ruby_lsp/tapioca/run_gem_rbi_check.rb +43 -43
  6. data/lib/ruby_lsp/tapioca/server_addon.rb +13 -10
  7. data/lib/tapioca/bundler_ext/auto_require_hook.rb +6 -14
  8. data/lib/tapioca/cli.rb +16 -8
  9. data/lib/tapioca/commands/abstract_dsl.rb +39 -66
  10. data/lib/tapioca/commands/abstract_gem.rb +25 -46
  11. data/lib/tapioca/commands/annotations.rb +28 -34
  12. data/lib/tapioca/commands/check_shims.rb +6 -15
  13. data/lib/tapioca/commands/command.rb +12 -26
  14. data/lib/tapioca/commands/command_without_tracker.rb +2 -5
  15. data/lib/tapioca/commands/configure.rb +11 -16
  16. data/lib/tapioca/commands/dsl_compiler_list.rb +2 -1
  17. data/lib/tapioca/commands/dsl_generate.rb +2 -1
  18. data/lib/tapioca/commands/dsl_verify.rb +2 -1
  19. data/lib/tapioca/commands/gem_generate.rb +5 -9
  20. data/lib/tapioca/commands/gem_sync.rb +2 -1
  21. data/lib/tapioca/commands/gem_verify.rb +3 -2
  22. data/lib/tapioca/commands/require.rb +3 -7
  23. data/lib/tapioca/commands/todo.rb +6 -10
  24. data/lib/tapioca/dsl/compiler.rb +36 -63
  25. data/lib/tapioca/dsl/compilers/aasm.rb +33 -44
  26. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +8 -7
  27. data/lib/tapioca/dsl/compilers/action_mailer.rb +6 -5
  28. data/lib/tapioca/dsl/compilers/action_text.rb +6 -5
  29. data/lib/tapioca/dsl/compilers/active_job.rb +6 -10
  30. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +10 -11
  31. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +5 -6
  32. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +5 -12
  33. data/lib/tapioca/dsl/compilers/active_record_associations.rb +17 -44
  34. data/lib/tapioca/dsl/compilers/active_record_columns.rb +20 -26
  35. data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +9 -8
  36. data/lib/tapioca/dsl/compilers/active_record_enum.rb +7 -6
  37. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +54 -62
  38. data/lib/tapioca/dsl/compilers/active_record_relations.rb +148 -209
  39. data/lib/tapioca/dsl/compilers/active_record_scope.rb +8 -13
  40. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +5 -4
  41. data/lib/tapioca/dsl/compilers/active_record_store.rb +5 -4
  42. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +19 -28
  43. data/lib/tapioca/dsl/compilers/active_resource.rb +19 -21
  44. data/lib/tapioca/dsl/compilers/active_storage.rb +6 -14
  45. data/lib/tapioca/dsl/compilers/active_support_concern.rb +9 -8
  46. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +8 -7
  47. data/lib/tapioca/dsl/compilers/active_support_time_ext.rb +5 -4
  48. data/lib/tapioca/dsl/compilers/config.rb +5 -4
  49. data/lib/tapioca/dsl/compilers/frozen_record.rb +7 -11
  50. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +9 -10
  51. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +6 -10
  52. data/lib/tapioca/dsl/compilers/identity_cache.rb +11 -39
  53. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +9 -18
  54. data/lib/tapioca/dsl/compilers/kredis.rb +7 -8
  55. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +5 -4
  56. data/lib/tapioca/dsl/compilers/protobuf.rb +13 -26
  57. data/lib/tapioca/dsl/compilers/rails_generators.rb +9 -11
  58. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +23 -13
  59. data/lib/tapioca/dsl/compilers/smart_properties.rb +32 -38
  60. data/lib/tapioca/dsl/compilers/state_machines.rb +15 -26
  61. data/lib/tapioca/dsl/compilers/url_helpers.rb +10 -9
  62. data/lib/tapioca/dsl/compilers.rb +4 -7
  63. data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +13 -16
  64. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +13 -28
  65. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +19 -15
  66. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +5 -24
  67. data/lib/tapioca/dsl/pipeline.rb +30 -58
  68. data/lib/tapioca/executor.rb +6 -12
  69. data/lib/tapioca/gem/events.rb +24 -34
  70. data/lib/tapioca/gem/listeners/base.rb +7 -10
  71. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +4 -2
  72. data/lib/tapioca/gem/listeners/foreign_constants.rb +5 -7
  73. data/lib/tapioca/gem/listeners/methods.rb +36 -47
  74. data/lib/tapioca/gem/listeners/mixins.rb +6 -18
  75. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +4 -2
  76. data/lib/tapioca/gem/listeners/sorbet_enums.rb +4 -2
  77. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +4 -2
  78. data/lib/tapioca/gem/listeners/sorbet_props.rb +4 -2
  79. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +4 -2
  80. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +7 -5
  81. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +6 -4
  82. data/lib/tapioca/gem/listeners/source_location.rb +15 -8
  83. data/lib/tapioca/gem/listeners/subconstants.rb +5 -4
  84. data/lib/tapioca/gem/listeners/yard_doc.rb +30 -23
  85. data/lib/tapioca/gem/pipeline.rb +107 -91
  86. data/lib/tapioca/gem_info.rb +1 -1
  87. data/lib/tapioca/gemfile.rb +64 -73
  88. data/lib/tapioca/helpers/cli_helper.rb +4 -7
  89. data/lib/tapioca/helpers/config_helper.rb +17 -29
  90. data/lib/tapioca/helpers/env_helper.rb +2 -5
  91. data/lib/tapioca/helpers/gem_helper.rb +5 -5
  92. data/lib/tapioca/helpers/git_attributes.rb +3 -3
  93. data/lib/tapioca/helpers/rbi_files_helper.rb +76 -73
  94. data/lib/tapioca/helpers/rbi_helper.rb +14 -22
  95. data/lib/tapioca/helpers/sorbet_helper.rb +9 -18
  96. data/lib/tapioca/helpers/source_uri.rb +15 -25
  97. data/lib/tapioca/helpers/test/content.rb +7 -10
  98. data/lib/tapioca/helpers/test/dsl_compiler.rb +20 -33
  99. data/lib/tapioca/helpers/test/isolation.rb +10 -14
  100. data/lib/tapioca/helpers/test/template.rb +6 -11
  101. data/lib/tapioca/internal.rb +18 -8
  102. data/lib/tapioca/loaders/dsl.rb +11 -19
  103. data/lib/tapioca/loaders/gem.rb +6 -21
  104. data/lib/tapioca/loaders/loader.rb +21 -39
  105. data/lib/tapioca/rbi_ext/model.rb +12 -37
  106. data/lib/tapioca/rbi_formatter.rb +10 -19
  107. data/lib/tapioca/rbs/rewriter.rb +55 -0
  108. data/lib/tapioca/repo_index.rb +7 -9
  109. data/lib/tapioca/runtime/attached_class_of_32.rb +1 -1
  110. data/lib/tapioca/runtime/attached_class_of_legacy.rb +2 -5
  111. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +23 -23
  112. data/lib/tapioca/runtime/generic_type_registry.rb +13 -23
  113. data/lib/tapioca/runtime/reflection.rb +81 -60
  114. data/lib/tapioca/runtime/source_location.rb +44 -0
  115. data/lib/tapioca/runtime/trackers/autoload.rb +7 -9
  116. data/lib/tapioca/runtime/trackers/constant_definition.rb +18 -14
  117. data/lib/tapioca/runtime/trackers/method_definition.rb +65 -0
  118. data/lib/tapioca/runtime/trackers/mixin.rb +8 -11
  119. data/lib/tapioca/runtime/trackers/required_ancestor.rb +3 -3
  120. data/lib/tapioca/runtime/trackers/tracker.rb +3 -6
  121. data/lib/tapioca/runtime/trackers.rb +5 -8
  122. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +9 -15
  123. data/lib/tapioca/sorbet_ext/name_patch.rb +2 -2
  124. data/lib/tapioca/sorbet_ext/proc_bind_patch.rb +1 -1
  125. data/lib/tapioca/static/requires_compiler.rb +6 -6
  126. data/lib/tapioca/static/symbol_loader.rb +14 -16
  127. data/lib/tapioca/static/symbol_table_parser.rb +8 -8
  128. data/lib/tapioca/version.rb +1 -1
  129. data/lib/tapioca.rb +22 -29
  130. metadata +27 -10
@@ -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
- sig { params(json: String).returns(RepoIndex) }
10
+ #: (String json) -> RepoIndex
13
11
  def from_json(json)
14
12
  RepoIndex.from_hash(JSON.parse(json))
15
13
  end
16
14
 
17
- sig { params(hash: T::Hash[String, T::Hash[T.untyped, T.untyped]]).returns(RepoIndex) }
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
- sig { void }
23
+ #: -> void
26
24
  def initialize
27
- @entries = T.let(Set.new, T::Set[String])
25
+ @entries = Set.new #: Set[String]
28
26
  end
29
27
 
30
- sig { params(gem_name: String).void }
28
+ #: (String gem_name) -> void
31
29
  def <<(gem_name)
32
30
  @entries.add(gem_name)
33
31
  end
34
32
 
35
- sig { returns(T::Enumerable[String]) }
33
+ #: -> T::Enumerable[String]
36
34
  def gems
37
35
  @entries.sort
38
36
  end
39
37
 
40
- sig { params(gem_name: String).returns(T::Boolean) }
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
- sig { params(singleton_class: Class).returns(T.nilable(Module)) }
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
- extend T::Helpers
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
- sig { returns(T::Array[Module]) }
10
+ #: Array[Module]
11
11
  attr_reader :dynamic_extends, :dynamic_includes
12
12
 
13
- sig { returns(T::Array[Symbol]) }
13
+ #: Array[Symbol]
14
14
  attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
15
15
 
16
- sig { returns(T::Array[Symbol]) }
16
+ #: Array[Symbol]
17
17
  attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
18
18
 
19
- sig { params(constant: Module).void }
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 = T.let([], T::Array[Symbol])
24
- class_attribute_writers = T.let([], T::Array[Symbol])
25
- class_attribute_predicates = T.let([], T::Array[Symbol])
23
+ class_attribute_readers = [] #: Array[Symbol]
24
+ class_attribute_writers = [] #: Array[Symbol]
25
+ class_attribute_predicates = [] #: Array[Symbol]
26
26
 
27
- instance_attribute_readers = T.let([], T::Array[Symbol])
28
- instance_attribute_writers = T.let([], T::Array[Symbol])
29
- instance_attribute_predicates = T.let([], T::Array[Symbol])
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 = T.let(mixins_from_modules.delete(constant) || [], T::Array[Module])
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 = T.let(mixins_from_modules.keys, T::Array[Module])
120
+ @dynamic_includes = mixins_from_modules.keys #: Array[Module]
121
121
 
122
- @class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol])
123
- @class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol])
124
- @class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol])
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 = T.let(instance_attribute_readers, T::Array[Symbol])
127
- @instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol])
128
- @instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol])
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
- sig { returns(T::Boolean) }
131
+ #: -> bool
132
132
  def empty_attributes?
133
133
  @class_attribute_readers.empty? && @class_attribute_writers.empty?
134
134
  end
135
135
 
136
- sig { params(tree: RBI::Tree).void }
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
- sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
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
- sig { params(mod: Module, dynamic_extends: T::Array[Module]).returns(T::Boolean) }
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
- sig { params(qualified_mixin_name: String).returns(T::Boolean) }
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 = T.let(
25
- {},
26
- T::Hash[String, Module],
27
- )
24
+ @generic_instances = {} #: Hash[String, Module]
28
25
 
29
- @type_variables = T.let(
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
- sig { params(raw_type: Module, underlying_type: Module).void }
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 = T.let(underlying_type, Module)
35
+ @underlying_type = underlying_type #: Module
42
36
  end
43
37
 
44
- sig { override.params(obj: T.untyped).returns(T::Boolean) }
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
- sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
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
- sig { params(instance: Object).returns(T::Boolean) }
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
- sig { params(constant: Module).returns(T.nilable(T::Array[TypeVariableModule])) }
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
- sig do
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
- sig { params(constant: Module, name: String).returns(Module) }
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
- sig { params(constant: T::Class[T.anything]).returns(T::Class[T.anything]) }
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
- sig { params(constant: Module).returns(T::Array[TypeVariableModule]) }
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 = T.let(Kernel.instance_method(:class), UnboundMethod)
22
- CONSTANTS_METHOD = T.let(Module.instance_method(:constants), UnboundMethod)
23
- NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
24
- SINGLETON_CLASS_METHOD = T.let(Object.instance_method(:singleton_class), UnboundMethod)
25
- ANCESTORS_METHOD = T.let(Module.instance_method(:ancestors), UnboundMethod)
26
- SUPERCLASS_METHOD = T.let(Class.instance_method(:superclass), UnboundMethod)
27
- OBJECT_ID_METHOD = T.let(BasicObject.instance_method(:__id__), UnboundMethod)
28
- EQUAL_METHOD = T.let(BasicObject.instance_method(:equal?), UnboundMethod)
29
- PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
30
- PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
31
- PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
32
- METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
33
- UNDEFINED_CONSTANT = T.let(Module.new.freeze, Module)
34
-
35
- REQUIRED_FROM_LABELS = T.let(["<top (required)>", "<main>"].freeze, T::Array[String])
36
-
37
- T::Sig::WithoutRuntime.sig { params(constant: BasicObject).returns(T::Boolean) }
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
- sig do
43
- params(
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
- sig { params(object: BasicObject).returns(T::Class[T.anything]).checked(:never) }
53
+ #: (BasicObject object) -> Class[top]
56
54
  def class_of(object)
57
55
  CLASS_METHOD.bind_call(object)
58
56
  end
59
57
 
60
- sig { params(constant: Module).returns(T::Array[Symbol]) }
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
- sig { params(constant: Module).returns(T.nilable(String)) }
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
- sig { params(constant: Module).returns(T::Class[T.anything]) }
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
- sig { params(constant: Module).returns(T::Array[Module]) }
74
+ #: (Module constant) -> Array[Module]
77
75
  def ancestors_of(constant)
78
76
  ANCESTORS_METHOD.bind_call(constant)
79
77
  end
80
78
 
81
- sig { params(constant: T::Class[T.anything]).returns(T.nilable(T::Class[T.anything])) }
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
- sig { params(object: BasicObject).returns(Integer).checked(:never) }
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
- sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
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
- sig { params(constant: Module).returns(T::Array[Symbol]) }
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
- sig { params(constant: Module).returns(T::Array[Symbol]) }
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
- sig { params(constant: Module).returns(T::Array[Symbol]) }
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
- sig { params(constant: Module).returns(T::Array[Module]) }
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
- sig { params(constant: Module).returns(T.nilable(String)) }
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
- sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
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
- sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
139
+ #: ((UnboundMethod | Method) method) -> untyped
138
140
  def signature_of(method)
139
141
  signature_of!(method)
140
- rescue LoadError, StandardError
142
+ rescue SignatureBlockError
141
143
  nil
142
144
  end
143
145
 
144
- sig { params(type: T::Types::Base).returns(String) }
146
+ #: (T::Types::Base type) -> String
145
147
  def name_of_type(type)
146
148
  type.to_s
147
149
  end
148
150
 
149
- sig { params(constant: Module, method: Symbol).returns(Method) }
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
- sig do
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
- sig { params(locations: T.nilable(T::Array[Thread::Backtrace::Location])).returns(String) }
191
+ #: (Array[Thread::Backtrace::Location]? locations) -> SourceLocation?
185
192
  def resolve_loc(locations)
186
- return "" unless locations
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 "" unless resolved_loc
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.absolute_path || ""
217
+ SourceLocation.from_loc([file, resolved_loc.lineno])
197
218
  end
198
219
 
199
- sig { params(constant: Module).returns(T::Set[String]) }
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
- sig { params(constant: Module).returns(T.untyped) }
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
- sig { params(constant: Module).returns(T::Boolean) }
233
+ #: (Module constant) -> bool
213
234
  def final_module?(constant)
214
235
  T::Private::Final.final_module?(constant)
215
236
  end
216
237
 
217
- sig { params(constant: Module).returns(T::Boolean) }
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
- sig { params(constant: Module).returns(T::Array[UnboundMethod]) }
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
- sig { params(constant: Module).returns(T::Array[UnboundMethod]) }
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
- sig { params(parent: Module, name: String).returns(T.nilable(Module)) }
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
- sig { params(method: UnboundMethod).returns(T::Boolean) }
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
- sig { params(name: String).returns(T::Boolean) }
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