From 6ce37a4cb54d92a544f78624aaf951e615b186ce Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 26 Sep 2024 14:59:19 +0500 Subject: [PATCH 01/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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))) - } - } - } } From 6ac15627725e22f7c9a23565198cf37c0d89b0f6 Mon Sep 17 00:00:00 2001 From: Mingun Date: Fri, 4 Oct 2024 19:32:39 +0500 Subject: [PATCH 13/16] Add support of absolute paths for enums on parse level --- .../kaitai/struct/exprlang/EnumRefSpec.scala | 56 +++++++++++++++++++ .../kaitai/struct/GraphvizClassCompiler.scala | 4 +- .../io/kaitai/struct/datatype/DataType.scala | 4 +- .../scala/io/kaitai/struct/exprlang/Ast.scala | 13 +++++ .../kaitai/struct/exprlang/Expressions.scala | 15 +++++ .../struct/languages/CSharpCompiler.scala | 2 +- .../kaitai/struct/languages/CppCompiler.scala | 4 +- .../struct/languages/JavaCompiler.scala | 6 +- .../struct/languages/RustCompiler.scala | 4 +- .../struct/precompile/ResolveTypes.scala | 33 +++++------ .../struct/problems/CompilationProblem.scala | 8 +-- .../struct/translators/TypeDetector.scala | 5 +- 12 files changed, 116 insertions(+), 38 deletions(-) create mode 100644 jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala new file mode 100644 index 000000000..7b953cf5a --- /dev/null +++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala @@ -0,0 +1,56 @@ +package io.kaitai.struct.exprlang + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers._ + +class EnumRefSpec extends AnyFunSpec { + describe("Expressions.parseEnumRef") { + describe("parses local enum refs") { + it("some_enum") { + Expressions.parseEnumRef("some_enum") should be(Ast.EnumRef( + false, Seq(), "some_enum" + )) + } + it("with spaces: ' some_enum '") { + Expressions.parseEnumRef(" some_enum ") should be(Ast.EnumRef( + false, Seq(), "some_enum" + )) + } + + it("::some_enum") { + Expressions.parseEnumRef("::some_enum") should be(Ast.EnumRef( + true, Seq(), "some_enum" + )) + } + it("with spaces: ' :: some_enum '") { + Expressions.parseEnumRef(" :: some_enum ") should be(Ast.EnumRef( + true, Seq(), "some_enum" + )) + } + } + + describe("parses path enum refs") { + it("some::enum") { + Expressions.parseEnumRef("some::enum") should be(Ast.EnumRef( + false, Seq("some"), "enum" + )) + } + it("with spaces: ' some :: enum '") { + Expressions.parseEnumRef(" some :: enum ") should be(Ast.EnumRef( + false, Seq("some"), "enum" + )) + } + + it("::some::enum") { + Expressions.parseEnumRef("::some::enum") should be(Ast.EnumRef( + true, Seq("some"), "enum" + )) + } + it("with spaces: ' :: some :: enum '") { + Expressions.parseEnumRef(" :: some :: enum ") should be(Ast.EnumRef( + true, Seq("some"), "enum" + )) + } + } + } +} diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index 22c746953..bbf1944c9 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -484,7 +484,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic { ): LanguageCompiler = ??? def type2class(name: List[String]) = name.last - def type2display(name: List[String]) = name.map(Utils.upperCamelCase).mkString("::") + def type2display(name: Seq[String]) = name.map(Utils.upperCamelCase).mkString("::") def dataTypeName(dataType: DataType, valid: Option[ValidationSpec]): String = { dataType match { @@ -508,7 +508,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic { val comma = if (bytesStr.isEmpty) "" else ", " s"str($bytesStr$comma$encoding)" case EnumType(name, basedOn) => - s"${dataTypeName(basedOn, valid)}→${type2display(name)}" + s"${dataTypeName(basedOn, valid)}→${type2display(name.fullName)}" case BitsType(width, bitEndian) => s"b$width${bitEndian.toSuffix}" case BitsType1(bitEndian) => s"b1${bitEndian.toSuffix}→bool" case _ => dataType.toString diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala index 96e6f4036..db868c521 100644 --- a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala +++ b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala @@ -252,7 +252,7 @@ object DataType { def isOwning = false } - case class EnumType(name: List[String], basedOn: IntType) extends DataType { + case class EnumType(name: Ast.EnumRef, basedOn: IntType) extends DataType { var enumSpec: Option[EnumSpec] = None /** @@ -487,7 +487,7 @@ object DataType { enumRef match { case Some(enumName) => r match { - case numType: IntType => EnumType(classNameToList(enumName), numType) + case numType: IntType => EnumType(Expressions.parseEnumRef(enumName), numType) case _ => throw KSYParseError(s"tried to resolve non-integer $r to enum", path).toException } diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala index 2acf1943b..d2a7a5c2d 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala @@ -141,5 +141,18 @@ object Ast { case object GtE extends cmpop } + /** + * Reference to an enum in scope. Scope is defined by the `absolute` flag and + * a path to a type (which can be empty) in which enum is defined. + */ + case class EnumRef(absolute: Boolean, typePath: Seq[String], name: String) { + /** @return Type path and name of enum in one list. */ + def fullName: Seq[String] = typePath :+ name + /** + * @return Enum designation name as human-readable string, to be used in compiler + * error messages. + */ + def asStr: String = fullName.mkString(if (absolute) "::" else "", "::", "") + } case class TypeWithArguments(typeName: typeId, arguments: expr.List) } diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala index 0ef98ce71..3f0e41a7d 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala @@ -195,6 +195,13 @@ object Expressions { case (path, Some(args)) => Ast.TypeWithArguments(path, args) } + def enumRef[$: P]: P[Ast.EnumRef] = P(Start ~ "::".!.? ~ NAME.rep(1, "::") ~ End).map { + case (absolute, names) => + // List have at least one element, so we always can split it into head and the last element + val typePath :+ enumName = names + Ast.EnumRef(absolute.nonEmpty, typePath.map(i => i.name), enumName.name) + } + class ParseException(val src: String, val failure: Parsed.Failure) extends RuntimeException(failure.msg) @@ -211,6 +218,14 @@ object Expressions { */ def parseTypeRef(src: String): Ast.TypeWithArguments = realParse(src, typeRef(_)) + /** + * Parse string with reference to enumeration definition, optionally in full path format. + * + * @param src Enum reference as string, like `::path::to::enum` + * @return Object that represents path to enum + */ + def parseEnumRef(src: String): Ast.EnumRef = realParse(src, enumRef(_)) + private def realParse[T](src: String, parser: P[_] => P[T]): T = { val r = fastparse.parse(src.trim, parser) r match { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala index 97693c473..4c338140a 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala @@ -688,7 +688,7 @@ object CSharpCompiler extends LanguageCompilerStatic case KaitaiStreamType | OwnedKaitaiStreamType => kstreamName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case at: ArrayType => { importList.add("System.Collections.Generic") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala index b0fdee657..5dad73d59 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala @@ -1149,7 +1149,7 @@ object CppCompiler extends LanguageCompilerStatic types2class(if (absolute) { t.enumSpec.get.name } else { - t.name + t.name.fullName }) case at: ArrayType => { @@ -1210,7 +1210,7 @@ object CppCompiler extends LanguageCompilerStatic ) } - def types2class(components: List[String]) = + def types2class(components: Seq[String]) = components.map(type2class).mkString("::") def type2class(name: String) = Utils.lowerUnderscoreCase(name) + "_t" diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala index ee19058d3..8db9076be 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -874,7 +874,7 @@ object JavaCompiler extends LanguageCompilerStatic case KaitaiStructType | CalcKaitaiStructType(_) => kstructName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case _: ArrayType => kaitaiType2JavaTypeBoxed(attrType, importList) @@ -918,7 +918,7 @@ object JavaCompiler extends LanguageCompilerStatic case KaitaiStructType | CalcKaitaiStructType(_) => kstructName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case at: ArrayType => { importList.add("java.util.ArrayList") @@ -929,7 +929,7 @@ object JavaCompiler extends LanguageCompilerStatic } } - def types2class(names: List[String]) = names.map(x => type2class(x)).mkString(".") + def types2class(names: Seq[String]) = names.map(x => type2class(x)).mkString(".") override def kstreamName: String = "KaitaiStream" override def kstructName: String = "KaitaiStruct" diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala index b0e885e7b..2bd3f5970 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala @@ -1312,7 +1312,7 @@ object RustCompiler def classTypeName(c: ClassSpec): String = s"${types2class(c.name)}" - def types2class(names: List[String]): String = + def types2class(names: Seq[String]): String = // TODO: Use `mod` to scope types instead of weird names names.map(x => type2class(x)).mkString("_") @@ -1339,7 +1339,7 @@ object RustCompiler case t: EnumType => val baseName = t.enumSpec match { case Some(spec) => s"${types2class(spec.name)}" - case None => s"${types2class(t.name)}" + case None => s"${types2class(t.name.fullName)}" } baseName 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 a59c186d6..0e50dda83 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -68,26 +68,19 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } } case et: EnumType => - 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? + try { + val resolver = new ClassTypeProvider(specs, curClass) + val ty = resolver.resolveEnum(Ast.typeId(et.name.absolute, et.name.typePath), et.name.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(et.name.typePath, curClass, path :+ "enum")) + case ex: EnumNotFoundError => + Log.enumResolve.info(() => s" => ??? (enum '${et.name}'): $ex") Some(EnumNotFoundErr(et.name, curClass, path :+ "enum")) } case st: SwitchType => 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 4c277622a..036a407cb 100644 --- a/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala +++ b/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala @@ -2,7 +2,7 @@ package io.kaitai.struct.problems import io.kaitai.struct.{JSON, Jsonable, Utils, problems} import io.kaitai.struct.datatype.DataType -import io.kaitai.struct.exprlang.Expressions +import io.kaitai.struct.exprlang.{Ast, Expressions} import io.kaitai.struct.format.{ClassSpec, Identifier, KSVersion} import fastparse.Parsed.Failure @@ -170,7 +170,7 @@ case class ParamMismatchError(idx: Int, argType: DataType, paramName: String, pa override def severity: ProblemSeverity = ProblemSeverity.Error } -case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) +case class TypeNotFoundErr(name: Seq[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}'" @@ -180,10 +180,10 @@ case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[S override def severity: ProblemSeverity = ProblemSeverity.Error } -case class EnumNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) +case class EnumNotFoundErr(ref: Ast.EnumRef, 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 '${ref.asStr}', searching from '${curClass.nameAsStr}'" override val coords: ProblemCoords = ProblemCoords(fileName, Some(path)) override def localizedInFile(fileName: String): CompilationProblem = copy(fileName = Some(fileName)) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala index 49cf61ef2..0f572f875 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala @@ -51,11 +51,12 @@ class TypeDetector(provider: TypeProvider) { case Ast.expr.InterpolatedStr(_) => CalcStrType case Ast.expr.Bool(_) => CalcBooleanType case Ast.expr.EnumByLabel(enumType, _, inType) => - val t = EnumType(inType.names.toList :+ enumType.name, CalcIntType) + val t = EnumType(Ast.EnumRef(false, inType.names.toList, enumType.name), CalcIntType) t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) t case Ast.expr.EnumById(enumType, _, inType) => - val t = EnumType(List(enumType.name), CalcIntType) + // TODO: May be create a type with a name that includes surrounding type? + val t = EnumType(Ast.EnumRef(false, List(), enumType.name), CalcIntType) t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) t case Ast.expr.Name(name: Ast.identifier) => provider.determineType(name.name).asNonOwning() From 420d877dc1a8bc9b100946338f695f9002b2c270 Mon Sep 17 00:00:00 2001 From: Mingun Date: Fri, 4 Oct 2024 22:22:22 +0500 Subject: [PATCH 14/16] Use Ast.EnumRef everywhere instead of Ast.typeId + Ast.identifier Because of new types type system enforses fixing https://github.com/kaitai-io/kaitai_struct/issues/857 This test need to be updated: [info] - expr_compare_enum2 *** FAILED *** [info] [expr_compare_enum2.ksy: /seq/1/if: [info] error: can't compare EnumType(EnumRef(false,List(),animal),Int1Type(false)) and Int1Type(true) [info] ] [info] did not equal [info] [expr_compare_enum2.ksy: /seq/1/if: [info] error: can't compare EnumType(List(animal),Int1Type(false)) and Int1Type(true) [info] ] (SimpleMatchers.scala:34) --- .../struct/ClassTypeProvider$Test.scala | 99 ++++++++++--------- .../struct/exprlang/ExpressionsSpec.scala | 13 +-- .../translators/TestTypeProviders.scala | 2 +- .../io/kaitai/struct/ClassTypeProvider.scala | 14 +-- .../kaitai/struct/GraphvizClassCompiler.scala | 4 +- .../scala/io/kaitai/struct/exprlang/Ast.scala | 6 +- .../kaitai/struct/exprlang/Expressions.scala | 11 +-- .../kaitai/struct/format/InstanceSpec.scala | 2 +- .../struct/precompile/ResolveTypes.scala | 2 +- .../struct/translators/BaseTranslator.scala | 10 +- .../translators/ExpressionValidator.scala | 12 +-- .../struct/translators/GoTranslator.scala | 10 +- .../struct/translators/RustTranslator.scala | 6 +- .../struct/translators/TypeDetector.scala | 13 ++- .../struct/translators/TypeProvider.scala | 2 +- 15 files changed, 101 insertions(+), 105 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 2a97388e8..2e3ff390c 100644 --- a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -624,35 +624,36 @@ class ClassTypeProvider$Test extends AnyFunSpec { 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")) + val none = Ast.EnumRef(false, Seq(), "e") + val one_e = Ast.EnumRef(false, Seq("one"), "e") + val one_unk = Ast.EnumRef(false, Seq("one"), "unknown") + val one_two = Ast.EnumRef(false, Seq("one", "two"), "e") + val unknown = Ast.EnumRef(false, Seq("unknown"), "e") describe("in 'root' context") { val resolver = new ClassTypeProvider(specs, root) it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_root) + resolver.resolveEnum(none) should be(e_root) } it("doesn't resolve 'one::e'") { - val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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.resolveEnum(one, "unknown") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root'") } } @@ -662,25 +663,25 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_1 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_root) + resolver.resolveEnum(none) should be(e_root) } it("resolves 'one::e'") { - resolver.resolveEnum(one, "e") should be(e_11) + 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1'") } } @@ -690,26 +691,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_2 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_2) + resolver.resolveEnum(none) should be(e_2) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2'") } } @@ -719,25 +720,25 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_11 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_11) + resolver.resolveEnum(none) should be(e_11) } it("resolves 'one::e'") { - resolver.resolveEnum(one, "e") should be(e_11) + 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::one'") } } @@ -747,26 +748,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_12 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_12) + resolver.resolveEnum(none) should be(e_12) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two'") } } @@ -776,26 +777,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_21 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_2) + resolver.resolveEnum(none) should be(e_2) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::one'") } } @@ -805,26 +806,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_22 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_2) + resolver.resolveEnum(none) should be(e_2) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::two'") } } @@ -834,26 +835,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_121 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_12) + resolver.resolveEnum(none) should be(e_12) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::one'") } } @@ -863,26 +864,26 @@ class ClassTypeProvider$Test extends AnyFunSpec { resolver.nowClass = child_122 it("resolves 'e'") { - resolver.resolveEnum(none, "e") should be(e_12) + resolver.resolveEnum(none) should be(e_12) } it("doesn't resolve 'one::e'") { - val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(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[EnumNotFoundError] thrownBy resolver.resolveEnum(one, "unknown") + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) 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") + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::two'") } } diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala index 309630fac..16bc7bee0 100644 --- a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala +++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala @@ -133,15 +133,14 @@ class ExpressionsSpec extends AnyFunSpec { // Enums it("parses port::http") { - Expressions.parse("port::http") should be (EnumByLabel(identifier("port"), identifier("http"))) + Expressions.parse("port::http") should be (EnumByLabel(EnumRef(false, Seq(), "port"), identifier("http"))) } it("parses some_type::port::http") { Expressions.parse("some_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(false, Seq("some_type"), "port"), identifier("http"), - typeId(absolute = false, Seq("some_type")) ) ) } @@ -149,9 +148,8 @@ class ExpressionsSpec extends AnyFunSpec { it("parses parent_type::child_type::port::http") { Expressions.parse("parent_type::child_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(false, Seq("parent_type", "child_type"), "port"), identifier("http"), - typeId(absolute = false, Seq("parent_type", "child_type")) ) ) } @@ -159,9 +157,8 @@ class ExpressionsSpec extends AnyFunSpec { it("parses ::parent_type::child_type::port::http") { Expressions.parse("::parent_type::child_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(true, Seq("parent_type", "child_type"), "port"), identifier("http"), - typeId(absolute = true, Seq("parent_type", "child_type")) ) ) } @@ -171,7 +168,7 @@ class ExpressionsSpec extends AnyFunSpec { Compare( BinOp( Attribute( - EnumByLabel(identifier("port"),identifier("http")), + EnumByLabel(EnumRef(false, Seq(), "port"), identifier("http")), identifier("to_i") ), Add, diff --git a/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala b/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala index 051ee216d..981ed227d 100644 --- a/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala +++ b/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala @@ -15,7 +15,7 @@ object TestTypeProviders { abstract class FakeTypeProvider extends TypeProvider { val nowClass = ClassSpec.opaquePlaceholder(List("top_class")) - override def resolveEnum(inType: Ast.typeId, enumName: String) = + override def resolveEnum(ref: Ast.EnumRef) = throw new NotImplementedError override def resolveType(typeName: Ast.typeId): DataType = { diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index fd74cf236..9875efaa8 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -90,18 +90,18 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends throw new FieldNotFoundError(attrName, inClass) } - override def resolveEnum(inType: Ast.typeId, enumName: String): EnumSpec = { - val inClass = if (inType.absolute) topClass else nowClass + override def resolveEnum(ref: Ast.EnumRef): EnumSpec = { + val inClass = if (ref.absolute) topClass else nowClass // When concrete type is not defined, search enum definition in all enclosing types - if (inType.names.isEmpty) { - resolveEnumName(inClass, enumName) + if (ref.typePath.isEmpty) { + resolveEnumName(inClass, ref.name) } else { - val ty = resolveTypePath(inClass, inType.names) - ty.enums.get(enumName) match { + val ty = resolveTypePath(inClass, ref.typePath) + ty.enums.get(ref.name) match { case Some(spec) => spec case None => - throw new EnumNotFoundInTypeError(enumName, ty) + throw new EnumNotFoundInTypeError(ref.name, ty) } } } diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index bbf1944c9..caf54dd53 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -347,8 +347,8 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends List() case _: Ast.expr.EnumByLabel => List() - case Ast.expr.EnumById(_, id, _) => - affectedVars(id) + case Ast.expr.EnumById(_, expr) => + affectedVars(expr) case Ast.expr.Attribute(value, attr) => if (attr.name == Identifier.SIZEOF) { val vars = value match { diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala index d2a7a5c2d..9523ea5de 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala @@ -76,8 +76,10 @@ object Ast { case class FloatNum(n: BigDecimal) extends expr case class Str(s: String) extends expr case class Bool(n: Boolean) extends expr - case class EnumByLabel(enumName: identifier, label: identifier, inType: typeId = EmptyTypeId) extends expr - case class EnumById(enumName: identifier, id: expr, inType: typeId = EmptyTypeId) extends expr + /** Take named enumeration constant from the specified enumeration. */ + case class EnumByLabel(ref: EnumRef, label: identifier) extends expr + /** Cast specified expression to the enumerated type. Used only by value instances with `enum` key. */ + case class EnumById(ref: EnumRef, expr: expr) extends expr case class Attribute(value: expr, attr: identifier) extends expr case class CastToType(value: expr, typeName: typeId) extends expr diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala index 3f0e41a7d..02c5994be 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala @@ -171,14 +171,11 @@ object Expressions { case (first, names: Seq[Ast.identifier]) => val isAbsolute = first.nonEmpty val (enumName, enumLabel) = names.takeRight(2) match { - case Seq(a, b) => (a, b) - } - val typePath = names.dropRight(2) - if (typePath.isEmpty) { - Ast.expr.EnumByLabel(enumName, enumLabel, Ast.EmptyTypeId) - } else { - Ast.expr.EnumByLabel(enumName, enumLabel, Ast.typeId(isAbsolute, typePath.map(_.name))) + case Seq(a, b) => (a.name, b) } + val typePath = names.dropRight(2).map(n => n.name) + val ref = Ast.EnumRef(isAbsolute, typePath, enumName) + Ast.expr.EnumByLabel(ref, enumLabel) } def byteSizeOfType[$: P]: P[Ast.expr.ByteSizeOfType] = diff --git a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala index 4a66307ed..8011bdb84 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala @@ -61,7 +61,7 @@ object InstanceSpec { case None => value case Some(enumName) => - Ast.expr.EnumById(Ast.identifier(enumName), value) + Ast.expr.EnumById(Expressions.parseEnumRef(enumName), value) } val ifExpr = ParseUtils.getOptValueExpression(srcMap, "if", path) 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 0e50dda83..fe003195f 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -70,7 +70,7 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) case et: EnumType => try { val resolver = new ClassTypeProvider(specs, curClass) - val ty = resolver.resolveEnum(Ast.typeId(et.name.absolute, et.name.typePath), et.name.name) + val ty = resolver.resolveEnum(et.name) Log.enumResolve.info(() => s" => ${ty.nameAsStr}") et.enumSpec = Some(ty) None diff --git a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala index 1dd550666..a36452bd9 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala @@ -59,11 +59,11 @@ abstract class BaseTranslator(val provider: TypeProvider) doInterpolatedStringLiteral(s) case Ast.expr.Bool(n) => doBoolLiteral(n) - case Ast.expr.EnumById(enumType, id, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) - doEnumById(enumSpec, translate(id)) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + val enumSpec = provider.resolveEnum(ref) + doEnumById(enumSpec, translate(expr)) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) doEnumByLabel(enumSpec, label.name) case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala b/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala index 8d2741282..5b214d467 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala @@ -31,13 +31,13 @@ class ExpressionValidator(val provider: TypeProvider) _: Ast.expr.FloatNum | _: Ast.expr.Str | _: Ast.expr.Bool => // all simple literals are good and valid - case Ast.expr.EnumById(enumType, id, inType) => - provider.resolveEnum(inType, enumType.name) - validate(id) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + provider.resolveEnum(ref) + validate(expr) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) if (!enumSpec.map.values.exists(_.name == label.name)) { - throw new EnumMemberNotFoundError(label.name, enumType.name, enumSpec.path.mkString("/")) + throw new EnumMemberNotFoundError(label.name, ref.name, enumSpec.path.mkString("/")) } case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala index 28647aec8..f9d600b5b 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala @@ -47,11 +47,11 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo trInterpolatedStringLiteral(s) case Ast.expr.Bool(n) => trBoolLiteral(n) - case Ast.expr.EnumById(enumType, id, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) - trEnumById(enumSpec.name, translate(id)) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + val enumSpec = provider.resolveEnum(ref) + trEnumById(enumSpec.name, translate(expr)) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) trEnumByLabel(enumSpec.name, label.name) case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala index 788d49182..c56d73b2d 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala @@ -396,10 +396,10 @@ class RustTranslator(provider: TypeProvider, config: RuntimeConfig) override def translate(v: Ast.expr): String = { v match { - case Ast.expr.EnumById(enumType, id, inType) => - id match { + case Ast.expr.EnumById(ref, expr) => + expr match { case ifExp: Ast.expr.IfExp => - val enumSpec = provider.resolveEnum(inType, enumType.name) + val enumSpec = provider.resolveEnum(ref) val enumName = RustCompiler.types2class(enumSpec.name) def toStr(ex: Ast.expr) = ex match { case Ast.expr.IntNum(n) => s"$enumName::try_from($n)?" diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala index 0f572f875..7866d764f 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala @@ -50,14 +50,13 @@ class TypeDetector(provider: TypeProvider) { case Ast.expr.Str(_) => CalcStrType case Ast.expr.InterpolatedStr(_) => CalcStrType case Ast.expr.Bool(_) => CalcBooleanType - case Ast.expr.EnumByLabel(enumType, _, inType) => - val t = EnumType(Ast.EnumRef(false, inType.names.toList, enumType.name), CalcIntType) - t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) + case Ast.expr.EnumByLabel(ref, _) => + val t = EnumType(ref, CalcIntType) + t.enumSpec = Some(provider.resolveEnum(ref)) t - case Ast.expr.EnumById(enumType, _, inType) => - // TODO: May be create a type with a name that includes surrounding type? - val t = EnumType(Ast.EnumRef(false, List(), enumType.name), CalcIntType) - t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) + case Ast.expr.EnumById(ref, _) => + val t = EnumType(ref, CalcIntType) + t.enumSpec = Some(provider.resolveEnum(ref)) t case Ast.expr.Name(name: Ast.identifier) => provider.determineType(name.name).asNonOwning() case Ast.expr.InternalName(id) => provider.determineType(id) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala index 9cf5548ad..e2d4eb117 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala @@ -16,7 +16,7 @@ trait TypeProvider { def determineType(attrId: Identifier): DataType def determineType(inClass: ClassSpec, attrName: String): DataType def determineType(inClass: ClassSpec, attrId: Identifier): DataType - def resolveEnum(typeName: Ast.typeId, enumName: String): EnumSpec + def resolveEnum(ref: Ast.EnumRef): EnumSpec def resolveType(typeName: Ast.typeId): DataType def isLazy(attrName: String): Boolean def isLazy(inClass: ClassSpec, attrName: String): Boolean From 2c0967df4b4689e59423b7ea6b47a054731b3f86 Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 17 Apr 2024 22:29:50 +0500 Subject: [PATCH 15/16] Fix leaking of types and enums from other imported specs to specs which does not import them explicitly --- .../scala/io/kaitai/struct/ClassTypeProvider.scala | 8 ++++++-- .../scala/io/kaitai/struct/format/ClassSpec.scala | 10 ++++++++++ .../io/kaitai/struct/precompile/LoadImports.scala | 14 ++++++++------ .../io/kaitai/struct/precompile/ResolveTypes.scala | 13 +++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index 9875efaa8..11dc24e2f 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -155,8 +155,12 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends case Some(upClass) => resolveTypeName(upClass, typeName) case None => classSpecs.get(typeName) match { - case Some(spec) => spec - case None => + // We should use that spec if it is imported in our file (which is represented + // by our top-level class). If `topClass` imports `classSpec`, we could try to + // resolve type in it + // TODO: if type is defined in spec, we could add a suggestion to error to add missing import + case Some(spec) if (topClass.imports.contains(spec)) => spec + case _ => throw new TypeNotFoundInHierarchyError(typeName, nowClass) } } diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala index 6a3cb0135..d2282f3ad 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala @@ -81,6 +81,16 @@ case class ClassSpec( var seqSize: Sized = NotCalculatedSized + /** + * The list of top-level type specifications which is imported to the file, + * which top-level type is represented by this class. + * + * This collection filled only for top-level classes (for which [[upClass]] is `None`). + * + * This collection is filled by the [[io.kaitai.struct.precompile.LoadImports]] pass. + */ + var imports = mutable.ListBuffer[ClassSpec]() + def toDataType: DataType = { val cut = CalcUserType(name, None) cut.classSpec = Some(this) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala index d25bfeb9b..16f7ae6b3 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala @@ -32,7 +32,7 @@ class LoadImports(specs: ClassSpecs) { loadImport( name, curClass.meta.path ++ List("imports", idx.toString), - Some(curClass.fileNameAsStr), + curClass, workDir ) }).map((x) => x.flatten) @@ -44,9 +44,10 @@ class LoadImports(specs: ClassSpecs) { Future.sequence(List(thisMetaFuture, nestedFuture)).map((x) => x.flatten) } - private def loadImport(name: String, path: List[String], inFile: Option[String], workDir: ImportPath): Future[List[ClassSpec]] = { + private def loadImport(name: String, path: List[String], curClass: ClassSpec, workDir: ImportPath): Future[List[ClassSpec]] = { Log.importOps.info(() => s".. LoadImports: loadImport($name, workDir = $workDir)") + val inFile = Some(curClass.fileNameAsStr) val impPath = ImportPath.fromString(name) val fullPath = ImportPath.add(workDir, impPath) @@ -63,8 +64,9 @@ class LoadImports(specs: ClassSpecs) { s".. LoadImports: loadImport($name, workDir = $workDir), got spec=$specNameAsStr" }) optSpec match { - case Some(spec) => - val specName = spec.name.head + case Some(importedSpec) => + curClass.imports += importedSpec + val specName = importedSpec.name.head // Check if spec name does not match file name. If it doesn't match, // it is probably already a serious error. if (name != specName) @@ -88,12 +90,12 @@ class LoadImports(specs: ClassSpecs) { val isNewSpec = specs.synchronized { val isNew = !specs.contains(specName) if (isNew) { - specs(specName) = spec + specs(specName) = importedSpec } isNew } if (isNewSpec) { - processClass(spec, ImportPath.updateWorkDir(workDir, impPath)) + processClass(importedSpec, ImportPath.updateWorkDir(workDir, impPath)) } else { Log.importOps.warn(() => s"... we have that already, ignoring") Future { List() } 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 fe003195f..ae69ffe83 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -10,8 +10,11 @@ import io.kaitai.struct.problems._ /** * A collection of methods that resolves user types and enum types, i.e. * converts names into ClassSpec / EnumSpec references. + * + * This step runs for each top-level [[format.ClassSpec]]. */ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) extends PrecompileStep { + /** Resolves references to types and enums in `topClass` and all its nested types. */ override def run(): Iterable[CompilationProblem] = topClass.mapRec(resolveUserTypes).map(problem => problem.localizedInType(topClass)) @@ -44,6 +47,16 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) private def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = resolveUserType(curClass, attr.dataType, attr.path) + /** + * Resolves any references to typee or enum in `dataType` used in `curClass` to + * a type definition, or returns [[TypeNotFoundErr]] or [[EnumNotFoundErr]] error. + * + * @param curClass Class that contains member + * @param dataType Definition of an attribute type which references to a type or enum need to be resolved + * @param path A path to the attribute in KSY where the error should be reported if reference is unknown + * + * @returns [[TypeNotFoundErr]] and/or [[EnumNotFoundErr]] error (several in case of `switch-on` type). + */ private def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { dataType match { case ut: UserType => From 11c1318d56cb0da8ebad9a02b4305bfff99986aa Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 17 Apr 2024 22:35:28 +0500 Subject: [PATCH 16/16] Remove unnecessary Option in filename for importAbsolute and importRelative In all cases this methods called with Some(...) --- .../io/kaitai/struct/format/JavaScriptClassSpecs.scala | 4 ++-- .../scala/io/kaitai/struct/formats/JavaClassSpecs.scala | 8 ++++---- .../main/scala/io/kaitai/struct/format/ClassSpecs.scala | 4 ++-- .../scala/io/kaitai/struct/precompile/LoadImports.scala | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala b/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala index b95b988a6..62e0eee33 100644 --- a/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala +++ b/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala @@ -13,9 +13,9 @@ class JavaScriptClassSpecs(importer: JavaScriptImporter, firstSpec: ClassSpec) val MODE_REL = "rel" val MODE_ABS = "abs" - override def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = + override def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = doImport(name, path, MODE_REL) - override def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = + override def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = doImport(name, path, MODE_ABS) def doImport(name: String, path: List[String], mode: String): Future[Option[ClassSpec]] = { diff --git a/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala b/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala index d5df4554a..49d5cf4f6 100644 --- a/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala +++ b/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala @@ -27,14 +27,14 @@ class JavaClassSpecs(relPath: String, absPaths: Seq[String], firstSpec: ClassSpe private val relFiles: concurrent.Map[String, ClassSpec] = new ConcurrentHashMap[String, ClassSpec]().asScala private val absFiles: concurrent.Map[String, ClassSpec] = new ConcurrentHashMap[String, ClassSpec]().asScala - override def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = Future { + override def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = Future { Log.importOps.info(() => s".. importing relative $name") JavaClassSpecs.cached(path, inFile, relFiles, name, (_) => JavaKSYParser.fileNameToSpec(s"$relPath/$name.ksy") ) } - override def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = Future { + override def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = Future { Log.importOps.info(() => s".. importing absolute $name") JavaClassSpecs.cached(path, inFile, absFiles, name, tryAbsolutePaths) } @@ -62,7 +62,7 @@ class JavaClassSpecs(relPath: String, absPaths: Seq[String], firstSpec: ClassSpe object JavaClassSpecs { def cached( path: List[String], - inFile: Option[String], + inFile: String, cacheMap: mutable.Map[String, ClassSpec], name: String, importOp: (String) => ClassSpec @@ -80,7 +80,7 @@ object JavaClassSpecs { cacheMap(name) = spec Some(spec) } catch { - case err: Throwable => throw ErrorInInput(err, path, inFile).toException + case err: Throwable => throw ErrorInInput(err, path, Some(inFile)).toException } } } diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala index ec803d136..5d36450be 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala @@ -59,6 +59,6 @@ abstract class ClassSpecs(val firstSpec: ClassSpec) extends mutable.HashMap[Stri } } - def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] - def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] + def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] + def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] } diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala index 16f7ae6b3..ab6a36772 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala @@ -47,7 +47,7 @@ class LoadImports(specs: ClassSpecs) { private def loadImport(name: String, path: List[String], curClass: ClassSpec, workDir: ImportPath): Future[List[ClassSpec]] = { Log.importOps.info(() => s".. LoadImports: loadImport($name, workDir = $workDir)") - val inFile = Some(curClass.fileNameAsStr) + val inFile = curClass.fileNameAsStr val impPath = ImportPath.fromString(name) val fullPath = ImportPath.add(workDir, impPath)