blob: 837f9f05daae45e942d50eadff9c43eaea44f563 [file] [log] [blame]
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.generators.builtins.arrays
import org.jetbrains.kotlin.generators.builtins.PrimitiveType
import org.jetbrains.kotlin.generators.builtins.generateBuiltIns.BuiltInsGenerator
import org.jetbrains.kotlin.generators.builtins.numbers.primitives.*
import java.io.PrintWriter
abstract class GenerateArrays(val writer: PrintWriter, val primitiveArrays: Boolean) : BuiltInsGenerator {
override fun generate() {
writer.print(generateFile().build())
}
private fun generateFile(): FileBuilder {
return file(this::class) { generateClasses() }.apply { this.modifyGeneratedFile() }
}
internal abstract class ArrayBuilder(val kind: PrimitiveType?) {
protected val arrayClassName = "${kind?.capitalized ?: ""}Array"
protected val arrayTypeName = arrayClassName + if (kind == null) "<T>" else ""
protected val elementTypeName = kind?.capitalized ?: "T"
protected val iteratorClassName = "${kind?.capitalized ?: ""}Iterator"
protected val arrayIteratorImplClassName = "${kind?.capitalized ?: ""}ArrayIterator"
fun FileBuilder.generateArrayClass() {
val typeLower = kind?.name?.lowercase() ?: "T"
klass {
expectActual = ExpectActualModifier.Actual
name = arrayClassName
if (kind == null) {
appendDoc("A generic array of objects. When targeting the JVM, instances of this class are represented as `T[]`.")
appendDoc("Array instances can be created using the [arrayOf], [arrayOfNulls] and [emptyArray]")
appendDoc("standard library functions.")
typeParam("T")
noPrimaryConstructor()
} else {
appendDoc("An array of ${typeLower}s. When targeting the JVM, instances of this class are represented as `$typeLower[]`.")
val defaultValue = when (kind) {
PrimitiveType.CHAR -> "null char (`\\u0000')"
PrimitiveType.BOOLEAN -> "`false`"
else -> "zero"
}
primaryConstructor {
appendDoc("Creates a new array of the specified [size], with all elements initialized to $defaultValue.")
appendDoc("@throws RuntimeException if the specified [size] is negative.")
visibility = MethodVisibility.PUBLIC
expectActual = ExpectActualModifier.Inherited(from = this@klass::expectActual)
parameter {
name = "size"
type = PrimitiveType.INT.capitalized
}
}.modifyPrimaryConstructor()
}
appendDoc("")
appendDoc("See [Kotlin language documentation](https://kotlinlang.org/docs/arrays.html)")
appendDoc("for more information on arrays.")
generatePropertiesAndInit()
generateSecondaryConstructor()
generateGetSet()
generateSize()
generateIterator()
}.modifyGeneratedClass()
modifyGeneratedFileAfterClass()
}
protected fun FileBuilder.generateArrayIteratorClass() {
klass {
visibility = MethodVisibility.PRIVATE
name = arrayIteratorImplClassName
if (kind == null) typeParam("T")
superType(iteratorClassName + if (kind == null) "<T>" else "()")
primaryConstructor {
parameter {
visibility = null
name = "val array"
type = arrayTypeName
}
}
classBody("""
private var index = 0
override fun hasNext() = index < array.size
override fun next${kind?.capitalized ?: ""}() = if (index < array.size) array[index++] else throw NoSuchElementException("${'$'}index")
""".trimIndent())
}
}
protected open fun ClassBuilder.generatePropertiesAndInit() {}
private fun ClassBuilder.generateSecondaryConstructor() {
secondaryConstructor {
visibility = MethodVisibility.PUBLIC
annotations += """Suppress("WRONG_MODIFIER_TARGET")"""
modifier("inline")
parameter {
name = "size"
type = PrimitiveType.INT.capitalized
}
parameter {
name = "init"
type = "(${PrimitiveType.INT.capitalized}) -> $elementTypeName"
}
appendDoc("Creates a new array of the specified [size], where each element is calculated by calling the specified")
appendDoc("[init] function.")
appendDoc("")
appendDoc("The function [init] is called for each array element sequentially starting from the first one.")
appendDoc("It should return the value for an array element given its index.")
appendDoc("")
appendDoc("@throws RuntimeException if the specified [size] is negative.")
noPrimaryConstructorCall()
}.modifySecondaryConstructor()
}
private fun ClassBuilder.generateGetSet() {
method {
appendDoc("Returns the array element at the given [index].")
appendDoc("")
appendDoc("This method can be called using the index operator:")
appendDoc("```")
appendDoc("value = array[index]")
appendDoc("```")
appendDoc("")
appendDoc("If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException] except in Kotlin/JS")
appendDoc("where the behavior is unspecified.")
signature {
methodName = "get"
isOperator = true
parameter {
name = "index"
type = PrimitiveType.INT.capitalized
}
returnType = elementTypeName
}
}.modifyGetOperator()
method {
appendDoc("Sets the array element at the given [index] to the given [value].")
appendDoc("")
appendDoc("This method can be called using the index operator:")
appendDoc("```")
appendDoc("array[index] = value")
appendDoc("```")
appendDoc("")
appendDoc("If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException] except in Kotlin/JS")
appendDoc("where the behavior is unspecified.")
signature {
methodName = "set"
isOperator = true
parameter {
name = "index"
type = PrimitiveType.INT.capitalized
}
parameter {
name = "value"
type = elementTypeName
}
returnType = "Unit"
}
}.modifySetOperator()
}
private fun ClassBuilder.generateSize() {
property {
appendDoc("Returns the number of elements in the array.")
name = "size"
type = PrimitiveType.INT.capitalized
}.modifySizeProperty()
}
private fun ClassBuilder.generateIterator() {
method {
appendDoc("Creates ${if (kind == null) "an [$iteratorClassName]" else "a specialized [$iteratorClassName]"} for iterating over the elements of the array.")
signature {
methodName = "iterator"
isOperator = true
returnType = iteratorClassName + if (kind == null) "<T>" else ""
}
}.modifyIterator()
}
protected open fun ClassBuilder.modifyGeneratedClass() {}
protected open fun PrimaryConstructorBuilder.modifyPrimaryConstructor() {}
protected open fun FileBuilder.modifyGeneratedFileAfterClass() {}
protected open fun SecondaryConstructorBuilder.modifySecondaryConstructor() {}
protected open fun MethodBuilder.modifyGetOperator() {}
protected open fun MethodBuilder.modifySetOperator() {}
protected open fun PropertyBuilder.modifySizeProperty() {}
protected open fun MethodBuilder.modifyIterator() {}
}
internal abstract fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder
private fun FileBuilder.generateClasses() {
if (primitiveArrays) {
for (kind in PrimitiveType.entries) {
with(arrayBuilder(kind)) { generateArrayClass() }
}
} else {
with(arrayBuilder(null)) { generateArrayClass() }
}
}
internal open fun FileBuilder.modifyGeneratedFile() {}
}
class GenerateCommonArrays(writer: PrintWriter, primitiveArrays: Boolean) : GenerateArrays(writer, primitiveArrays) {
override fun FileBuilder.modifyGeneratedFile() {
import("kotlin.internal.ActualizeByJvmBuiltinProvider")
}
override fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder = object : ArrayBuilder(kind) {
override fun ClassBuilder.modifyGeneratedClass() {
annotations += "ActualizeByJvmBuiltinProvider"
expectActual = ExpectActualModifier.Expect
}
}
}
class GenerateJvmArrays(writer: PrintWriter, primitiveArrays: Boolean) : GenerateArrays(writer, primitiveArrays) {
override fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder = object : ArrayBuilder(kind) {
override fun ClassBuilder.modifyGeneratedClass() {
expectActual = ExpectActualModifier.Unspecified
}
}
}
class GenerateJsArrays(writer: PrintWriter, primitiveArrays: Boolean) : GenerateArrays(writer, primitiveArrays) {
override fun FileBuilder.modifyGeneratedFile() {
suppress("UNUSED_PARAMETER")
}
override fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder = object : ArrayBuilder(kind) {
override fun SecondaryConstructorBuilder.modifySecondaryConstructor() {
annotations.removeAll { it.startsWith("Suppress") }
annotations += """Suppress("WRONG_MODIFIER_TARGET", "PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED")"""
}
override fun MethodBuilder.modifyGetOperator() {
annotations += """Suppress("NON_ABSTRACT_FUNCTION_WITH_NO_BODY")"""
}
override fun MethodBuilder.modifySetOperator() {
annotations += """Suppress("NON_ABSTRACT_FUNCTION_WITH_NO_BODY")"""
}
override fun PropertyBuilder.modifySizeProperty() {
annotations += """Suppress("MUST_BE_INITIALIZED_OR_BE_ABSTRACT")"""
}
override fun MethodBuilder.modifyIterator() {
annotations += """Suppress("NON_ABSTRACT_FUNCTION_WITH_NO_BODY")"""
}
}
}
class GenerateWasmArrays(writer: PrintWriter, primitiveArrays: Boolean) : GenerateArrays(writer, primitiveArrays) {
override fun FileBuilder.modifyGeneratedFile() {
import("kotlin.wasm.internal.*")
suppress("UNUSED_PARAMETER")
}
override fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder = object : ArrayBuilder(kind) {
private val storageArrayType = when (kind) {
null -> "WasmAnyArray"
PrimitiveType.BOOLEAN -> "WasmByteArray"
else -> "Wasm${kind.capitalized}Array"
}
override fun ClassBuilder.modifyGeneratedClass() {
if (kind == null) {
primaryConstructor {
annotations += "PublishedApi"
visibility = MethodVisibility.INTERNAL
parameter {
name = "size"
type = PrimitiveType.INT.capitalized
}
}
}
}
override fun ClassBuilder.generatePropertiesAndInit() {
property {
visibility = MethodVisibility.INTERNAL
expectActual = ExpectActualModifier.Unspecified
name = "storage"
type = storageArrayType
}
classBody("""
init {
if (size < 0) throw IllegalArgumentException("Negative array size")
storage = $storageArrayType(size)
}
@WasmPrimitiveConstructor
@Suppress("PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED")
internal constructor(storage: $storageArrayType)
""".trimIndent())
}
override fun SecondaryConstructorBuilder.modifySecondaryConstructor() {
annotations.removeAll { it.startsWith("Suppress") }
annotations += """Suppress("WRONG_MODIFIER_TARGET", "TYPE_PARAMETER_AS_REIFIED")"""
primaryConstructorCall("size")
}
override fun MethodBuilder.modifyGetOperator() {
"""
rangeCheck(index, storage.len())
${
when (kind) {
null -> "@Suppress(\"UNCHECKED_CAST\") return storage.get(index) as T"
PrimitiveType.BOOLEAN -> "return storage.get(index).reinterpretAsInt().reinterpretAsBoolean()"
else -> "return storage.get(index)"
}
}
""".trimIndent().setAsBlockBody()
}
override fun MethodBuilder.modifySetOperator() {
"""
rangeCheck(index, storage.len())
storage.set(index, value${if (kind == PrimitiveType.BOOLEAN) ".reinterpretAsByte()" else ""})
""".trimIndent().setAsBlockBody()
}
override fun PropertyBuilder.modifySizeProperty() {
"storage.len()".setAsExpressionGetterBody()
}
override fun MethodBuilder.modifyIterator() {
"$arrayIteratorImplClassName(this)".setAsExpressionBody()
}
override fun FileBuilder.modifyGeneratedFileAfterClass() {
generateArrayIteratorClass()
if (kind == PrimitiveType.BOOLEAN) {
method {
annotations += "WasmNoOpCast"
signature {
visibility = MethodVisibility.PRIVATE
methodName = "Boolean.reinterpretAsByte"
returnType = PrimitiveType.BYTE.capitalized
}
"implementedAsIntrinsic".setAsExpressionBody()
}
}
}
}
}
class GenerateNativeArrays(writer: PrintWriter, primitiveArrays: Boolean) : GenerateArrays(writer, primitiveArrays) {
override fun FileBuilder.modifyGeneratedFile() {
import("kotlin.native.internal.*")
import("kotlin.native.internal.escapeAnalysis.Escapes")
if (!primitiveArrays) {
import("kotlin.native.internal.escapeAnalysis.PointsTo")
}
}
override fun arrayBuilder(kind: PrimitiveType?): ArrayBuilder = object : ArrayBuilder(kind) {
override fun ClassBuilder.modifyGeneratedClass() {
annotations += """ExportTypeInfo("the${arrayClassName}TypeInfo")"""
method {
expectActual = ExpectActualModifier.Unspecified
signature {
annotations += """GCUnsafeCall("Kotlin_${arrayClassName}_getArrayLength")"""
annotations += """Escapes.Nothing"""
methodName = "getArrayLength"
visibility = MethodVisibility.PRIVATE
isExternal = true
returnType = PrimitiveType.INT.capitalized
}
}
}
override fun PrimaryConstructorBuilder.modifyPrimaryConstructor() {
annotations += """Suppress("UNUSED_PARAMETER")"""
}
override fun ClassBuilder.generatePropertiesAndInit() {
if (kind == null) {
classBody(
"""
@PublishedApi
@ExportForCompiler
internal constructor(@Suppress("UNUSED_PARAMETER") size: Int) {}
""".trimIndent()
)
}
}
override fun FileBuilder.modifyGeneratedFileAfterClass() {
generateArrayIteratorClass()
}
override fun SecondaryConstructorBuilder.modifySecondaryConstructor() {
annotations.removeAll { it.startsWith("Suppress") }
annotations += """Suppress("TYPE_PARAMETER_AS_REIFIED", "WRONG_MODIFIER_TARGET")"""
primaryConstructorCall("size")
"""
for (i in 0..size - 1) {
this[i] = init(i)
}
""".trimIndent().setAsBlockBody()
}
override fun MethodBuilder.modifyGetOperator() {
annotations += """GCUnsafeCall("Kotlin_${arrayClassName}_get")"""
if (kind == null) {
annotations += """PointsTo(0x000, 0x000, 0x002) // ret -> this.intestines"""
} else {
annotations += """Escapes.Nothing"""
}
modifySignature {
isExternal = true
}
}
override fun MethodBuilder.modifySetOperator() {
annotations += """GCUnsafeCall("Kotlin_${arrayClassName}_set")"""
if (kind == null) {
annotations += """PointsTo(0x0300, 0x0000, 0x0000, 0x0000) // this.intestines -> value"""
} else {
annotations += """Escapes.Nothing"""
}
modifySignature {
isExternal = true
}
}
override fun PropertyBuilder.modifySizeProperty() {
"getArrayLength()".setAsExpressionGetterBody()
}
override fun MethodBuilder.modifyIterator() {
"$arrayIteratorImplClassName(this)".setAsExpressionBody()
}
}
}