tapioca 0.4.25 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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 +21 -33
  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 +10 -12
  41. data/lib/tapioca/generator.rb +129 -45
  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 -10
  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 +3 -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