- Updates DslList and ExtensionList to take a lazy supplier lambda () -> List<E>, retaining secondary constructors for binary backward compatibility with existing compiled proto code.

PiperOrigin-RevId: 966853174
This commit is contained in:
Protobuf Team Bot 2026-08-18 15:58:19 -07:00 committed by Copybara-Service
parent 6e4f4ca54a
commit 37f78578e7
4 changed files with 188 additions and 5 deletions

View file

@ -30,6 +30,10 @@
package com.google.protobuf.kotlin
private val UNINITIALIZED_SUPPLIER: () -> Nothing = {
throw IllegalStateException("Should not be called")
}
/**
* A simple wrapper around a [List] with an extra generic parameter that can be used to disambiguate
* extension methods.
@ -41,7 +45,42 @@ package com.google.protobuf.kotlin
@Suppress("unused") // the unused type parameter
class DslList<E, P : DslProxy>
@OnlyForUseByGeneratedProtoCode
constructor(private val delegate: List<E>) : List<E> by delegate {
constructor(private val delegateSupplier: () -> List<E>) : List<E> {
@Suppress("UNCHECKED_CAST")
@OnlyForUseByGeneratedProtoCode
constructor(delegate: List<E>) : this(UNINITIALIZED_SUPPLIER as () -> List<E>) {
memoizedDelegate = delegate
}
private var memoizedDelegate: List<E>? = null
private val delegate: List<E>
get() {
var result = memoizedDelegate
if (result == null) {
result = delegateSupplier()
memoizedDelegate = result
}
return result
}
override val size: Int
get() = delegate.size
override fun isEmpty(): Boolean = delegate.isEmpty()
override fun contains(element: E): Boolean = delegate.contains(element)
override fun containsAll(elements: Collection<E>): Boolean = delegate.containsAll(elements)
override fun get(index: Int): E = delegate[index]
override fun indexOf(element: E): Int = delegate.indexOf(element)
override fun lastIndexOf(element: E): Int = delegate.lastIndexOf(element)
override fun subList(fromIndex: Int, toIndex: Int): List<E> = delegate.subList(fromIndex, toIndex)
override fun iterator(): Iterator<E> = UnmodifiableIterator(delegate.iterator())
override fun listIterator(): ListIterator<E> = UnmodifiableListIterator(delegate.listIterator())

View file

@ -33,6 +33,10 @@ package com.google.protobuf.kotlin
import com.google.protobuf.ExtensionLite
import com.google.protobuf.MessageLite
private val UNINITIALIZED_SUPPLIER: () -> Nothing = {
throw IllegalStateException("Should not be called")
}
/**
* Implementation for ExtensionList and ExtensionListLite. Like [DslList], represents an
* unmodifiable view of a repeated proto field -- in this case, an extension field -- but supports
@ -40,8 +44,48 @@ import com.google.protobuf.MessageLite
*/
class ExtensionList<E, M : MessageLite>
@OnlyForUseByGeneratedProtoCode
constructor(val extension: ExtensionLite<M, List<E>>, private val delegate: List<E>) :
List<E> by delegate {
constructor(
val extension: ExtensionLite<M, List<E>>,
private val delegateSupplier: () -> List<E>,
) : List<E> {
@Suppress("UNCHECKED_CAST")
@OnlyForUseByGeneratedProtoCode
constructor(
extension: ExtensionLite<M, List<E>>,
delegate: List<E>,
) : this(extension, UNINITIALIZED_SUPPLIER as () -> List<E>) {
memoizedDelegate = delegate
}
private var memoizedDelegate: List<E>? = null
private val delegate: List<E>
get() {
var result = memoizedDelegate
if (result == null) {
result = delegateSupplier()
memoizedDelegate = result
}
return result
}
override val size: Int
get() = delegate.size
override fun isEmpty(): Boolean = delegate.isEmpty()
override fun contains(element: E): Boolean = delegate.contains(element)
override fun containsAll(elements: Collection<E>): Boolean = delegate.containsAll(elements)
override fun get(index: Int): E = delegate[index]
override fun indexOf(element: E): Int = delegate.indexOf(element)
override fun lastIndexOf(element: E): Int = delegate.lastIndexOf(element)
override fun subList(fromIndex: Int, toIndex: Int): List<E> = delegate.subList(fromIndex, toIndex)
override fun iterator(): Iterator<E> = UnmodifiableIterator(delegate.iterator())
override fun listIterator(): ListIterator<E> = UnmodifiableListIterator(delegate.listIterator())

View file

@ -85,13 +85,59 @@ class DslListTest {
@Test
fun equality() {
EqualsTester()
.addEqualityGroup(DslList<Int, DummyProxy>(listOf(1, 2)), listOf(1, 2))
.addEqualityGroup(DslList<Int, DummyProxy>(listOf(2, 2)), listOf(2, 2))
.addEqualityGroup(
DslList<Int, DummyProxy>(listOf(1, 2)),
DslList<Int, DummyProxy> { listOf(1, 2) },
listOf(1, 2),
)
.addEqualityGroup(
DslList<Int, DummyProxy>(listOf(2, 2)),
DslList<Int, DummyProxy> { listOf(2, 2) },
listOf(2, 2),
)
.addEqualityGroup(
DslList<Int, DummyProxy>(emptyList()),
DslList<String, DummyProxy>(emptyList()),
DslList<Int, DummyProxy> { emptyList() },
DslList<String, DummyProxy> { emptyList() },
emptyList<Int>(),
)
.testEquals()
}
@Test
fun supplierNotInvokedOnConstruction() {
var supplierCalled = false
val dslList = DslList<Int, DummyProxy> {
supplierCalled = true
listOf(1, 2, 3)
}
assertThat(supplierCalled).isFalse()
assertThat(dslList).containsExactly(1, 2, 3).inOrder()
assertThat(supplierCalled).isTrue()
}
@Test
fun supplierEvaluatedOnlyOnFirstReadOperation() {
var callCount = 0
val dslList = DslList<Int, DummyProxy> {
callCount++
listOf(1, 2, 1)
}
assertThat(callCount).isEqualTo(0)
assertThat(dslList.size).isEqualTo(3)
assertThat(callCount).isEqualTo(1)
assertThat(dslList[0]).isEqualTo(1)
assertThat(callCount).isEqualTo(1)
assertThat(dslList.isEmpty()).isFalse()
assertThat(dslList.contains(2)).isTrue()
assertThat(dslList.containsAll(listOf(1, 2))).isTrue()
assertThat(dslList.indexOf(1)).isEqualTo(0)
assertThat(dslList.lastIndexOf(1)).isEqualTo(2)
assertThat(dslList.subList(0, 2)).containsExactly(1, 2).inOrder()
assertThat(dslList.iterator().hasNext()).isTrue()
assertThat(dslList.listIterator().hasNext()).isTrue()
assertThat(dslList.listIterator(1).next()).isEqualTo(2)
assertThat(callCount).isEqualTo(1)
}
}

View file

@ -118,17 +118,71 @@ class ExtensionListTest {
EqualsTester()
.addEqualityGroup(
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, listOf(1, 2)),
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) { listOf(1, 2) },
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.differentExtension, listOf(1, 2)),
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.differentExtension) { listOf(1, 2) },
listOf(1, 2),
)
.addEqualityGroup(
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, listOf(2, 2)),
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) { listOf(2, 2) },
listOf(2, 2),
)
.addEqualityGroup(
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, emptyList()),
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) { emptyList() },
emptyList<Int>(),
)
.testEquals()
}
@Test
fun supplierNotInvokedOnConstruction() {
var supplierCalled = false
val extensionList =
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) {
supplierCalled = true
listOf(1, 2, 3)
}
assertThat(supplierCalled).isFalse()
assertThat(extensionList).containsExactly(1, 2, 3).inOrder()
assertThat(supplierCalled).isTrue()
}
@Test
fun extensionPropertyAccessDoesNotInvokeSupplier() {
var supplierCalled = false
val extensionList =
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) {
supplierCalled = true
listOf(1, 2, 3)
}
assertThat(extensionList.extension).isEqualTo(TestProto.repeatedExtension)
assertThat(supplierCalled).isFalse()
}
@Test
fun supplierEvaluatedOnlyOnFirstReadOperation() {
var callCount = 0
val extensionList =
ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension) {
callCount++
listOf(1, 2, 1)
}
assertThat(callCount).isEqualTo(0)
assertThat(extensionList.size).isEqualTo(3)
assertThat(callCount).isEqualTo(1)
assertThat(extensionList[0]).isEqualTo(1)
assertThat(callCount).isEqualTo(1)
assertThat(extensionList.isEmpty()).isFalse()
assertThat(extensionList.contains(2)).isTrue()
assertThat(extensionList.containsAll(listOf(1, 2))).isTrue()
assertThat(extensionList.indexOf(1)).isEqualTo(0)
assertThat(extensionList.lastIndexOf(1)).isEqualTo(2)
assertThat(extensionList.subList(0, 2)).containsExactly(1, 2).inOrder()
assertThat(extensionList.iterator().hasNext()).isTrue()
assertThat(extensionList.listIterator().hasNext()).isTrue()
assertThat(extensionList.listIterator(1).next()).isEqualTo(2)
assertThat(callCount).isEqualTo(1)
}
}