tapioca 0.4.20 → 0.4.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 567eff35f7ee24b52dafcd45ceb40ccaf042b6e0661c13cc5dcb87228c21c792
4
- data.tar.gz: 0c5143d8407d5583ccd80edbb047fb0b7500b2de8bba1717723a92f97994c045
3
+ metadata.gz: 832665c6f2a41c3eb5cfda2b0939b4a3f2d29eb624b64de49565b8275f461326
4
+ data.tar.gz: 29e1f7066050ce4f308b1dc8591dd7de236e19560618a37c9120a4835ac1b295
5
5
  SHA512:
6
- metadata.gz: d82321ab3b2516a4410825a6dfe4b253d7cccf49abc8c62db0b2a8cdbe13f064aa43df759108685ba06c36073c7a59cfda4ff361cdad1bfc736d7e557ae3ada6
7
- data.tar.gz: 0b9bd1ff91ca97cbbb1922e9915a8d046421ff40f3cd2774b4518c979f44635507b48fcce93a904770b1a65e7214a9897ce5444ce26ee6d532523a595e24ec96
6
+ metadata.gz: a16a1125e343398d7a2190aea74ed2fafc3b9ad5d4aaac8a2b72ae4a75cd65bdb651dcf84528189eec39296eebb42382f847221651190e611f1c7260923653ba
7
+ data.tar.gz: d636730273b8eb9d828de5609b73c73cb677ec0cf372a1ae5d028604ec53422cbe3c607d557c0b4070dadf704c0023cef1dfb40d09ddb61327d7793db65a0ef0
@@ -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
@@ -145,8 +145,6 @@ module Tapioca
145
145
  }
146
146
  )
147
147
 
148
- constant_lookup = {}
149
-
150
148
  compiler.run do |constant, contents|
151
149
  constant_name = Module.instance_method(:name).bind(constant).call
152
150
 
@@ -159,13 +157,12 @@ module Tapioca
159
157
 
160
158
  if filename
161
159
  rbi_files_to_purge.delete(filename)
162
- constant_lookup[filename.relative_path_from(outpath)] = constant_name
163
160
  end
164
161
  end
165
162
  say("")
166
163
 
167
164
  if should_verify
168
- perform_dsl_verification(outpath, constant_lookup)
165
+ perform_dsl_verification(outpath)
169
166
  else
170
167
  purge_stale_dsl_rbi_files(rbi_files_to_purge)
171
168
 
@@ -596,17 +593,15 @@ module Tapioca
596
593
  end.sort
597
594
  end
598
595
 
599
- sig { params(dir: Pathname, constant_lookup: T::Hash[String, String]).void }
600
- def perform_dsl_verification(dir, constant_lookup)
596
+ sig { params(dir: Pathname).void }
597
+ def perform_dsl_verification(dir)
601
598
  diff = verify_dsl_rbi(tmp_dir: dir)
602
599
 
603
600
  if diff.empty?
604
601
  say("Nothing to do, all RBIs are up-to-date.")
605
602
  else
606
- constants = T.unsafe(constant_lookup).values_at(*diff.keys).join(" ")
607
-
608
603
  say("RBI files are out-of-date, please run:")
609
- say(" `#{Config::DEFAULT_COMMAND} dsl #{constants}`")
604
+ say(" `#{Config::DEFAULT_COMMAND} dsl`")
610
605
 
611
606
  say("")
612
607
 
@@ -100,7 +100,7 @@ module Tapioca
100
100
  # the generic class `Foo[Bar]` is still a `Foo`. That is:
101
101
  # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
102
102
  # if we just clone the class. But subclassing works just fine.
103
- create_sealed_safe_subclass(constant)
103
+ create_safe_subclass(constant)
104
104
  else
105
105
  # This can only be a module and it is fine to just clone modules
106
106
  # since they can't have instances and will not have `is_a?` relationships.
@@ -151,28 +151,31 @@ module Tapioca
151
151
  end
152
152
 
153
153
  sig { params(constant: Class).returns(Class) }
154
- def create_sealed_safe_subclass(constant)
155
- # If the constant is not sealed let's just bail early.
156
- # We just return a subclass of the constant.
157
- return Class.new(constant) unless T::Private::Sealed.sealed_module?(constant)
158
-
159
- # Since sealed classes can normally not be subclassed, we need to trick
160
- # sealed classes into thinking that the generic type we are
161
- # creating by subclassing is actually safe for sealed types.
162
- #
163
- # Get the filename the sealed class was declared in
164
- decl_file = constant.instance_variable_get(:@sorbet_sealed_module_decl_file)
154
+ def create_safe_subclass(constant)
155
+ # Lookup the "inherited" class method
156
+ inherited_method = constant.method(:inherited)
157
+ # and the module that defines it
158
+ owner = inherited_method.owner
159
+
160
+ # If no one has overriden the inherited method yet, just subclass
161
+ return Class.new(constant) if Class == owner
162
+
165
163
  begin
166
- # Clear the current declaration filename on the class
167
- constant.remove_instance_variable(:@sorbet_sealed_module_decl_file)
168
- # Make this file be the declaration filename so that Sorbet runtime
169
- # does not shout at us for an invalid subclassing.
170
- T.cast(constant, T::Helpers).sealed!
164
+ # Otherwise, some inherited method could be preventing us
165
+ # from creating subclasses, so let's override it and rescue
166
+ owner.send(:define_method, :inherited) do |s|
167
+ begin
168
+ inherited_method.call(s)
169
+ rescue
170
+ # Ignoring errors
171
+ end
172
+ end
173
+
171
174
  # return a subclass
172
175
  Class.new(constant)
173
176
  ensure
174
- # Reinstate the original declaration filename
175
- constant.instance_variable_set(:@sorbet_sealed_module_decl_file, decl_file)
177
+ # Reinstate the original inherited method back.
178
+ owner.send(:define_method, :inherited, inherited_method)
176
179
  end
177
180
  end
178
181
 
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.20"
5
+ VERSION = "0.4.21"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.20
4
+ version: 0.4.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-04-01 00:00:00.000000000 Z
14
+ date: 2021-04-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler