tapioca 0.4.17 → 0.4.22

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.
@@ -371,7 +371,6 @@ module Tapioca
371
371
  return unless signature
372
372
 
373
373
  return_type = signature.return_type
374
- return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
375
374
  return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
376
375
 
377
376
  return_type.to_s
@@ -382,10 +381,11 @@ module Tapioca
382
381
  signature = T::Private::Methods.signature_for_method(column_type.method(method))
383
382
  return unless signature
384
383
 
385
- arg_type = signature.arg_types.first.last
386
- return if T::Types::Simple === arg_type && T::Generic === arg_type.raw_type
384
+ # Arg types is an array [name, type] entries, so we desctructure the type of
385
+ # first argument to get the first argument type
386
+ _, first_argument_type = signature.arg_types.first
387
387
 
388
- arg_type.to_s
388
+ first_argument_type.to_s
389
389
  end
390
390
 
391
391
  sig { params(type: String).returns(String) }
@@ -60,7 +60,7 @@ module Tapioca
60
60
 
61
61
  model.create_module(module_name) do |mod|
62
62
  scope_method_names.each do |scope_method|
63
- generate_scope_method(scope_method, mod)
63
+ generate_scope_method(scope_method.to_s, mod)
64
64
  end
65
65
  end
66
66
 
@@ -123,7 +123,7 @@ module Tapioca
123
123
  # Compile a Ruby method return type into a Parlour type
124
124
  sig do
125
125
  params(method_def: T.any(Method, UnboundMethod))
126
- .returns(String)
126
+ .returns(T.nilable(String))
127
127
  end
128
128
  def compile_method_return_type_to_parlour(method_def)
129
129
  signature = T::Private::Methods.signature_for_method(method_def)
@@ -62,33 +62,99 @@ module Tapioca
62
62
  # end
63
63
  # ~~~
64
64
  class Protobuf < Base
65
+ # Parlour doesn't support type members out of the box, so adding the
66
+ # ability to do that here. This should be upstreamed.
67
+ class TypeMember < Parlour::RbiGenerator::RbiObject
68
+ extend T::Sig
69
+
70
+ sig { params(other: Object).returns(T::Boolean) }
71
+ def ==(other)
72
+ TypeMember === other && name == other.name
73
+ end
74
+
75
+ sig do
76
+ override
77
+ .params(indent_level: Integer, options: Parlour::RbiGenerator::Options)
78
+ .returns(T::Array[String])
79
+ end
80
+ def generate_rbi(indent_level, options)
81
+ [options.indented(indent_level, "#{name} = type_member")]
82
+ end
83
+
84
+ sig do
85
+ override
86
+ .params(others: T::Array[Parlour::RbiGenerator::RbiObject])
87
+ .returns(T::Boolean)
88
+ end
89
+ def mergeable?(others)
90
+ others.all? { |other| self == other }
91
+ end
92
+
93
+ sig { override.params(others: T::Array[Parlour::RbiGenerator::RbiObject]).void }
94
+ def merge_into_self(others); end
95
+
96
+ sig { override.returns(String) }
97
+ def describe
98
+ "Type Member (#{name})"
99
+ end
100
+ end
101
+
102
+ class Field < T::Struct
103
+ prop :name, String
104
+ prop :type, String
105
+ prop :init_type, String
106
+ prop :default, String
107
+
108
+ extend T::Sig
109
+
110
+ sig { returns(Parlour::RbiGenerator::Parameter) }
111
+ def to_init
112
+ Parlour::RbiGenerator::Parameter.new("#{name}:", type: init_type, default: default)
113
+ end
114
+ end
115
+
65
116
  extend T::Sig
66
117
 
67
118
  sig do
68
119
  override.params(
69
120
  root: Parlour::RbiGenerator::Namespace,
70
- constant: T.class_of(Google::Protobuf::MessageExts)
121
+ constant: Module
71
122
  ).void
72
123
  end
73
124
  def decorate(root, constant)
74
- descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
75
- return unless descriptor.any?
76
-
77
125
  root.path(constant) do |klass|
78
- descriptor.each do |desc|
79
- create_descriptor_method(klass, desc)
126
+ if constant == Google::Protobuf::RepeatedField
127
+ create_type_members(klass, "Elem")
128
+ elsif constant == Google::Protobuf::Map
129
+ create_type_members(klass, "Key", "Value")
130
+ else
131
+ descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
132
+ fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
133
+ fields.sort_by!(&:name)
134
+
135
+ create_method(klass, "initialize", parameters: fields.map!(&:to_init))
80
136
  end
81
137
  end
82
138
  end
83
139
 
84
140
  sig { override.returns(T::Enumerable[Module]) }
85
141
  def gather_constants
86
- classes = T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class])
87
- classes.select { |c| c < Google::Protobuf::MessageExts && !c.singleton_class? }
142
+ marker = Google::Protobuf::MessageExts::ClassMethods
143
+ results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
144
+ results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
88
145
  end
89
146
 
90
147
  private
91
148
 
