Skip to content
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

Calculate parent for types that does not used in the current spec #280

Merged
merged 5 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading