ConstructorSpec

Why

At the moment of writing, KotlinPoet doesn't have a ConstructorSpec. Because KotlinPoetDSL want's to allow building the code in an intuitive way, this means that the primary constructor should be able to define fields. Therefor, KotlinPoetDSL needs to have a ConstructorSpec

Implementation

ConstructorSpec is a combination of a list of PropertySpecs, a FunctionSpec and a boolean indicates if it is a primary or a secondary constructor. Asking for the properties will either return its list with PropertySpecs or an empty list, based on if it's a primary or secondary constructor.

the val/var - keywords will be hidden (e.g. the properties will be hidden) if the

Construction

You can build the constructor in a view ways:

  • implicit from the clazz-call

  • using buildConstructor

  • for interoperability, using a funSpec.

Implicit from the clazz-call

When creating a class, you can create a primary constructor implicitly from the clazz-function:

file("", "Hello"){
    //--------------------- doesn't create a primary construcor
    clazz("First") // class First
    
    
    //-------------------- interoperability
    val param = ParameterSpec.builder("par", String::class).build()
    clazz("Second", param)// class Second(par: String)
    
    val immutable = PropertySpec.builder("immutable", String::class).build()
    clazz("Third", immutable) //class Third(val prop:  String)

    val mutableProp = PropertySpec.varBuilder("mutable", String::class).build()
    clazz("Fourth", mutableProp) //class Fourth(var prop:  String)
    
    
    //------------------- KotlinPoetDSLs way
    clazz("Fifth", "prop" of String::class) // Fifth(prop : String)
    clazz("Sixth", "prop" varOf String::class) // Sixth(var prop : String)
    clazz("Sixth", "prop" valOf String::class) // Sixth(val prop : String)
}

Stand-alone using buildConstructor

You can create a standalone ConstructorSpec using createConstructor. Inside the function, you can use the real DSL to create a constructor. Inside the lambda, you need to return a ConstructorSpec, so the curly brackets. Technically it's not needed, but overhere I wanted to be strict.

fun main(args: Array<String>) {
    val constr1 = createConstructor {
        private.constructor("prop" valOf String::class) {}
    }
    constr1 // private constructor(prop : String)
    constr1.toPrimary() // private constructor(val prop : String)

    val constr2 = createConstructor {
        internal.primary.constructor("prop" varOf String::class)
    }
    constr2 // internal primary constructor(var prop : String)

    val constr3 = createConstructor {
        constructor("prop" of String::class){}
    }
    constr3 //constructor(prop : String)
    constr3.toPrimary() //primary constructor(prop : String)
    
    createConstructor {
        constructor("prop" of String::class).thiz("prop"){}
    } //constructor(prop: kotlin.String) : this(prop)

    createConstructor {
        constructor("prop" of String::class).zuper("prop"){}
    } //constructor(prop: kotlin.String) : super(prop)
}

before V0.2, the toPrimary() is not checked for thizz and zuper, meaning it will fail, once it is added to the class.

Adding Stand-alone constructors to the class

you can add those to the class on the following way:

val constr1 = buildConstructor {
        primary.constructor()
    }
    file("", "HelloWorld") {
        clazz("One") {
            // will always pass the constructor like it is created
            // this means private is ignored
            private.accept(constr1) 
        }
        
        
        clazz("Two") {
            // will be dropped in V0.2, due to "strange" working
            public.constructor(constr1) // will merge modifiers
        }
        
        clazz("Three"){
            accept(constr1.buildUpon { 
                addModifier(KModifier.PUBLIC)
            })
        }
    }

in V0.2, the code of public constructor will be replaced with:

constr1.attach{ 
    addModifiers(KModifier.PUBLIC)
}

Then you can create the function constructor yourself, or give it a different implementation that makes sense to you.

Interoperability

ConstructorSpec is interoperable with KotlinPoet

Creating ConstructorSpec

allowed at the moment

val func = FunSpec.constructorBuilder().addParameter(
    ParameterSpec.builder("hi", String::class).build()
).build()

val secondaryConstructor = func.toConstructor()
val primaryConstructor = func.toPrimaryConstructor(
    ("hi" valOf String::class).toPropertySpec()
)

allowed in V0.2

val option1 = func.toPrimaryConstructor(
    "one" of String::class, "two" valOf String::class
)

val option2 = func.toPrimaryConstructor(
    "hi" to Variable.Mutability.MUTABLE, //where you import MUTABLE, of course ;-)
    "two" to  Mutability.NONE
)

Using ConstructorSpec

val const = buildConstructor { 
    private.primary.constructor(
        "arg1" valOf String::class,
        "arg2" varOf Int::class
    )
}

TypeSpec.classBuilder("Clazz")
    .addConstructor(const)
    .build()
//class Clazz(val arg1: String, var arg2 : Int)

Getting KotlinPoetTypes

As defined in the intro, the types are already from KotlinPoet:

val const = buildConstructor {
    private.constructor(
        "arg1" valOf String::class,
        "arg2" varOf Int::class
    ){}
}

const.funSpec // FunSpec: private constructor(arg1: kotlin.String, arg2: kotlin.Int)

const.toPrimary().properties 
// Properties:  [val arg1: kotlin.String = arg1\n, var arg2: kotlin.Int = arg2\n]

In 0.2, toPrimary is no longer needed: a field allProperties is added, where all the properties are visible.

Last updated