Default case in "load" switch-case throws exception instead of silent read ending.

Improved error descriptions, fixed issue with transient properties
without backing field (Kotlin/kotlinx.serialization#2)

Don't add serialClassDesc property if user already defined one
(Kotlin/kotlinx.serialization#10)

Bump version to 0.1.1

Fix issue with mixing serial name with real property name (Kotlin/kotlinx.serialization#13)
This commit is contained in:
Leonid Startsev
2017-09-21 16:22:00 +03:00
parent 02d0322029
commit 8a250b07df
9 changed files with 44 additions and 24 deletions
@@ -17,7 +17,7 @@
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-maven-serialization-plugin</artifactId>
<packaging>jar</packaging>
<version>0.1</version>
<version>0.1.1</version>
<description>Serialization plugin for Maven</description>
@@ -3,7 +3,7 @@ plugins {
}
group = 'org.jetbrains.kotlinx'
version = '0.1'
version = '0.1.1'
apply plugin: 'kotlin'
@@ -80,13 +80,13 @@ abstract class SerializerCodegen(declaration: KtPureClassOrObject, bindingContex
fun getPropertyToGenerate(
classDescriptor: ClassDescriptor,
name: String,
isReturnTypeOk: (KotlinType) -> Boolean
isReturnTypeOk: (PropertyDescriptor) -> Boolean
): PropertyDescriptor? =
classDescriptor.unsubstitutedMemberScope.getContributedVariables(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
.singleOrNull { property ->
property.kind.let { kind -> kind == CallableMemberDescriptor.Kind.SYNTHESIZED || kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE } &&
property.modality != Modality.FINAL &&
property.returnType != null &&
isReturnTypeOk(property.returnType!!)
isReturnTypeOk(property)
}
}
@@ -272,20 +272,20 @@ class SerializerJsTranslator(declaration: KtPureClassOrObject,
bitMasks[i / 32],
JsIntLiteral(bitPos)
).makeStmt()
// if (!readAll) break -- but only if this is last prop
if (i != orderedProperties.lastIndex)
+JsIf(JsAstUtils.not(readAllVar), JsBreak())
else {
// if (readAll) breakLoop else break
+JsIf(readAllVar, JsBreak(loopRef), JsBreak())
}
// if (!readAll) break
+JsIf(JsAstUtils.not(readAllVar), JsBreak())
}
}
// case -1, default: break loop
case(JsIntLiteral(-1)) {}
default {
// case -1: break loop
case(JsIntLiteral(-1)) {
+JsBreak(loopRef)
}
// default: throw
default {
val excClassRef = serializableDescriptor.getClassFromSerializationPackage("UnknownFieldException")
.let { context.getQualifiedReference(it) }
+JsThrow(JsNew(excClassRef, listOf(indexVar)))
}
}
}, loop)
@@ -51,6 +51,7 @@ internal val kSerializerArrayType = Type.getObjectType("[Lkotlinx/serialization/
internal val serializationExceptionName = "kotlinx/serialization/SerializationException"
internal val serializationExceptionMissingFieldName = "kotlinx/serialization/MissingFieldException"
internal val serializationExceptionUnknownIndexName = "kotlinx/serialization/UnknownFieldException"
val OPT_MASK_TYPE: Type = Type.INT_TYPE
val OPT_MASK_BITS = 32
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen
@@ -140,6 +141,8 @@ class SerializableCodegenImpl(
goTo(nextLabel)
visitLabel(setLbl)
// setting defaultValue
if (classCodegen.bindingContext[BindingContext.BACKING_FIELD_REQUIRED, prop.descriptor] != true)
throw CompilationException("Optional properties without backing fields doesn't have much sense, maybe you want transient?", null, getProp(prop))
exprCodegen.genInitProperty(prop)
visitLabel(nextLabel)
}
@@ -150,6 +153,7 @@ class SerializableCodegenImpl(
val serializedProps = properties.serializableProperties.map { it.descriptor }
(descToProps - serializedProps)
.filter { classCodegen.shouldInitializeProperty(it.value) }
.forEach { (_, prop) -> classCodegen.initializeProperty(exprCodegen, prop) }
(paramsToProps - serializedProps)
.forEach { (t, u) -> exprCodegen.genInitParam(t, u) }
@@ -168,7 +172,8 @@ class SerializableCodegenImpl(
load(0, thisAsmType)
if (!superClass.isInternalSerializable) {
require(superClass.constructors.firstOrNull { it.valueParameters.isEmpty() } != null) { "Non-serializable parent of serializable class must have no arg constructor" }
require(superClass.constructors.firstOrNull { it.valueParameters.isEmpty() } != null,
{ "Non-serializable parent of serializable $serializableDescriptor must have no arg constructor" })
// call
invokespecial(superType, "<init>", "()V", false)
@@ -191,14 +196,18 @@ class SerializableCodegenImpl(
}
?: getParam(prop)?.let {
this.v.load(0, thisAsmType)
if (!it.hasDefaultValue()) throw CompilationException("Optional field ${it.name} in primary constructor of serializable " +
"$serializableDescriptor must have default value", null, it)
this.gen(it.defaultValue, prop.asmType)
this.v.putfield(thisAsmType.internalName, prop.name, prop.asmType.descriptor)
this.v.putfield(thisAsmType.internalName, prop.descriptor.name.asString(), prop.asmType.descriptor)
}
?: throw IllegalStateException()
private fun ExpressionCodegen.genInitParam(prop: PropertyDescriptor, param: KtParameter) {
this.v.load(0, thisAsmType)
val mapType = classCodegen.typeMapper.mapType(prop.type)
if (!param.hasDefaultValue()) throw CompilationException("Transient field ${param.name} in primary constructor of serializable " +
"$serializableDescriptor must have default value", null, param)
this.gen(param.defaultValue, mapType)
this.v.putfield(thisAsmType.internalName, prop.name.asString(), mapType.descriptor)
}
@@ -210,6 +210,7 @@ class SerializerCodegenImpl(
val labeledProperties = orderedProperties.filter { !it.transient }
val readAllLabel = Label()
val readEndLabel = Label()
val incorrectIndLabel = Label()
val labels = arrayOfNulls<Label>(labeledProperties.size + 2)
labels[0] = readAllLabel // READ_ALL
labels[1] = readEndLabel // READ_DONE
@@ -217,8 +218,7 @@ class SerializerCodegenImpl(
labels[i + 2] = Label()
}
load(indexVar, Type.INT_TYPE)
// todo: readEnd is currently default, should probably throw exception instead
tableswitch(-2, labeledProperties.size - 1, readEndLabel, *labels)
tableswitch(-2, labeledProperties.size - 1, incorrectIndLabel, *labels)
// readAll: readAll := true
visitLabel(readAllLabel)
iconst(1)
@@ -315,6 +315,15 @@ class SerializerCodegenImpl(
}
// return
areturn(serializableAsmType)
// throwing an exception in default branch (if no index matched)
visitLabel(incorrectIndLabel)
anew(Type.getObjectType(serializationExceptionUnknownIndexName))
dup()
load(indexVar, Type.INT_TYPE)
invokespecial(serializationExceptionUnknownIndexName, "<init>", "(I)V", false)
checkcast(Type.getObjectType("java/lang/Throwable"))
athrow()
}
}
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
internal val packageFqName = FqName("kotlinx.serialization")
internal val internalPackageFqName = FqName("kotlinx.serialization.internal")
@@ -147,8 +148,8 @@ fun getSerializableClassDescriptorBySerializer(serializerDescriptor: ClassDescri
return classDescriptor
}
// todo: serialization: do an actual check
fun ClassDescriptor.checkSerializableClassPropertyResult(type: KotlinType): Boolean = true
fun ClassDescriptor.checkSerializableClassPropertyResult(prop: PropertyDescriptor): Boolean =
prop.returnType!!.isSubtypeOf(getClassFromSerializationPackage("KSerialClassDesc").toSimpleType(false)) // todo: cache lookup
// todo: serialization: do an actual check better that just number of parameters
fun ClassDescriptor.checkSaveMethodParameters(parameters: List<ValueParameterDescriptor>): Boolean =
@@ -94,10 +94,10 @@ object KSerializerDescriptorResolver {
fromSupertypes: ArrayList<PropertyDescriptor>,
name: Name,
result: MutableSet<PropertyDescriptor>) {
val classDescriptor = getSerializableClassDescriptorBySerializer(thisDescriptor) ?: return
// todo: check if it is already defined
if (name == SERIAL_DESC_FIELD_NAME)
result.add(createSerializableClassPropertyDescriptor(thisDescriptor, classDescriptor))
val classDescriptor = getSerializableClassDescriptorBySerializer(thisDescriptor) ?: return
if (name == SERIAL_DESC_FIELD_NAME && result.none(thisDescriptor::checkSerializableClassPropertyResult) &&
fromSupertypes.none { thisDescriptor.checkSerializableClassPropertyResult(it) && it.modality == Modality.FINAL} )
result.add(createSerializableClassPropertyDescriptor(thisDescriptor, classDescriptor))
}