149
+ sig { params(klass: Parlour::RbiGenerator::Namespace, names: String).void }
150
+ def create_type_members(klass, *names)
151
+ klass.create_extend("T::Generic")
152
+
153
+ names.each do |name|
154
+ klass.children << TypeMember.new(klass.generator, name)
155
+ end
156
+ end
157
+
92
158
  sig do
93
159
  params(
94
160
  descriptor: Google::Protobuf::FieldDescriptor
@@ -113,30 +179,80 @@ module Tapioca
113
179
  end
114
180
  end
115
181
 
182
+ sig { params(descriptor: Google::Protobuf::FieldDescriptor).returns(Field) }
183
+ def field_of(descriptor)
184
+ if descriptor.label == :repeated
185
+ # Here we're going to check if the submsg_name is named according to
186
+ # how Google names map entries.
187
+ # https://github.com/protocolbuffers/protobuf/blob/f82e26/ruby/ext/google/protobuf_c/defs.c#L1963-L1966
188
+ if descriptor.submsg_name.to_s.end_with?("_MapEntry_#{descriptor.name}")
189
+ key = descriptor.subtype.lookup('key')
190
+ value = descriptor.subtype.lookup('value')
191
+
192
+ key_type = type_of(key)
193
+ value_type = type_of(value)
194
+ type = "Google::Protobuf::Map[#{key_type}, #{value_type}]"
195
+
196
+ default_args = [key.type.inspect, value.type.inspect]
197
+ default_args << value_type if %i[enum message].include?(value.type)
198
+
199
+ Field.new(
200
+ name: descriptor.name,
201
+ type: type,
202
+ init_type: "T.any(#{type}, T::Hash[#{key_type}, #{value_type}])",
203
+ default: "Google::Protobuf::Map.new(#{default_args.join(', ')})"
204
+ )
205
+ else
206
+ elem_type = type_of(descriptor)
207
+ type = "Google::Protobuf::RepeatedField[#{elem_type}]"
208
+
209
+ default_args = [descriptor.type.inspect]
210
+ default_args << elem_type if %i[enum message].include?(descriptor.type)
211
+
212
+ Field.new(
213
+ name: descriptor.name,
214
+ type: type,
215
+ init_type: "T.any(#{type}, T::Array[#{elem_type}])",
216
+ default: "Google::Protobuf::RepeatedField.new(#{default_args.join(', ')})"
217
+ )
218
+ end
219
+ else
220
+ type = type_of(descriptor)
221
+
222
+ Field.new(
223
+ name: descriptor.name,
224
+ type: type,
225
+ init_type: type,
226
+ default: "nil"
227
+ )
228
+ end
229
+ end
230
+
116
231
  sig do
117
232
  params(
118
233
  klass: Parlour::RbiGenerator::Namespace,
119
234
  desc: Google::Protobuf::FieldDescriptor,
120
- ).void
235
+ ).returns(Field)
121
236
  end
122
237
  def create_descriptor_method(klass, desc)
123
- name = desc.name
124
- type = type_of(desc)
238
+ field = field_of(desc)
125
239
 
126
240
  create_method(
127
241
  klass,
128
- name,
129
- return_type: type
242
+ field.name,
243
+ return_type: field.type
130
244
  )
131
245
 
132
246
  create_method(
133
247
  klass,
134
- "#{name}=",
248
+ "#{field.name}=",
135
249
  parameters: [
136
- Parlour::RbiGenerator::Parameter.new("value", type: type),
250
+ Parlour::RbiGenerator::Parameter.new("value", type: field.type),
137
251
  ],
138
- return_type: type
252
+ return_type: field.type
139
253
  )
254
+
255
+ field
140
256
  end
141
257
  end
142
258
  end
@@ -89,7 +89,7 @@ module Tapioca
89
89
  class UrlHelpers < Base
90
90
  extend T::Sig
91
91
 
92
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
92
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
93
93
  def decorate(root, constant)
94
94
  case constant
95
95
  when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
@@ -127,7 +127,7 @@ module Tapioca
127
127
 
128
128
  private
129
129
 
130
- sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
130
+ sig { params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
131
131
  def generate_module_for(root, constant)
132
132
  root.create_module(T.must(constant.name)) do |mod|
133
133
  mod.create_include("::ActionDispatch::Routing::UrlFor")
@@ -143,7 +143,7 @@ module Tapioca
143
143
  end
144
144
  end
145
145
 
146
- sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
146
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: Module, helper_module: Module).void }
147
147
  def create_mixins_for(mod, constant, helper_module)
148
148
  include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
149
149
  extend_helper = constant.singleton_class.ancestors.include?(helper_module)
@@ -22,7 +22,7 @@ module Tapioca
22
22
  name_in_project?(files, req)
23
23
  end
24
24
  end.sort.uniq.map do |name|
25
- "require '#{name}'\n"
25
+ "require \"#{name}\"\n"
26
26
  end.join
27
27
  end
28
28
 
@@ -23,24 +23,34 @@ module Tapioca
23
23
  @indent = indent
24
24
  @seen = Set.new
25
25
  @alias_namespace ||= Set.new
