diff --git a/lib/protoboeuf/codegen.rb b/lib/protoboeuf/codegen.rb index f553029..b28ebcc 100644 --- a/lib/protoboeuf/codegen.rb +++ b/lib/protoboeuf/codegen.rb @@ -2,7 +2,7 @@ require "erb" require "syntax_tree" -require_relative "codegen_type_helper" +require_relative "type_helper" module ProtoBoeuf class CodeGen diff --git a/lib/protoboeuf/codegen_type_helper.rb b/lib/protoboeuf/codegen_type_helper.rb deleted file mode 100644 index b3ef6dd..0000000 --- a/lib/protoboeuf/codegen_type_helper.rb +++ /dev/null @@ -1,106 +0,0 @@ -# frozen_string_literal: true - -module ProtoBoeuf - class CodeGen - module TypeHelper - TYPE_MAPPING = { - :TYPE_INT32 => "Integer", - :TYPE_SINT32 => "Integer", - :TYPE_UINT32 => "Integer", - :TYPE_INT64 => "Integer", - :TYPE_SINT64 => "Integer", - :TYPE_FIXED64 => "Integer", - :TYPE_SFIXED64 => "Integer", - :TYPE_FIXED32 => "Integer", - :TYPE_SFIXED32 => "Integer", - :TYPE_UINT64 => "Integer", - :TYPE_STRING => "String", - :TYPE_DOUBLE => "Float", - :TYPE_FLOAT => "Float", - :TYPE_BYTES => "String", - :TYPE_ENUM => "Symbol", - "google.protobuf.BoolValue" => "ProtoBoeuf::Protobuf::BoolValue", - "google.protobuf.Int32Value" => "ProtoBoeuf::Protobuf::Int32Value", - "google.protobuf.Int64Value" => "ProtoBoeuf::Protobuf::Int64Value", - "google.protobuf.UInt32Value" => "ProtoBoeuf::Protobuf::UInt32Value", - "google.protobuf.UInt64Value" => "ProtoBoeuf::Protobuf::UInt64Value", - "google.protobuf.FloatValue" => "ProtoBoeuf::Protobuf::FloatValue", - "google.protobuf.DoubleValue" => "ProtoBoeuf::Protobuf::DoubleValue", - "google.protobuf.StringValue" => "ProtoBoeuf::Protobuf::StringValue", - "google.protobuf.BytesValue" => "ProtoBoeuf::Protobuf::BytesValue", - "google.protobuf.Timestamp" => "ProtoBoeuf::Protobuf::Timestamp", - }.freeze - - def type_signature(params: nil, returns: nil, newline: false) - return "" unless generate_types - - sig = [] - sig << "params(#{params.map { |k, v| "#{k}: #{convert_type(v)}" }.join(", ")})" if params - sig << "returns(#{returns})" if returns - sig << "void" unless returns - - complete_sig = "sig { #{sig.join(".")} }" - complete_sig += "\n" if newline - - complete_sig - end - - def initialize_type_signature(fields) - return "" unless generate_types - - type_signature(params: fields_to_params(fields), newline: true) - end - - def reader_type_signature(field) - type_signature(returns: convert_field_type(field)) - end - - def extend_t_sig - return "" unless generate_types - - "extend T::Sig" - end - - private - - def convert_type(converted_type, optional: false, array: false) - converted_type = "T::Array[#{converted_type}]" if array - converted_type = "T.nilable(#{converted_type})" if optional - converted_type - end - - def convert_field_type(field) - converted_type = if map_field?(field) - map_type = self.map_type(field) - "T::Hash[#{convert_field_type(map_type.field[0])}, #{convert_field_type(map_type.field[1])}]" - else - case field.type - when :TYPE_BOOL - "T::Boolean" - when :TYPE_MESSAGE - field.type_name.delete_prefix(".").split(".").join("::") - else - TYPE_MAPPING.fetch(field.type) - end - end - - convert_type( - converted_type, - optional: field.label == :TYPE_OPTIONAL, - array: field.label == :LABEL_REPEATED && !map_field?(field), - ) - end - - def field_to_params(field) - [field.name, convert_field_type(field)] - end - - def fields_to_params(fields) - fields - .flat_map { |field| field_to_params(field) } - .each_slice(2) - .to_h - end - end - end -end diff --git a/lib/protoboeuf/type_helper.rb b/lib/protoboeuf/type_helper.rb new file mode 100644 index 0000000..a220665 --- /dev/null +++ b/lib/protoboeuf/type_helper.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module ProtoBoeuf + module TypeHelper + TYPE_MAPPING = { + :TYPE_INT32 => "Integer", + :TYPE_SINT32 => "Integer", + :TYPE_UINT32 => "Integer", + :TYPE_INT64 => "Integer", + :TYPE_SINT64 => "Integer", + :TYPE_FIXED64 => "Integer", + :TYPE_SFIXED64 => "Integer", + :TYPE_FIXED32 => "Integer", + :TYPE_SFIXED32 => "Integer", + :TYPE_UINT64 => "Integer", + :TYPE_STRING => "String", + :TYPE_DOUBLE => "Float", + :TYPE_FLOAT => "Float", + :TYPE_BYTES => "String", + :TYPE_ENUM => "Symbol", + "google.protobuf.BoolValue" => "ProtoBoeuf::Protobuf::BoolValue", + "google.protobuf.Int32Value" => "ProtoBoeuf::Protobuf::Int32Value", + "google.protobuf.Int64Value" => "ProtoBoeuf::Protobuf::Int64Value", + "google.protobuf.UInt32Value" => "ProtoBoeuf::Protobuf::UInt32Value", + "google.protobuf.UInt64Value" => "ProtoBoeuf::Protobuf::UInt64Value", + "google.protobuf.FloatValue" => "ProtoBoeuf::Protobuf::FloatValue", + "google.protobuf.DoubleValue" => "ProtoBoeuf::Protobuf::DoubleValue", + "google.protobuf.StringValue" => "ProtoBoeuf::Protobuf::StringValue", + "google.protobuf.BytesValue" => "ProtoBoeuf::Protobuf::BytesValue", + "google.protobuf.Timestamp" => "ProtoBoeuf::Protobuf::Timestamp", + }.freeze + + def type_signature(params: nil, returns: nil, newline: false) + return "" unless generate_types + + sig = [] + sig << "params(#{params.map { |k, v| "#{k}: #{convert_type(v)}" }.join(", ")})" if params + sig << "returns(#{returns})" if returns + sig << "void" unless returns + + complete_sig = "sig { #{sig.join(".")} }" + complete_sig += "\n" if newline + + complete_sig + end + + def initialize_type_signature(fields) + return "" unless generate_types + + type_signature(params: fields_to_params(fields), newline: true) + end + + def reader_type_signature(field) + type_signature(returns: convert_field_type(field)) + end + + def extend_t_sig + return "" unless generate_types + + "extend T::Sig" + end + + private + + def convert_type(converted_type, optional: false, array: false) + converted_type = "T::Array[#{converted_type}]" if array + converted_type = "T.nilable(#{converted_type})" if optional + converted_type + end + + def convert_field_type(field) + converted_type = if map_field?(field) + map_type = self.map_type(field) + "T::Hash[#{convert_field_type(map_type.field[0])}, #{convert_field_type(map_type.field[1])}]" + else + case field.type + when :TYPE_BOOL + "T::Boolean" + when :TYPE_MESSAGE + field.type_name.delete_prefix(".").split(".").join("::") + else + TYPE_MAPPING.fetch(field.type) + end + end + + convert_type( + converted_type, + optional: field.label == :TYPE_OPTIONAL, + array: field.label == :LABEL_REPEATED && !map_field?(field), + ) + end + + def field_to_params(field) + [field.name, convert_field_type(field)] + end + + def fields_to_params(fields) + fields + .flat_map { |field| field_to_params(field) } + .each_slice(2) + .to_h + end + end +end diff --git a/test/gem_test.rb b/test/gem_test.rb index b7c4ef9..8d135fa 100644 --- a/test/gem_test.rb +++ b/test/gem_test.rb @@ -4,29 +4,62 @@ class GemTest < ProtoBoeuf::Test def test_can_be_required - Tempfile.create do |file| - file.write(<<~RUBY) - require "bundler/inline" + assert_rb_runs_successfully(<<~RUBY, "Failed to require the gem") + require "bundler/inline" + + gemfile do + gem "protoboeuf", path: "#{__dir__}/..", require: true + end + + def assert_autoload(mod, name) + # We need to escape the string interpolation so it doesn't evaluate before we actually write this code to disk. + raise "\#{mod}::\#{name} not configured to autoload" unless mod.autoload?(name) + end + + assert_autoload(::ProtoBoeuf, :CodeGen) + assert_autoload(::ProtoBoeuf, :Google) + assert_autoload(::ProtoBoeuf::Google, :Api) + assert_autoload(::ProtoBoeuf::Google::Api, :FieldBehavior) + assert_autoload(::ProtoBoeuf::Google, :Protobuf) + assert_autoload(::ProtoBoeuf::Google::Protobuf, :Any) + assert_autoload(::ProtoBoeuf::Google::Protobuf, :FileDescriptorProto) + assert_autoload(::ProtoBoeuf::Google::Protobuf, :FileDescriptorSet) + + exit 0 + RUBY + end + + def test_codegen_can_be_required + # Test that we fixed a circular require bug: https://github.com/Shopify/protoboeuf/issues/173 - gemfile do - gem "protoboeuf", path: "#{__dir__}/..", require: true - end + _, stderr, _ = assert_rb_runs_successfully(<<~RUBY) + require "bundler/inline" - ::ProtoBoeuf + gemfile do + gem "protoboeuf", path: "#{__dir__}/..", require: false + end - # The following should autoloaded - ::ProtoBoeuf::CodeGen - ::ProtoBoeuf::Google::Api::FieldBehavior - ::ProtoBoeuf::Google::Protobuf::Any - ::ProtoBoeuf::Google::Protobuf::FileDescriptorProto - ::ProtoBoeuf::Google::Protobuf::FileDescriptorSet + require "protoboeuf" + require "protoboeuf/codegen" - exit 0 - RUBY + exit 0 + RUBY + refute_match(/warning: loading in progress, circular require considered harmful/, stderr) + end + + private + + def assert_rb_runs_successfully(ruby, msg = "Ruby returned non-zero exit status") + Tempfile.create do |file| + file.write(ruby) file.flush - assert(system(RbConfig.ruby, "-v", file.path), "Failed to require the gem") + stdout, stderr, status = Open3.capture3(RbConfig.ruby, "-v", file.path) + + assert_equal(0, status, msg) + + [stdout, stderr, status] end end end