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
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
151 changes: 59 additions & 92 deletions jacodb-go/src/main/kotlin/org/jacodb/go/api/GoType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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() {
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -129,77 +97,37 @@ 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(
val params: TupleType,
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(
val fields: List<GoType>?,
val tags: List<String>?
) : 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>
) : GoType, AbstractGoType() {
override val typeName: String
get(): String {
return "(" + types.joinToString(", ") + ")"
}
get() = renderGoType(this)
}

class TypeParam(
Expand All @@ -210,15 +138,54 @@ class UnionType(
val terms: List<GoType>
) : 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<GoType, Boolean>()

fun render(current: GoType): String {
if (visiting.put(current, true) != null) return "<recursive:${current.javaClass.simpleName}>"

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)
}
88 changes: 88 additions & 0 deletions jacodb-go/src/test/kotlin/org/jacodb/go/api/GoTypeIdentityTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<GoType, String>(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"))
}
}
Loading