26
+ @symbol_queue = T.let(symbols.sort.dup, T::Array[String])
26
27
  end
27
28
 
28
29
  sig { returns(String) }
29
30
  def generate
30
- symbols
31
- .sort
32
- .map { |symbol| generate_from_symbol(symbol) }
33
- .compact
34
- .join("\n\n")
35
- .concat("\n")
31
+ rbi = RBI::Tree.new
32
+
33
+ generate_from_symbol(rbi, T.must(@symbol_queue.shift)) until @symbol_queue.empty?
34
+
35
+ rbi.nest_singleton_methods!
36
+ rbi.nest_non_public_methods!
37
+ rbi.group_nodes!
38
+ rbi.sort_nodes!
39
+ rbi.string
36
40
  end
37
41
 
38
42
  private
39
43
 
44
+ def add_to_symbol_queue(name)
45
+ @symbol_queue << name unless symbols.include?(name) || symbol_ignored?(name)
46
+ end
47
+
40
48
  sig { returns(T::Set[String]) }
41
49
  def symbols
42
- symbols = Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(gem.files)
43
- symbols.union(engine_symbols(symbols))
50
+ @symbols ||= begin
51
+ symbols = Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(gem.files)
52
+ symbols.union(engine_symbols(symbols))
53
+ end
44
54
  end
45
55
 
46
56
  sig { params(symbols: T::Set[String]).returns(T::Set[String]) }
@@ -65,28 +75,30 @@ module Tapioca
65
75
  Set.new
66
76
  end
67
77
 
68
- sig { params(symbol: String).returns(T.nilable(String)) }
69
- def generate_from_symbol(symbol)
78
+ sig { params(tree: RBI::Tree, symbol: String).void }
79
+ def generate_from_symbol(tree, symbol)
70
80
  constant = resolve_constant(symbol)
71
81
 
72
82
  return unless constant
73
83
 
74
- compile(symbol, constant)
84
+ compile(tree, symbol, constant)
75
85
  end
76
86
 
77
- sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) }
78
- def resolve_constant(symbol, inherit: false)
79
- Object.const_get(symbol, inherit)
87
+ sig do
88
+ params(
89
+ symbol: String,
90
+ inherit: T::Boolean,
91
+ namespace: Module
92
+ ).returns(BasicObject).checked(:never)
93
+ end
94
+ def resolve_constant(symbol, inherit: false, namespace: Object)
95
+ namespace.const_get(symbol, inherit)
80
96
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
81
97
  nil
82
98
  end
83
99
 
84
- sig do
85
- params(name: T.nilable(String), constant: BasicObject)
86
- .returns(T.nilable(String))
87
- .checked(:never)
88
- end
89
- def compile(name, constant)
100
+ sig { params(tree: RBI::Tree, name: T.nilable(String), constant: BasicObject).void.checked(:never) }
101
+ def compile(tree, name, constant)
90
102
  return unless constant
91
103
  return unless name
92
104
  return if name.strip.empty?
@@ -94,33 +106,28 @@ module Tapioca
94
106
  return if name.downcase == name
95
107
  return if alias_namespaced?(name)
96
108
  return if seen?(name)
97
- return unless parent_declares_constant?(name)
98
109
  return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
99
110
 
100
111
  mark_seen(name)
101
- compile_constant(name, constant)
112
+ compile_constant(tree, name, constant)
102
113
  end
103
114
 
104
- sig do
105
- params(name: String, constant: BasicObject)
106
- .returns(T.nilable(String))
107
- .checked(:never)
108
- end
109
- def compile_constant(name, constant)
115
+ sig { params(tree: RBI::Tree, name: String, constant: BasicObject).void.checked(:never) }
116
+ def compile_constant(tree, name, constant)
110
117
  case constant
111
118
  when Module
112
119
  if name_of(constant) != name
113
- compile_alias(name, constant)
120
+ compile_alias(tree, name, constant)
114
121
  else
115
- compile_module(name, constant)
122
+ compile_module(tree, name, constant)
116
123
  end
117
124
  else
118
- compile_object(name, constant)
125
+ compile_object(tree, name, constant)
119
126
  end
120
127
  end
121
128
 
122
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
123
- def compile_alias(name, constant)
129
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
130
+ def compile_alias(tree, name, constant)
124
131
  return if symbol_ignored?(name)
125
132
 
126
133
  target = name_of(constant)
@@ -131,114 +138,99 @@ module Tapioca
131
138
 
132
139
  return if IGNORED_SYMBOLS.include?(name)
133
140
 
134
- indented("#{name} = #{target}")
141
+ tree << RBI::Const.new(name, target)
135
142
  end
136
143
 
137
- sig do
138
- params(name: String, value: BasicObject)
139
- .returns(T.nilable(String))
140
- .checked(:never)
141
- end
142
- def compile_object(name, value)
144
+ sig { params(tree: RBI::Tree, name: String, value: BasicObject).void.checked(:never) }
145
+ def compile_object(tree, name, value)
143
146
  return if symbol_ignored?(name)
144
147
  klass = class_of(value)
