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 =>