Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,25 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int, bitEndian) =>
s"$io.ReadBitsInt${Utils.upperCamelCase(bitEndian.toSuffix)}($width)"
case t: UserType =>
val addParams = t.args.map((a) => expression(a)).mkString(", ")
s"New${GoCompiler.types2class(t.classSpec.get.name)}($addParams)"
val className = GoCompiler.types2class(t.classSpec.get.name)
// In Go, numeric types are not implicitly convertible (e.g. `int` -> `uint8`), so we
// must cast constructor arguments to the parameter types declared by the target class.
// Otherwise generated code won't compile for common patterns like `type: foo(bar)` where
// `bar` is `int` but `foo` expects `u1`.
val castedArgs = t.classSpec.get.params.zipAll(t.args, null, null).collect {
case (p: ParamDefSpec, a: Ast.expr) =>
val argExpr = expression(a)
p.dataType match {
// Pointer / interface-y parameters: avoid forcing casts (can be invalid or redundant).
case _: UserType | KaitaiStreamType | OwnedKaitaiStreamType | AnyType =>
argExpr
case dt =>
val nt = kaitaiType2NativeType(dt)
// Pointer types require parentheses in conversions: (*T)(expr)
if (nt.startsWith("*")) s"($nt)($argExpr)" else s"$nt($argExpr)"
}
}.mkString(", ")
s"New$className($castedArgs)"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,47 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo
out.dec
out.puts("}")
ResultLocalVar(v1)
case (_: IntType, _: IntType, Ast.operator.LShift | Ast.operator.RShift |
Ast.operator.BitAnd | Ast.operator.BitOr | Ast.operator.BitXor |
Ast.operator.Add | Ast.operator.Sub | Ast.operator.Mult | Ast.operator.Div) =>
// Go is strict about integer types: operations on `uint8` / `int8` etc produce the same
// narrow type, and you can't assign them to `int` without an explicit conversion.
//
// TypeDetector infers CalcIntType for any integer binop, and Go backend represents it as
// `int`, so we cast *typed* operands (e.g. `this.Bytes[i]`) to `int` where needed, while
// keeping integer literals untyped (to preserve nicer output and not break TranslatorSpec).
def isUntypedIntConst(e: Ast.expr): Boolean = e match {
case Ast.expr.IntNum(_) => true
case Ast.expr.UnaryOp(Ast.unaryop.Minus, Ast.expr.IntNum(_)) => true
case _ => false
}

val thisPrec = OPERATOR_PRECEDENCE(op)

def maybeCastToInt(e: Ast.expr): String = {
if (isUntypedIntConst(e)) {
// Keep literals untyped to preserve concise code like `1 + 2`.
translate(e, thisPrec)
} else {
detectType(e) match {
case CalcIntType =>
// Already represented as `int` in Go backend; just parenthesize as needed.
translate(e, thisPrec)
case _ =>
// Cast runtime/narrow integer types (uint8/int16/etc) to `int` so the whole
// expression is `int`-typed and can be assigned to CalcIntType variables.
s"int(${translate(e)})"
}
}
}

val leftStr = maybeCastToInt(left)
val rightStr = maybeCastToInt(right)
val opStr = binOp(op)
val exprStr =
if (thisPrec <= extPrec) s"($leftStr $opStr $rightStr)"
else s"$leftStr $opStr $rightStr"
ResultString(exprStr)
case _ =>
ResultString(genericBinOp(left, op, right, extPrec))
}
Expand Down Expand Up @@ -261,15 +302,22 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo

def trIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): ResultLocalVar = {
val v1 = allocateLocalVar()
val typ = detectType(ifTrue)
// Important: the resulting type of an `if` expression is the combined type of both branches.
// If we use only `ifTrue` type here, Go code can fail to compile when the other branch yields
// a different numeric type (e.g. `uint8` vs `int`), which Go won't implicitly convert.
val typ = detectType(Ast.expr.IfExp(condition, ifTrue, ifFalse))
out.puts(s"var ${localVarName(v1)} ${GoCompiler.kaitaiType2NativeType(typ)};")
out.puts(s"if (${translate(condition)}) {")
out.inc
out.puts(s"${localVarName(v1)} = ${translate(ifTrue)}")
val ifTrueExpr =
if (detectType(ifTrue) == typ) translate(ifTrue) else resToStr(doCast(ifTrue, typ))
out.puts(s"${localVarName(v1)} = $ifTrueExpr")
out.dec
out.puts("} else {")
out.inc
out.puts(s"${localVarName(v1)} = ${translate(ifFalse)}")
val ifFalseExpr =
if (detectType(ifFalse) == typ) translate(ifFalse) else resToStr(doCast(ifFalse, typ))
out.puts(s"${localVarName(v1)} = $ifFalseExpr")
out.dec
out.puts("}")
ResultLocalVar(v1)
Expand Down