From e0fb1b84c1207d62ec444331e8ff2a8608254782 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 14:59:19 +0500 Subject: [PATCH 01/12] Rename auxiliary functions that implements resolving type and enum names The same names makes it difficult to recognize where which function is used even with help from IDE Also make some functions private to not pollute public namespace. The other methods will be tested so cannot be made private because it creates difficulties to test them --- .../io/kaitai/struct/ClassTypeProvider.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index b544dc6a8..cd6dadbe4 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -118,16 +118,16 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends } override def resolveEnum(inType: Ast.typeId, enumName: String): EnumSpec = - resolveEnum(resolveClassSpec(inType), enumName) + resolveEnumName(resolveClassSpec(inType), enumName) - def resolveEnum(inClass: ClassSpec, enumName: String): EnumSpec = { + private def resolveEnumName(inClass: ClassSpec, enumName: String): EnumSpec = { inClass.enums.get(enumName) match { case Some(spec) => spec case None => // let's try upper levels of hierarchy inClass.upClass match { - case Some(upClass) => resolveEnum(upClass, enumName) + case Some(upClass) => resolveEnumName(upClass, enumName) case None => throw new EnumNotFoundError(enumName, nowClass) } @@ -137,26 +137,26 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends override def resolveType(typeName: Ast.typeId): DataType = resolveClassSpec(typeName).toDataType - def resolveClassSpec(typeName: Ast.typeId): ClassSpec = - resolveClassSpec( + private def resolveClassSpec(typeName: Ast.typeId): ClassSpec = + resolveTypePath( if (typeName.absolute) topClass else nowClass, typeName.names ) - def resolveClassSpec(inClass: ClassSpec, typeName: Seq[String]): ClassSpec = { - if (typeName.isEmpty) + def resolveTypePath(inClass: ClassSpec, path: Seq[String]): ClassSpec = { + if (path.isEmpty) return inClass - val headTypeName :: restTypesNames = typeName.toList - val nextClass = resolveClassSpec(inClass, headTypeName) + val headTypeName :: restTypesNames = path.toList + val nextClass = resolveTypeName(inClass, headTypeName) if (restTypesNames.isEmpty) { nextClass } else { - resolveClassSpec(nextClass, restTypesNames) + resolveTypePath(nextClass, restTypesNames) } } - def resolveClassSpec(inClass: ClassSpec, typeName: String): ClassSpec = { + def resolveTypeName(inClass: ClassSpec, typeName: String): ClassSpec = { if (inClass.name.last == typeName) return inClass @@ -166,7 +166,7 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends case None => // let's try upper levels of hierarchy inClass.upClass match { - case Some(upClass) => resolveClassSpec(upClass, typeName) + case Some(upClass) => resolveTypeName(upClass, typeName) case None => classSpecs.get(typeName) match { case Some(spec) => spec From a467d6d4da185bc1508afa7b7eafa81ea2e1bc96 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 19:03:10 +0500 Subject: [PATCH 02/12] Wrap type name in '' because it is not always clear that part of a message is a type name Especially when the type name is something very generic, like 'test' --- .../main/scala/io/kaitai/struct/precompile/Exceptions.scala | 6 +++--- .../io/kaitai/struct/problems/CompilationProblem.scala | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala index 1ac6035bb..a22e25c20 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala @@ -16,11 +16,11 @@ class WrongMethodCall(val dataType: MethodArgType, val methodName: String, val e sealed abstract class NotFoundError(msg: String) extends ExpressionError(msg) class TypeNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find type '$name', searching from ${curClass.nameAsStr}") + extends NotFoundError(s"unable to find type '$name', searching from '${curClass.nameAsStr}'") class FieldNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to access '$name' in ${curClass.nameAsStr} context") + extends NotFoundError(s"unable to access '$name' in '${curClass.nameAsStr}' context") class EnumNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find enum '$name', searching from ${curClass.nameAsStr}") + extends NotFoundError(s"unable to find enum '$name', searching from '${curClass.nameAsStr}'") class EnumMemberNotFoundError(val label: String, val enumName: String, val enumDefPath: String) extends NotFoundError(s"unable to find enum member '$enumName::$label' (enum '$enumName' defined at /$enumDefPath)") diff --git a/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala b/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala index 2779887fb..4c277622a 100644 --- a/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala +++ b/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala @@ -173,7 +173,7 @@ case class ParamMismatchError(idx: Int, argType: DataType, paramName: String, pa case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) extends CompilationProblem { - override def text = s"unable to find type '${name.mkString("::")}', searching from ${curClass.nameAsStr}" + override def text = s"unable to find type '${name.mkString("::")}', searching from '${curClass.nameAsStr}'" override val coords: ProblemCoords = ProblemCoords(fileName, Some(path)) override def localizedInFile(fileName: String): CompilationProblem = copy(fileName = Some(fileName)) @@ -183,7 +183,7 @@ case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[S case class EnumNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) extends CompilationProblem { - override def text = s"unable to find enum '${name.mkString("::")}', searching from ${curClass.nameAsStr}" + override def text = s"unable to find enum '${name.mkString("::")}', searching from '${curClass.nameAsStr}'" override val coords: ProblemCoords = ProblemCoords(fileName, Some(path)) override def localizedInFile(fileName: String): CompilationProblem = copy(fileName = Some(fileName)) From 1cec26efa5a83cd7cb2b8e7f67a20e87394295f0 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 16:55:21 +0500 Subject: [PATCH 03/12] Add tests for type name resolution Error text does not checked yet, because it will be changed and even those tests which passed now, would fail due to that Failures (12): [info] ClassTypeProvider$Test: [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:287) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:324) [info] - doesn't resolve 'two::one' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:336) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:361) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:398) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:435) [info] - doesn't resolve 'two::one' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:447) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:472) [info] - doesn't resolve 'two::one' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:484) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:509) [info] - doesn't resolve 'one::two' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:546) [info] - doesn't resolve 'two::one' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.TypeNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:558) --- .../struct/ClassTypeProvider$Test.scala | 566 ++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala diff --git a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala new file mode 100644 index 000000000..5063b8916 --- /dev/null +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -0,0 +1,566 @@ +package io.kaitai.struct + +import java.util.NoSuchElementException +import io.kaitai.struct.format.{ClassSpec, ClassSpecs} +import io.kaitai.struct.formats.{JavaClassSpecs, JavaKSYParser} +import io.kaitai.struct.precompile.{EnumNotFoundError, MarkupClassNames, TypeNotFoundError} +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers._ + +class ClassTypeProvider$Test extends AnyFunSpec { + val root = ClassSpec.fromYaml(JavaKSYParser.stringToYaml(""" + meta: + id: root + types: + child_1: + types: + one: {} # child_11 + two: # child_12 + types: + one: {} # child_121 + two: {} # child_122 + child_2: + types: + one: {} # child_21 + two: {} # child_22 + """), None) + val specs = new JavaClassSpecs("", Seq(), root) + // Calculates full class names needed for work of the provider + new MarkupClassNames(specs).run() + + val child_1 = root.types.get("child_1").getOrElse(throw new NoSuchElementException("'child_1' not found")) + val child_2 = root.types.get("child_2").getOrElse(throw new NoSuchElementException("'child_2' not found")) + + val child_11 = child_1.types.get("one").getOrElse(throw new NoSuchElementException("'child_11' not found")) + val child_12 = child_1.types.get("two").getOrElse(throw new NoSuchElementException("'child_12' not found")) + + val child_21 = child_2.types.get("one").getOrElse(throw new NoSuchElementException("'child_21' not found")) + val child_22 = child_2.types.get("two").getOrElse(throw new NoSuchElementException("'child_22' not found")) + + val child_121 = child_12.types.get("one").getOrElse(throw new NoSuchElementException("'child_121' not found")) + val child_122 = child_12.types.get("two").getOrElse(throw new NoSuchElementException("'child_122' not found")) + + describe("resolveTypeName") { + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves 'root'") { + resolver.resolveTypeName(root, "root") should be(root) // self-reference + } + + it("doesn't resolve 'one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "one") + } + + it("doesn't resolve 'two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "two") + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "unknown") + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_1, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_1, "one") should be(child_11) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_1, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_1, "unknown") + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_2, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_2, "one") should be(child_21) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_2, "two") should be(child_22) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_2, "unknown") + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_11, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_11, "one") should be(child_11) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_11, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_11, "unknown") + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_12, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_12, "one") should be(child_121) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_12, "two") should be(child_12) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_12, "unknown") + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_21, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_21, "one") should be(child_21) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_21, "two") should be(child_22) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_21, "unknown") + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_22, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_22, "one") should be(child_21) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_22, "two") should be(child_22) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_22, "unknown") + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_121, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_121, "one") should be(child_121) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_121, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_121, "unknown") + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_122, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_122, "one") should be(child_121) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_122, "two") should be(child_122) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_122, "unknown") + } + } + } + + describe("resolveTypePath") { + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves empty path") { + resolver.resolveTypePath(root, Seq()) should be(root) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(root, Seq("root")) should be(root) // self-reference + } + + it("doesn't resolve 'one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one")) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "unknown")) + } + + it("doesn't resolve 'two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two")) + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "one")) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "unknown")) + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_1, Seq()) should be(child_1) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_1, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_1, Seq("one")) should be(child_11) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_1, Seq("two")) should be(child_12) + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_1, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("two", "unknown")) + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_2, Seq()) should be(child_2) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_2, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_2, Seq("one")) should be(child_21) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_2, Seq("two")) should be(child_22) + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "one")) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "unknown")) + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_11, Seq()) should be(child_11) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_11, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_11, Seq("one")) should be(child_11) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_11, Seq("two")) should be(child_12) + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_11, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("two", "unknown")) + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_12, Seq()) should be(child_12) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_12, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_12, Seq("one")) should be(child_121) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_12, Seq("two")) should be(child_12) // self-reference + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_12, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("two", "unknown")) + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_21, Seq()) should be(child_21) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_21, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_21, Seq("one")) should be(child_21) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_21, Seq("two")) should be(child_22) + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "one")) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "unknown")) + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_22, Seq()) should be(child_22) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_22, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_22, Seq("one")) should be(child_21) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_22, Seq("two")) should be(child_22) // self-reference + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "one")) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "unknown")) + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_121, Seq()) should be(child_121) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_121, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_121, Seq("one")) should be(child_121) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_121, Seq("two")) should be(child_12) + } + + it("doesn't resolve 'two::one'") { + resolver.resolveTypePath(child_121, Seq("two", "one")) should be(child_121) // self-reference + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("two", "unknown")) + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_122, Seq()) should be(child_122) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_122, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_122, Seq("one")) should be(child_121) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "two")) + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "unknown")) + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_122, Seq("two")) should be(child_122) // self-reference + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "one")) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "unknown")) + } + } + } +} From 2f0f5c04e7c445476c2539e7af5f286d999f4874 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 20:16:54 +0500 Subject: [PATCH 04/12] Fix type resolution in expressions Related https://github.com/kaitai-io/kaitai_struct/issues/786 --- .../struct/ClassTypeProvider$Test.scala | 45 +++++++++++++++++++ .../io/kaitai/struct/ClassTypeProvider.scala | 17 ++++--- .../kaitai/struct/precompile/Exceptions.scala | 7 ++- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala index 5063b8916..5457ee6c7 100644 --- a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -50,14 +50,17 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "one") + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "two") + thrown.getMessage should be("unable to find type 'two', searching from 'root'") } it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root'") } } @@ -79,6 +82,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_1, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1'") } } @@ -100,6 +104,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_2, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2'") } } @@ -121,6 +126,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_11, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::one'") } } @@ -142,6 +148,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_12, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two'") } } @@ -163,6 +170,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_21, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::one'") } } @@ -184,6 +192,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_22, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::two'") } } @@ -205,6 +214,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_121, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::one'") } } @@ -226,6 +236,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_122, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::two'") } } } @@ -244,26 +255,32 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") } it("doesn't resolve 'two::one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") } it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") } } @@ -285,10 +302,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::one'") } it("resolves 'two'") { @@ -301,6 +320,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") } } @@ -322,10 +342,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") } it("resolves 'two'") { @@ -334,10 +356,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") } it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") } } @@ -359,10 +383,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::one'") } it("resolves 'two'") { @@ -375,6 +401,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") } } @@ -396,10 +423,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") } it("resolves 'two'") { @@ -412,6 +441,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") } } @@ -433,10 +463,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") } it("resolves 'two'") { @@ -445,10 +477,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") } it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") } } @@ -470,10 +504,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") } it("resolves 'two'") { @@ -482,10 +518,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") } it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") } } @@ -507,10 +545,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") } it("resolves 'two'") { @@ -523,6 +563,7 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") } } @@ -544,10 +585,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") } it("resolves 'two'") { @@ -556,10 +599,12 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'two::one'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_1::two::two'") } it("doesn't resolve 'two::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::two'") } } } diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index cd6dadbe4..11a9b08fb 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -4,7 +4,7 @@ import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType._ import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ -import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundError, TypeUndecidedError} +import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundInHierarchyError, TypeNotFoundInTypeError, TypeUndecidedError} import io.kaitai.struct.translators.TypeProvider class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends TypeProvider { @@ -148,12 +148,15 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends return inClass val headTypeName :: restTypesNames = path.toList - val nextClass = resolveTypeName(inClass, headTypeName) - if (restTypesNames.isEmpty) { - nextClass - } else { - resolveTypePath(nextClass, restTypesNames) + var nextClass = resolveTypeName(inClass, headTypeName) + for (name <- restTypesNames) { + nextClass = nextClass.types.get(name) match { + case Some(spec) => spec + case None => + throw new TypeNotFoundInTypeError(name, nextClass) + } } + nextClass } def resolveTypeName(inClass: ClassSpec, typeName: String): ClassSpec = { @@ -171,7 +174,7 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends classSpecs.get(typeName) match { case Some(spec) => spec case None => - throw new TypeNotFoundError(typeName, nowClass) + throw new TypeNotFoundInHierarchyError(typeName, nowClass) } } } diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala index a22e25c20..e531ba48b 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala @@ -15,8 +15,11 @@ class WrongMethodCall(val dataType: MethodArgType, val methodName: String, val e extends ExpressionError(s"wrong arguments to method call `$methodName` on $dataType: expected ${expectedSigs.mkString(" or ")}, got $actualSig") sealed abstract class NotFoundError(msg: String) extends ExpressionError(msg) -class TypeNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find type '$name', searching from '${curClass.nameAsStr}'") +sealed abstract class TypeNotFoundError(msg: String) extends NotFoundError(msg) +class TypeNotFoundInHierarchyError(val name: String, val curClass: ClassSpec) + extends TypeNotFoundError(s"unable to find type '$name', searching from '${curClass.nameAsStr}'") +class TypeNotFoundInTypeError(val name: String, val curClass: ClassSpec) + extends TypeNotFoundError(s"unable to find type '$name' in '${curClass.nameAsStr}'") class FieldNotFoundError(val name: String, val curClass: ClassSpec) extends NotFoundError(s"unable to access '$name' in '${curClass.nameAsStr}' context") class EnumNotFoundError(val name: String, val curClass: ClassSpec) From 595efb67b882582bc4a2bdfc4752f2c7dbb4e01c Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 22:02:45 +0500 Subject: [PATCH 05/12] Add tests for enum resolution in expressions Error text does not checked yet, because it will be changed and even those tests which passed now, would fail due to that Failures (6): [info] ClassTypeProvider$Test: [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:690) [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:740) [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:765) [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:790) [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:815) [info] - doesn't resolve 'one::e' *** FAILED *** [info] Expected exception io.kaitai.struct.precompile.EnumNotFoundError to be thrown, but no exception was thrown (ClassTypeProvider$Test.scala:840) --- .../struct/ClassTypeProvider$Test.scala | 249 +++++++++++++++++- 1 file changed, 247 insertions(+), 2 deletions(-) diff --git a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala index 5457ee6c7..7e5729196 100644 --- a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -1,6 +1,7 @@ package io.kaitai.struct import java.util.NoSuchElementException +import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format.{ClassSpec, ClassSpecs} import io.kaitai.struct.formats.{JavaClassSpecs, JavaKSYParser} import io.kaitai.struct.precompile.{EnumNotFoundError, MarkupClassNames, TypeNotFoundError} @@ -11,15 +12,23 @@ class ClassTypeProvider$Test extends AnyFunSpec { val root = ClassSpec.fromYaml(JavaKSYParser.stringToYaml(""" meta: id: root + enums: + e: {} # e_root types: child_1: types: - one: {} # child_11 - two: # child_12 + one: # child_11 + enums: + e: {} # e_11 + two: # child_12 + enums: + e: {} # e_12 types: one: {} # child_121 two: {} # child_122 child_2: + enums: + e: {} # e_2 types: one: {} # child_21 two: {} # child_22 @@ -608,4 +617,240 @@ class ClassTypeProvider$Test extends AnyFunSpec { } } } + + describe("resolveEnum") { + val e_root = root.enums.get("e").getOrElse(throw new NoSuchElementException("'e_root' not found")) + val e_11 = child_11.enums.get("e").getOrElse(throw new NoSuchElementException("'e_11' not found")) + val e_12 = child_12.enums.get("e").getOrElse(throw new NoSuchElementException("'e_12' not found")) + val e_2 = child_2.enums.get("e").getOrElse(throw new NoSuchElementException("'e_2' not found")) + + val none = Ast.typeId(false, Seq()) + val one = Ast.typeId(false, Seq("one")) + val one_two = Ast.typeId(false, Seq("one", "two")) + val unknown = Ast.typeId(false, Seq("unknown")) + + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_root) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_root) + } + + it("resolves 'one::e'") { + resolver.resolveEnum(one, "e") should be(e_11) + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_11) + } + + it("resolves 'one::e'") { + resolver.resolveEnum(one, "e") should be(e_11) + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 + + it("resolves 'e'") { + resolver.resolveEnum(none, "e") should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + } + } + } } From 293f4a251efc92b8b590cb7a8f3f6624fec89a3f Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 22:53:27 +0500 Subject: [PATCH 06/12] Fix enum resolution in expressions Related https://github.com/kaitai-io/kaitai_struct/issues/1028 --- .../struct/ClassTypeProvider$Test.scala | 34 +++++++++++++++++++ .../io/kaitai/struct/ClassTypeProvider.scala | 21 +++++++++--- .../kaitai/struct/precompile/Exceptions.scala | 9 +++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala index 7e5729196..2a97388e8 100644 --- a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -638,18 +638,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'one::unknown'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find type 'one', searching from 'root'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root'") } } @@ -667,14 +671,17 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1'") } } @@ -688,18 +695,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2'") } } @@ -717,14 +728,17 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::one'") } } @@ -738,18 +752,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two'") } } @@ -763,18 +781,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::one'") } } @@ -788,18 +810,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::two'") } } @@ -813,18 +839,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::one'") } } @@ -838,18 +868,22 @@ class ClassTypeProvider$Test extends AnyFunSpec { it("doesn't resolve 'one::e'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "e") + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") } it("doesn't resolve 'one::two::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two, "e") + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") } it("doesn't resolve 'one::unknown'") { val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") } it("doesn't resolve 'unknown::e'") { val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown, "e") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::two'") } } } diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index 11a9b08fb..21a9cd74b 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -4,7 +4,7 @@ import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType._ import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ -import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundInHierarchyError, TypeNotFoundInTypeError, TypeUndecidedError} +import io.kaitai.struct.precompile.{EnumNotFoundInHierarchyError, EnumNotFoundInTypeError, FieldNotFoundError, TypeNotFoundInHierarchyError, TypeNotFoundInTypeError, TypeUndecidedError} import io.kaitai.struct.translators.TypeProvider class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends TypeProvider { @@ -117,8 +117,21 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends throw new FieldNotFoundError(attrName, inClass) } - override def resolveEnum(inType: Ast.typeId, enumName: String): EnumSpec = - resolveEnumName(resolveClassSpec(inType), enumName) + override def resolveEnum(inType: Ast.typeId, enumName: String): EnumSpec = { + val inClass = if (inType.absolute) topClass else nowClass + // When concrete type is not defined, search enum definition in all enclosing types + if (inType.names.isEmpty) { + resolveEnumName(inClass, enumName) + } else { + val ty = resolveTypePath(inClass, inType.names) + ty.enums.get(enumName) match { + case Some(spec) => + spec + case None => + throw new EnumNotFoundInTypeError(enumName, ty) + } + } + } private def resolveEnumName(inClass: ClassSpec, enumName: String): EnumSpec = { inClass.enums.get(enumName) match { @@ -129,7 +142,7 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends inClass.upClass match { case Some(upClass) => resolveEnumName(upClass, enumName) case None => - throw new EnumNotFoundError(enumName, nowClass) + throw new EnumNotFoundInHierarchyError(enumName, nowClass) } } } diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala index e531ba48b..37abe2ecf 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala @@ -22,8 +22,13 @@ class TypeNotFoundInTypeError(val name: String, val curClass: ClassSpec) extends TypeNotFoundError(s"unable to find type '$name' in '${curClass.nameAsStr}'") class FieldNotFoundError(val name: String, val curClass: ClassSpec) extends NotFoundError(s"unable to access '$name' in '${curClass.nameAsStr}' context") -class EnumNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find enum '$name', searching from '${curClass.nameAsStr}'") + +sealed abstract class EnumNotFoundError(msg: String) extends NotFoundError(msg) +class EnumNotFoundInHierarchyError(val name: String, val curClass: ClassSpec) + extends EnumNotFoundError(s"unable to find enum '$name', searching from '${curClass.nameAsStr}'") +class EnumNotFoundInTypeError(val name: String, val curClass: ClassSpec) + extends EnumNotFoundError(s"unable to find enum '$name' in '${curClass.nameAsStr}'") + class EnumMemberNotFoundError(val label: String, val enumName: String, val enumDefPath: String) extends NotFoundError(s"unable to find enum member '$enumName::$label' (enum '$enumName' defined at /$enumDefPath)") From 88c9a6bafca660463f790e6a6200ae7c82c43706 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 22:55:17 +0500 Subject: [PATCH 07/12] Remove now used only in one place `ClassTypeProvider.resolveClassSpec` --- .../src/main/scala/io/kaitai/struct/ClassTypeProvider.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index 21a9cd74b..811b2f35b 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -148,13 +148,10 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends } override def resolveType(typeName: Ast.typeId): DataType = - resolveClassSpec(typeName).toDataType - - private def resolveClassSpec(typeName: Ast.typeId): ClassSpec = resolveTypePath( if (typeName.absolute) topClass else nowClass, typeName.names - ) + ).toDataType def resolveTypePath(inClass: ClassSpec, path: Seq[String]): ClassSpec = { if (path.isEmpty) From b2be2d59f9c02b64e936abca4cbdbf168de254a5 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 23:06:33 +0500 Subject: [PATCH 08/12] Make all internal methods of ResolveTypes pass private --- .../io/kaitai/struct/precompile/ResolveTypes.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index f6f0c888b..4fb8e27e0 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -19,7 +19,7 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) * ClassSpec. * @param curClass class to start from, might be top-level class */ - def resolveUserTypes(curClass: ClassSpec): Iterable[CompilationProblem] = { + private def resolveUserTypes(curClass: ClassSpec): Iterable[CompilationProblem] = { val seqProblems: Iterable[CompilationProblem] = curClass.seq.flatMap((attr) => resolveUserTypeForMember(curClass, attr)) @@ -40,10 +40,10 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) seqProblems ++ instancesProblems ++ paramsProblems } - def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = + private def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = resolveUserType(curClass, attr.dataType, attr.path) - def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { + private def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { dataType match { case ut: UserType => val (resClassSpec, problems) = resolveUserType(curClass, ut.name, path ++ List("type")) @@ -68,7 +68,7 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } } - def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { + private def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { val res = realResolveUserType(curClass, typeName, path) res match { @@ -136,7 +136,7 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } } - def resolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { + private def resolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { Log.enumResolve.info(() => s"resolveEnumSpec: at ${curClass.name} doing ${typeName.mkString("|")}") val res = realResolveEnumSpec(curClass, typeName) From 738fa45b148958365d79aeb7b32a958b97d97d03 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 23:34:18 +0500 Subject: [PATCH 09/12] Fix enum resolution in `enum` keys This commit also unify enum resolution in expression language and in `enum` keys Fixes https://github.com/kaitai-io/kaitai_struct/issues/1028 --- .../struct/precompile/ResolveTypes.scala | 89 +++++-------------- 1 file changed, 23 insertions(+), 66 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index 4fb8e27e0..9111ebd2a 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -1,8 +1,9 @@ package io.kaitai.struct.precompile -import io.kaitai.struct.Log +import io.kaitai.struct.{ClassTypeProvider, Log} import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType.{ArrayType, EnumType, SwitchType, UserType} +import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ import io.kaitai.struct.problems._ @@ -50,11 +51,27 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) ut.classSpec = resClassSpec problems case et: EnumType => - et.enumSpec = resolveEnumSpec(curClass, et.name) - if (et.enumSpec.isEmpty) { - Some(EnumNotFoundErr(et.name, curClass, path ++ List("enum"))) - } else { - None + et.name match { + case typePath :+ name => + try { + val resolver = new ClassTypeProvider(specs, curClass) + val ty = resolver.resolveEnum(Ast.typeId(false, typePath), name) + Log.enumResolve.info(() => s" => ${ty.nameAsStr}") + et.enumSpec = Some(ty) + None + } catch { + case ex: TypeNotFoundError => + Log.typeResolve.info(() => s" => ??? (while resolving enum '${et.name}'): $ex") + Log.enumResolve.info(() => s" => ??? (enclosing type not found, enum '${et.name}'): $ex") + Some(TypeNotFoundErr(typePath, curClass, path :+ "enum")) + case ex: EnumNotFoundError => + Log.enumResolve.info(() => s" => ??? (enum '${et.name}'): $ex") + Some(EnumNotFoundErr(et.name, curClass, path :+ "enum")) + } + case _ => + Log.enumResolve.info(() => s" => ??? (enum '${et.name}' without name)") + // TODO: Maybe more specific error about empty name? + Some(EnumNotFoundErr(et.name, curClass, path :+ "enum")) } case st: SwitchType => st.cases.flatMap { case (caseName, ut) => @@ -135,64 +152,4 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } } } - - private def resolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { - Log.enumResolve.info(() => s"resolveEnumSpec: at ${curClass.name} doing ${typeName.mkString("|")}") - - val res = realResolveEnumSpec(curClass, typeName) - res match { - case None => { - Log.enumResolve.info(() => s" => ???") - res - } - case Some(x) => { - Log.enumResolve.info(() => s" => ${x.nameAsStr}") - res - } - } - } - - private def realResolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { - // First, try to do it in current class - - // If we're seeking composite name, we only have to resolve the very first - // part of it at this stage - val firstName :: restNames = typeName - - val resolvedHere = if (restNames.isEmpty) { - curClass.enums.get(firstName) - } else { - curClass.types.get(firstName).flatMap((nestedClass) => - resolveEnumSpec(nestedClass, restNames) - ) - } - - resolvedHere match { - case Some(_) => resolvedHere - case None => - // No luck resolving here, let's try upper levels, if they exist - curClass.upClass match { - case Some(upClass) => - resolveEnumSpec(upClass, typeName) - case None => - // Check this class if it's top-level class - if (curClass.name.head == firstName) { - resolveEnumSpec(curClass, restNames) - } else { - // Check if top-level specs has this name - // If there's None => no luck at all - val resolvedTop = specs.get(firstName) - resolvedTop match { - case None => None - case Some(classSpec) => if (restNames.isEmpty) { - // resolved everything, but this points to a type name, not enum name - None - } else { - resolveEnumSpec(classSpec, restNames) - } - } - } - } - } - } } From a1831ef49854879aee1b7f5d1860da970587cd2e Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 23:43:43 +0500 Subject: [PATCH 10/12] Fix type resolution in `type` keys This commit also unify type resolution in expression language and in `type` keys Fixes https://github.com/kaitai-io/kaitai_struct/issues/786 --- .../struct/precompile/ResolveTypes.scala | 55 +++---------------- 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index 9111ebd2a..c7d672b80 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -86,7 +86,13 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } private def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { - val res = realResolveUserType(curClass, typeName, path) + val res = try { + val resolver = new ClassTypeProvider(specs, curClass) + Some(resolver.resolveTypePath(curClass, typeName)) + } catch { + case _: TypeNotFoundError => + None + } res match { case None => @@ -105,51 +111,4 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) (res, None) } } - - private def realResolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): Option[ClassSpec] = { - Log.typeResolve.info(() => s"resolveUserType: at ${curClass.name} doing ${typeName.mkString("|")}") - - // First, try to do it in current class - - // If we're seeking composite name, we only have to resolve the very first - // part of it at this stage - val firstName :: restNames = typeName - - val resolvedHere = curClass.types.get(firstName).flatMap((nestedClass) => - if (restNames.isEmpty) { - // No further names to resolve, here's our answer - Some(nestedClass) - } else { - // Try to resolve recursively - realResolveUserType(nestedClass, restNames, path) - } - ) - - resolvedHere match { - case Some(_) => resolvedHere - case None => - // No luck resolving here, let's try upper levels, if they exist - curClass.upClass match { - case Some(upClass) => - realResolveUserType(upClass, typeName, path) - case None => - // Check this class if it's top-level class - if (curClass.name.head == firstName) { - Some(curClass) - } else { - // Check if top-level specs has this name - // If there's None => no luck at all - val resolvedTop = specs.get(firstName) - resolvedTop match { - case None => None - case Some(classSpec) => if (restNames.isEmpty) { - resolvedTop - } else { - realResolveUserType(classSpec, restNames, path) - } - } - } - } - } - } } From 0f990feff9455217a34764266070dae2ee5bdab1 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 23:47:34 +0500 Subject: [PATCH 11/12] Remove unnecessary variable and matching --- .../io/kaitai/struct/precompile/ResolveTypes.scala | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index c7d672b80..25301ff99 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -86,16 +86,13 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } private def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { - val res = try { + try { val resolver = new ClassTypeProvider(specs, curClass) - Some(resolver.resolveTypePath(curClass, typeName)) + val ty = resolver.resolveTypePath(curClass, typeName) + Log.typeResolve.info(() => s" => ${ty.nameAsStr}") + (Some(ty), None) } catch { case _: TypeNotFoundError => - None - } - - res match { - case None => // Type definition not found if (opaqueTypes) { // Generate special "opaque placeholder" ClassSpec @@ -106,9 +103,6 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) Log.typeResolve.info(() => " => ??? (opaque type are disabled => error)") (None, Some(TypeNotFoundErr(typeName, curClass, path))) } - case Some(x) => - Log.typeResolve.info(() => s" => ${x.nameAsStr}") - (res, None) } } } From bd7b4b4bb8b1a5707ceb6533ccd095d44accbe8d Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 23:55:51 +0500 Subject: [PATCH 12/12] Inline real type resolution method It not so big and this unifies handling of types and enums --- .../struct/precompile/ResolveTypes.scala | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index 25301ff99..a59c186d6 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -47,9 +47,26 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) private def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { dataType match { case ut: UserType => - val (resClassSpec, problems) = resolveUserType(curClass, ut.name, path ++ List("type")) - ut.classSpec = resClassSpec - problems + try { + val resolver = new ClassTypeProvider(specs, curClass) + val ty = resolver.resolveTypePath(curClass, ut.name) + Log.typeResolve.info(() => s" => ${ty.nameAsStr}") + ut.classSpec = Some(ty) + None + } catch { + case _: TypeNotFoundError => + // Type definition not found + if (opaqueTypes) { + // Generate special "opaque placeholder" ClassSpec + Log.typeResolve.info(() => " => ??? (generating opaque type)") + ut.classSpec = Some(ClassSpec.opaquePlaceholder(ut.name)) + None + } else { + // Opaque types are disabled => that is an error + Log.typeResolve.info(() => " => ??? (opaque type are disabled => error)") + Some(TypeNotFoundErr(ut.name, curClass, path :+ "type")) + } + } case et: EnumType => et.name match { case typePath :+ name => @@ -84,25 +101,4 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) None } } - - private def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { - try { - val resolver = new ClassTypeProvider(specs, curClass) - val ty = resolver.resolveTypePath(curClass, typeName) - Log.typeResolve.info(() => s" => ${ty.nameAsStr}") - (Some(ty), None) - } catch { - case _: TypeNotFoundError => - // Type definition not found - if (opaqueTypes) { - // Generate special "opaque placeholder" ClassSpec - Log.typeResolve.info(() => " => ??? (generating opaque type)") - (Some(ClassSpec.opaquePlaceholder(typeName)), None) - } else { - // Opaque types are disabled => that is an error - Log.typeResolve.info(() => " => ??? (opaque type are disabled => error)") - (None, Some(TypeNotFoundErr(typeName, curClass, path))) - } - } - } }