145
- return if name_of(klass)&.start_with?("T::Types::", "T::Private::")
148
+ klass_name = name_of(klass)
149
+
150
+ if klass_name == "T::Private::Types::TypeAlias"
151
+ tree << RBI::Const.new(name, "T.type_alias { #{T.unsafe(value).aliased_type} }")
152
+ return
153
+ end
154
+
155
+ return if klass_name&.start_with?("T::Types::", "T::Private::")
156
+
157
+ type_name = klass_name || "T.untyped"
158
+ # TODO: Do this in a more generic and clean way.
159
+ type_name = "#{type_name}[T.untyped]" if type_name == "ObjectSpace::WeakMap"
146
160
 
147
- type_name = public_module?(klass) && name_of(klass) || "T.untyped"
148
- indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
161
+ tree << RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})")
149
162
  end
150
163
 
151
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
152
- def compile_module(name, constant)
153
- return unless public_module?(constant)
164
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
165
+ def compile_module(tree, name, constant)
154
166
  return unless defined_in_gem?(constant, strict: false)
155
167
 
156
- header =
168
+ scope =
157
169
  if constant.is_a?(Class)
158
- indented("class #{name}#{compile_superclass(constant)}")
170
+ superclass = compile_superclass(constant)
171
+ RBI::Class.new(name, superclass_name: superclass)
159
172
  else
160
- indented("module #{name}")
173
+ RBI::Module.new(name)
161
174
  end
162
175
 
163
- body = compile_body(name, constant)
176
+ compile_body(scope, name, constant)
164
177
 
165
- return if symbol_ignored?(name) && body.nil?
178
+ return if symbol_ignored?(name) && scope.empty?
166
179
 
167
- [
168
- header,
169
- body,
170
- indented("end"),
171
- compile_subconstants(name, constant),
172
- ].select { |b| !b.nil? && b.strip != "" }.join("\n")
180
+ tree << scope
181
+ compile_subconstants(tree, name, constant)
173
182
  end
174
183
 
175
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
176
- def compile_body(name, constant)
177
- with_indentation do
178
- methods = compile_methods(name, constant)
179
-
180
- return if symbol_ignored?(name) && methods.nil?
181
-
182
- [
183
- compile_module_helpers(constant),
184
- compile_mixins(constant),
185
- compile_mixes_in_class_methods(constant),
186
- compile_props(constant),
187
- compile_enums(constant),
188
- methods,
189
- ].select { |b| b != "" }.join("\n\n")
190
- end
184
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
185
+ def compile_body(tree, name, constant)
186
+ compile_methods(tree, name, constant)
187
+ compile_module_helpers(tree, constant)
188
+ compile_type_variables(tree, constant)
189
+ compile_mixins(tree, constant)
190
+ compile_mixes_in_class_methods(tree, constant)
191
+ compile_props(tree, constant)
192
+ compile_enums(tree, constant)
191
193
  end
192
194
 
193
- sig { params(constant: Module).returns(String) }
194
- def compile_module_helpers(constant)
195
+ sig { params(tree: RBI::Tree, constant: Module).void }
196
+ def compile_module_helpers(tree, constant)
195
197
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
196
198
 
197
- helpers = []
198
- helpers << indented("#{abstract_type}!") if abstract_type
199
- helpers << indented("final!") if T::Private::Final.final_module?(constant)
200
- helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
201
-
202
- helpers.join("\n")
199
+ tree << RBI::Helper.new(abstract_type.to_s) if abstract_type
200
+ tree << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
201
+ tree << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
203
202
  end
204
203
 
205
- sig { params(constant: Module).returns(String) }
206
- def compile_props(constant)
207
- return "" unless T::Props::ClassMethods === constant
204
+ sig { params(tree: RBI::Tree, constant: Module).void }
205
+ def compile_props(tree, constant)
206
+ return unless T::Props::ClassMethods === constant
208
207
 
209
208
  constant.props.map do |name, prop|
210
- method = "prop"
211
- method = "const" if prop.fetch(:immutable, false)
212
209
  type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
213
210
 
214
- if prop.key?(:default)
215
- indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
211
+ default = prop.key?(:default) ? "T.unsafe(nil)" : nil
212
+ tree << if prop.fetch(:immutable, false)
213
+ RBI::TStructConst.new(name.to_s, type, default: default)
216
214
  else
217
- indented("#{method} :#{name}, #{type}")
215
+ RBI::TStructProp.new(name.to_s, type, default: default)
218
216
  end
219
- end.join("\n")
217
+ end
220
218
  end
221
219
 
222
- sig { params(constant: Module).returns(String) }
223
- def compile_enums(constant)
224
- return "" unless T::Enum > constant
220
+ sig { params(tree: RBI::Tree, constant: Module).void }
221
+ def compile_enums(tree, constant)
222
+ return unless T::Enum > constant
225
223
 
226
- enums = T.cast(constant, T::Enum).values.map do |enum_type|
224
+ enums = T.unsafe(constant).values.map do |enum_type|
227
225
  enum_type.instance_variable_get(:@const_name).to_s
228
226
  end
229
227
 
