-
Notifications
You must be signed in to change notification settings - Fork 362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add KEEP for sealed inline classes #312
base: master
Are you sure you want to change the base?
Conversation
proposals/sealed-inline-classes.md
Outdated
|
||
## Motivation / use cases | ||
|
||
The main use-case for sealed inline classes is `Result`. Currently it is decraled as inline class with underlying type `Any?` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: decraled
proposals/sealed-inline-classes.md
Outdated
|
||
This is the reason for the following restrictions. | ||
|
||
- Noinline children (classes and objects) should have `value` modifier, as shown in the examples. Since the noinline children can be boxed, they cannot have stable identity. In other words, they are identitiless, and we use `value` modifier to mark them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: identitiless
proposals/sealed-inline-classes.md
Outdated
|
||
`<hash>` is computed using usual [inline classes mangling rules](https://github.com/Kotlin/KEEP/blob/master/proposals/inline-classes.md#mangling-rules). | ||
|
||
- When we pass nullable sealed inline class, it is mapped to boxed selaed inline class: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: selaed
proposals/sealed-inline-classes.md
Outdated
the compiler generates the following code | ||
|
||
```kotlin | ||
val ic = Integet.valueOf(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: Integet
proposals/sealed-inline-classes.md
Outdated
val ic = Integet.valueOf(1) | ||
``` | ||
|
||
The following rulues for passing to function and returning from the function apply: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: rulues
proposals/sealed-inline-classes.md
Outdated
fun `toString-impl`(value: Any?): String = "I3${`$value`}" | ||
``` | ||
|
||
The logic for `toString` applies for all methods, decrared in interfaces: if the method is not overridden in inline child, we do not generate redirect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: decrared
|
||
```kotlin | ||
// In I1 | ||
synthetic fun `str-impl`(value: Any?): String = I2.`str-impl`(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synthetic
has been used inconsistently in the examples.
See the following lines or the definition of the $value
property.
proposals/sealed-inline-classes.md
Outdated
|
||
```kotlin | ||
class IC { | ||
val $value: Any? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a Kotlin property?
We should expect on JVM?
private Object $value
public Object get$value()
* **Status**: Experimental | ||
* **Prototype**: TODO | ||
|
||
Discussion of this proposal is held in [this issue](TODO). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, create an issue for discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
The changes also rely on [inline classes with generic underlying value](https://youtrack.jetbrains.com/issue/KT-32162/Allow-generics-for-inline-classes). | ||
|
||
So, to create the `Result` value one will use constructors instead of factory functions. In other words, instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a separate design discussion on whether we shall deprecate Result.success
and Result.failure
in favor or Result.Success
and Result.Failure
constructors. Please, add it to open issues.
} | ||
``` | ||
|
||
here, we can use `Number` as the underlying type. However, adding non-`Number` child will change the underlying type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to replace "we can use Number" with "we could have used Number" to let a reader understand what's the actual design we are going with and what is just a digression on other potential alternatives we have considered in the design.
``` | ||
|
||
## Is checks | ||
Consider the following example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example does not compile because sealed inline class child cannot be distinguished from another one
|
||
- If sealed inline class is a child of another sealed inline class, there can be no other inline children, including other sealed inline class, since sealed inline classes are mapped to `Any?`, which is open class, which other classes override and there is no way to distinguish `Any?` from other type. | ||
|
||
- Value objects must be a child of sealed inline class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal doesn't explain what a "value object" is and when it's allowed
``` | ||
|
||
Note, that `O2` does not override `toString`, so we should call `Any.toString()` | ||
is case of `O2`, since `I2` does not override `toString` as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is case of - typo?
@JvmInline | ||
sealed value class I2 : I1() | ||
|
||
sealed object O1: I1() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be value object
instead of sealed object
here?
The proposed design solves the
Consider the following example with different design: @JvmInline
sealed value class Color protected constructor(val value: Long) {
@JvmInline
value class Fixed(color: Int) : Color(color.toLong() and 0xFFFFFFFFL) {
val color: Int get() = value.toInt()
override fun toString(): String = "Color.Fixed(#%08X)".format(color)
}
@JvmInline
value class Resource(@ColorRes colorRes: Int) : Color((1L shl 32) or (colorRes.toLong() and 0xFFFFFFFFL)) {
val resId: Int @ColorRes get() = value.toInt()
override fun toString(): String = "Color.Resource(0x${resId.toString(16)})"
}
operator fun getClass(): KClass<out Color> = when (value ushr 32) {
0L -> Fixed::class
1L -> Resource::class
else -> throw AssertionError()
}
} Probable desugaring: @JvmInline
sealed value class Color {
static fun constructor-impl(value: Long): Long = value
@JvmInline
value class Fixed {
static fun constructor-impl(color: Int): Long {
val expressionPassedToSuper = color.toLong() and 0xFFFFFFFFL
val value = Color.constructor-impl(expressionPassedToSuper)
Intrinsics.requireConsistentInlineCtor(Fixed::class, Color.getClass(value))
return value
}
// val color ...
// fun toString() ...
}
@JvmInline
value class Resource { /* contents identical */ }
// static fun getClass() ...
// proxies for 'overridden' functions
static fun toString-impl(value: Long) = when (getClass(value)) {
Fixed::class -> Fixed.toString-impl(value)
Resource::class -> Resource.toString-impl(value)
else -> throw NoWhenBranchMatchedException()
}
static fun hashCode-impl(value: Long) { /* either `when (getClass()) {}` or default value.hashCode() */ }
static fun equals-impl(value: Long, other: Long) { /* either `when (getClass()) {}` or default value.equals(other) */ }
} Probable emulation on top of current Kotlin: @JvmInline
value class Color(val value: Long) {
@JvmInline
value class Fixed @PublishedApi internal constructor(val value: Long) {
constructor(color: Int) : this(color.toLong() and 0xFFFFFFFFL)
val color: Int get() = value.toInt()
override fun toString(): String = "Color.Fixed(#%08X)".format(color)
fun upcast() = Color(value)
}
@JvmInline
value class Resource @PublishedApi internal constructor(val value: Long) {
constructor(@ColorRes colorRes: Int) : this((1L shl 32) or (colorRes.toLong() and 0xFFFFFFFFL))
val resId: Int @ColorRes get() = value.toInt()
override fun toString(): String = "Color.Resource(0x${resId.toString(16)})"
fun upcast() = Color(value)
}
inline fun <R> switch(
fixed: (Fixed) -> R,
resource: (Resource) -> R,
): R = when (value ushr 32) {
0L -> fixed(Fixed(value))
1L -> resource(Resource(value))
else -> throw AssertionError()
}
fun asFixed(): Fixed =
if ((value ushr 32) == 0L) Fixed(value) else throw TypeCastException()
fun safeAsFixed(): Fixed? =
if ((value ushr 32) == 0L) Fixed(value) else null
fun asResource(): Resource =
if ((value ushr 32) == 1L) Resource(value) else throw TypeCastException()
fun safeAsResource(): Resource? =
if ((value ushr 32) == 1L) Resource(value) else null
} |
No description provided.