Comments (7)
Hey, @Zhou-Kt, thanks for your appreciation and for closing this issue from here. While I don't think such problems can be universally fixed from Kotlin's side, this specific one could indeed be worked around by changing the way singletons are generated.
So I created this YouTrack issue. If you wish, you can track any progress there.
from compose-multiplatform.
First, I created a Minimal Reproducible Example (MRE) that presents the same exact issue:
abstract class Route {
object Home : Route()
object Personal : Route()
companion object {
@JvmStatic val home = Home
@JvmStatic val personal = Personal
}
}
fun main() {
Route.Home // this causes the issue
println(Route.home) // null
println(Route.personal) // Route$Personal@somehash
}
In the MRE,
- I got rid of Compose entirely — in fact I wouldn't expect the Compose compiler to do anything about those classes given they don't even contain
@Composable
s or anything. - I replaced the
sealed
modifier ofRoute
withabstract
— in factsealed
does nothing besides providing additional guarantees from the Kotlin compiler about which subclasses are allowed, and similar guarantees by the JVM because of a JVM attribute added to it. - I removed the
data
modifier fromHome
andPersonal
— in factdata
does nothing besides auto-generating implementations forAny.hashCode()
,Any.equals(Any?)
, andAny.toString()
, plus generating acopy
method andcomponentN()
methods for constructor properties (and none of these members are referenced in the MRE, so they're irrelevant). - I removed the
Info
class entirely, as I felt that that was unrelated and that the real culprit was theRoute.Home
reference inmain
. - I replaced the
Route.list
companion field with ahome
and apersonal
fields initialized to the singleton instances ofHome
andPersonal
, respectively — this modification maintains the same side effects of the instance constructor ofRoute.Companion
and therefore of its static constructor as well (that is, excluding thatlistOf
was the issue, which seemed unrelated). - I added the
@JvmField
annotation to theRoute.home
andRoute.personal
fields to get us rid of redundant getters for them and to make them static so we can forget of the generatedRoute.Companion
class.
Interestingly, when removing the first line inside the main
function, where Route.Home
is first referenced, the issue disappears.
Suspecting that the issue would be about the side effects of the static constructors initializing fields, I wanted to know the precise order in which they were called. Therefore, I set out to insert some println
statements to debug. However, there's no way to add a static constructor to an object
definition in Kotlin code, because init
inside an object
definition is its instance constructor. For this purpose, I converted the object
definitions to plain classes and defined the static field INSTANCE
(see [1]) from within Kotlin code. The result is in this Kotlin playground.
Note that in this modified example, the Route.Home
reference inside main
is no longer referencing to the singleton instance of Route.Home
as it did when using Kotlin object
s, because that would now be Route.Home.INSTANCE
. It is instead just forcing the JVM to load the Route.Home
class, which in turn calls its static constructor, which still initializes the INSTANCE
field. Therefore, the difference is just that we got rid of a field read, but field reads have no side effects in a single-threaded environment. Plus, interestingly, if we load Route
before we load Home
(as shown in the first line inside main
), the issue disappears. Which leads us to believe that the the issue is a side effect of loading Home
before Route
.
By running the same Kotlin playground, we can observe this output:
Attempting to call Home.<clinit>
Route.<clinit> start
Route.<clinit> after Route.home is initialized
Personal.<clinit> start
Personal.<clinit> end
Route.<clinit> end
Home.<clinit> start
Home.<clinit> end
Attempting to call Route.<clinit>
null
Route$Personal@7530d0a
This tells us that Route.home
is initialized (and therefore Home.INSTANCE
referenced) before the static constructor of Home
is called, which is where Home.INSTANCE
would be initialized. Which means that when Route.home
is initialized, Home.INSTANCE
is still uninitialized, and therefore null
as the JVM specifies.
By inspecting the generated bytecode, it seems like the Kotlin compiler didn't make any mistakes, so I would conclude that this is actually a JVM issue, whether it's intended behavior or not. Whether the Kotlin compiler could spot situations like this and introduce a fix is another matter.
The fact is, when Home
is loaded, as explained above, a new instance of it is created. However, Home
inherits from Route
, which means Route
must be loaded before the instance constructor of Home
can be called. And loading Route
has the side effect of calling its own static constructor. Which explains why the Route
's static constructor is invariantly invoked before Home
's, which makes it so Route.home
is initialized before Home.INSTANCE
is initialized. And by the time Route.personal
is getting initialized, Route.home
was already initialized, and so Route
was as well.
This looks like a cyclic reference in JVM class loading.
A quick fix would be to lazy
-initialize Route.list
, as follows:
sealed class Route {
data object Home : Route()
data object Personal: Route()
companion object {
val list by lazy { listOf(Home, Personal) }
}
}
To further exclude Kotlin from causing this issue, here's a Java equivalent that results in the same:
public class Program {
public static abstract class Route {
public static final Home home = Home.INSTANCE;
public static final Personal personal = Personal.INSTANCE;
public static class Home extends Route {
private Home() {}
public static Home INSTANCE = new Home();
}
public static class Personal extends Route {
private Personal() {}
public static Personal INSTANCE = new Personal();
}
}
public static void main(String[] args) {
var x = Route.Home.INSTANCE;
System.out.println(Route.home);
System.out.println(Route.personal);
}
}
*[1]: A static INSTANCE
field is generated by the Kotlin compiler in the class of each object
definition to store its singleton instance.
from compose-multiplatform.
Wow, that's so professional👍. Thank you for your help.
from compose-multiplatform.
@DavideCannizzo my bad, i've reopened the issue and switched to your title (the old title isn't appropriate).
from compose-multiplatform.
@Zhou-Kt, I'm not sure if it is of any use to keep this issue open — after all, this doesn't depend on Compose, and at best could be worked around in Kotlin compiler, where I opened a separate issue via YouTrack which I linked in my previous comment.
Anyway, at this point we can wait and see what the Compose Multiplatform team says. I'm just a user of this library, and I don't even have the permission to close this issue.
from compose-multiplatform.
@Zhou-Kt, I'm not sure if it is of any use to keep this issue open — after all, this doesn't depend on Compose, and at best could be worked around in Kotlin compiler, where I opened a separate issue via YouTrack which I linked in my previous comment.
Anyway, at this point we can wait and see what the Compose Multiplatform team says. I'm just a user of this library, and I don't even have the permission to close this issue.
Yes, at first I thought it was the effect of mutableStateListOf, so that I brought the issue here. One more issue means one more chance to be solved.
from compose-multiplatform.
Thanks, @DavideCannizzo, for the detailed analysis.
As there's nothing for the Compose Multiplatform team to do here, I'm closing the ticket.
from compose-multiplatform.
Related Issues (20)
- Hello! How to format decimal values using only Kotlin classes HOT 1
- UIKitVIew with MKMapView subview crashes with kotlin.IllegalStateException: Size is out of range. HOT 1
- DialogWindow throw the exception when showing - Jetpack Compose desktop HOT 19
- iOS 17 hidden trackpad doesn't work in the TextFields (hold space bar and swipe to move cursor) HOT 2
- Drop frames when scrolling on iOS (only smooth when start screen recording) HOT 1
- API Reference for Compose Multiplatform's Composable Functions? HOT 1
- 1.6.1 gradle plugin is incompatible with Kotlin 2.0.0-RC1 HOT 5
- Long press haptic feedback not working on iOS
- PagerState.currentPageOffsetFraction Incorrect Value Calculation HOT 2
- [Desktop] Mouse wheel-scrolling a JScrollPane stops when cursor is over a ComposePanel
- Confusion in reading PNG image from composeResources/files/assets/ in v1.6.0 HOT 1
- "Unresolved reference: let" since Compose Multiplatform 1.6.1 on Android Studio HOT 2
- Gradle task build (allTests) fails on default web project generated by kmp.jetbrains.com HOT 3
- Cannot change dependencies of dependency configuration 'X' after it has been included in dependency resolution HOT 2
- DateRangePicker ignore first click HOT 1
- [iOS] Using Compose inside a ShareExtensionViewController renders it black HOT 4
- Cursor position is displayed on the left edge when textAlign is set to center in BasicTextField HOT 1
- Pressing the return key in TextField triggers the parent container's clickable HOT 3
- Support `commonResources` to `R`/`Icon` HOT 1
- Tray menus not working on Fedora HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from compose-multiplatform.