tapioca 0.4.24 → 0.5.1

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -14
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +256 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  13. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  14. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  15. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  16. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  17. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  18. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  19. data/lib/tapioca/compilers/dsl/active_support_concern.rb +108 -0
  20. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  21. data/lib/tapioca/compilers/dsl/base.rb +96 -82
  22. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  23. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  24. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  25. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  26. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  27. data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
  28. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  29. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  30. data/lib/tapioca/compilers/dsl_compiler.rb +22 -38
  31. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  32. data/lib/tapioca/compilers/sorbet.rb +26 -5
  33. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +139 -154
  34. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  35. data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
  36. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  37. data/lib/tapioca/config.rb +2 -0
  38. data/lib/tapioca/config_builder.rb +4 -2
  39. data/lib/tapioca/constant_locator.rb +6 -8
  40. data/lib/tapioca/gemfile.rb +26 -19
  41. data/lib/tapioca/generator.rb +127 -43
  42. data/lib/tapioca/generic_type_registry.rb +25 -98
  43. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  44. data/lib/tapioca/internal.rb +1 -9
  45. data/lib/tapioca/loader.rb +14 -48
  46. data/lib/tapioca/rbi_ext/model.rb +122 -0
  47. data/lib/tapioca/reflection.rb +131 -0
  48. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  49. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  50. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  51. data/lib/tapioca/version.rb +1 -1
  52. data/lib/tapioca.rb +2 -0
  53. metadata +34 -22
  54. data/lib/tapioca/cli/main.rb +0 -146
  55. data/lib/tapioca/core_ext/class.rb +0 -28
  56. data/lib/tapioca/core_ext/string.rb +0 -18
  57. data/lib/tapioca/rbi/model.rb +0 -405
  58. data/lib/tapioca/rbi/printer.rb +0 -410
  59. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  60. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  61. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  62. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  63. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -0,0 +1,108 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/compilers/sorbet"
5
+
6
+ begin
7
+ require "active_support"
8
+ rescue LoadError
9
+ return
10
+ end
11
+
12
+ return unless Tapioca::Compilers::Sorbet.supports?(:mixes_in_class_methods_multiple_args)
13
+
14
+ module Tapioca
15
+ module Compilers
16
+ module Dsl
17
+ # `Tapioca::Compilers::Dsl::ActiveSupportConcern` generates RBI files for classes that both `extend`
18
+ # `ActiveSupport::Concern` and `include` another class that extends `ActiveSupport::Concern`
19
+ #
20
+ # For example for the following hierarchy:
21
+ #
22
+ # ~~~rb
23
+ # # concern.rb
24
+ # module Foo
25
+ # extend ActiveSupport::Concern
26
+ # module ClassMethods; end
27
+ # end
28
+ #
29
+ # module Bar
30
+ # extend ActiveSupport::Concern
31
+ # module ClassMethods; end
32
+ # include Foo
33
+ # end
34
+ #
35
+ # class Baz
36
+ # include Bar
37
+ # end
38
+ # ~~~
39
+ #
40
+ # this generator will produce the RBI file `concern.rbi` with the following content:
41
+ #
42
+ # ~~~rbi
43
+ # # typed: true
44
+ # module Bar
45
+ # mixes_in_class_methods(::Foo::ClassMethods)
46
+ # end
47
+ # ~~~
48
+ class ActiveSupportConcern < Base
49
+ extend T::Sig
50
+
51
+ sig { override.params(root: RBI::Tree, constant: Module).void }
52
+ def decorate(root, constant)
53
+ dependencies = linearized_dependencies_of(constant)
54
+
55
+ mixed_in_class_methods = dependencies
56
+ .uniq # Deduplicate
57
+ .map do |concern| # Map to class methods module name, if exists
58
+ "#{qualified_name_of(concern)}::ClassMethods" if concern.const_defined?(:ClassMethods)
59
+ end
60
+ .compact # Remove non-existent records
61
+
62
+ return if mixed_in_class_methods.empty?
63
+
64
+ root.create_path(constant) do |mod|
65
+ mixed_in_class_methods.each do |mix|
66
+ mod.create_mixes_in_class_methods(mix)
67
+ end
68
+ end
69
+ end
70
+
71
+ sig { override.returns(T::Enumerable[Module]) }
72
+ def gather_constants
73
+ # Find all Modules that are:
74
+ all_modules.select do |mod|
75
+ # named (i.e. not anonymous)
76
+ name_of(mod) &&
77
+ # not singleton classes
78
+ !mod.singleton_class? &&
79
+ # extend ActiveSupport::Concern, and
80
+ mod.singleton_class < ActiveSupport::Concern &&
81
+ # have dependencies (i.e. include another concern)
82
+ !dependencies_of(mod).empty?
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ sig { params(concern: Module).returns(T::Array[Module]) }
89
+ def dependencies_of(concern)
90
+ concern.instance_variable_get(:@_dependencies)
91
+ end
92
+
93
+ sig { params(concern: Module).returns(T::Array[Module]) }
94
+ def linearized_dependencies_of(concern)
95
+ # Grab all the dependencies of the concern
96
+ dependencies = dependencies_of(concern)
97
+
98
+ # Flatten this concern's dependencies and all of their dependencies
99
+ dependencies.flat_map do |dependency|
100
+ # Linearize dependencies of the current dependency,
101
+ # which, itself, is a concern
102
+ linearized_dependencies_of(dependency) << dependency
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,10 +1,10 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_support"
6
+ # The following is needed due to https://github.com/rails/rails/pull/41610
7
+ require "active_support/core_ext/module/delegation"
8
8
  rescue LoadError
