Skip to content

ruby: No way to access oneof field if it shares the name of a method on Object #21736

@jez

Description

@jez

If a oneof field shares the name of a Ruby method defined on Object or Kernel, there is no way to get the oneof field.

Normally, you do something like my_message.my_oneof to get the value. But for a proto definition like oneof method { ... }, that would mean doing my_message.method, which then attempts to call Kernel#method in the Ruby VM (e.g., to look up a method with the given name). That then fails, because method takes a required arg, but that's irrelevant: what it's not doing is looking up the oneof field named method on the message.

Normal, non-oneof fields work around this via the [] method on AbstractMessage, so that you can do my_message["method"]. That access pattern does not work when method is a oneof field instead of a normal field on the message.

Since protobuf allows oneof fields defined with arbitrary names (including names that collide with Ruby method names), there needs to be a way to access the field even in the presence of these collisions.

To my knowledge, there is no workaround for this in the mean time.

Update: #21736 (comment)

What version of protobuf and what language are you using?

Language: Ruby

❯ bundle info google-protobuf
  * google-protobuf (4.30.2)
        Summary: Protocol Buffers
        Homepage: https://developers.google.com/protocol-buffers
        Source Code: https://github.com/protocolbuffers/protobuf/tree/v4.30.2/ruby
        Path: /pay/home/jez/sandbox/vendor/bundle/ruby/3.3.0/gems/google-protobuf-4.30.2-x86_64-linux

❯ bin/protoc --version
libprotoc 31.0-rc2

What operating system (Linux, Windows, ...) and version?

Linux (platform agnostic)

What runtime / compiler are you using (e.g., python version or gcc version)

Ruby 3.3, but should be version agnostic

What did you do?

Gemfile

source 'https://rubygems.org'

gem 'google-protobuf'

Gemfile.lock

GEM
  remote: https://rubygems.org/
  specs:
    bigdecimal (3.1.9)
    google-protobuf (4.30.2)
      bigdecimal
      rake (>= 13)
    google-protobuf (4.30.2-aarch64-linux)
      bigdecimal
      rake (>= 13)
    google-protobuf (4.30.2-arm64-darwin)
      bigdecimal
      rake (>= 13)
    google-protobuf (4.30.2-x86-linux)
      bigdecimal
      rake (>= 13)
    google-protobuf (4.30.2-x86_64-darwin)
      bigdecimal
      rake (>= 13)
    google-protobuf (4.30.2-x86_64-linux)
      bigdecimal
      rake (>= 13)
    rake (13.2.1)

PLATFORMS
  aarch64-linux
  arm64-darwin
  ruby
  x86-linux
  x86_64-darwin
  x86_64-linux

DEPENDENCIES
  google-protobuf

BUNDLED WITH
   2.5.22

test_message.proto

message TestMessageEither {
  oneof either {
    string left = 1;
    int32 right = 2;
  }
}

message TestMessageMethod {
  oneof method {
    string left = 1;
    int32 right = 2;
  }
}

test_message_pb.rb

# frozen_string_literal: true
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: test_message.proto

require 'google/protobuf'


descriptor_data = "\n\x12test_message.proto\">\n\x11TestMessageEither\x12\x0e\n\x04left\x18\x01 \x01(\tH\x00\x12\x0f\n\x05right\x18\x02 \x01(\x05H\x00\x42\x08\n\x06\x65ither\">\n\x11TestMessageMethod\x12\x0e\n\x04left\x18\x01 \x01(\tH\x00\x12\x0f\n\x05right\x18\x02 \x01(\x05H\x00\x42\x08\n\x06method"

pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)

TestMessageEither = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("TestMessageEither").msgclass
TestMessageMethod = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("TestMessageMethod").msgclass

foo.rb

require 'google/protobuf'
require_relative './test_message_pb.rb'

def try(testcase)
  print(testcase)
  print(": ")
  begin
    yield
  rescue => e
    STDERR.puts("#{e.class}: #{e.message}")
    STDERR.puts(e.backtrace)
  end
  puts
end

puts "----- Expected behavior ------------------------------------------------"
msg = TestMessageEither.new(left: "hello")
try("msg") { p(msg) }
try("msg.left") { p(msg.left) }
try('msg["left"]') { p(msg["left"]) }
try("msg.either") { p(msg.either) }
try('msg["either"]') { p(msg["either"]) }

puts "----- Unexpected behavior ----------------------------------------------"
msg = TestMessageMethod.new(left: "hello")
try("msg") { p(msg) }
try("msg.left") { p(msg.left) }
try('msg["left"]') { p(msg["left"]) }
try("msg.method") { p(msg.method) }
try('msg["method"]') { p(msg["method"]) }

.ruby-version

3.3.5
bundle exec ruby foo.rb

What did you expect to see

No exception for the msg.method line, or at least something useful for the msg["method"] line.

What did you see instead?

❯ bundle exec ruby foo.rb
----- Expected behavior ------------------------------------------------
msg: <TestMessageEither: left: "hello">

msg.left: "hello"

msg["left"]: "hello"

msg.either: :left

msg["either"]: nil

----- Unexpected behavior ----------------------------------------------
msg: <TestMessageMethod: left: "hello">

msg.left: "hello"

msg["left"]: "hello"

msg.method: ArgumentError: wrong number of arguments (given 0, expected 1)
foo.rb:29:in `method'
foo.rb:29:in `block in <main>'
foo.rb:8:in `try'
foo.rb:29:in `<main>'

msg["method"]: nil

Anything else we should know about your project / environment

I have included a zipfile if you'd like to avoid copy/pasting a bunch of files:

test.zip

unzip test.zip
cd sandbox
bundle install
bundle exec ruby foo.rb

Metadata

Metadata

Assignees

Labels

bugrubyuntriagedauto added to all issues by default when created.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions