diff --git a/ruby/compatibility_tests/v3.0.0/tests/basic.rb b/ruby/compatibility_tests/v3.0.0/tests/basic.rb index 7228144cb165..d45c19678f15 100755 --- a/ruby/compatibility_tests/v3.0.0/tests/basic.rb +++ b/ruby/compatibility_tests/v3.0.0/tests/basic.rb @@ -667,8 +667,8 @@ def test_map_msg_enum_valuetypes assert m["z"] == :C m["z"] = 2 assert m["z"] == :B - m["z"] = 4 - assert m["z"] == 4 + m["z"] = 5 + assert m["z"] == 5 assert_raise RangeError do m["z"] = :Z end diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index 6b8bbaa3c5e5..31d7dbbb4470 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -1290,15 +1290,20 @@ VALUE build_module_from_enumdesc(VALUE _enumdesc) { int n = upb_EnumDef_ValueCount(e); for (int i = 0; i < n; i++) { const upb_EnumValueDef* ev = upb_EnumDef_Value(e, i); - const char* name = upb_EnumValueDef_Name(ev); + char* name = strdup(upb_EnumValueDef_Name(ev)); int32_t value = upb_EnumValueDef_Number(ev); if (name[0] < 'A' || name[0] > 'Z') { - rb_warn( + if (name[0] >= 'a' && name[0] <= 'z') { + name[0] -= 32; // auto capitalize + } else { + rb_warn( "Enum value '%s' does not start with an uppercase letter " "as is required for Ruby constants.", name); + } } rb_define_const(mod, name, INT2NUM(value)); + free(name); } rb_define_singleton_method(mod, "lookup", enum_lookup, 1); diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java index 65328676e11e..0eb7c939cb00 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java @@ -162,9 +162,10 @@ private RubyModule buildModuleFromDescriptor(ThreadContext context) { boolean defaultValueRequiredButNotFound = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; for (EnumValueDescriptor value : descriptor.getValues()) { - String name = value.getName(); - // Make sure its a valid constant name before trying to create it - if (Character.isUpperCase(name.codePointAt(0))) { + String name = fixEnumConstantName(value.getName()); + // Make sure it's a valid constant name before trying to create it + int ch = name.codePointAt(0); + if (Character.isUpperCase(ch)) { enumModule.defineConstant(name, runtime.newFixnum(value.getNumber())); } else { runtime @@ -189,6 +190,22 @@ private RubyModule buildModuleFromDescriptor(ThreadContext context) { return enumModule; } + private static String fixEnumConstantName(String name) { + if (name != null && name.length() > 0) { + int ch = name.codePointAt(0); + if (ch >= 'a' && ch <= 'z') { + // Protobuf enums can start with lowercase letters, while Ruby's constant should + // always start with uppercase letters. We tolerate this case by capitalizing + // the first character if possible. + return new StringBuilder() + .appendCodePoint(Character.toUpperCase(ch)) + .append(name.substring(1)) + .toString(); + } + } + return name; + } + private EnumDescriptor descriptor; private EnumDescriptorProto.Builder builder; private IRubyObject name; diff --git a/ruby/tests/basic_test.proto b/ruby/tests/basic_test.proto index fb70f479db5b..d480d48e548b 100644 --- a/ruby/tests/basic_test.proto +++ b/ruby/tests/basic_test.proto @@ -73,6 +73,7 @@ enum TestEnum { A = 1; B = 2; C = 3; + v0 = 4; } message TestEmbeddedMessageParent { diff --git a/ruby/tests/basic_test_proto2.proto b/ruby/tests/basic_test_proto2.proto index 0c1a2b98363f..ac705ed6305d 100644 --- a/ruby/tests/basic_test_proto2.proto +++ b/ruby/tests/basic_test_proto2.proto @@ -69,6 +69,7 @@ enum TestEnum { A = 1; B = 2; C = 3; + v0 = 4; } enum TestNonZeroEnum { diff --git a/ruby/tests/common_tests.rb b/ruby/tests/common_tests.rb index 5918c8a8b136..928842553f3b 100644 --- a/ruby/tests/common_tests.rb +++ b/ruby/tests/common_tests.rb @@ -331,14 +331,16 @@ def test_rptfield_enum l.push :A l.push :B l.push :C - assert l.count == 3 + l.push :v0 + assert l.count == 4 assert_raise RangeError do l.push :D end assert l[0] == :A + assert l[3] == :v0 - l.push 4 - assert l[3] == 4 + l.push 5 + assert l[4] == 5 end def test_rptfield_initialize @@ -542,8 +544,8 @@ def test_map_msg_enum_valuetypes assert m["z"] == :C m["z"] = 2 assert m["z"] == :B - m["z"] = 4 - assert m["z"] == 4 + m["z"] = 5 + assert m["z"] == 5 assert_raise RangeError do m["z"] = :Z end @@ -712,14 +714,17 @@ def test_enum_lookup assert proto_module::TestEnum::A == 1 assert proto_module::TestEnum::B == 2 assert proto_module::TestEnum::C == 3 + assert proto_module::TestEnum::V0 == 4 assert proto_module::TestEnum::lookup(1) == :A assert proto_module::TestEnum::lookup(2) == :B assert proto_module::TestEnum::lookup(3) == :C + assert proto_module::TestEnum::lookup(4) == :v0 assert proto_module::TestEnum::resolve(:A) == 1 assert proto_module::TestEnum::resolve(:B) == 2 assert proto_module::TestEnum::resolve(:C) == 3 + assert proto_module::TestEnum::resolve(:v0) == 4 end def test_enum_const_get_helpers @@ -788,7 +793,7 @@ def test_enum_getter_only_enums assert_raise(NoMethodError) { m.a } assert_raise(NoMethodError) { m.a_const_const } end - + def test_repeated_push m = proto_module::TestMessage.new @@ -1762,7 +1767,7 @@ def test_freeze assert_raise(FrozenErrorType) { m.repeated_msg = proto_module::TestMessage2.new } assert_raise(FrozenErrorType) { m.repeated_enum = :A } end - + def test_eq m1 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2']) m2 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2']) diff --git a/ruby/tests/generated_code.proto b/ruby/tests/generated_code.proto index bfdfa5aa7809..5f017ba7ca71 100644 --- a/ruby/tests/generated_code.proto +++ b/ruby/tests/generated_code.proto @@ -67,6 +67,8 @@ enum TestEnum { A = 1; B = 2; C = 3; + + v0 = 4; } message testLowercaseNested { diff --git a/ruby/tests/generated_code_proto2.proto b/ruby/tests/generated_code_proto2.proto index 1e957219fac6..1a50b84be560 100644 --- a/ruby/tests/generated_code_proto2.proto +++ b/ruby/tests/generated_code_proto2.proto @@ -68,6 +68,8 @@ enum TestEnum { A = 1; B = 2; C = 3; + + v0 = 4; } message TestUnknown { diff --git a/ruby/tests/repeated_field_test.rb b/ruby/tests/repeated_field_test.rb index 881810cdc5e1..de968699605c 100755 --- a/ruby/tests/repeated_field_test.rb +++ b/ruby/tests/repeated_field_test.rb @@ -697,6 +697,7 @@ def fill_test_msg(test_msg) value :A, 1 value :B, 2 value :C, 3 + value :v0, 4 end end