I have a problem with generics mismatching types. ...
# server
c
I have a problem with generics mismatching types. Ignore JPA annotations The following class hierarchy
Copy code
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
sealed class ComparableAuthority<A : ComparableAuthority<A>> : Comparable<A> {
    @Id
    @GeneratedValue
    open val id: Long? = null

    @get:Transient
    abstract val authority: String
}
Copy code
@Entity
class SimpleNamedAuthority(
    @Column(unique = true)
    val authorityName: String
) : ComparableAuthority<SimpleNamedAuthority>() {
    override fun compareTo(other: SimpleNamedAuthority): Int = authorityName.compareTo(other.authorityName)
    override val authority: String = authorityName
    override fun toString(): String = "SimpleNamedAuthority(authorityName='$authorityName')"
}
And other matching class hierarchy
Copy code
sealed interface ComparableGrantedAuthority<A : ComparableGrantedAuthority<A>> : GrantedAuthority, Comparable<A>
Copy code
data class SimpleNamedAuthority(val authorityName: String) : ComparableGrantedAuthority<SimpleNamedAuthority> {
    override fun getAuthority(): String = authorityName
    override fun compareTo(other: SimpleNamedAuthority): Int = authorityName.compareTo(other.authorityName)
}
Trying to create a converter between them
Copy code
object AuthorityConverters {

    fun <IN : ComparableAuthority<IN>, OUT : ComparableGrantedAuthority<OUT>> IN.toModel(): OUT =
        when {
            this is SimpleNamedAuthorityEntity -> toModel()
            else -> throw IllegalStateException("BOOM")
        }


    fun SimpleNamedAuthorityEntity.toModel(): SimpleNamedAuthorityModel = SimpleNamedAuthorityModel(authorityName)

}
Results in compile-time-error
Type mismatch: inferred type is SimpleNamedAuthority but OUT was expected
How can I best solve this issue?
s
Your converter function declares to return
OUT : ComparableGrantedAuthority<OUT>>
where OUT can be any
ComparableGrantedAuthority
, but the function's body returns only
SimpleNamedAuthorityModel
.
SimpleNamedAuthorityModel
may be the only subtype of sealed
ComparableGrantedAuthority
, but that's not how the sealed class works. From generics standpoint OUT can be any
ComparableGrantedAuthority
, so imagining there is type
X
implementing
ComparableGrantedAuthority
, it would be possible to invoke function with
SimpleNamedAuthorityEntity
as
IN
and
X
as
OUT
.
SimpleNamedAuthorityModel
is not a subtype of
X
therefore you have expected and actual type-mismatch. Are you planning to add more branches to When? If not, why not just return
SimpleNamedAuthorityModel
without using
OUT
parameter at all? If you are planning to add more branches and do not have any other compile time relation between
IN
and
OUT
, then I think you might have to omit
OUT
from function generics and make the return type
ComparableGrantedAuthority<*>
- This will allow you to return any
ComparableGrantedAuthority
. You could then use exhaustive
when
on the returned type to check which instance of the sealed class it is. And if you are planning to map objects to specific types, then maybe it would be simpler to have different functions for different return types?
c
The initial plan was to experiment with various ways of representing spring security's authorization
GrantedAuthority
and authorization in general. Currently, in all the examples that I have seen, authorization has been represented as a verb (or a role encompassing a number of verbs) that represents a specific action. All of those implementations where known at compile-time (hence the sealed interface definition) and there were to sides of implementation one for the persistence layer and one to be used by the application (spring security, aka model). I was planning for the converter function that when I invoke it, I would specify which type I am returning and It would retain the type at compile-time. I was planning on a different
GratnedAuthority
implementation where this is more data than just a String representing an action/verb. Regardless, I'll redo that project again at some point in the future.