9
9
  return
10
10
  end
@@ -65,7 +65,7 @@ module Tapioca
65
65
  sig do
66
66
  override
67
67
  .params(
68
- root: Parlour::RbiGenerator::Namespace,
68
+ root: RBI::Tree,
69
69
  constant: T.class_of(::ActiveSupport::CurrentAttributes)
70
70
  )
71
71
  .void
@@ -75,7 +75,7 @@ module Tapioca
75
75
  instance_methods = instance_methods_for(constant) - dynamic_methods
76
76
  return if dynamic_methods.empty? && instance_methods.empty?
77
77
 
78
- root.path(constant) do |current_attributes|
78
+ root.create_path(constant) do |current_attributes|
79
79
  dynamic_methods.each do |method|
80
80
  method = method.to_s
81
81
  # We want to generate each method both on the class
@@ -95,7 +95,7 @@ module Tapioca
95
95
 
96
96
  sig { override.returns(T::Enumerable[Module]) }
97
97
  def gather_constants
98
- ::ActiveSupport::CurrentAttributes.descendants
98
+ descendants_of(::ActiveSupport::CurrentAttributes)
99
99
  end
100
100
 
101
101
  private
@@ -110,11 +110,16 @@ module Tapioca
110
110
  constant.instance_methods(false)
111
111
  end
112
112
 
113
- sig { params(klass: Parlour::RbiGenerator::Namespace, method: String, class_method: T::Boolean).void }
113
+ sig { params(klass: RBI::Scope, method: String, class_method: T::Boolean).void }
114
114
  def generate_method(klass, method, class_method:)
115
115
  if method.end_with?("=")
116
- parameter = Parlour::RbiGenerator::Parameter.new("value", type: "T.untyped")
117
- klass.create_method(method, class_method: class_method, parameters: [parameter], return_type: "T.untyped")
116
+ parameter = create_param("value", type: "T.untyped")
117
+ klass.create_method(
118
+ method,
119
+ class_method: class_method,
120
+ parameters: [parameter],
121
+ return_type: "T.untyped"
122
+ )
118
123
  else
119
124
  klass.create_method(method, class_method: class_method, return_type: "T.untyped")
120
125
  end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
4
+ require "tapioca/rbi_ext/model"
5
5
 
6
6
  module Tapioca
7
7
  module Compilers
@@ -10,6 +10,8 @@ module Tapioca
10
10
  extend T::Sig
11
11
  extend T::Helpers
12
12
 
13
+ include Reflection
14
+
13
15
  abstract!
14
16
 
15
17
  sig { returns(T::Set[Module]) }
@@ -29,65 +31,111 @@ module Tapioca
29
31
  abstract
30
32
  .type_parameters(:T)
31
33
  .params(
32
- root: Parlour::RbiGenerator::Namespace,
34
+ tree: RBI::Tree,
33
35
  constant: T.type_parameter(:T)
34
36
  )
35
37
  .void
36
38
  end
37
- def decorate(root, constant); end
39
+ def decorate(tree, constant); end
38
40
 
39
41
  sig { abstract.returns(T::Enumerable[Module]) }
40
42
  def gather_constants; end
41
43
 
42
44
  private
43
45
 
44
- SPECIAL_METHOD_NAMES = T.let(
45
- %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `].freeze,
46
- T::Array[String]
47
- )
48
-
49
- sig { params(name: String).returns(T::Boolean) }
50
- def valid_method_name?(name)
51
- return true if SPECIAL_METHOD_NAMES.include?(name)
52
- !!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
46
+ sig { returns(T::Enumerable[Class]) }
47
+ def all_classes
48
+ @all_classes = T.let(@all_classes, T.nilable(T::Enumerable[Class]))
49
+ @all_classes ||= T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class]).each
53
50
  end
54
51
 
55
- sig do
56
- params(
57
- namespace: Parlour::RbiGenerator::Namespace,
58
- name: String,
59
- options: T::Hash[T.untyped, T.untyped]
60
- ).void
61
- end
62
- def create_method(namespace, name, options = {})
63
- return unless valid_method_name?(name)
64
- T.unsafe(namespace).create_method(name, **options)
52
+ sig { returns(T::Enumerable[Module]) }
53
+ def all_modules
54
+ @all_modules = T.let(@all_modules, T.nilable(T::Enumerable[Module]))
55
+ @all_modules ||= T.cast(ObjectSpace.each_object(Module), T::Enumerable[Module]).each
65
56
  end
66
57
 
67
- # Create a Parlour method inside `namespace` from its Ruby definition
58
+ # Get the types of each parameter from a method signature
68
59
  sig do
69
60
  params(
70
- namespace: Parlour::RbiGenerator::Namespace,
71
61
  method_def: T.any(Method, UnboundMethod),
72
- class_method: T::Boolean
73
- ).void
62
+ signature: T.untyped # as `T::Private::Methods::Signature` is private
63
+ ).returns(T::Array[String])
74
64
  end
75
- def create_method_from_def(namespace, method_def, class_method: false)
76
- create_method(
77
- namespace,
65
+ def parameters_types_from_signature(method_def, signature)
66
+ params = T.let([], T::Array[String])
67
+
68
+ return method_def.parameters.map { "T.untyped" } unless signature
69
+
70
+ # parameters types
71
+ signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
72
+
73
+ # keyword parameters types
74
+ signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
75
+
76
+ # rest parameter type
77
+ params << signature.rest_type.to_s if signature.has_rest
78
+
79
+ # special case `.void` in a proc
80
+ unless signature.block_name.nil?
81
+ params << signature.block_type.to_s.gsub("returns(<VOID>)", "void")
82
+ end
83
+
84
+ params
85
+ end
86
+
87
+ sig { params(scope: RBI::Scope, method_def: T.any(Method, UnboundMethod), class_method: T::Boolean).void }
88
+ def create_method_from_def(scope, method_def, class_method: false)
89
+ scope.create_method(
78
90
  method_def.name.to_s,
79
- parameters: compile_method_parameters_to_parlour(method_def),
80
- return_type: compile_method_return_type_to_parlour(method_def),
91
+ parameters: compile_method_parameters_to_rbi(method_def),
92
+ return_type: compile_method_return_type_to_rbi(method_def),
81
93
  class_method: class_method
82
94
  )
83
95
  end
84
96
 
85
- # Compile a Ruby method parameters into Parlour parameters
86
- sig do
87
- params(method_def: T.any(Method, UnboundMethod))
88
- .returns(T::Array[Parlour::RbiGenerator::Parameter])
97
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
98
+ def create_param(name, type:)
99
+ create_typed_param(RBI::Param.new(name), type)
100
+ end
101
+
102
+ sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
103
+ def create_opt_param(name, type:, default:)
104
+ create_typed_param(RBI::OptParam.new(name, default), type)
105
+ end
106
+
107
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
108
+ def create_rest_param(name, type:)
109
+ create_typed_param(RBI::RestParam.new(name), type)
110
+ end
111
+
112
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
113
+ def create_kw_param(name, type:)
114
+ create_typed_param(RBI::KwParam.new(name), type)
115
+ end
116
+
117
+ sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
118
+ def create_kw_opt_param(name, type:, default:)
119
+ create_typed_param(RBI::KwOptParam.new(name, default), type)
89
120
  end
90
- def compile_method_parameters_to_parlour(method_def)
121
+
122
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
123
+ def create_kw_rest_param(name, type:)
124
+ create_typed_param(RBI::KwRestParam.new(name), type)
125
+ end
126
+
127
+ sig { params(name: String, type: String).returns(RBI::TypedParam) }
128
+ def create_block_param(name, type:)
129
+ create_typed_param(RBI::BlockParam.new(name), type)
130
+ end
131
+
132
+ sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
133
+ def create_typed_param(param, type)
134
+ RBI::TypedParam.new(param: param, type: type)
135
+ end
136
+
137
+ sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
138
+ def compile_method_parameters_to_rbi(method_def)
91
139
  signature = T::Private::Methods.signature_for_method(method_def)
92
140
  method_def = signature.nil? ? method_def : signature.method
93
141
  method_types = parameters_types_from_signature(method_def, signature)
@@ -97,72 +145,38 @@ module Tapioca
97
145
 
98
146
  name ||= fallback_arg_name
99
147
  name = name.to_s.gsub(/&|\*/, fallback_arg_name) # avoid incorrect names from `delegate`
100
- method_type = method_types[index]
148
+ method_type = T.must(method_types[index])
101
149
 
102
150
  case type
103
151
  when :req
104
- ::Parlour::RbiGenerator::Parameter.new(name, type: method_type)
152
+ create_param(name, type: method_type)
105
153
  when :opt
106
- ::Parlour::RbiGenerator::Parameter.new(name, type: method_type, default: 'T.unsafe(nil)')
154
+ create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
107
155
  when :rest
108
- ::Parlour::RbiGenerator::Parameter.new("*#{name}", type: method_type)
156
+ create_rest_param(name, type: method_type)
109
157
  when :keyreq
110
- ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type)
158
+ create_kw_param(name, type: method_type)
111
159
  when :key
112
- ::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type, default: 'T.unsafe(nil)')
160
+ create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
113
161
  when :keyrest
114
- ::Parlour::RbiGenerator::Parameter.new("**#{name}", type: method_type)
162
+ create_kw_rest_param(name, type: method_type)
115
163
  when :block
116
- ::Parlour::RbiGenerator::Parameter.new("&#{name}", type: method_type)
164
+ create_block_param(name, type: method_type)
117
165
  else
118
166
  raise "Unknown type `#{type}`."
119
167
  end
120
168
  end
121
169
  end
122
170
 
123
- # Compile a Ruby method return type into a Parlour type
124
- sig do
125
- params(method_def: T.any(Method, UnboundMethod))
126
- .returns(T.nilable(String))
127
- end
128
- def compile_method_return_type_to_parlour(method_def)
171
+ sig { params(method_def: T.any(Method, UnboundMethod)).returns(String) }
172
+ def compile_method_return_type_to_rbi(method_def)
129
173
  signature = T::Private::Methods.signature_for_method(method_def)
130
- return_type = signature.nil? ? 'T.untyped' : signature.return_type.to_s
131
- # Map <VOID> to `nil` since `nil` means a `void` return for Parlour
132
- return_type = nil if return_type == "<VOID>"
174
+ return_type = signature.nil? ? "T.untyped" : name_of_type(signature.return_type)
175
+ return_type = "void" if return_type == "<VOID>"
133
176
  # Map <NOT-TYPED> to `T.untyped`
134
177
  return_type = "T.untyped" if return_type == "<NOT-TYPED>"
135
178
  return_type
136
179
  end
137
-
138
- # Get the types of each parameter from a method signature
139
- sig do
140
- params(
141
- method_def: T.any(Method, UnboundMethod),
142
- signature: T.untyped # as `T::Private::Methods::Signature` is private
143
- ).returns(T::Array[String])
144
- end
145
- def parameters_types_from_signature(method_def, signature)
146
- params = T.let([], T::Array[String])
147
-
148
- return method_def.parameters.map { 'T.untyped' } unless signature
149
-
150
- # parameters types
151
- signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
152
-
153
- # keyword parameters types
154
- signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
155
-
156
- # rest parameter type
157
- params << signature.rest_type.to_s if signature.has_rest
158
-
159
- # special case `.void` in a proc
160
- unless signature.block_name.nil?
161
- params << signature.block_type.to_s.gsub('returns(<VOID>)', 'void')
162
- end
163
-
164
- params
165
- end
166
180
  end
167
181
  end
168
182
  end
@@ -0,0 +1,111 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "config"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Compilers
12
+ module Dsl
13
+ # `Tapioca::Compilers::Dsl::Config` generates RBI files for classes generated by the
14
+ # [`config`](https://github.com/rubyconfig/config) gem.
15
+ #
16
+ # The gem creates a `Config::Options` instance based on the settings files and/or
17
+ # env variables. It then assigns this instance to a constant with a configurable name,
18
+ # by default `Settings`. Application code uses methods on this constant to read off
19
+ # config values.
20
+ #
21
+ # For a setting file like the following:
22
+ # ```yaml
23
+ # ---
24
+ # github:
25
+ # token: 12345
26
+ # client_id: 54321
27
+ # client_secret: super_secret
28
+ # ```
29
+ # and a `Config` setup like:
30
+ # ```ruby
31
+ # Config.setup do |config|
32
+ # config.const_name = "AppSettings"
33
+ # end
34
+ # ```
35
+ # this generator will produce the following RBI file:
36
+ # ```rbi
37
+ # AppSettings = T.let(T.unsafe(nil), AppSettingsConfigOptions)
38
+ #
39
+ # class AppSettingsConfigOptions < ::Config::Options
40
+ # sig { returns(T.untyped) }
41
+ # def github; end
42
+ #
43
+ # sig { params(value: T.untyped).returns(T.untyped) }
44
+ # def github=(value); end
45
+ # end
46
+ # ```
47
+ class Config < Base
48
+ extend T::Sig
49
+
50
+ CONFIG_OPTIONS_SUFFIX = "ConfigOptions"
51
+
52
+ sig { override.params(root: RBI::Tree, constant: Module).void }
53
+ def decorate(root, constant)
54
+ # The constant we are given is the specialized config options type
55
+ option_class_name = constant.name
56
+ return unless option_class_name
57
+
58
+ # Grab the config constant name and the actual config constant
59
+ config_constant_name = option_class_name
60
+ .gsub(/#{CONFIG_OPTIONS_SUFFIX}$/, "")
61
+ config_constant = Object.const_get(config_constant_name)
62
+
63
+ # Look up method names from the keys of the config constant
64
+ method_names = config_constant.keys
65
+
66
+ return if method_names.empty?
67
+
68
+ root.create_constant(config_constant_name, value: "T.let(T.unsafe(nil), #{option_class_name})")
69
+
70
+ root.create_class(option_class_name, superclass_name: "::Config::Options") do |mod|
71
+ # We need this to be generic only becuase `Config::Options` is an
72
+ # enumerable and, thus, needs to redeclare the `Elem` type member.
73
+ #
74
+ # We declare it as a fixed member of `T.untyped` so that if anyone
75
+ # enumerates the entries, we don't make any assumptions about their
76
+ # types.
77
+ mod.create_extend("T::Generic")
78
+ mod.create_type_member("Elem", value: "type_member(fixed: T.untyped)")
79
+
80
+ method_names.each do |method_name|
81
+ # Create getter method
82
+ mod.create_method(
83
+ method_name.to_s,
84
+ return_type: "T.untyped"
85
+ )
86
+
87
+ # Create setter method
88
+ mod.create_method(
89
+ "#{method_name}=",
90
+ parameters: [create_param("value", type: "T.untyped")],
91
+ return_type: "T.untyped"
92
+ )
93
+ end
94
+ end
95
+ end
96
+
97
+ sig { override.returns(T::Enumerable[Module]) }
98
+ def gather_constants
99
+ name = ::Config.const_name
100
+ return [] unless Object.const_defined?(name)
101
+
102
+ config_object = Object.const_get(name)
103
+ options_class_name = "#{name}#{CONFIG_OPTIONS_SUFFIX}"
104
+ Object.const_set(options_class_name, config_object.singleton_class)
105
+
106
+ Array(config_object.singleton_class)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end