Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
davebenvenuti committed Jan 29, 2025
1 parent a962cde commit 1e83d3a
Show file tree
Hide file tree
Showing 17 changed files with 952 additions and 12 deletions.
60 changes: 50 additions & 10 deletions lib/protoboeuf/codegen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ def result(message, toplevel_enums, generate_types:, requires:, syntax:, options
end
end

attr_reader :message, :fields, :oneof_fields, :syntax
attr_reader :optional_fields, :enum_field_types
attr_reader :message, :fields, :oneof_fields, :syntax, :optional_fields, :enum_field_types, :oneof_json_names

def initialize(message, toplevel_enums, generate_types:, requires:, syntax:, options:)
@message = message
Expand All @@ -109,6 +108,7 @@ def initialize(message, toplevel_enums, generate_types:, requires:, syntax:, opt
@oneof_fields = []
@enum_fields = []
@oneof_selection_fields = []
@oneof_json_names = {}

optional_field_count = 0

Expand All @@ -123,6 +123,7 @@ def initialize(message, toplevel_enums, generate_types:, requires:, syntax:, opt
optional_field_count += 1
elsif field.has_oneof_index?
(@oneof_fields[field.oneof_index] ||= []) << field
@oneof_json_names[field.name] = field.json_name
elsif field.type == :TYPE_ENUM
@enum_fields << field
else
Expand Down Expand Up @@ -164,27 +165,46 @@ def conversion
field.has_oneof_index? && !field.optional?
end

oneofs = @oneof_selection_fields.map do |field|
"send(#{field.name.dump}).tap { |f| result[f.to_sym] = send(f) if f }"
end
oneofs = @oneof_selection_fields

<<~RUBY
#{type_signature(returns: "T::Hash[Symbol, T.untyped]")}
def to_h
result = {}
#{(oneofs + fields.map { |field| convert_field(field) }).join("\n")}
#{oneofs.map { |oneof| hash_assignment_for_oneof(oneof, json: false) }.join("\n")}
#{fields.map { |field| hash_assignment_for_field(field, json: false) }.join("\n")}
result
end
#{type_signature(params: "T::Hash[T.untyped, T.untyped]", returns: "T::Hash[Symbol, T.untyped]")}
def as_json(options = {})
result = {}
#{oneofs.map { |oneof| hash_assignment_for_oneof(oneof, json: true) }.join("\n")}
#{fields.map { |field| hash_assignment_for_field(field, json: true) }.join("\n")}
result
end
def to_json(options = {})
require 'json'
JSON.dump(as_json(options))
end
RUBY
end

def convert_field(field)
def hash_assignment_for_oneof(oneof, json: false)
"send(field.name.dump).tap { |f| result[f.to_sym] = send(f) if f }"
end

def hash_assignment_for_field(field, json: false)
key_string = "#{(json ? field.json_name : field.name).dump}.to_sym"
recurse_with = json ? "as_json" : "to_h"

if field.repeated?
"result['#{field.name}'.to_sym] = #{iv_name(field)}"
"result[#{key_string}] = #{iv_name(field)}"
elsif field.type == :TYPE_MESSAGE
"result['#{field.name}'.to_sym] = #{iv_name(field)}.to_h"
"result[#{key_string}] = #{iv_name(field)}.#{recurse_with}"
else
"result['#{field.name}'.to_sym] = #{iv_name(field)}"
"result[#{key_string}] = #{iv_name(field)}"
end
end

Expand Down Expand Up @@ -782,6 +802,7 @@ def initialize_code
initialize_type_signature(fields) +
"def initialize(" + initialize_signature + ")\n" +
init_bitmask(message) +
init_oneof_json_names(message) +
initialize_oneofs +
fields.map { |field|
if field.has_oneof_index? && !field.optional?
Expand Down Expand Up @@ -910,6 +931,13 @@ def iv_name(field)
"@#{field.name}"
end

# Our generated code needs to keep a hash of oneof field names to their corresponding JSON name. This is
# unnecessary for other field types since we can access the json_name upon code generation, but the particular
# oneof field isn't known until runtime.
def oneof_json_name(field)
"@_oneof_json_names[:#{field.name}]"
end

def initialize_required_field(field)
<<~RUBY
#{bounds_check(field, lvar_read(field)).chomp}
Expand Down Expand Up @@ -1650,6 +1678,18 @@ def init_bitmask(msg)
end
end

ONEOF_JSON_NAMES = ERB.new(<<~RUBY, trim_mode: "-")
@_oneof_json_names = {
<%- oneof_json_names.each do |field_name, json_name| -%>
:<%= field_name.dump %> => <%= json_name.dump %>,
<%- end %>
}
RUBY

def init_oneof_json_names(_msg)
ONEOF_JSON_NAMES.result(binding)
end

def set_bitmask(field) # rubocop:disable Naming/AccessorMethodName
i = @optional_field_bit_lut.fetch(field.number) || raise("optional field should have a bit")
"@_bitmask |= #{format("%#018x", 1 << i)}"
Expand Down
11 changes: 10 additions & 1 deletion lib/protoboeuf/decorated_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ class DecoratedField

extend Forwardable

def_delegators :@original_field, :name, :label, :type_name, :type, :number, :options, :oneof_index, :has_oneof_index?
def_delegators :@original_field,
:name,
:label,
:type_name,
:type,
:number,
:options,
:oneof_index,
:has_oneof_index?,
:json_name

def initialize(field:, message:, syntax:)
@original_field = field
Expand Down
16 changes: 16 additions & 0 deletions lib/protoboeuf/google/protobuf/any.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions lib/protoboeuf/google/protobuf/boolvalue.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions lib/protoboeuf/google/protobuf/bytesvalue.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1e83d3a

Please sign in to comment.