230
- content = [
231
- indented('enums do'),
232
- *enums.map { |e| indented(" #{e} = new") }.join("\n"),
233
- indented('end'),
234
- ]
235
-
236
- content.join("\n")
228
+ tree << RBI::TEnumBlock.new(enums)
237
229
  end
238
230
 
239
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
240
- def compile_subconstants(name, constant)
241
- output = constants_of(constant).sort.uniq.map do |constant_name|
231
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
232
+ def compile_subconstants(tree, name, constant)
233
+ constants_of(constant).sort.uniq.map do |constant_name|
242
234
  symbol = (name == "Object" ? "" : name) + "::#{constant_name}"
243
235
  subconstant = resolve_constant(symbol)
244
236
 
@@ -247,15 +239,58 @@ module Tapioca
247
239
  next if (Object == constant || BasicObject == constant) && Module === subconstant
248
240
  next unless subconstant
249
241
 
250
- compile(symbol, subconstant)
251
- end.compact
242
+ compile(tree, symbol, subconstant)
243
+ end
244
+ end
252
245
 
253
- return "" if output.empty?
246
+ sig { params(tree: RBI::Tree, constant: Module).void }
247
+ def compile_type_variables(tree, constant)
248
+ compile_type_variable_declarations(tree, constant)
254
249
 
255
- "\n" + output.join("\n\n")
250
+ sclass = RBI::SingletonClass.new
251
+ compile_type_variable_declarations(sclass, singleton_class_of(constant))
252
+ tree << sclass if sclass.nodes.length > 1
256
253
  end
257
254
 
258
- sig { params(constant: Class).returns(String) }
255
+ sig { params(tree: RBI::Tree, constant: Module).void }
256
+ def compile_type_variable_declarations(tree, constant)
257
+ # Try to find the type variables defined on this constant, bail if we can't
258
+ type_variables = GenericTypeRegistry.lookup_type_variables(constant)
259
+ return unless type_variables
260
+
261
+ # Create a map of subconstants (via their object ids) to their names.
262
+ # We need this later when we want to lookup the name of the registered type
263
+ # variable via the value of the type variable constant.
264
+ subconstant_to_name_lookup = constants_of(constant).map do |constant_name|
265
+ [
266
+ object_id_of(resolve_constant(constant_name.to_s, namespace: constant)),
267
+ constant_name,
268
+ ]
269
+ end.to_h
270
+
271
+ # Map each type variable to its string representation.
272
+ #
273
+ # Each entry of `type_variables` maps an object_id to a String,
274
+ # and the order they are inserted into the hash is the order they should be
275
+ # defined in the source code.
276
+ #
277
+ # By looping over these entries and then getting the actual constant name
278
+ # from the `subconstant_to_name_lookup` we defined above, gives us all the
279
+ # information we need to serialize type variable definitions.
280
+ type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable|
281
+ constant_name = subconstant_to_name_lookup[type_variable_id]
282
+ # Here, we know that constant_value will be an instance of
283
+ # T::Types::CustomTypeVariable, which knows how to serialize
284
+ # itself to a type_member/type_template
285
+ tree << RBI::TypeMember.new(constant_name.to_s, serialized_type_variable)
286
+ end
287
+
288
+ return if type_variable_declarations.empty?
289
+
290
+ tree << RBI::Extend.new("T::Generic")
291
+ end
292
+
293
+ sig { params(constant: Class).returns(T.nilable(String)) }
259
294
  def compile_superclass(constant)
260
295
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
261
296
 
@@ -263,15 +298,6 @@ module Tapioca
263
298
  constant_name = name_of(constant)
264
299
  constant = superclass
265
300
 
266
- # Some classes have superclasses that are private constants
267
- # so if we generate code with that superclass, the output
268
- # will not be compilable (since private constants are not
269
- # publicly visible).
270
- #
271
- # So we skip superclasses that are not public and walk up the
272
- # chain.
273
- next unless public_module?(superclass)
274
-
275
301
  # Some types have "themselves" as their superclass
276
302
  # which can happen via:
277
303
  #
@@ -291,7 +317,9 @@ module Tapioca
291
317
  # B = A
292
318
  # A.superclass.name #=> "B"
293
319
  # B #=> A
294
- superclass_name = T.must(name_of(superclass))
320
+ superclass_name = name_of(superclass)
321
+ next unless superclass_name
322
+
295
323
  resolved_superclass = resolve_constant(superclass_name)
296
324
  next unless Module === resolved_superclass
297
325
  next if name_of(resolved_superclass) == constant_name
@@ -300,17 +328,19 @@ module Tapioca
300
328
  break
301
329
  end
302
330
 
303
- return "" if superclass == ::Object || superclass == ::Delegator
304
- return "" if superclass.nil?
331
+ return if superclass == ::Object || superclass == ::Delegator
332
+ return if superclass.nil?
305
333
 
306
334
  name = name_of(superclass)
307
- return "" if name.nil? || name.empty?
335
+ return if name.nil? || name.empty?
336
+
337
+ add_to_symbol_queue(name)
308
338
 
309
- " < ::#{name}"
339
+ "::#{name}"
310
340
  end
