tapioca 0.4.17 → 0.4.22

Sign up to get free protection for your applications and to get access to all the features.
@@ -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