Skip to content

Commit 2191070

Browse files
authored
Merge pull request #280 from Mingun/fix-parent-calculation
Calculate parent for types that does not used in the current spec
2 parents db78a74 + aa58a1a commit 2191070

File tree

4 files changed

+43
-11
lines changed

4 files changed

+43
-11
lines changed

shared/src/main/scala/io/kaitai/struct/Main.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.kaitai.struct
22

3-
import io.kaitai.struct.format.{ClassSpec, ClassSpecs, GenericStructClassSpec, MetaSpec}
3+
import io.kaitai.struct.format.{ClassSpec, ClassSpecs, MetaSpec}
44
import io.kaitai.struct.languages.{GoCompiler, NimCompiler, RustCompiler}
55
import io.kaitai.struct.languages.components.LanguageCompilerStatic
66
import io.kaitai.struct.precompile._
@@ -60,10 +60,6 @@ object Main {
6060
val styleWarnings = new StyleCheckIds(specs).run()
6161
val encodingProblems = new CanonicalizeEncodingNames(specs).run()
6262

63-
specs.forEachTopLevel((_, spec) => {
64-
spec.parentClass = GenericStructClassSpec
65-
})
66-
6763
resolveTypeProblems ++ typeValidatorProblems ++ styleWarnings ++ encodingProblems
6864
}
6965

shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ object DataType {
232232
override def isOwning: Boolean = false
233233
}
234234

235+
/** Represents `_parent: false` expression which means that type explicitly has no parent. */
235236
val USER_TYPE_NO_PARENT = Ast.expr.Bool(false)
236237

237238
case object AnyType extends DataType

shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,20 @@ import scala.collection.mutable
1515
sealed trait ClassSpecLike {
1616
def toDataType: DataType
1717
}
18+
/**
19+
* Type was not yet calculated. If that type returned during calculation, then
20+
* cyclic reference is present and it is impossible to calculate actual type.
21+
*
22+
* Parent type of each KSY-defined type initialized to that value and refined
23+
* later based on type usage.
24+
*/
1825
case object UnknownClassSpec extends ClassSpecLike {
1926
override def toDataType: DataType = CalcKaitaiStructType()
2027
}
28+
/**
29+
* Type is calculated as a type that able to store any KSY-defined type.
30+
* Usually it have a name `KaitaiStruct` in corresponding language runtime library.
31+
*/
2132
case object GenericStructClassSpec extends ClassSpecLike {
2233
override def toDataType: DataType = CalcKaitaiStructType()
2334
}

shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ import io.kaitai.struct.datatype.DataType.{ArrayTypeInStream, SwitchType, UserTy
66
import io.kaitai.struct.format._
77
import io.kaitai.struct.translators.TypeDetector
88

9+
/**
10+
* Precompile step that calculates actual parent types of KSY-defined types
11+
* (the type of the `_parent` built-in property).
12+
*/
913
class ParentTypes(classSpecs: ClassSpecs) {
1014
def run(): Unit = {
1115
classSpecs.foreach { case (_, curClass) => markup(curClass) }
16+
classSpecs.forEachTopLevel((_, spec) => {
17+
spec.parentClass = GenericStructClassSpec
18+
})
1219
}
1320

1421
def markup(curClass: ClassSpec): Unit = {
15-
Log.typeProcParent.info(() => s"markupParentTypes(${curClass.nameAsStr})")
22+
Log.typeProcParent.info(() => s"ParentTypes.markup(${curClass.nameAsStr})")
1623

1724
if (curClass.seq.nonEmpty)
1825
Log.typeProcParent.info(() => s"... seq")
@@ -30,30 +37,42 @@ class ParentTypes(classSpecs: ClassSpecs) {
3037
// value instances have no effect on parenting, just do nothing
3138
}
3239
}
40+
41+
if (curClass.types.nonEmpty)
42+
Log.typeProcParent.info(() => s"... types")
43+
curClass.types.foreach { case (_, ty) =>
44+
// If parent is not decided yet, calculate it
45+
if (ty.parentClass == UnknownClassSpec) {
46+
markup(ty)
47+
}
48+
}
3349
}
3450

51+
/** Calculates `parent` of `dt` */
3552
private
3653
def markupParentTypesAdd(curClass: ClassSpec, dt: DataType): Unit = {
3754
dt match {
3855
case userType: UserType =>
39-
(userType.forcedParent match {
56+
userType.forcedParent match {
57+
// `parent` key is not specified in attribute
4058
case None =>
41-
Some(curClass)
59+
markupParentAs(curClass, userType)
60+
// `parent: false` specified in attribute
4261
case Some(DataType.USER_TYPE_NO_PARENT) =>
4362
Log.typeProcParent.info(() => s"..... no parent type added")
44-
None
63+
// `parent: <expression>` specified in attribute
4564
case Some(parent) =>
4665
val provider = new ClassTypeProvider(classSpecs, curClass)
4766
val detector = new TypeDetector(provider)
4867
val parentType = detector.detectType(parent)
4968
Log.typeProcParent.info(() => s"..... enforced parent type = $parentType")
5069
parentType match {
5170
case ut: UserType =>
52-
Some(ut.classSpec.get)
71+
markupParentAs(ut.classSpec.get, userType)
5372
case other =>
5473
throw new TypeMismatchError(s"parent=$parent is expected to be either of user type or `false`, but $other found")
5574
}
56-
}).foreach((parentClass) => markupParentAs(parentClass, userType))
75+
}
5776
case switchType: SwitchType =>
5877
switchType.cases.foreach {
5978
case (_, ut: UserType) =>
@@ -77,6 +96,11 @@ class ParentTypes(classSpecs: ClassSpecs) {
7796
}
7897
}
7998

99+
/**
100+
* If parent of `child` is not calculated yet, makes `parent` the parent
101+
* type. Otherwise, if `parent` is different from existing parent, replaces
102+
* parent type with the most generic KS type for user types.
103+
*/
80104
def markupParentAs(parent: ClassSpec, child: ClassSpec): Unit = {
81105
// Don't allow type usages across spec boundaries to affect parent resolution
82106
if (child.isExternal(parent)) {

0 commit comments

Comments
 (0)