Self type

You can implement self type through generics.

self type with abstract classes

open class Query1{
    var prop0: Int = 0
        protected set

    abstract class QueryBuilderA<SELF>
            where SELF : QueryBuilderA<SELF> {

        protected abstract var instance: Query1
        abstract val builder: SELF

        fun setProp0(v: Int): SELF = 
            builder.apply { instance.prop0 = v }

        abstract fun build(): Query1
    }
}

class QueryExt : Query1() {
    var prop1: Int = 0
        protected set

    // if you don't like where use:
    // abstract class QueryBuilderA2<SELF>:  QueryBuilderA<QueryBuilderA2<SELF>>()
    abstract class QueryBuilderA2<SELF> : QueryBuilderA<SELF>()
            where SELF : QueryBuilderA2<SELF> {

        override var instance: Query1 = QueryExt()

        fun setProp1(v: Int): SELF = builder.apply {
            instance.apply { this as QueryExt; prop1 = v }
        }
    }

    // concrete builder
    class Builder : QueryBuilderA2<Builder>() {
        override val builder: Builder = this
        override fun build(): QueryExt = instance as QueryExt
    }
}

// using
val q = QueryExt.Builder()
    .setProp0(1)
    .setProp1(2)
    .build()

self type with interfaces

// interface with self type
interface QueryBuilderI<SELF>
        where SELF : QueryBuilderI<SELF>{
        
    // or you can use: fun getSelf(): SELF    
    val builder : SELF  
    
    fun someDefaultMethod(arg: Int): SELF {
        return builder
    }
}

// extend interface with self type
interface QueryBuilderI2<SELF>: QueryBuilderI<SELF>
        where SELF : QueryBuilderI2<SELF<{

    fun setProp1(arg: Int): SELF
    fun setProp2(arg: Int): SELF
}

// concrete class
class QB2 :  QueryBuilderI2<QB2> {

    override val builder: QB2 = this

    override fun setProp2(arg: Int): QB2 {
        return builder
    }

    override fun setProp1(arg: Int): QB2 {
        return builder
    }
}

// using
QB2().someDefaultMethod(1) // ok, method return QB2
     .setProp2(2) // so we can call setProp2/setProp1
     .someDefaultMethod(2) 
     .setProp1(1)
     
// create variable
val builder : QueryBuilderI2<QB2> = QB2() 
builder.someDefaultMethod(1).setProp1(1)

// now setProp1/setProp2 methods are not available
// useful when you want to restrict client
// by the first interface
val builder2 : QueryBuilderI<QB2> = builder
builder2.someDefaultMethod(2)