An Urgent notification still renders as a plain one — find and fix the bug
Both notification kinds are rendered from one feed. The Urgent versions of render() and badge are never used: every line comes out as a plain notification.
Constraints: the feed must stay a List<Notification>, the loop must stay as it is, and an Urgent element must print alarm URGENT: prod down.
open class Notification(val text: String)
class Urgent(text: String) : Notification(text)
fun Notification.render(): String = "note: $text"
fun Urgent.render(): String = "URGENT: $text"
val Notification.badge: String get() = "info"
val Urgent.badge: String get() = "alarm"
fun main() {
val feed: List<Notification> = listOf(Notification("build ok"), Urgent("prod down"))
feed.forEach { println("${it.badge} ${it.render()}") }
// info note: build ok
// info note: prod down <-- expected: alarm URGENT: prod down
}
Find and fix the error.
An extension resolves on the declared static type, never the runtime type: it compiles to a static function and overrides nothing, so it: Notification always picks the Notification versions. Make both open members and override them.
- ✗Expecting an extension to be virtual and to override a version on the base class
- ✗Reading the receiver as the runtime type of the value rather than its declared type
- ✗Marking the class
openand expecting that to change how the extension resolves
- →What would you do instead if
Notificationcame from a library you cannot modify? - →Which version wins when a member function and an extension share one signature?
Why the base version runs
Extensions are statically dispatched. The compiler turns fun Notification.render() into a plain static function that takes the receiver as its first parameter — nothing is added to the Notification class and there is no virtual slot for it. So an extension is chosen by the declared type of the expression, not by the object's runtime type.
Inside forEach over a List<Notification>, the parameter it is declared as Notification. For every element — including the Urgent one — Notification.render() and Notification.badge are selected. The Urgent extension overrides nothing: it is simply never picked at that call site.
The fix: make the behaviour members and override it
open class Notification(val text: String) {
open val badge: String get() = "info"
open fun render(): String = "note: $text"
}
class Urgent(text: String) : Notification(text) {
override val badge: String get() = "alarm"
override fun render(): String = "URGENT: $text"
}
fun main() {
val feed: List<Notification> = listOf(Notification("build ok"), Urgent("prod down"))
feed.forEach { println("${it.badge} ${it.render()}") }
// info note: build ok
// alarm URGENT: prod down
}
Members are dispatched virtually, so the override in Urgent wins on the runtime type.
Had Notification come from a library where members cannot be added, the polymorphism would have to be restored by hand — when (it) { is Urgent -> it.render() else -> it.render() } — because an extension will never become virtual.