Skip to content

Commit

Permalink
🐛 Polymorphic/value classes: actually inline sub-types that are inline
Browse files Browse the repository at this point in the history
  • Loading branch information
berlix committed Sep 27, 2023
1 parent 648865d commit 5a7829b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ fun TypeContext.field(
name: Identifier,
number: FieldNumber,
annotations: List<Annotation>,
descriptor: SerialDescriptor
descriptor: SerialDescriptor,
forceEncodeZeroValue: Boolean = false
): Field {
val typeDescriptor = descriptor.actual
val nullable = descriptor.isNullable || typeDescriptor.isNullable
return when (val kind = typeDescriptor.kind) {
is PrimitiveKind ->
field(name, scalar(annotations, kind), number, nullable)
field(name, scalar(annotations, kind), number, nullable, forceEncodeZeroValue)
StructureKind.CLASS, StructureKind.OBJECT, SerialKind.ENUM, is PolymorphicKind ->
field(name, root.namedType(typeDescriptor), number, nullable)
field(name, root.namedType(typeDescriptor), number, nullable, forceEncodeZeroValue)
SerialKind.CONTEXTUAL ->
field(
name,
Expand All @@ -53,7 +54,7 @@ fun TypeContext.field(
)
StructureKind.LIST ->
if (typeDescriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE)
field(name, FieldEncoding.Bytes, number, nullable)
field(name, FieldEncoding.Bytes, number, nullable, forceEncodeZeroValue)
else if (nullable)
optionalListField(typeDescriptor, name, number, annotations)
else
Expand All @@ -70,12 +71,13 @@ private fun TypeContext.field(
name: Identifier,
type: FieldEncoding,
number: FieldNumber,
optional: Boolean
optional: Boolean,
encodeZeroValue: Boolean
): Field = Field(
name,
type,
number,
if (optional) FieldRule.Optional else FieldRule.Singular,
fieldEncoder(type, number, optional),
fieldEncoder(type, number, encodeZeroValue || optional),
fieldDecoder(type)
)
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,24 @@ private fun TypeContext.messageOfPolymorphicClass(
}
}

private fun TypeContext.fieldForSubType(subDescriptor: SerialDescriptor, numberIterator: FieldNumberIterator): Field {
val subTypeRef = root.namedType(subDescriptor)
val number = FieldNumber(
subDescriptor.annotations.filterIsInstance<ProtoNumber>()
.firstOrNull()?.number
?: numberIterator.next()
)
return Field(
Identifier(
subTypeRef.name.value.replaceFirstChar { it.lowercaseChar() }
private fun TypeContext.fieldForSubType(subDescriptor: SerialDescriptor, numberIterator: FieldNumberIterator): Field =
field(
Identifier(simpleTypeName(subDescriptor).replaceFirstChar { it.lowercaseChar() }),
FieldNumber(
subDescriptor.annotations.filterIsInstance<ProtoNumber>()
.firstOrNull()?.number
?: numberIterator.next()
),
subTypeRef,
number,
encoder = fieldEncoder(subTypeRef, number, true),
decoder = fieldDecoder(subTypeRef)
emptyList(),
subDescriptor,
forceEncodeZeroValue = true
)
}

private fun fieldNumberIteratorFromSubTypes(descriptors: Iterable<SerialDescriptor>): FieldNumberIterator {
return FieldNumberIterator(
private fun fieldNumberIteratorFromSubTypes(descriptors: Iterable<SerialDescriptor>): FieldNumberIterator =
FieldNumberIterator(
descriptors.mapNotNull {
it.annotations.filterIsInstance<ProtoNumber>().firstOrNull()?.number
}.requireNoDuplicates { duplicatedNumber ->
"Duplicate field number $duplicatedNumber in sub-types: ${descriptors.map { it.serialName }}"
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,52 @@ class InlineIntegrationTest : StringSpec({
)
}

"creates class hierarchy with value sub-class" {
givenSchema(
SuperClass.serializer().descriptor,
encodeZeroValues = true
)
verifySchema(
"""
message SuperClass {
oneof subtypes {
int32 subValueClassWithInt = 1;
string subValueClassWithString = 2;
}
}
"""
)
verifyConversion<SuperClass>(
SubValueClassWithInt(0),
"1: 0"
)
verifyConversion<SuperClass>(
SubValueClassWithInt(1),
"1: 1"
)
verifyConversion<SuperClass>(
SubValueClassWithString(""),
"2: {}"
)
verifyConversion<SuperClass>(
SubValueClassWithString("foo"),
"""2: { "foo" }"""
)

givenSchema(
SuperClass.serializer().descriptor,
encodeZeroValues = false
)
verifyConversion<SuperClass>(
SubValueClassWithInt(0),
"1: 0"
)
verifyConversion<SuperClass>(
SubValueClassWithString(""),
"2: {}"
)
}

"does not create synthetic top-level message from value class with custom serializer" {
givenSchema(StringIntValueClass.serializer().descriptor)
verifySchemaGenerationFails()
Expand Down Expand Up @@ -454,4 +500,17 @@ class InlineIntegrationTest : StringSpec({

override fun serialize(encoder: Encoder, value: StringIntValueClass) = encoder.encodeString("${value.value}")
}

@Serializable
sealed interface SuperClass

@JvmInline
@Serializable
@ProtoNumber(1)
value class SubValueClassWithInt(val int: Int) : SuperClass

@JvmInline
@Serializable
@ProtoNumber(2)
value class SubValueClassWithString(val string: String) : SuperClass
}

0 comments on commit 5a7829b

Please sign in to comment.