Skip to content

Commit

Permalink
Merge pull request #280 from Mingun/fix-parent-calculation
Browse files Browse the repository at this point in the history
Calculate parent for types that does not used in the current spec
  • Loading branch information
generalmimon authored Apr 15, 2024
2 parents db78a74 + aa58a1a commit 2191070
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 11 deletions.
6 changes: 1 addition & 5 deletions shared/src/main/scala/io/kaitai/struct/Main.scala
Original file line number Diff line number Diff line change
@@ -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._
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -30,30 +37,42 @@ 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: <expression>` specified in attribute
case Some(parent) =>
val provider = new ClassTypeProvider(classSpecs, curClass)
val detector = new TypeDetector(provider)
val parentType = detector.detectType(parent)
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) =>
Expand All @@ -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)) {
Expand Down

0 comments on commit 2191070

Please sign in to comment.