From 6ce37a4cb54d92a544f78624aaf951e615b186ce 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 0dee55827..e51725ef5 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -91,16 +91,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) } @@ -110,26 +110,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 @@ -139,7 +139,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 9cc21cf35d23347aad9ea89f0f5ddb93feb7a330 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 04ba42123bac96f209525a06856a33e71f90821e 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 4ec107f5c65cbabd16b75edb58e12faf5fd30fd5 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 e51725ef5..7e95c62eb 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 { @@ -121,12 +121,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 = { @@ -144,7 +147,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 ce3875e2a2c34cbc455827fdd60eb950a00c6e81 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 80334070372da6fe2ae09a90e5875f01e1da679d 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 7e95c62eb..1cd617529 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 { @@ -90,8 +90,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 { @@ -102,7 +115,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 374dc8bee1ef3348671280a85ab1d18f35866b20 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 1cd617529..fd74cf236 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -121,13 +121,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 64d770b241b27f184cd0caaeadb9c3c6833d6668 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 9cac8d27e5aa0bea54824254258a28c3875f539a 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 7697cace2842a017161dc2fbaff6c2c274922862 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 a4706e7f2f8f90d6afb988c2e6dc688dcae035ee 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 3e68dcc7ec6a1c1439352a9421ae65e661333ce7 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))) - } - } - } }