protocolbuffers / protocolbuffers/protobuf
[Ruby] Cannot read message-typed custom options from a rebuilt DescriptorPool (FieldDescriptor#get raises TypeError)
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 72k
- Forks
- 16.3k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 140
Description
What
Reading a message-typed custom option via FieldDescriptor#get raises TypeError when the extension is resolved from a DescriptorPool that was built from serialized FileDescriptorProtos (i.e. not the process's generated pool). Scalar custom options read fine in the same setup, and message-typed options read fine from DescriptorPool.generated_pool.
This is exactly the situation a protoc plugin is in: it receives already-compiled descriptors in a CodeGeneratorRequest and must rebuild a pool from them to resolve custom-option extensions.
message_opt => raises TypeError: wrong argument type nil (expected Google::Protobuf::Descriptor)
Repro
repro.proto:
syntax = "proto3";
package repro;
import "google/protobuf/descriptor.proto";
message Meta { string owner = 1; }
extend google.protobuf.MessageOptions {
string scalar_opt = 50001;
Meta message_opt = 50002;
}
message Subject {
option (repro.scalar_opt) = "hello";
option (repro.message_opt) = { owner: "team" };
}
repro.rb:
require "google/protobuf"
require "google/protobuf/descriptor_pb"
# repro.fds stands in for a CodeGeneratorRequest's proto_file: repro.proto plus
# its dependency, google/protobuf/descriptor.proto.
# protoc --include_imports --descriptor_set_out=repro.fds repro.proto
fds = Google::Protobuf::FileDescriptorSet.decode(File.read("repro.fds"))
# Rebuild a pool from the received files so the option extensions resolve.
pool = Google::Protobuf::DescriptorPool.new
fds.file.each { |f| pool.add_serialized_file(f.to_proto) }
subject = fds.file.flat_map { |f| f.message_type.to_a }.find { |m| m.name == "Subject" }
options = pool.lookup("google.protobuf.MessageOptions").msgclass.decode(subject.options.to_proto)
scalar_ext = pool.lookup("repro.scalar_opt")
message_ext = pool.lookup("repro.message_opt")
puts "scalar_opt present? #{scalar_ext.has?(options)}" # true
puts "message_opt present? #{message_ext.has?(options)}" # true
puts "scalar_opt => #{scalar_ext.get(options).inspect}" # "hello"
puts "message_opt => #{message_ext.get(options).inspect}" # raises TypeError
Actual
scalar_opt present? true
message_opt present? true
scalar_opt => "hello"
message_opt => (raises) TypeError: wrong argument type nil (expected Google::Protobuf::Descriptor)
The option is present (has? is true) and round-trips through serialization, but get cannot materialize its value.
Expected
message_ext.get(options) returns the Meta message { owner: "team" }, the way scalar_ext.get(options) returns "hello".
Key diagnostic
The failure is specific to reading from a rebuilt pool. If the same extension is registered directly in DescriptorPool.generated_pool (e.g. via generated Ruby code, or by adding the file to the generated pool), get returns the message value correctly. It only fails when the extension is resolved from a separately-built DescriptorPool — which is the only option available to a protoc plugin, since the descriptors in a CodeGeneratorRequest are not in the generated pool.
Relationship to earlier issues
This is a gap left by two previously-closed issues:
- #1198 ("No way to use custom options in Ruby", closed as completed) added the ability to read custom options via
DescriptorPool.generated_pool.lookup(ext_name).get(options). - #16834 ("[Ruby] unable to read custom options from descriptor", closed as completed) documented that same
ext.get(options)pattern, and a follow-up there gave the guidance for plugins: build your ownDescriptorPoolfrom the request's files and read through it.
That guidance works for scalar and repeated-scalar options, so those issues correctly resolved the common case. It does not work for message-typed options: get raises TypeError for them specifically when read from a rebuilt pool, so a plugin still cannot read message-typed custom options (a common shape — e.g. protovalidate rules, google.api.http). The earlier issues did not cover this case.
Environment
- google-protobuf 4.35.1 (also reproduces on 4.28.0)
- libprotoc 33.0
- Ruby 3.4.1
- macOS (arm64-darwin)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with repro.rb and the rebuilt DescriptorPool path using add_serialized_file, then inspect DescriptorPool#lookup and the extension get call for message-typed options. Reproduce the TypeError with the provided repro.proto and verify that message_ext.get(options) returns the Meta message while scalar options continue to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- backend-api-design, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100