How does an extension property desugar on the JVM? What does a top-level `val String.lastIndex get() = length - 1` compile to?
answer
- Extension prop -> static getX/setX methods
- Receiver = first parameter ($this$...)
- Lives on FooKt (file class)
- val=getter, var=getter+setter
- Static call -> static resolution, not virtual
basics
~20 sOn the JVM it becomes a plain static method (a getter, and a setter for var) that takes the receiver as its first argument. There is no field; calling the property just calls that static method.
solid answer
~40 sAn extension property has no field, so it compiles to **static accessor methods** on the file's synthetic class (e.g. `MyFileKt`). A top-level `val String.lastIndex: Int get() = length - 1` becomes roughly `public static int getLastIndex(String $this$lastIndex)` — the receiver is passed as the **first parameter** (`$receiver`/`$this$...`), not bound as a JVM instance. A `var` adds a corresponding `setX(receiver, value)` static method. Kotlin call sites `"abc".lastIndex` are rewritten to `MyFileKt.getLastIndex("abc")`. Because it's static dispatch keyed on the **declared** parameter type, extension property resolution is **static** (compile-time), not virtual. From Java you'd call `MyFileKt.getLastIndex("abc")`; `@JvmName` on the file or on accessors can rename the generated method.
code
kotlin · 5 lines// Strings.kt
@file:JvmName("StringExt")
val String.lastIndex: Int get() = length - 1
// Java sees: int i = StringExt.getLastIndex("abc");go deeper
Aware it 'turns into a method' but fuzzy on the details.
States that it becomes a static getter taking the receiver as an argument.
Describes the FooKt class, getX(receiver) signature, val/var difference, and @JvmName interop.
Links desugaring to static resolution, zero-alloc perf, Java-interop ergonomics, and naming/ABI stability concerns.
## The core idea: static-method desugaring Kotlin has no concept of properties at the JVM bytecode level beyond fields + accessor methods. Since an extension property owns **no field**, the compiler emits only **static methods** — a getter, plus a setter for `var`. ## Where the methods live Top-level declarations in `Foo.kt` go onto a synthetic class named **`FooKt`** by default (changeable with a file-level `@JvmName`). Given: ```kotlin // file: Strings.kt val String.lastIndex: Int get() = length - 1 var StringBuilder.firstChar: Char get() = this[0] set(value) { setCharAt(0, value) } ``` the compiler produces, conceptually: ```java public final class StringsKt { public static int getLastIndex(String $this$lastIndex) { return $this$lastIndex.length() - 1; } public static char getFirstChar(StringBuilder $this$firstChar) { return $this$firstChar.charAt(0); } public static void setFirstChar(StringBuilder $this$firstChar, char value) { $this$firstChar.setCharAt(0, value); } } ``` ### Key facts - The **receiver becomes the first parameter** (`$this$...`). There is no `this` JVM instance for the method. - Naming follows the JavaBean convention: `getX` / `setX` (and `isX` for `Boolean`-ish names). - A `val` -> getter only; a `var` -> getter + setter. ## Resolution is static, by consequence Because the call compiles to a normal static method whose parameter type is the **declared receiver type**, the method chosen depends on the **static (compile-time) type** of the expression — not the runtime type. Extension members therefore are **not** polymorphic/virtual. (Dispatch-vs-extension resolution details are a sibling topic; here the relevant point is simply *why* it's static — there's no vtable entry.) ## Calling from Java ```java int i = StringsKt.getLastIndex("abc"); // 2 ``` Use `@JvmName("...")` on the file (to rename `StringsKt`) or on an accessor (`@get:JvmName`) to control the generated names for cleaner interop. ## Member extension properties Declared inside a class, an extension property has two receivers (dispatch + extension). It then compiles to an **instance** method on that class taking the extension receiver as a parameter — still no field, still static-style dispatch on the extension receiver type. ## Why this matters Understanding the desugaring explains the no-field rule, the static resolution, the Java call shape, and the performance profile (a plain static call, zero allocation, easily inlined).
- How do you change the generated Java method name?Use a file-level `@JvmName` to rename the container class, or `@get:JvmName`/`@set:JvmName` on the accessor to rename the getter/setter itself.
- Why does the static-method desugaring imply non-virtual resolution?A static method has no vtable slot; the compiler binds the call to the method matching the receiver's declared (static) type, so there's no runtime overriding.
saying these in an interview costs you the question
- Claiming the property compiles to a JVM field
- Saying the receiver is the implicit `this` of an instance method (for top-level)
- Believing extension properties can be overridden virtually
- Not knowing the receiver is the first method parameter