Class: Tapioca::Dsl::Compilers::Protobuf

Inherits:
Tapioca::Dsl::Compiler show all
Extended by:
T::Sig
Defined in:
lib/tapioca/dsl/compilers/protobuf.rb

Overview

‘Tapioca::Dsl::Compilers::Protobuf` decorates RBI files for subclasses of [`Google::Protobuf::MessageExts`](github.com/protocolbuffers/protobuf/tree/master/ruby).

For example, with the following “cart.rb” file:

~~~rb Google::Protobuf::DescriptorPool.generated_pool.build do

add_file("cart.proto", :syntax => :proto3) do
  add_message "MyCart" do
    optional :shop_id, :int32, 1
    optional :customer_id, :int64, 2
    optional :number_value, :double, 3
    optional :string_value, :string, 4
  end
end

end ~~~

this compiler will produce the RBI file ‘cart.rbi` with the following content:

~~~rbi # cart.rbi # typed: strong class Cart < Google::Protobuf::AbstractMessage

sig { returns(Integer) }
def customer_id; end

sig { params(value: Integer).returns(Integer) }
def customer_id=(value); end

sig { returns(Integer) }
def shop_id; end

sig { params(value: Integer).returns(Integer) }
def shop_id=(value); end

sig { returns(String) }
def string_value; end

sig { params(value: String).returns(String) }
def string_value=(value); end

sig { returns(Float) }
def number_value; end

sig { params(value: Float).returns(Float) }
def number_value=(value); end

end ~~~

Please note that you might have to ignore the originally generated Protobuf Ruby files to avoid _Redefining constant_ issues when doing type checking. Do this by extending your Sorbet config file:

~~~ –ignore=/path/to/proto/cart_pb.rb ~~~

Defined Under Namespace

Classes: Field

Constant Summary collapse

ConstantType =
type_member { { fixed: T::Class[T.anything] } }
FIELD_RE =
/^[a-z_][a-zA-Z0-9_]*$/

Constants included from Runtime::Reflection

Runtime::Reflection::ANCESTORS_METHOD, Runtime::Reflection::CLASS_METHOD, Runtime::Reflection::CONSTANTS_METHOD, Runtime::Reflection::EQUAL_METHOD, Runtime::Reflection::METHOD_METHOD, Runtime::Reflection::NAME_METHOD, Runtime::Reflection::OBJECT_ID_METHOD, Runtime::Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Runtime::Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Runtime::Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Runtime::Reflection::REQUIRED_FROM_LABELS, Runtime::Reflection::SINGLETON_CLASS_METHOD, Runtime::Reflection::SUPERCLASS_METHOD, Runtime::Reflection::UNDEFINED_CONSTANT

Constants included from SorbetHelper

SorbetHelper::FEATURE_REQUIREMENTS, SorbetHelper::SORBET_BIN, SorbetHelper::SORBET_EXE_PATH_ENV_VAR, SorbetHelper::SORBET_GEM_SPEC, SorbetHelper::SORBET_PAYLOAD_URL, SorbetHelper::SPOOM_CONTEXT

Instance Attribute Summary

Attributes inherited from Tapioca::Dsl::Compiler

#constant, #options, #root

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Tapioca::Dsl::Compiler

#add_error, #compiler_enabled?, handles?, #initialize, processable_constants, requested_constants=

Methods included from T::Generic::TypeStoragePatch

#[], #has_attached_class!, #type_member, #type_template

Methods included from Runtime::Reflection

#abstract_type_of, #ancestors_of, #are_equal?, #class_of, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #final_module?, #inherited_ancestors_of, #method_of, #name_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #sealed_module?, #signature_of, #signature_of!, #singleton_class_of, #superclass_of

Methods included from Runtime::AttachedClassOf

#attached_class_of

Methods included from RBIHelper

#as_nilable_type, #as_non_nilable_type, #create_block_param, #create_kw_opt_param, #create_kw_param, #create_kw_rest_param, #create_opt_param, #create_param, #create_rest_param, #create_typed_param, #sanitize_signature_types, serialize_type_variable, #valid_method_name?, #valid_parameter_name?

Methods included from SorbetHelper

#sorbet, #sorbet_path, #sorbet_supports?

Constructor Details

This class inherits a constructor from Tapioca::Dsl::Compiler

Class Method Details

.gather_constantsObject



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/tapioca/dsl/compilers/protobuf.rb', line 159

def gather_constants
  marker = Google::Protobuf::MessageExts::ClassMethods

  enum_modules = ObjectSpace.each_object(Google::Protobuf::EnumDescriptor).map(&:enummodule)

  results = if Google::Protobuf.const_defined?(:AbstractMessage)
    abstract_message_const = ::Google::Protobuf.const_get(:AbstractMessage)
    descendants_of(abstract_message_const) - [abstract_message_const]
  else
    T.cast(
      ObjectSpace.each_object(marker).to_a,
      T::Array[Module],
    )
  end

  results = results.concat(enum_modules)
  results.any? ? results + [Google::Protobuf::RepeatedField, Google::Protobuf::Map] : []
end

Instance Method Details

#decorateObject



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/tapioca/dsl/compilers/protobuf.rb', line 82

def decorate
  root.create_path(constant) do |klass|
    if constant == Google::Protobuf::RepeatedField
      create_type_members(klass, "Elem")
    elsif constant == Google::Protobuf::Map
      create_type_members(klass, "Key", "Value")
    else
      descriptor = T.unsafe(constant).descriptor

      case descriptor
      when Google::Protobuf::EnumDescriptor
        descriptor.to_h.each do |sym, val|
          # For each enum value, create a namespaced constant on the root rather than an un-namespaced
          # constant within the class. This is because un-namespaced constants might conflict with reserved
          # Ruby words, such as "BEGIN." By namespacing them, we avoid this problem.
          #
          # Invalid syntax:
          # class Foo
          #   BEGIN = 3
          # end
          #
          # Valid syntax:
          # Foo::BEGIN = 3
          root.create_constant("#{constant}::#{sym}", value: val.to_s)
        end

        klass.create_method(
          "lookup",
          parameters: [create_param("number", type: "Integer")],
          return_type: "T.nilable(Symbol)",
          class_method: true,
        )
        klass.create_method(
          "resolve",
          parameters: [create_param("symbol", type: "Symbol")],
          return_type: "T.nilable(Integer)",
          class_method: true,
        )
        klass.create_method(
          "descriptor",
          return_type: "Google::Protobuf::EnumDescriptor",
          class_method: true,
        )
      when Google::Protobuf::Descriptor
        raise "#{klass} is not a RBI::Class" unless klass.is_a?(RBI::Class)

        klass.superclass_name = "Google::Protobuf::AbstractMessage"
        descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
        fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
        fields.sort_by!(&:name)

        parameters = fields.map do |field|
          create_kw_opt_param(field.name, type: field.init_type, default: field.default)
        end

        if fields.all? { |field| FIELD_RE.match?(field.name) }
          klass.create_method("initialize", parameters: parameters, return_type: "void")
        else
          # One of the fields has an incorrect name for a named parameter so creating the default initialize for
          # it would create a RBI with a syntax error.
          # The workaround is to create an initialize that takes a **kwargs instead.
          kwargs_parameter = create_kw_rest_param("fields", type: "T.untyped")
          klass.create_method("initialize", parameters: [kwargs_parameter], return_type: "void")
        end
      else
        add_error(<<~MSG.strip)
          Unexpected descriptor class `#{descriptor.class.name}` for `#{constant}`
        MSG
      end
    end
  end
end