311
341
 
312
- sig { params(constant: Module).returns(String) }
313
- def compile_mixins(constant)
342
+ sig { params(tree: RBI::Tree, constant: Module).void }
343
+ def compile_mixins(tree, constant)
314
344
  singleton_class = singleton_class_of(constant)
315
345
 
316
346
  interesting_ancestors = interesting_ancestors_of(constant)
@@ -319,42 +349,46 @@ module Tapioca
319
349
  prepend = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
320
350
  include = interesting_ancestors.drop(prepend.size + 1)
321
351
  extend = interesting_singleton_class_ancestors.reject do |mod|
322
- !public_module?(mod) || Module != class_of(mod) || are_equal?(mod, singleton_class)
352
+ Module != class_of(mod) || are_equal?(mod, singleton_class)
323
353
  end
324
354
 
325
- prepends = prepend
355
+ prepend
326
356
  .reverse
327
357
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
328
- .select(&method(:public_module?))
329
358
  .map do |mod|
359
+ add_to_symbol_queue(name_of(mod))
360
+
330
361
  # TODO: Sorbet currently does not handle prepend
331
362
  # properly for method resolution, so we generate an
332
363
  # include statement instead
333
- indented("include(#{qualified_name_of(mod)})")
364
+ qname = qualified_name_of(mod)
365
+ tree << RBI::Include.new(T.must(qname))
334
366
  end
335
367
 
336
- includes = include
368
+ include
337
369
  .reverse
338
370
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
339
- .select(&method(:public_module?))
340
371
  .map do |mod|
341
- indented("include(#{qualified_name_of(mod)})")
372
+ add_to_symbol_queue(name_of(mod))
373
+
374
+ qname = qualified_name_of(mod)
375
+ tree << RBI::Include.new(T.must(qname))
342
376
  end
343
377
 
344
- extends = extend
378
+ extend
345
379
  .reverse
346
380
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
347
- .select(&method(:public_module?))
348
381
  .map do |mod|
349
- indented("extend(#{qualified_name_of(mod)})")
350
- end
382
+ add_to_symbol_queue(name_of(mod))
351
383
 
352
- (prepends + includes + extends).join("\n")
384
+ qname = qualified_name_of(mod)
385
+ tree << RBI::Extend.new(T.must(qname))
386
+ end
353
387
  end
354
388
 
355
- sig { params(constant: Module).returns(String) }
356
- def compile_mixes_in_class_methods(constant)
357
- return "" if constant.is_a?(Class)
389
+ sig { params(tree: RBI::Tree, constant: Module).void }
390
+ def compile_mixes_in_class_methods(tree, constant)
391
+ return if constant.is_a?(Class)
358
392
 
359
393
  mixins_from_modules = {}
360
394
 
@@ -386,11 +420,13 @@ module Tapioca
386
420
  dynamic_extends_from_dynamic_includes = mixins_from_modules.values.flatten
387
421
  dynamic_extends = all_dynamic_extends - dynamic_extends_from_dynamic_includes
388
422
 
389
- result = all_dynamic_includes
423
+ all_dynamic_includes
390
424
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
391
- .select(&method(:public_module?))
392
425
  .map do |mod|
393
- indented("include(#{qualified_name_of(mod)})")
426
+ add_to_symbol_queue(name_of(mod))
427
+
428
+ qname = qualified_name_of(mod)
429
+ tree << RBI::Include.new(T.must(qname))
394
430
  end.join("\n")
395
431
 
396
432
  ancestors = singleton_class_of(constant).ancestors
@@ -402,84 +438,57 @@ module Tapioca
402
438
  mixed_in_module = if extends_as_concern && Module === class_methods_module
403
439
  class_methods_module
404
440
  else
405
- dynamic_extends.find do |mod|
406
- mod != constant && public_module?(mod)
407
- end
441
+ dynamic_extends.find { |mod| mod != constant }
408
442
  end
409
443
 
410
- return result if mixed_in_module.nil?
444
+ return if mixed_in_module.nil?
411
445
 
412
446
  qualified_name = qualified_name_of(mixed_in_module)
413
- return result if qualified_name == ""
447
+ return if qualified_name.nil? || qualified_name == ""
414
448
 
415
- [
416
- result,
417
- indented("mixes_in_class_methods(#{qualified_name})"),
418
- ].select { |b| b != "" }.join("\n\n")
449
+ tree << RBI::MixesInClassMethods.new(qualified_name)
419
450
  rescue
420
- ""
451
+ nil # silence errors
421
452
  end
422
453
 
423
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
424
- def compile_methods(name, constant)
425
- initialize_method = compile_method(
454
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
455
+ def compile_methods(tree, name, constant)
456
+ compile_method(
457
+ tree,
426
458
  name,
427
459
  constant,
428
460
  initialize_method_for(constant)
429
461
  )
430
462
 
431
- instance_methods = compile_directly_owned_methods(name, constant)
432
- singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
433
-
434
- return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
435
-
436
- [
437
- initialize_method || "",
438
- instance_methods,
439
- singleton_methods,
440
- ].select { |b| b.strip != "" }.join("\n\n")
463
+ compile_directly_owned_methods(tree, name, constant)
464
+ compile_directly_owned_methods(tree, name, singleton_class_of(constant))
441
465
  end
442
466
 
443
- sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
444
- def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
445
- indent_step = 0
446
- preamble = nil
447
- postamble = nil
448
-
449
- if mod.singleton_class?
450
- indent_step = 1
451
- preamble = indented("class << self")
452
- postamble = indented("end")
453
- end
454
-
455
- methods = with_indentation(indent_step) do
456
- method_names_by_visibility(mod)
457
- .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
458
- .flat_map do |visibility, method_list|
459
- compiled = method_list.sort!.map do |name|
460
- next if name == :initialize
461
- compile_method(module_name, mod, mod.instance_method(name))
462
- end
463
- compiled.compact!
464
-
465
- unless compiled.empty? || visibility == :public
466
- # add visibility badge
467
- compiled.unshift('', indented(visibility.to_s), '')
467
+ sig do
468
+ params(
469
+ tree: RBI::Tree,
470
+ module_name: String,
471
+ mod: Module,
472
+ for_visibility: T::Array[Symbol]
473
+ ).void
474
+ end
475
+ def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
476
+ method_names_by_visibility(mod)
477
+ .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
478
+ .each do |visibility, method_list|
479
+ method_list.sort!.map do |name|
480
+ next if name == :initialize
481
+ vis = case visibility
482
+ when :protected
483
+ RBI::Visibility::Protected
484
+ when :private
485
+ RBI::Visibility::Private
486
+ else
487
+ RBI::Visibility::Public
468
488
  end
469
-
470
- compiled
489
+ compile_method(tree, module_name, mod, mod.instance_method(name), vis)
471
490
  end
472
- .compact
473
- .join("\n")
474
- end
475
-
476
- return "" if methods.strip == ""
477
-
478
- [
479
- preamble,
480
- methods,
481
- postamble,
482
- ].compact.join("\n")
491
+ end
483
492
  end
484
493
 
485
494
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -503,12 +512,14 @@ module Tapioca
503
512
 
504
513
  sig do
505
514
  params(
515
+ tree: RBI::Tree,
506
516
  symbol_name: String,
507
517
  constant: Module,
508
- method: T.nilable(UnboundMethod)
509
- ).returns(T.nilable(String))
518
+ method: T.nilable(UnboundMethod),
519
+ visibility: RBI::Visibility
520
+ ).void
510
521
  end
511
- def compile_method(symbol_name, constant, method)
522
+ def compile_method(tree, symbol_name, constant, method, visibility = RBI::Visibility::Public)
512
523
  return unless method
513
524
  return unless method.owner == constant
514
525
  return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
@@ -555,37 +566,34 @@ module Tapioca
555
566
  [type, name]
556
567
  end
557
568
 
558
- parameter_list = sanitized_parameters.map do |type, name|
569
+ rbi_method = RBI::Method.new(method_name, is_singleton: constant.singleton_class?, visibility: visibility)
570
+ rbi_method.sigs << compile_signature(signature, sanitized_parameters) if signature
571
+
572
+ sanitized_parameters.each do |type, name|
559
573
  case type
560
574
  when :req
561
- name
575
+ rbi_method << RBI::Param.new(name)
562
576
  when :opt
563
- "#{name} = T.unsafe(nil)"
577
+ rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
564
578
  when :rest
565
- "*#{name}"
579
+ rbi_method << RBI::RestParam.new(name)
566
580
  when :keyreq
567
- "#{name}:"
581
+ rbi_method << RBI::KwParam.new(name)
568
582
  when :key
569
- "#{name}: T.unsafe(nil)"
583
+ rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
570
584
  when :keyrest
571
- "**#{name}"
585
+ rbi_method << RBI::KwRestParam.new(name)
572
586
  when :block
573
- "&#{name}"
587
+ rbi_method << RBI::BlockParam.new(name)
574
588
  end
575
- end.join(', ')
576
-
577
- parameter_list = "(#{parameter_list})" if parameter_list != ""
578
- signature_str = indented(compile_signature(signature, sanitized_parameters)) if signature
589
+ end
579
590
 
580
- [
581
- signature_str,
582
- indented("def #{method_name}#{parameter_list}; end"),
583
- ].compact.join("\n")
591
+ tree << rbi_method
584
592
  end
585
593
 
586
594
  TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
587
595
 
588
- sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(String) }
596
+ sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(RBI::Sig) }
589
597
  def compile_signature(signature, parameters)
590
598
  parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
591
599
  parameter_types.merge!(signature.kwarg_types)
@@ -593,41 +601,35 @@ module Tapioca
593
601
  parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
594
602
  parameter_types[signature.block_name] = signature.block_type if signature.block_name
595
603
 
596
- params = parameters.map do |_, name|
597
- type = parameter_types[name.to_sym]
598
- "#{name}: #{type}"
599
- end.join(", ")
604
+ sig = RBI::Sig.new
600
605
 
