diff --git a/shared/src/main/scala/io/kaitai/struct/Main.scala b/shared/src/main/scala/io/kaitai/struct/Main.scala index 0ab95da3b..7ac34ef6e 100644 --- a/shared/src/main/scala/io/kaitai/struct/Main.scala +++ b/shared/src/main/scala/io/kaitai/struct/Main.scala @@ -1,6 +1,6 @@ package io.kaitai.struct -import io.kaitai.struct.format.{ClassSpec, ClassSpecs, GenericStructClassSpec, MetaSpec} +import io.kaitai.struct.format.{ClassSpec, ClassSpecs, MetaSpec} import io.kaitai.struct.languages.{GoCompiler, NimCompiler, RustCompiler} import io.kaitai.struct.languages.components.LanguageCompilerStatic import io.kaitai.struct.precompile._ @@ -60,10 +60,6 @@ object Main { val styleWarnings = new StyleCheckIds(specs).run() val encodingProblems = new CanonicalizeEncodingNames(specs).run() - specs.forEachTopLevel((_, spec) => { - spec.parentClass = GenericStructClassSpec - }) - resolveTypeProblems ++ typeValidatorProblems ++ styleWarnings ++ encodingProblems } diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala index 1bcc45ae5..546c0afbe 100644 --- a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala +++ b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala @@ -232,6 +232,7 @@ object DataType { override def isOwning: Boolean = false } + /** Represents `_parent: false` expression which means that type explicitly has no parent. */ val USER_TYPE_NO_PARENT = Ast.expr.Bool(false) case object AnyType extends DataType diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala index 644b53971..6a3cb0135 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala @@ -15,9 +15,20 @@ import scala.collection.mutable sealed trait ClassSpecLike { def toDataType: DataType } +/** + * Type was not yet calculated. If that type returned during calculation, then + * cyclic reference is present and it is impossible to calculate actual type. + * + * Parent type of each KSY-defined type initialized to that value and refined + * later based on type usage. + */ case object UnknownClassSpec extends ClassSpecLike { override def toDataType: DataType = CalcKaitaiStructType() } +/** + * Type is calculated as a type that able to store any KSY-defined type. + * Usually it have a name `KaitaiStruct` in corresponding language runtime library. + */ case object GenericStructClassSpec extends ClassSpecLike { override def toDataType: DataType = CalcKaitaiStructType() } diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala index a3e0a40b5..2929f4e5f 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala @@ -6,13 +6,20 @@ import io.kaitai.struct.datatype.DataType.{ArrayTypeInStream, SwitchType, UserTy import io.kaitai.struct.format._ import io.kaitai.struct.translators.TypeDetector +/** + * Precompile step that calculates actual parent types of KSY-defined types + * (the type of the `_parent` built-in property). + */ class ParentTypes(classSpecs: ClassSpecs) { def run(): Unit = { classSpecs.foreach { case (_, curClass) => markup(curClass) } + classSpecs.forEachTopLevel((_, spec) => { + spec.parentClass = GenericStructClassSpec + }) } def markup(curClass: ClassSpec): Unit = { - Log.typeProcParent.info(() => s"markupParentTypes(${curClass.nameAsStr})") + Log.typeProcParent.info(() => s"ParentTypes.markup(${curClass.nameAsStr})") if (curClass.seq.nonEmpty) Log.typeProcParent.info(() => s"... seq") @@ -30,18 +37,30 @@ class ParentTypes(classSpecs: ClassSpecs) { // value instances have no effect on parenting, just do nothing } } + + if (curClass.types.nonEmpty) + Log.typeProcParent.info(() => s"... types") + curClass.types.foreach { case (_, ty) => + // If parent is not decided yet, calculate it + if (ty.parentClass == UnknownClassSpec) { + markup(ty) + } + } } + /** Calculates `parent` of `dt` */ private def markupParentTypesAdd(curClass: ClassSpec, dt: DataType): Unit = { dt match { case userType: UserType => - (userType.forcedParent match { + userType.forcedParent match { + // `parent` key is not specified in attribute case None => - Some(curClass) + markupParentAs(curClass, userType) + // `parent: false` specified in attribute case Some(DataType.USER_TYPE_NO_PARENT) => Log.typeProcParent.info(() => s"..... no parent type added") - None + // `parent: ` specified in attribute case Some(parent) => val provider = new ClassTypeProvider(classSpecs, curClass) val detector = new TypeDetector(provider) @@ -49,11 +68,11 @@ class ParentTypes(classSpecs: ClassSpecs) { Log.typeProcParent.info(() => s"..... enforced parent type = $parentType") parentType match { case ut: UserType => - Some(ut.classSpec.get) + markupParentAs(ut.classSpec.get, userType) case other => throw new TypeMismatchError(s"parent=$parent is expected to be either of user type or `false`, but $other found") } - }).foreach((parentClass) => markupParentAs(parentClass, userType)) + } case switchType: SwitchType => switchType.cases.foreach { case (_, ut: UserType) => @@ -77,6 +96,11 @@ class ParentTypes(classSpecs: ClassSpecs) { } } + /** + * If parent of `child` is not calculated yet, makes `parent` the parent + * type. Otherwise, if `parent` is different from existing parent, replaces + * parent type with the most generic KS type for user types. + */ def markupParentAs(parent: ClassSpec, child: ClassSpec): Unit = { // Don't allow type usages across spec boundaries to affect parent resolution if (child.isExternal(parent)) {