diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbde172..1f8bbfaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ test_message_option: google.protobuf.descriptor.FieldDescriptor = ... test_message_option: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.MessageOptions, typing.Text] = ... ``` - Now requires [types-protobuf](https://pypi.org/project/types-protobuf/) 3.17.1 +- Fix [#238](https://github.com/dropbox/mypy-protobuf/issues/238) - handling enum variants that name conflict with EnumTypeWrapper methods ## 2.6 diff --git a/mypy_protobuf/main.py b/mypy_protobuf/main.py index 0f71054d..9e506996 100644 --- a/mypy_protobuf/main.py +++ b/mypy_protobuf/main.py @@ -76,6 +76,14 @@ "yield", } +PROTO_ENUM_RESERVED = { + "Name", + "Value", + "keys", + "values", + "items", +} + def _mangle_global_identifier(name: str) -> str: """ @@ -214,8 +222,10 @@ def _write_line(self, line: str, *args: Any) -> None: else: self.lines.append(self.indent + line.format(*args)) - def write_enum_values(self, enum: d.EnumDescriptorProto, value_type: str) -> None: - for val in enum.value: + def write_enum_values( + self, values: Sequence[d.EnumValueDescriptorProto], value_type: str + ) -> None: + for val in values: if val.name in PYTHON_RESERVED: continue @@ -251,7 +261,7 @@ def write_enums( l("{} = {}", _mangle_global_identifier(enum.name), enum.name) l("") - self.write_enum_values(enum, prefix + enum.name + ".V") + self.write_enum_values(enum.value, prefix + enum.name + ".V") l("") # do a type-ignore to avoid the circular dependency. It's ugly. @@ -271,7 +281,10 @@ def write_enums( "DESCRIPTOR: {} = ...", self._import("google.protobuf.descriptor", "EnumDescriptor"), ) - self.write_enum_values(enum, prefix + enum.name + ".V") + self.write_enum_values( + [v for v in enum.value if v.name not in PROTO_ENUM_RESERVED], + prefix + enum.name + ".V", + ) l("") def write_messages( diff --git a/proto/testproto/test.proto b/proto/testproto/test.proto index d8233314..651cb7e7 100644 --- a/proto/testproto/test.proto +++ b/proto/testproto/test.proto @@ -13,6 +13,18 @@ enum OuterEnum { BAR = 2; } +enum NamingConflicts { + // Naming conflicts! + Name = 1; + Value = 2; + keys = 3; + values = 4; + items = 5; + // See https://github.com/protocolbuffers/protobuf/issues/8803 + // proto itself generates broken code when DESCRIPTOR is there + // DESCRIPTOR = 8; +} + message Simple1 { enum InnerEnum { INNER1 = 1; diff --git a/test/generated/testproto/test_pb2.pyi.expected b/test/generated/testproto/test_pb2.pyi.expected index e2dfcdf1..679b8c1f 100644 --- a/test/generated/testproto/test_pb2.pyi.expected +++ b/test/generated/testproto/test_pb2.pyi.expected @@ -34,6 +34,20 @@ class _OuterEnum(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Out FOO = OuterEnum.V(1) BAR = OuterEnum.V(2) +class NamingConflicts(metaclass=_NamingConflicts): + V = typing.NewType('V', builtins.int) + +global___NamingConflicts = NamingConflicts + +Name = NamingConflicts.V(1) +Value = NamingConflicts.V(2) +keys = NamingConflicts.V(3) +values = NamingConflicts.V(4) +items = NamingConflicts.V(5) + +class _NamingConflicts(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[NamingConflicts.V], builtins.type): # type: ignore + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor = ... + class Simple1(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor = ... class InnerEnum(metaclass=_InnerEnum): diff --git a/test/test_generated_mypy.py b/test/test_generated_mypy.py index 9011be62..51e66541 100644 --- a/test/test_generated_mypy.py +++ b/test/test_generated_mypy.py @@ -31,6 +31,8 @@ Extensions1, Extensions2, FOO, + Name as NamingConflicts_Name, + NamingConflicts, OuterEnum, Simple1, Simple2, @@ -202,6 +204,13 @@ def test_enum(): assert OuterEnum.Value(u"BAR") == OuterEnum.Value(b"BAR") +def test_enum_naming_conflicts(): + # type: () -> None + assert NamingConflicts.Name(NamingConflicts_Name) == "Name" + assert NamingConflicts.Value("Name") == 1 + assert NamingConflicts_Name == 1 + + def test_has_field_proto2(): # type: () -> None """For HasField which is typed with Literal"""