3

I have a generic function which needs to instantiate an object of its generic argument and pass it to an instance of some interface.
As far as I know, the only way to instantiate that generic object is making the function inline and reifying that type parameter. But I do not want to expose the implementation of that interface.
The problem is that inlined functions can not use internal classes.

What I basically want to is this:

/* The interface I want to expose */
interface Params<T> {
    val doc: T
}

/* The implementation I do not want to expose */
internal class ParamsImpl<T> (override val doc: T) : Params<T> 


/* The function */
inline fun <reified T> doSomething(init: Params<T>.() -> Unit) {
    val doc= T::class.java.newInstance()
    val params = ParamsImpl(doc) // Can't compile since ParamsImpl is internal 

    params.init()
}
Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93
danielspaniol
  • 2,228
  • 21
  • 38

2 Answers2

5

You can fix this by adding an intermediary method to create your ParamsImpl instance:

fun <T> createImpl(doc: T): Params<T> = ParamsImpl(doc)

inline fun <reified T> doSomething(init: Params<T>.() -> Unit) {
    val doc = T::class.java.newInstance()
    val params = createImpl(doc)

    params.init()
}

This method doesn't have to be inlined (since you're just passing a generic instance to it that you've already created in the doSomething method), therefore it can use ParamsImpl in its private implementation.

It's also important that it's return type is Params<T> so that it only exposes that type to the users of doSomething.


Edit:

To hide the creation method, you can use the @PublishedApi annotation in combination with internal visibility:

@PublishedApi internal fun <T> createImpl(doc: T): Params<T> = ParamsImpl(doc)

This will enable its use in inline functions, but hide it from other users.

This means that in other modules, it won't be visible in Kotlin code, and it will give you an error when you try to call it from Java (this, of course, can be suppressed, but this is the best guarantee that Kotlin can give you for its internal visiblity when doing interop).

You can also use the constructor and mark the ParamsImpl class itself with @PublishedApi, if you like that more.

zsmb13
  • 85,752
  • 11
  • 221
  • 226
0

This is because an inline function will be inlined into call-site function and the visibility of the ParamsImpl class is internal.

You can easily to solve this problem by makes doSomething's visibility to internal.

But you also can extract another non-inline method to solve the problem, for example:

inline fun <reified T> doSomething(init: Params<T>.() -> Unit) {
    val doc= T::class.java.newInstance()

    newParams(doc).init()
}

fun <T> newParams(doc: T): Params<T> {
    return ParamsImpl<T>(doc)
}

IF you don't want to expose ParamsImpl to others, you must using reflection, for example:

inline fun <reified T> doSomething(init: Params<T>.() -> Unit) {
    val doc = T::class.java.newInstance()
    //             v--- the full qualifier class name 
    Class.forName("pkg.ParamsImpl").getDeclaredConstructor(Any::class.java).run {
        isAccessible = true
        @Suppress("UNCHECKED_CAST")
        (newInstance(doc) as Params<T>).init()
    }
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • `doSomething` needs to be public. Having a public function to create `Params` is not what I want since I only want to be able to create them internally – danielspaniol Jul 25 '17 at 08:18
  • @danielspaniol sir, how about it now? – holi-java Jul 25 '17 at 08:27
  • that looks really ugly. I hoped there would be a nicer solution than using reflection. I don't get why it is not possible to just use the `internal` in the `inline` function. – danielspaniol Jul 25 '17 at 08:31
  • @danielspaniol hi, there is no way to do that, because `ParamImpl` will inlined into call-site function, however its visibility is internal, so the compiler will report such a error. if you don't want to expose the internal `ParamsImpl`, the only way is to use *reflection*. – holi-java Jul 25 '17 at 08:32
  • I thought since Kotlins `internal` maps to Javas `public` it should not be to problematic for the compiler – danielspaniol Jul 25 '17 at 08:33
  • @danielspaniol sir, you can see the [visibility](https://kotlinlang.org/docs/reference/visibility-modifiers.html) as further. it's the kotlin compiler problem. because Kotlin is already assert that the `internal` visibility only can be access in the same module. – holi-java Jul 25 '17 at 08:37