601
- returns = type_of(signature.return_type)
606
+ parameters.each do |_, name|
607
+ type = sanitize_signature_types(parameter_types[name.to_sym].to_s)
608
+ add_to_symbol_queue(type)
609
+ sig << RBI::SigParam.new(name, type)
610
+ end
602
611
 
603
- type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
604
- type_parameters = ".type_parameters(#{type_parameters})" unless type_parameters.empty?
612
+ return_type = type_of(signature.return_type)
613
+ sig.return_type = sanitize_signature_types(return_type)
614
+ add_to_symbol_queue(sig.return_type)
605
615
 
606
- mode = case signature.mode
616
+ parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
617
+ sig.type_params << k
618
+ end
619
+
620
+ case signature.mode
607
621
  when "abstract"
608
- ".abstract"
622
+ sig.is_abstract = true
609
623
  when "override"
610
- ".override"
624
+ sig.is_override = true
611
625
  when "overridable_override"
612
- ".overridable.override"
626
+ sig.is_overridable = true
627
+ sig.is_override = true
613
628
  when "overridable"
614
- ".overridable"
615
- else
616
- ""
629
+ sig.is_overridable = true
617
630
  end
618
631
 
619
- signature_body = +""
620
- signature_body << mode
621
- signature_body << type_parameters
622
- signature_body << ".params(#{params})" unless params.empty?
623
- signature_body << ".returns(#{returns})"
624
- signature_body = signature_body
625
- .gsub(".returns(<VOID>)", ".void")
626
- .gsub("<NOT-TYPED>", "T.untyped")
627
- .gsub(".params()", "")
628
- .gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
629
-
630
- "sig { #{signature_body} }"
632
+ sig
631
633
  end
632
634
 
633
635
  sig { params(symbol_name: String).returns(T::Boolean) }
@@ -643,27 +645,6 @@ module Tapioca
643
645
  !!name.match(/^[[:word:]]+[?!=]?$/)
644
646
  end
645
647
 
646
- sig do
647
- type_parameters(:U)
648
- .params(
649
- step: Integer,
650
- _blk: T.proc
651
- .returns(T.type_parameter(:U))
652
- )
653
- .returns(T.type_parameter(:U))
654
- end
655
- def with_indentation(step = 1, &_blk)
656
- @indent += 2 * step
657
- yield
658
- ensure
659
- @indent -= 2 * step
660
- end
661
-
662
- sig { params(str: String).returns(String) }
663
- def indented(str)
664
- " " * @indent + str
665
- end
666
-
667
648
  sig { params(method: UnboundMethod).returns(T::Boolean) }
668
649
  def method_in_gem?(method)
669
650
  source_location = method.source_location&.first
@@ -721,33 +702,6 @@ module Tapioca
721
702
  nil
722
703
  end
723
704
 
724
- def parent_declares_constant?(name)
725
- name_parts = name.split("::")
726
-
727
- parent_name = name_parts[0...-1].join("::")
728
- parent_name = parent_name[2..-1] if parent_name.start_with?("::")
729
- parent_name = 'Object' if parent_name == ""
730
- parent = T.cast(resolve_constant(parent_name), T.nilable(Module))
731
-
732
- return false unless parent
733
-
734
- constants_of(parent).include?(name_parts.last.to_sym)
735
- end
736
-
737
- sig { params(constant: Module).returns(T::Boolean) }
738
- def public_module?(constant)
739
- constant_name = name_of(constant)
740
- return false unless constant_name
741
- return false if constant_name.start_with?('T::Private')
742
-
743
- begin
744
- # can't use !! here because the constant might override ! and mess with us
745
- Module === eval(constant_name) # rubocop:disable Security/Eval
746
- rescue NameError
747
- false
748
- end
749
- end
750
-
751
705
  sig { params(constant: BasicObject).returns(Class).checked(:never) }
752
706
  def class_of(constant)
753
707
  Kernel.instance_method(:class).bind(constant).call
@@ -829,7 +783,7 @@ module Tapioca
829
783
  begin
830
784
  target = Kernel.instance_method(:send).bind(constant).call(:target)
831
785
  rescue NoMethodError
832
- return nil
786
+ return
833
787
  end
834
788
 
835
789
  raw_name_of(target)
@@ -859,12 +813,21 @@ module Tapioca
859
813
  nil
860
814
  end
861
815
 
862
- sig { params(constant: Module).returns(String) }
816
+ sig { params(sig_string: String).returns(String) }
817
+ def sanitize_signature_types(sig_string)
818
+ sig_string
819
+ .gsub(".returns(<VOID>)", ".void")
820
+ .gsub("<VOID>", "void")
821
+ .gsub("<NOT-TYPED>", "T.untyped")
822
+ .gsub(".params()", "")
823
+ end
824
+
825
+ sig { params(constant: T::Types::Base).returns(String) }
863
826
  def type_of(constant)
864
827
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
865
828
  end
866
829
 
867
- sig { params(object: Object).returns(T::Boolean).checked(:never) }
830
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
868
831
  def object_id_of(object)
869
832
  Object.instance_method(:object_id).bind(object).call
870
833
  end