diff --git a/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala b/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala index 11ffa4744..af6a370dc 100644 --- a/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala +++ b/compiler-plugin/src/main/scala/scalapb/compiler/DescriptorImplicits.scala @@ -384,10 +384,13 @@ class DescriptorImplicits private[compiler] ( case FieldDescriptor.JavaType.BYTE_STRING => "_root_.com.google.protobuf.ByteString" case FieldDescriptor.JavaType.STRING => "_root_.scala.Predef.String" case FieldDescriptor.JavaType.MESSAGE => - fd.getMessageType.scalaType - .fullNameWithMaybeRoot(fd.getContainingType.fields.map(_.scalaName)) + val contextNames = fd.getContainingType.fields.map(_.scalaName) ++ + fd.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) + fd.getMessageType.scalaType.fullNameWithMaybeRoot(contextNames) case FieldDescriptor.JavaType.ENUM => - fd.getEnumType.scalaType.fullNameWithMaybeRoot(fd.getContainingType.fields.map(_.scalaName)) + val contextNames = fd.getContainingType.fields.map(_.scalaName) ++ + fd.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) + fd.getEnumType.scalaType.fullNameWithMaybeRoot(contextNames) } def singleScalaTypeName = customSingleScalaTypeName.getOrElse(baseSingleScalaTypeName) diff --git a/compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala b/compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala index 5cc59b8be..1851cd731 100644 --- a/compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala +++ b/compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala @@ -188,11 +188,15 @@ class ProtobufGenerator( .mkString("_root_.com.google.protobuf.ByteString.copyFrom(Array[Byte](", ", ", "))") case FieldDescriptor.JavaType.STRING => escapeScalaString(defaultValue.asInstanceOf[String]) case FieldDescriptor.JavaType.MESSAGE => + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) field.getMessageType.scalaType - .fullNameWithMaybeRoot(field.getContainingType) + ".defaultInstance" + .fullNameWithMaybeRoot(contextNames) + ".defaultInstance" case FieldDescriptor.JavaType.ENUM => + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) field.getEnumType.scalaType - .fullNameWithMaybeRoot(field.getContainingType) + "." + defaultValue + .fullNameWithMaybeRoot(contextNames) + "." + defaultValue .asInstanceOf[EnumValueDescriptor] .scalaName .asSymbol @@ -217,13 +221,22 @@ class ProtobufGenerator( case FieldDescriptor.JavaType.BYTE_STRING => Identity case FieldDescriptor.JavaType.STRING => Identity case FieldDescriptor.JavaType.MESSAGE => - FunctionApplication(field.getMessageType.scalaType.fullName + ".fromJavaProto") + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) + FunctionApplication( + field.getMessageType.scalaType.fullNameWithMaybeRoot(contextNames) + ".fromJavaProto" + ) case FieldDescriptor.JavaType.ENUM => + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) if (!field.legacyEnumFieldTreatedAsClosed()) MethodApplication("intValue") andThen FunctionApplication( - field.getEnumType.scalaType.fullName + ".fromValue" + field.getEnumType.scalaType.fullNameWithMaybeRoot(contextNames) + ".fromValue" + ) + else + FunctionApplication( + field.getEnumType.scalaType.fullNameWithMaybeRoot(contextNames) + ".fromJavaValue" ) - else FunctionApplication(field.getEnumType.scalaType.fullName + ".fromJavaValue") } baseValueConversion andThen toCustomTypeExpr(field) } @@ -289,12 +302,20 @@ class ProtobufGenerator( case FieldDescriptor.JavaType.BYTE_STRING => Identity case FieldDescriptor.JavaType.STRING => Identity case FieldDescriptor.JavaType.MESSAGE => - FunctionApplication(field.getMessageType.scalaType.fullName + ".toJavaProto") + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) + FunctionApplication( + field.getMessageType.scalaType.fullNameWithMaybeRoot(contextNames) + ".toJavaProto" + ) case FieldDescriptor.JavaType.ENUM => + val contextNames = field.getContainingType.fields.map(_.scalaName) ++ + field.getContainingType.getRealOneofs.asScala.map(_.scalaName.nameSymbol) if (!field.legacyEnumFieldTreatedAsClosed()) (MethodApplication("value") andThen maybeBox("_root_.scala.Int.box")) else - FunctionApplication(field.getEnumType.scalaType.fullName + ".toJavaValue") + FunctionApplication( + field.getEnumType.scalaType.fullNameWithMaybeRoot(contextNames) + ".toJavaValue" + ) } } diff --git a/e2e/src/main/protobuf/location/v1/location.proto b/e2e/src/main/protobuf/location/v1/location.proto new file mode 100644 index 000000000..970a33cd5 --- /dev/null +++ b/e2e/src/main/protobuf/location/v1/location.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package location.v1; + +message Coordinate { + double latitude = 1; + double longitude = 2; +} \ No newline at end of file diff --git a/e2e/src/main/protobuf/oneof_import_conflict.proto b/e2e/src/main/protobuf/oneof_import_conflict.proto new file mode 100644 index 000000000..dd96521cc --- /dev/null +++ b/e2e/src/main/protobuf/oneof_import_conflict.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package com.thesamet.proto.e2e; + +import "location/v1/location.proto"; + +// Test case for oneof field name conflicting with imported package name. +// This ensures ScalaPB correctly adds _root_ prefixes to avoid naming conflicts. +message OneofImportConflictTest { + oneof location { // This field name conflicts with the imported package "location" + location.v1.Coordinate location_coordinate = 2; + } +} + +// Additional test with multiple conflicts +message MultipleConflictTest { + oneof location { + location.v1.Coordinate coord = 1; + } + + // Regular field with same type should also get _root_ prefix due to oneof conflict + location.v1.Coordinate regular_coord = 2; +} diff --git a/e2e/src/test/scala/OneofImportConflictSpec.scala b/e2e/src/test/scala/OneofImportConflictSpec.scala new file mode 100644 index 000000000..ac7932c35 --- /dev/null +++ b/e2e/src/test/scala/OneofImportConflictSpec.scala @@ -0,0 +1,45 @@ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import com.thesamet.proto.e2e.oneof_import_conflict.{OneofImportConflict, MultipleConflictTest} + +class OneofImportConflictSpec extends AnyFlatSpec with Matchers { + + "oneof field conflicting with imported package name" should "generate correct code with _root_ prefixes" in { + // Test basic oneof conflict - the fact this compiles means _root_ prefixes work + val coord = _root_.location.v1.location.Coordinate(latitude = 1.0, longitude = 2.0) + + val message = OneofImportConflict( + location = OneofImportConflict.Location.LocationCoordinate(coord) + ) + + // Test functionality works correctly + message.getLocationCoordinate shouldBe coord + message.location.isLocationCoordinate shouldBe true + message.location.locationCoordinate shouldBe Some(coord) + + // Test serialization round-trip + val bytes = message.toByteArray + val parsed = OneofImportConflict.parseFrom(bytes) + parsed shouldBe message + } + + "multiple field types with import conflicts" should "all use _root_ prefixes consistently" in { + val coord1 = _root_.location.v1.location.Coordinate(latitude = 3.0, longitude = 4.0) + val coord2 = _root_.location.v1.location.Coordinate(latitude = 5.0, longitude = 6.0) + + val message = MultipleConflictTest( + location = MultipleConflictTest.Location.Coord(coord1), + regularCoord = Some(coord2) + ) + + // Test both oneof and regular fields work correctly + message.location.isCoord shouldBe true + message.location.coord shouldBe Some(coord1) + message.regularCoord shouldBe Some(coord2) + + // Test serialization round-trip + val bytes = message.toByteArray + val parsed = MultipleConflictTest.parseFrom(bytes) + parsed shouldBe message + } +}