Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/protoboeuf/codegen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "erb"
require "syntax_tree"
require_relative "codegen_type_helper"
require_relative "type_helper"

module ProtoBoeuf
class CodeGen
Expand Down
106 changes: 0 additions & 106 deletions lib/protoboeuf/codegen_type_helper.rb

This file was deleted.

104 changes: 104 additions & 0 deletions lib/protoboeuf/type_helper.rb
Original file line number Diff line number Diff line change
@@ -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
65 changes: 49 additions & 16 deletions test/gem_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like being specific, but I'm a bit worried that any change in the warning message could cause this test to pass unexpectedly. These messages aren't really stable across Ruby versions. I'm not sure if JRuby or TruffleRuby even emit them identically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you suggest refuting a match on a subset of the message, like /circular require/? I suppose that's more likely to be consistent across platforms and versions. Our generated code currently produces other warnings so simply asserting that we don't have any warnings won't work I'm afraid.

end

private

def assert_rb_runs_successfully(ruby, msg = "Ruby returned non-zero exit status")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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