KMutableProperty generation


#1

I want to use KProperty as an identifier for an API that looks like the following:

class DEntity {
  fun <T> onChange (prop :KProperty<T>, fn (T, T) -> Unit) = …


  protected fun <T> dvalue (initVal :T) : DProperty<T> = …
  protected inner class DProperty<T> (…) {
  operator fun getValue (…) = …
  operator fun setValue (…) = …
  }
}

used like so:

class TestEntity {
  val name :String by dvalue("")
}


val ent = TestEntity()
ent.onChange(TestEntity::name) { nv, ov -> … }

and this all works fine, but I discovered that every time I write code like TestEntity::name a whole new class is generated that implements MutablePropertyReference, and these classes are identical to the one generated for TestEntity and placed into the $delegatedProperties array. I can diff the javap output and see that the only thing that changed is the class name:

% diff javap1 javap2

2c2

< final class ddb.DDBTest$testCRUD$1 extends kotlin.jvm.internal.MutablePropertyReference1 {

---

> final class ddb.DDBTest$TestEntity$age$1 extends kotlin.jvm.internal.MutablePropertyReference1 {

7c7

<   0: new           #2                  // class ddb/DDBTest$testCRUD$1

---

>   0: new           #2                  // class ddb/DDBTest$TestEntity$age$1

14c14

<   ddb.DDBTest$testCRUD$1();

--- >   ddb.DDBTest$TestEntity$age$1();

And when I say every time, I mean every time. If I reference TestEntity::age twice in a single method, I get two totally unnecessary copies of the KMutableProperty singleton class.

Why doesn’t the compiler just use the one defined in the TestEntity class? This undermines my whole plan for a typesafe reactive property API. Boo hoo!


#2

Thank you for the report. The fact that a subclass is generated for each property referenced by '::' is a known issue and it's planned to be fixed by caching them per file -- please create an issue at youtrack.jetbrains.com to increase the chances of that happening soon! Also currently a subclass is generated for each delegated property (and its instance is saved to the $delegatedProperties array you mention) -- this is in fact already fixed and will ship in the next milestone (presumably in the middle of November).


#3

Thanks, I will definitely file an issue.

I’m curious though, why is it necessary to generate these even for each file in which they occur? Why can’t all code everywhere just use the one SINGLETON instance created in the class that declares the property?


#4

Because that would mean we'd generate a class for each property out there, even for those that are not used via property references, which is too much.


#5

Or, if you mean reusing those KMutableProperty instances that are already cached for each delegated property -- that would make changing the property from delegated to non-delegated a binary-breaking change.


#6

Ah, good points. I wasn't thinking about the zillions of non-delegated properties out there. Certainly you don't want to generate classes for those unless somone uses them.

It’s a shame that you have to support file-by-file compilation. A single cache of property instances for a whole module would be tolerable for my use case, and would be practical if you could rely on rebuilding an entire module at a time. But a separate class for every file containing a property reference (even if repeated references in the same file were shared) is a pretty steep price to pay. I think I’ll just have to work around it by manually caching a reference in the class that declares the property.

Thanks for clarifying things for me!