From a839f86ca8895e30dd6db6bc7f5b920c343cde07 Mon Sep 17 00:00:00 2001 From: Aleksei Menshutin Date: Tue, 13 Oct 2026 05:13:51 +0300 Subject: [PATCH 1/2] Correct Go type names and equality with safe recursive inspection --- .../main/kotlin/org/jacodb/go/api/GoType.kt | 151 +++++++----------- .../org/jacodb/go/api/GoTypeIdentityTest.kt | 88 ++++++++++ 2 files changed, 147 insertions(+), 92 deletions(-) create mode 100644 jacodb-go/src/test/kotlin/org/jacodb/go/api/GoTypeIdentityTest.kt diff --git a/jacodb-go/src/main/kotlin/org/jacodb/go/api/GoType.kt b/jacodb-go/src/main/kotlin/org/jacodb/go/api/GoType.kt index 55ffd67b8..38fd9d546 100644 --- a/jacodb-go/src/main/kotlin/org/jacodb/go/api/GoType.kt +++ b/jacodb-go/src/main/kotlin/org/jacodb/go/api/GoType.kt @@ -18,6 +18,7 @@ package org.jacodb.go.api import org.jacodb.api.common.CommonType import org.jacodb.api.common.CommonTypeName +import java.util.IdentityHashMap interface GoType : CommonType, CommonTypeName { override val nullable: Boolean? @@ -26,15 +27,17 @@ interface GoType : CommonType, CommonTypeName { abstract class AbstractGoType : GoType { override fun equals(other: Any?): Boolean { - if (other !is GoType) { + if (other !is GoType || javaClass != other.javaClass) { return false } return this.typeName == other.typeName } override fun hashCode(): Int { - return javaClass.hashCode() + return 31 * javaClass.hashCode() + typeName.hashCode() } + + override fun toString(): String = typeName } class NullType : GoType, AbstractGoType() { @@ -50,20 +53,7 @@ class ArrayType( val elementType: GoType ) : GoType, AbstractGoType() { override val typeName: String - get() = "[${len}]${elementType.typeName}" - - override fun equals(other: Any?): Boolean { - if (other !is ArrayType) { - return false - } - return other.elementType == elementType && other.len == len - } - - override fun hashCode(): Int { - var result = typeName.hashCode() - result = 31 * result + elementType.hashCode() - return result - } + get() = renderGoType(this) } class BasicType( @@ -75,15 +65,7 @@ class ChanType( val elementType: GoType ) : GoType, AbstractGoType() { override val typeName: String - get(): String { - val res = elementType.typeName - return when (direction) { - 0L -> "chan $res" - 1L -> "<-chan $res" - 2L -> "chan <-$res" - else -> "" - } - } + get() = renderGoType(this) } class InterfaceType( @@ -99,21 +81,7 @@ class MapType( val valueType: GoType ) : GoType, AbstractGoType() { override val typeName: String - get() = "map[${keyType.typeName}]${valueType.typeName}" - - override fun equals(other: Any?): Boolean { - if (other !is MapType) { - return false - } - return other.keyType == keyType && other.valueType == valueType - } - - override fun hashCode(): Int { - var result = typeName.hashCode() - result = 31 * result + keyType.hashCode() - result = 31 * result + valueType.hashCode() - return result - } + get() = renderGoType(this) } class NamedType( @@ -129,20 +97,7 @@ class PointerType( var baseType: GoType ) : GoType, AbstractGoType() { override val typeName: String - get() = baseType.typeName - - override fun equals(other: Any?): Boolean { - if (other !is PointerType) { - return false - } - return other.baseType == baseType - } - - override fun hashCode(): Int { - var result = typeName.hashCode() - result = 31 * result + baseType.hashCode() - return result - } + get() = renderGoType(this) } class SignatureType( @@ -150,29 +105,14 @@ class SignatureType( val results: TupleType ) : GoType, AbstractGoType() { override val typeName: String - get(): String { - return "func " + params.typeName + " " + results.typeName - } + get() = renderGoType(this) } class SliceType( val elementType: GoType ) : GoType, AbstractGoType() { override val typeName: String - get() = "[]${elementType.typeName}" - - override fun equals(other: Any?): Boolean { - if (other !is SliceType) { - return false - } - return other.elementType == elementType - } - - override fun hashCode(): Int { - var result = typeName.hashCode() - result = 31 * result + elementType.hashCode() - return result - } + get() = renderGoType(this) } class StructType( @@ -180,26 +120,14 @@ class StructType( val tags: List? ) : GoType, AbstractGoType() { override val typeName: String - get(): String { - var res = "struct {\n" - fields!!.forEachIndexed { ind, elem -> - res += elem.typeName - if (tags != null && tags.size > ind) { - res += " " + tags[ind] - } - res += "\n" - } - return "$res}" - } + get() = renderGoType(this) } class TupleType( val types: List ) : GoType, AbstractGoType() { override val typeName: String - get(): String { - return "(" + types.joinToString(", ") + ")" - } + get() = renderGoType(this) } class TypeParam( @@ -210,15 +138,54 @@ class UnionType( val terms: List ) : GoType, AbstractGoType() { override val typeName: String - get(): String { - var res = "enum {\n" - for (t in terms) { - res += t.typeName + ",\n" - } - return "$res}" - } + get() = renderGoType(this) } class OpaqueType( override val typeName: String ) : GoType, AbstractGoType() + +/** Named declarations terminate recursion; mutable placeholder cycles are also safe to inspect. */ +private fun renderGoType(type: GoType): String { + val visiting = IdentityHashMap() + + fun render(current: GoType): String { + if (visiting.put(current, true) != null) return "" + + val result = when (current) { + is ArrayType -> "[${current.len}]${render(current.elementType)}" + is ChanType -> { + val element = render(current.elementType) + when (current.direction) { + 0L -> "chan $element" + 1L -> "<-chan $element" + 2L -> "chan <-$element" + else -> "invalid chan $element" + } + } + is MapType -> "map[${render(current.keyType)}]${render(current.valueType)}" + is PointerType -> "*${render(current.baseType)}" + is SignatureType -> "func ${render(current.params)} ${render(current.results)}" + is SliceType -> "[]${render(current.elementType)}" + is StructType -> buildString { + append("struct {\n") + current.fields.orEmpty().forEachIndexed { index, field -> + append(render(field)) + val tag = current.tags?.getOrNull(index) + if (tag != null) append(" $tag") + append('\n') + } + append('}') + } + is TupleType -> current.types.joinToString(prefix = "(", postfix = ")", separator = ", ") { render(it) } + is UnionType -> current.terms.joinToString(prefix = "enum {\n", postfix = "}", separator = ",\n") { + render(it) + } + else -> current.typeName + } + visiting.remove(current) + return result + } + + return render(type) +} diff --git a/jacodb-go/src/test/kotlin/org/jacodb/go/api/GoTypeIdentityTest.kt b/jacodb-go/src/test/kotlin/org/jacodb/go/api/GoTypeIdentityTest.kt new file mode 100644 index 000000000..d4c6cfa2c --- /dev/null +++ b/jacodb-go/src/test/kotlin/org/jacodb/go/api/GoTypeIdentityTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.go.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class GoTypeIdentityTest { + @Test + fun distinguishesPrimitiveTuplePositionsAndPointerFields() { + val integer = BasicType(typeName = "int") + val pointer = PointerType(baseType = integer) + val floatingTuple = TupleType( + types = listOf(BasicType(typeName = "bool"), BasicType(typeName = "float32"), integer), + ) + val stringTuple = TupleType( + types = listOf(BasicType(typeName = "bool"), integer, BasicType(typeName = "rune")), + ) + + assertNotEquals(integer as GoType, pointer as GoType) + assertNotEquals(pointer as GoType, integer as GoType) + assertEquals("*int", pointer.typeName) + assertNotEquals(floatingTuple, stringTuple) + assertNotEquals( + StructType(fields = listOf(integer), tags = null), + StructType(fields = listOf(pointer), tags = null), + ) + } + + @Test + fun equalCompositeKeysHaveEqualHashesAndWorkInMaps() { + val first = MapType( + keyType = PointerType(baseType = BasicType(typeName = "int")), + valueType = TupleType(types = listOf(BasicType(typeName = "bool"), BasicType(typeName = "int"))), + ) + val second = MapType( + keyType = PointerType(baseType = BasicType(typeName = "int")), + valueType = TupleType(types = listOf(BasicType(typeName = "bool"), BasicType(typeName = "int"))), + ) + + assertEquals(first, second) + assertEquals(first.hashCode(), second.hashCode()) + assertEquals("value", mapOf(first to "value")[second]) + } + + @Test + fun namedRecursionKeepsNominalIdentityStable() { + val node = NamedType(underlyingType = NullType(), name = "pkg.Node", methods = emptyList()) + val originalHash = node.hashCode() + val pointer = PointerType(baseType = node) + + node.underlyingType = StructType(fields = listOf(pointer), tags = null) + + assertEquals(originalHash, node.hashCode()) + assertEquals("*pkg.Node", pointer.typeName) + assertTrue(node.underlyingType.typeName.contains("*pkg.Node")) + assertNotEquals(node as GoType, BasicType(typeName = "pkg.Node") as GoType) + } + + @Test + fun mutablePointerPlaceholderCyclesAreSafeToInspectAndCompare() { + val first = PointerType(baseType = NullType()) + val second = PointerType(baseType = NullType()) + + first.baseType = first + second.baseType = second + + assertEquals(first, second) + assertEquals(first.hashCode(), second.hashCode()) + assertTrue(first.toString().contains("recursive")) + } +} From 043845a17d814ff00c1208816bb950f997e41d46 Mon Sep 17 00:00:00 2001 From: Aleksei Menshutin Date: Tue, 13 Oct 2026 06:13:33 +0300 Subject: [PATCH 2/2] Pin compatible Node declarations before ArkAnalyzer installation --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 775e86160..6f6431ca7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -82,6 +82,7 @@ jobs: echo "ARKANALYZER_DIR=$(realpath $DEST_DIR)" >> $GITHUB_ENV cd $DEST_DIR + npm pkg set "devDependencies.@types/node=18.19.130" npm install npm run build