Skip to content

Make generic type of Assertion.Builder covariant #303

@zarebski-m

Description

@zarebski-m

Currently T in Assertion.Builder is invariant. Is there a specific reason for that?

I have stumbled upon a problem with a custom assertion I was writing. I have a structure which is essentially a tree of maps:

data class Node(id: String, children: MutableMap<String, Node>)

fun buildTree(): Map<String, Node> = TODO("irrelevant")

I wanted to create two assertions:

fun Assertion.Builder<Map<String, Node>>.node(
    id: String,
    childrenAssertion: Assertion.Builder<Map<String, Node>>.() -> Unit
) {
    withValue(id) {
        get { this.id }.isEqualTo(id)
        get { children }.childrenAssertion()
    }
}

fun Assertion.Builder<Map<String, Node>>.leaf(id: String) {
    withValue(id) {
        get { this.id }.isEqualTo(id)
        get { children }.isEmpty()
    }
}

// usage
expectThat(buildTree()) {
    node("node1") {
        node("node2") {
            leaf("leaf1")
        }
        leaf("leaf2")
        leaf("leaf3")
    }
}

But it didn't work because childrenAssertion binds T to Map<*, *> where get { children } returns builder with T bound to MutableMap<*, *>. The solution was to use call-site projection:

fun Assertion.Builder<out Map<String, Node>>.node(
    id: String,
    childrenAssertion: Assertion.Builder<out Map<String, Node>>.() -> Unit
)

But then I wondered why Assertion.Builder is not covariant in the first place? What would be the drawbacks of defining it as Assertion.Builder<out T>?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions