lib/tapioca/compilers/dsl/protobuf.rb in tapioca-0.4.20 vs lib/tapioca/compilers/dsl/protobuf.rb in tapioca-0.4.21
- old
+ new
@@ -60,37 +60,103 @@
# sig { params(value: Float).returns(Float) }
# def number_value=(value); end
# end
# ~~~
class Protobuf < Base
+ # Parlour doesn't support type members out of the box, so adding the
+ # ability to do that here. This should be upstreamed.
+ class TypeMember < Parlour::RbiGenerator::RbiObject
+ extend T::Sig
+
+ sig { params(other: Object).returns(T::Boolean) }
+ def ==(other)
+ TypeMember === other && name == other.name
+ end
+
+ sig do
+ override
+ .params(indent_level: Integer, options: Parlour::RbiGenerator::Options)
+ .returns(T::Array[String])
+ end
+ def generate_rbi(indent_level, options)
+ [options.indented(indent_level, "#{name} = type_member")]
+ end
+
+ sig do
+ override
+ .params(others: T::Array[Parlour::RbiGenerator::RbiObject])
+ .returns(T::Boolean)
+ end
+ def mergeable?(others)
+ others.all? { |other| self == other }
+ end
+
+ sig { override.params(others: T::Array[Parlour::RbiGenerator::RbiObject]).void }
+ def merge_into_self(others); end
+
+ sig { override.returns(String) }
+ def describe
+ "Type Member (#{name})"
+ end
+ end
+
+ class Field < T::Struct
+ prop :name, String
+ prop :type, String
+ prop :init_type, String
+ prop :default, String
+
+ extend T::Sig
+
+ sig { returns(Parlour::RbiGenerator::Parameter) }
+ def to_init
+ Parlour::RbiGenerator::Parameter.new("#{name}:", type: init_type, default: default)
+ end
+ end
+
extend T::Sig
sig do
override.params(
root: Parlour::RbiGenerator::Namespace,
- constant: T.class_of(Google::Protobuf::MessageExts)
+ constant: Module
).void
end
def decorate(root, constant)
- descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
- return unless descriptor.any?
-
root.path(constant) do |klass|
- descriptor.each do |desc|
- create_descriptor_method(klass, desc)
+ if constant == Google::Protobuf::RepeatedField
+ create_type_members(klass, "Elem")
+ elsif constant == Google::Protobuf::Map
+ create_type_members(klass, "Key", "Value")
+ else
+ descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
+ fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
+ fields.sort_by!(&:name)
+
+ create_method(klass, "initialize", parameters: fields.map!(&:to_init))
end
end
end
sig { override.returns(T::Enumerable[Module]) }
def gather_constants
- classes = T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class])
- classes.select { |c| c < Google::Protobuf::MessageExts && !c.singleton_class? }
+ marker = Google::Protobuf::MessageExts::ClassMethods
+ results = T.cast(ObjectSpace.each_object(marker).to_a, T::Array[Module])
+ results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
end
private
+ sig { params(klass: Parlour::RbiGenerator::Namespace, names: String).void }
+ def create_type_members(klass, *names)
+ klass.create_extend("T::Generic")
+
+ names.each do |name|
+ klass.children << TypeMember.new(klass.generator, name)
+ end
+ end
+
sig do
params(
descriptor: Google::Protobuf::FieldDescriptor
).returns(String)
end
@@ -111,33 +177,83 @@
else
"T.untyped"
end
end
+ sig { params(descriptor: Google::Protobuf::FieldDescriptor).returns(Field) }
+ def field_of(descriptor)
+ if descriptor.label == :repeated
+ # Here we're going to check if the submsg_name is named according to
+ # how Google names map entries.
+ # https://github.com/protocolbuffers/protobuf/blob/f82e26/ruby/ext/google/protobuf_c/defs.c#L1963-L1966
+ if descriptor.submsg_name.to_s.end_with?("_MapEntry_#{descriptor.name}")
+ key = descriptor.subtype.lookup('key')
+ value = descriptor.subtype.lookup('value')
+
+ key_type = type_of(key)
+ value_type = type_of(value)
+ type = "Google::Protobuf::Map[#{key_type}, #{value_type}]"
+
+ default_args = [key.type.inspect, value.type.inspect]
+ default_args << value_type if %i[enum message].include?(value.type)
+
+ Field.new(
+ name: descriptor.name,
+ type: type,
+ init_type: "T.any(#{type}, T::Hash[#{key_type}, #{value_type}])",
+ default: "Google::Protobuf::Map.new(#{default_args.join(', ')})"
+ )
+ else
+ elem_type = type_of(descriptor)
+ type = "Google::Protobuf::RepeatedField[#{elem_type}]"
+
+ default_args = [descriptor.type.inspect]
+ default_args << elem_type if %i[enum message].include?(descriptor.type)
+
+ Field.new(
+ name: descriptor.name,
+ type: type,
+ init_type: "T.any(#{type}, T::Array[#{elem_type}])",
+ default: "Google::Protobuf::RepeatedField.new(#{default_args.join(', ')})"
+ )
+ end
+ else
+ type = type_of(descriptor)
+
+ Field.new(
+ name: descriptor.name,
+ type: type,
+ init_type: type,
+ default: "nil"
+ )
+ end
+ end
+
sig do
params(
klass: Parlour::RbiGenerator::Namespace,
desc: Google::Protobuf::FieldDescriptor,
- ).void
+ ).returns(Field)
end
def create_descriptor_method(klass, desc)
- name = desc.name
- type = type_of(desc)
+ field = field_of(desc)
create_method(
klass,
- name,
- return_type: type
+ field.name,
+ return_type: field.type
)
create_method(
klass,
- "#{name}=",
+ "#{field.name}=",
parameters: [
- Parlour::RbiGenerator::Parameter.new("value", type: type),
+ Parlour::RbiGenerator::Parameter.new("value", type: field.type),
],
- return_type: type
+ return_type: field.type
)
+
+ field
end
end
end
end
end