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..7fabe6a3c 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala @@ -253,9 +253,19 @@ object ClassSpec { private def checkDupId(prevAttrOpt: Option[MemberSpec], id: String, nowAttr: YAMLPath): Unit = { prevAttrOpt match { case Some(prevAttr) => + // Report error at position where referenced param / attribute / instance is defined. + // Add `id` for attributes in `seq` and `params`, do not add for instances + val path = nowAttr match { + case _: InstanceSpec => nowAttr.path + case _ => nowAttr.path :+ "id" + } + val prevPath = prevAttr match { + case _: InstanceSpec => prevAttr.path + case _ => prevAttr.path :+ "id" + } throw KSYParseError.withText( - s"duplicate attribute ID '$id', previously defined at /${prevAttr.pathStr}", - nowAttr.path + s"duplicate attribute ID '$id', previously defined at /${prevPath.mkString("/")}", + path ) case None => // no dups, ok diff --git a/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala index 7f4381b55..41796f418 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala @@ -114,7 +114,7 @@ object MetaSpec { ) } if (ver > KSVersion.current) - throw KSYParseError.incompatibleVersion(ver, KSVersion.current, path ++ List("ks-version")) + throw KSYParseError.incompatibleVersion(ver, KSVersion.current, path :+ "ks-version") } val endian: Option[Endianness] = Endianness.fromYaml(srcMap.get("endian"), 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 f6f0c888b..1b740edde 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -41,27 +41,49 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) } def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = - resolveUserType(curClass, attr.dataType, attr.path) + resolveUserType(curClass, attr.dataType, attr.path, None) - def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { + /** + * Resolves the `dataType` (thing referenced in the `type` property in YAML) of some attribute + * defined in `curClass`. + * + * @param curClass The user-defined type (= class) with attribute whose type is being resolved + * @param dataType The type of the attribute being resolved + * @param path YAML path to the attribute where diagnostics should be reported + * @param caseExpr Contains the specific case name (actually an expresion) if the attribute type + * is switchable type (defined by `switch-on`) + */ + def resolveUserType( + curClass: ClassSpec, + dataType: DataType, + path: List[String], + caseExpr: Option[String], + ): Iterable[CompilationProblem] = { dataType match { case ut: UserType => - val (resClassSpec, problems) = resolveUserType(curClass, ut.name, path ++ List("type")) + val (resClassSpec, problems) = resolveUserType( + curClass, + ut.name, + caseExpr match { + case Some(case_) => path ++ List("type", "cases", case_) + case None => path :+ "type" + } + ) 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"))) + Some(EnumNotFoundErr(et.name, curClass, path :+ "enum")) } else { None } case st: SwitchType => st.cases.flatMap { case (caseName, ut) => - resolveUserType(curClass, ut, path ++ List("type", "cases", caseName.toString)) + resolveUserType(curClass, ut, path, Some(caseName.toString)) } case at: ArrayType => - resolveUserType(curClass, at.elType, path) + resolveUserType(curClass, at.elType, path, caseExpr) case _ => // not a user type, nothing to resolve None diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/StyleCheckIds.scala b/shared/src/main/scala/io/kaitai/struct/precompile/StyleCheckIds.scala index 4c4753847..45f0fae75 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/StyleCheckIds.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/StyleCheckIds.scala @@ -39,16 +39,27 @@ class StyleCheckIds(specs: ClassSpecs) extends PrecompileStep { } } + /** + * @param spec The user defined type (= class) in which `attr` is defined + * @param attr Attribute that references an attribute with a potentially non-conformant name + * (for example, `content`. Its `size` property should refers to `len_content` attribute) + */ def getSizeRefProblem(spec: ClassSpec, attr: MemberSpec): Option[CompilationProblem] = { getSizeReference(spec, attr.dataType).flatMap(sizeRefAttr => { val existingName = sizeRefAttr.id.humanReadable val goodName = s"len_${attr.id.humanReadable}" if (existingName != goodName) { + // Report error at position where referenced attribute is defined. + // Add `id` for attributes in `seq`, do not add for instances + val path = sizeRefAttr match { + case _: InstanceSpec => sizeRefAttr.path + case _ => sizeRefAttr.path :+ "id" + } Some(StyleWarningSizeLen( goodName, existingName, attr.id.humanReadable, - ProblemCoords(path = Some(sizeRefAttr.path ++ List("id"))) + ProblemCoords(path = Some(path)) )) } else { None @@ -56,16 +67,27 @@ class StyleCheckIds(specs: ClassSpecs) extends PrecompileStep { }) } + /** + * @param spec The user defined type (= class) in which `attr` is defined + * @param attr Attribute that references an attribute with a potentially non-conformant name + * (for example, `content`. Its `repeat-expr` property should refers to `num_content` attribute) + */ def getRepeatExprRefProblem(spec: ClassSpec, attr: AttrLikeSpec): Option[CompilationProblem] = { getRepeatExprReference(spec, attr).flatMap(repeatExprRefAttr => { val existingName = repeatExprRefAttr.id.humanReadable val goodName = s"num_${attr.id.humanReadable}" if (existingName != goodName) { + // Report error at position where referenced attribute is defined. + // Add `id` for attributes in `seq`, do not add for instances + val path = repeatExprRefAttr match { + case _: InstanceSpec => repeatExprRefAttr.path + case _ => repeatExprRefAttr.path :+ "id" + } Some(StyleWarningRepeatExprNum( goodName, existingName, attr.id.humanReadable, - ProblemCoords(path = Some(repeatExprRefAttr.path ++ List("id"))) + ProblemCoords(path = Some(path)) )) } else { None