Skip to content

Commit

Permalink
Add functions that can be used by compiler plugin for intrinsics (#2031)
Browse files Browse the repository at this point in the history
  • Loading branch information
sandwwraith committed Sep 15, 2022
1 parent 79de734 commit f8d605d
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 35 deletions.
6 changes: 4 additions & 2 deletions build.gradle
Expand Up @@ -14,13 +14,15 @@ buildscript {
}
ext.experimentalsEnabled = ["-progressive", "-opt-in=kotlin.Experimental",
"-opt-in=kotlin.ExperimentalMultiplatform",
"-opt-in=kotlinx.serialization.InternalSerializationApi"
"-opt-in=kotlinx.serialization.InternalSerializationApi",
"-P", "plugin:org.jetbrains.kotlinx.serialization:disableIntrinsic=false"
]

ext.experimentalsInTestEnabled = ["-progressive", "-opt-in=kotlin.Experimental",
"-opt-in=kotlin.ExperimentalMultiplatform",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=kotlinx.serialization.InternalSerializationApi"
"-opt-in=kotlinx.serialization.InternalSerializationApi",
"-P", "plugin:org.jetbrains.kotlinx.serialization:disableIntrinsic=false"
]
ext.koverEnabled = property('kover.enabled') ?: true

Expand Down
3 changes: 3 additions & 0 deletions core/api/kotlinx-serialization-core.api
Expand Up @@ -119,6 +119,9 @@ public abstract interface annotation class kotlinx/serialization/Serializer : ja
}

public final class kotlinx/serialization/SerializersKt {
public static final fun noCompiledSerializer (Ljava/lang/String;)Lkotlinx/serialization/KSerializer;
public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
Expand Down
32 changes: 32 additions & 0 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Expand Up @@ -216,3 +216,35 @@ private fun <T : Any> KSerializer<T>.nullable(shouldBeNullable: Boolean): KSeria
if (shouldBeNullable) return nullable
return this as KSerializer<T?>
}


/**
* Overloads of [noCompiledSerializer] should never be called directly.
* Instead, compiler inserts calls to them when intrinsifying [serializer] function.
*
* If no serializer has been found in compile time, call to [noCompiledSerializer] inserted instead.
*/
@Suppress("unused")
@PublishedApi
internal fun noCompiledSerializer(forClass: String): KSerializer<*> {
throw SerializationException(
"Cannot find serializer for class $forClass.\n" +
"Make sure that this class marked with @Serializable annotation," +
"or provide serializer explicitly, or use proper SerializersModule"
)
}

// Used when compiler intrinsic is inserted
@OptIn(ExperimentalSerializationApi::class)
@Suppress("unused")
@PublishedApi
internal fun noCompiledSerializer(module: SerializersModule, kClass: KClass<*>): KSerializer<*> {
return module.getContextual(kClass) ?: kClass.serializerNotRegistered()
}

@OptIn(ExperimentalSerializationApi::class)
@Suppress("unused")
@PublishedApi
internal fun noCompiledSerializer(module: SerializersModule, kClass: KClass<*>, argSerializers: Array<KSerializer<*>>): KSerializer<*> {
return module.getContextual(kClass, argSerializers.asList()) ?: kClass.serializerNotRegistered()
}
20 changes: 20 additions & 0 deletions core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt
Expand Up @@ -4,8 +4,10 @@

package kotlinx.serialization

import kotlinx.serialization.modules.*
import kotlinx.serialization.test.noJsLegacy
import kotlin.test.*
import kotlin.time.*

class CachedSerializersTest {
@Serializable
Expand Down Expand Up @@ -34,4 +36,22 @@ class CachedSerializersTest {
fun testAbstractSerializersAreSame() = noJsLegacy {
assertSame(Abstract.serializer(), Abstract.serializer())
}


@OptIn(ExperimentalTime::class)
@Test
@Ignore // TODO: Unignore after 1.8.0 update
fun testSerializersAreIntrinsified() {
val m = SerializersModule { }
val direct = measureTime {
Object.serializer()
}
val directMs = direct.inWholeMicroseconds
val indirect = measureTime {
m.serializer<Object>()
}
val indirectMs = indirect.inWholeMicroseconds
if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart")
}
}

Expand Up @@ -44,9 +44,6 @@ class SerializersLookupEnumTest {
}
}

@Polymorphic
enum class EnumPolymorphic

@Serializable
enum class PlainEnum

Expand All @@ -72,17 +69,4 @@ class SerializersLookupEnumTest {
assertFails { serializer<EnumExternalClass>() }
}
}

@Test
fun testEnumPolymorphic() {
if (isJvm()) {
assertEquals(
PolymorphicSerializer(EnumPolymorphic::class).descriptor,
serializer<EnumPolymorphic>().descriptor
)
} else {
// FIXME serializer<PolymorphicEnum> is broken for K/JS and K/Native. Remove `assertFails` after fix
assertFails { serializer<EnumPolymorphic>() }
}
}
}
Expand Up @@ -43,9 +43,6 @@ class SerializersLookupObjectTest {
}
}

@Polymorphic
object ObjectPolymorphic

@Serializable
object PlainObject

Expand Down Expand Up @@ -73,18 +70,4 @@ class SerializersLookupObjectTest {
assertIs<ObjectExternalClassSerializer>(serializer<ObjectExternalClass>())
}
}

@Test
fun testEnumPolymorphic() {
if (isJvm()) {
assertEquals(
PolymorphicSerializer(ObjectPolymorphic::class).descriptor,
serializer<ObjectPolymorphic>().descriptor
)
} else {
// FIXME serializer<PolymorphicObject> is broken for K/JS and K/Native. Remove `assertFails` after fix
assertFails { serializer<ObjectPolymorphic>() }
}

}
}
Expand Up @@ -15,6 +15,7 @@ import org.junit.Test
import java.lang.reflect.*
import kotlin.reflect.*
import kotlin.test.*
import kotlin.time.*

class SerializerByTypeTest {

Expand Down Expand Up @@ -283,4 +284,19 @@ class SerializerByTypeTest {
serializer(typeTokenOf<Array<NonSerializable>>())
}
}

@OptIn(ExperimentalTime::class)
@Test
@Ignore // TODO: Unignore after 1.8.0 update
fun testSerializersAreIntrinsified() {
val direct = measureTime {
Json.encodeToString(IntData.serializer(), IntData(10))
}
val directMs = direct.inWholeMicroseconds
val indirect = measureTime {
Json.encodeToString(IntData(10))
}
val indirectMs = indirect.inWholeMicroseconds
if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart")
}
}

0 comments on commit f8d605d

Please sign in to comment.