1

The question is very simple: (using Kotlin 1.3.71)

I have the following data alike this one:

data class Location(val lat: Double, val lng: Double)

I want to achieve a type-safety with a call like so:

val loc = location {
    lat = 2.0
    lng = 2.0
}

To achieve so I built:


fun location(builder: LocationBuilder.() -> Unit): Location {
    val lb = LocationBuilder().apply(builder)
    return Location(lb.lat!!, lb.lng!!)
}

data class LocationBuilder(
    var lat: Double? = null,
    var lng: Double? = null
)

To avoid having !! operators I would like to write a contract that helps the compiler infere a smartcast that says that the attributes lat and lng are not null but I have not been able to do that successfully.

I've tried things without success and I believe it might be because I am not fully understanding the dynamics of contracts. Those are the style of:



fun LocationBuilder.buildSafely(dsl: LocationBuilder.()->Unit): LocationBuilder {
    contract {
        returnsNonNull() implies (this@buildSafely.lat != null && this@buildSafely.lng != null)
    }
    apply(dsl)
    if(lat == null || lng == null) throw IllegalArgumentException("Invalid args")

    return this
}

fun location(builder: LocationBuilder.()->Unit): Location {
    val configuredBuilder = LocationBuilder().buildSafely(builder)

    return Location(configuredBuilder.lat, configuredBuilder.lng)
    /* I would expect a smart cast but I am getting a compile error stating that lat and lng may still be null */
}

So the question is:

Can this be done with the current Kotlin version? If so, how?

Some random IT boy
  • 7,569
  • 2
  • 21
  • 47
  • Does this answer your question? [How to make field required in kotlin DSL builders](https://stackoverflow.com/questions/53651519/how-to-make-field-required-in-kotlin-dsl-builders) – IlyaMuravjov Apr 04 '20 at 20:57
  • Not really. It gets really close, but it uses methods in the builder DSL I would like to use properties as I show on the sample code provided – Some random IT boy Apr 04 '20 at 21:35
  • Unfortunately, _contracts are allowed only for functions_, so you can't specify them for properties, at least for now. – IlyaMuravjov Apr 04 '20 at 21:58

3 Answers3

4

This is currently not possible. A contract can't be based on the properties of the class in the contract, so when you check latitude or longitude in the contract, that's not allowed.

Francesc
  • 25,014
  • 10
  • 66
  • 84
0

You could just replace the '{}' for '()' and use named arguments:

val loc = Location(
  lat = 2.0
  lng = 2.0
)

Also note that this is the constructor of the class, no builder needed :) This works for all calls in Kotlin.

See https://kotlinlang.org/docs/reference/functions.html#named-arguments

loosetie
  • 34
  • 2
-1

You can do something like this:

import kotlin.properties.Delegates

fun location(builder: LocationBuilder.() -> Unit): Location {
    val lb = LocationBuilder().apply(builder)
    return Location(lb.lat, lb.lng)
}


class LocationBuilder {
    var lat by Delegates.notNull<Double>()
    var lng by Delegates.notNull<Double>()
}

data class Location(
    val lat: Double,
    val lng: Double
)

fun main() {
    val l = location {
        lat = 2.0
        lng = 8.0
    }

    println(l)
}

But you won't have compilation time exception for the nulls. It means that it doesn't force you to set both properties (lat and lng)

fritsMaister
  • 532
  • 1
  • 4
  • 18