"Members of private classes can-not be accessed from non-private inline functions" - why?


#1

Hello,

I ran into this while trying to update to rc-1036. Example:

class Test {
    private class Extender: Base() {
        private fun doSomething() {
            doInline { "Test" }
        }
    }

    private abstract class Base {
        protected fun duplicate(s: String) = s + s

        protected inline fun doInline(block: () -> String): String {
            return duplicate(block())
        }
    }
}

Basically, it’s an inline function calling a non-inline helper function. Why was this changed to be forbidden? If I’m not mistaken, inlining would replace doInline { "Test" } by duplicate("Test"), which would be allowed.

Thanks in advance.


#2

Since the code of your iniline function will be inlined in a different class, the private class Extender might be inaccessible there. To make it work, lift Extender’s visibility to protected


#3

I doubt it would help.
Extender is the class where the doInline is inlined to, and it’s not accessed inside the inline function. It’s the protected duplicate function of the private Base class is accessed, and since the doInline is also protected, it’s perfectly visible from the places where doInline could be inlined, namely the Extender class.


#4

Yup, that’s what I mean. Unless I’m missing something here, any class that could access doInline would have to have access to duplicate as well, since they’re both protected. I don’t know how the visibility of the containing class could make any difference.


#5

I’ve filed this as KT-11014


#6

While it is clear that at jvm level there is no inline function and the called functions must have the right visibility, it would be worthwhile to still hide the infrastructure functions, classes and objects from being explicitly referenced (you can use them, but not their names).


#7

@CLX KT-11014 is fixed and in Kotlin 1.1 your example compiles without warnings.


#8

While this is compiling, it looks like it won’t actually run, and I have no idea why. In our code we have this:

protected inline fun <reified TError> call(call: Call<TBody>, event: BaseEvent<TBody, TError>? = null, sticky: Boolean = true, async: Boolean = true, noinline onCompletion: (NetworkResponseWrapper<TBody, TError>) -> Unit = {}) {
        this.call?.cancel()
        this.call = call
        if (async) {
            launch {
                _call(call, event, TError::class.java, onCompletion, sticky)
            }
        } else {
            _call(call, event, TError::class.java, onCompletion, sticky)
        }
    }

    /**
     * Not intended to be used directly see call
     */
    protected fun <TError> _call(call: Call<TBody>, event: BaseEvent<TBody, TError>?, type: Class<TError>, onSuccess: (NetworkResponseWrapper<TBody, TError>) -> Unit, sticky: Boolean) {
        val res = handleCall(call, type)
        if (event != null && !call.isCanceled) {
            event.response = res
            val sendSticky = hasSubscribersForEvent(event.javaClass) || sticky
            postEvent(event, sendSticky)
        }
        this.call = null
        onSuccess(res)
    }

For which call() is then called from a class extending base service. This works if they are in the same package, but does NOT work if they are in they are not, with the exception stating _call() is not accessible to the extending class. Specifically,

05-17 12:03:02.670 23835-23963/com.heartmath.globalcoherencenetwork.internal.debug E/AndroidRuntime: FATAL EXCEPTION: ForkJoinPool.commonPool-worker-1
    Process: com.heartmath.globalcoherencenetwork.internal.debug, PID: 23835
    java.lang.IllegalAccessError: Method 'void com.heartmath.globalcoherencenetwork.data.BaseService._call(retrofit2.Call, com.heartmath.globalcoherencenetwork.model.event.BaseEvent, java.lang.Class, kotlin.jvm.functions.Function1, boolean)' is inaccessible to class 'com.heartmath.globalcoherencenetwork.data.service.LoginService$login$$inlined$call$1' (declaration of 'com.heartmath.globalcoherencenetwork.data.service.LoginService$login$$inlined$call$1' appears in base.apk)
        at com.heartmath.globalcoherencenetwork.data.service.LoginService$login$$inlined$call$1.doResume(BaseService.kt:22)
        at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:54)
        at kotlinx.coroutines.experimental.DispatchTask.run(CoroutineDispatcher.kt:129)
        at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1412)
        at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:285)
        at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1152)
        at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1990)
        at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1938)
        at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:

I should mention that the login service does extend the BaseService


#9

On the JVM protection is enforced at runtime, not only in the compiler. As inline methods disappear into the calling code, this calling code must have the correct access.

In your case the launch block creates a closure for the async call. This closure is automatically generated but certainly not a child of the class defining _call. As such protected access is not enough (it’s only for children). In normal cases synthetic accessors get generated to allow broader access in the/a language than allowed by the JVM. It seems that in this case the accessors that would make the non-inline version valid are either not invoked or not generated (inline functions are implemented through some nasty bytecode manipulation).

For this particular case, as the only use of inline is the TError class, I would write a non-inline version that takes a parameter for that class and which is called by the version with the reified type.


#10

This looks like a bug, the invocation of _call from the launch lambda should be prohibited here. Could you report it to the tracker?


#12

I went to post it, but it seems that this is already posted under this issue: https://youtrack.jetbrains.com/issue/KT-22625. The issue looks like it’s the simplest case example already, I don’t think it would necessarily be useful to add my case, but I can if you still think it’s useful.