[JS IR] Add test with boolean in external interface
[JS IR] Add possibility to safely access Boolean in external declaration [JS IR] Add diagnostic for booleans in externals
This commit is contained in:
+14
-1
@@ -127,7 +127,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
||||
|
||||
@Argument(
|
||||
value = "-Xir-dce-runtime-diagnostic",
|
||||
valueDescription = "{$DCE_RUNTIME_DIAGNOSTIC_LOG|$DCE_RUNTIME_DIAGNOSTIC_EXCEPTION}",
|
||||
valueDescription = "{$RUNTIME_DIAGNOSTIC_LOG|$RUNTIME_DIAGNOSTIC_EXCEPTION}",
|
||||
description = "Enable runtime diagnostics when performing DCE instead of removing declarations"
|
||||
)
|
||||
var irDceRuntimeDiagnostic: String? by NullableStringFreezableVar(null)
|
||||
@@ -157,6 +157,19 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
||||
@Argument(value = "-Xir-base-class-in-metadata", description = "Write base class into metadata")
|
||||
var irBaseClassInMetadata: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xir-safe-external-boolean",
|
||||
description = "Safe access via Boolean() to Boolean properties in externals to safely cast falsy values."
|
||||
)
|
||||
var irSafeExternalBoolean: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xir-safe-external-boolean-diagnostic",
|
||||
valueDescription = "{$RUNTIME_DIAGNOSTIC_LOG|$RUNTIME_DIAGNOSTIC_EXCEPTION}",
|
||||
description = "Enable runtime diagnostics when access safely to boolean in external declarations"
|
||||
)
|
||||
var irSafeExternalBooleanDiagnostic: String? by NullableStringFreezableVar(null)
|
||||
|
||||
@Argument(value = "-Xir-per-module", description = "Splits generated .js per-module")
|
||||
var irPerModule: Boolean by FreezableVar(false)
|
||||
|
||||
|
||||
+2
-2
@@ -29,6 +29,6 @@ public interface K2JsArgumentConstants {
|
||||
String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never";
|
||||
String SOURCE_MAP_SOURCE_CONTENT_INLINING = "inlining";
|
||||
|
||||
String DCE_RUNTIME_DIAGNOSTIC_LOG = "log";
|
||||
String DCE_RUNTIME_DIAGNOSTIC_EXCEPTION = "exception";
|
||||
String RUNTIME_DIAGNOSTIC_LOG = "log";
|
||||
String RUNTIME_DIAGNOSTIC_EXCEPTION = "exception";
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode.OK
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.DCE_RUNTIME_DIAGNOSTIC_EXCEPTION
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.DCE_RUNTIME_DIAGNOSTIC_LOG
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_EXCEPTION
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_LOG
|
||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
|
||||
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
|
||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||
@@ -267,7 +267,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
mainArguments = mainCallArguments,
|
||||
generateFullJs = !arguments.irDce,
|
||||
generateDceJs = arguments.irDce,
|
||||
dceRuntimeDiagnostic = DceRuntimeDiagnostic.resolve(
|
||||
dceRuntimeDiagnostic = RuntimeDiagnostic.resolve(
|
||||
arguments.irDceRuntimeDiagnostic,
|
||||
messageCollector
|
||||
),
|
||||
@@ -277,6 +277,11 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||
legacyPropertyAccess = arguments.irLegacyPropertyAccess,
|
||||
baseClassIntoMetadata = arguments.irBaseClassInMetadata,
|
||||
safeExternalBoolean = arguments.irSafeExternalBoolean,
|
||||
safeExternalBooleanDiagnostic = RuntimeDiagnostic.resolve(
|
||||
arguments.irSafeExternalBooleanDiagnostic,
|
||||
messageCollector
|
||||
),
|
||||
)
|
||||
|
||||
val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!!
|
||||
@@ -436,15 +441,15 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun DceRuntimeDiagnostic.Companion.resolve(
|
||||
fun RuntimeDiagnostic.Companion.resolve(
|
||||
value: String?,
|
||||
messageCollector: MessageCollector
|
||||
): DceRuntimeDiagnostic? = when (value?.lowercase()) {
|
||||
DCE_RUNTIME_DIAGNOSTIC_LOG -> DceRuntimeDiagnostic.LOG
|
||||
DCE_RUNTIME_DIAGNOSTIC_EXCEPTION -> DceRuntimeDiagnostic.EXCEPTION
|
||||
): RuntimeDiagnostic? = when (value?.lowercase()) {
|
||||
RUNTIME_DIAGNOSTIC_LOG -> RuntimeDiagnostic.LOG
|
||||
RUNTIME_DIAGNOSTIC_EXCEPTION -> RuntimeDiagnostic.EXCEPTION
|
||||
null -> null
|
||||
else -> {
|
||||
messageCollector.report(STRONG_WARNING, "Unknown DCE runtime diagnostic '$value'")
|
||||
messageCollector.report(STRONG_WARNING, "Unknown runtime diagnostic '$value'")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.isExported
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.ClassReferenceLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
@@ -23,9 +22,8 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.js.config.removingBody
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.util.*
|
||||
|
||||
@@ -109,11 +107,6 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
|
||||
return rootDeclarations
|
||||
}
|
||||
|
||||
private fun DceRuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) =
|
||||
when (this) {
|
||||
DceRuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog
|
||||
DceRuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException
|
||||
}
|
||||
|
||||
private fun processUselessDeclarations(
|
||||
modules: Iterable<IrModuleFragment>,
|
||||
@@ -173,6 +166,16 @@ private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) =
|
||||
when (this) {
|
||||
RuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog
|
||||
RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException
|
||||
}
|
||||
|
||||
private fun RuntimeDiagnostic.removingBody(): Boolean {
|
||||
return this != RuntimeDiagnostic.LOG
|
||||
}
|
||||
|
||||
private fun IrDeclaration.processWithDiagnostic(context: JsIrBackendContext) {
|
||||
when (this) {
|
||||
is IrFunction -> processFunctionWithDiagnostic(context)
|
||||
|
||||
@@ -164,6 +164,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
||||
val jsUnreachableDeclarationLog = getInternalFunction("unreachableDeclarationLog")
|
||||
val jsUnreachableDeclarationException = getInternalFunction("unreachableDeclarationException")
|
||||
|
||||
val jsNativeBoolean = getInternalFunction("nativeBoolean")
|
||||
val jsBooleanInExternalLog = getInternalFunction("booleanInExternalLog")
|
||||
val jsBooleanInExternalException = getInternalFunction("booleanInExternalException")
|
||||
|
||||
// Coroutines
|
||||
|
||||
val jsCoroutineContext
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmen
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -45,10 +45,12 @@ class JsIrBackendContext(
|
||||
override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context
|
||||
override val scriptMode: Boolean = false,
|
||||
override val es6mode: Boolean = false,
|
||||
val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
||||
val dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
|
||||
val propertyLazyInitialization: Boolean = false,
|
||||
val legacyPropertyAccess: Boolean = false,
|
||||
val baseClassIntoMetadata: Boolean = false,
|
||||
val safeExternalBoolean: Boolean = false,
|
||||
val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
|
||||
) : JsCommonBackendContext {
|
||||
val fileToInitializationFuns: MutableMap<IrFile, IrSimpleFunction?> = mutableMapOf()
|
||||
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
|
||||
|
||||
@@ -391,6 +391,12 @@ private val copyPropertyAccessorBodiesLoweringPass = makeDeclarationTransformerP
|
||||
prerequisite = setOf(propertyAccessorInlinerLoweringPhase)
|
||||
)
|
||||
|
||||
private val booleanPropertyInExternalLowering = makeBodyLoweringPhase(
|
||||
::BooleanPropertyInExternalLowering,
|
||||
name = "BooleanPropertyInExternalLowering",
|
||||
description = "Lowering which wrap boolean in external declarations with Boolean() call and add diagnostic for such cases"
|
||||
)
|
||||
|
||||
private val foldConstantLoweringPhase = makeBodyLoweringPhase(
|
||||
{ FoldConstantLowering(it, true) },
|
||||
name = "FoldConstantLowering",
|
||||
@@ -779,6 +785,7 @@ private val loweringList = listOf<Lowering>(
|
||||
removeInitializersForLazyProperties,
|
||||
propertyAccessorInlinerLoweringPhase,
|
||||
copyPropertyAccessorBodiesLoweringPass,
|
||||
booleanPropertyInExternalLowering,
|
||||
foldConstantLoweringPhase,
|
||||
privateMembersLoweringPhase,
|
||||
privateMemberUsagesLoweringPhase,
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.ir.declarations.StageController
|
||||
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
|
||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||
import org.jetbrains.kotlin.ir.util.noUnboundLeft
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -48,7 +48,7 @@ fun compile(
|
||||
generateFullJs: Boolean = true,
|
||||
generateDceJs: Boolean = false,
|
||||
dceDriven: Boolean = false,
|
||||
dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
||||
dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
|
||||
es6mode: Boolean = false,
|
||||
multiModule: Boolean = false,
|
||||
relativeRequirePath: Boolean = false,
|
||||
@@ -56,6 +56,8 @@ fun compile(
|
||||
legacyPropertyAccess: Boolean = false,
|
||||
baseClassIntoMetadata: Boolean = false,
|
||||
lowerPerModule: Boolean = false,
|
||||
safeExternalBoolean: Boolean = false,
|
||||
safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
|
||||
): CompilerResult {
|
||||
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer, moduleToName) =
|
||||
loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory)
|
||||
@@ -78,7 +80,9 @@ fun compile(
|
||||
dceRuntimeDiagnostic = dceRuntimeDiagnostic,
|
||||
propertyLazyInitialization = propertyLazyInitialization,
|
||||
legacyPropertyAccess = legacyPropertyAccess,
|
||||
baseClassIntoMetadata = baseClassIntoMetadata
|
||||
baseClassIntoMetadata = baseClassIntoMetadata,
|
||||
safeExternalBoolean = safeExternalBoolean,
|
||||
safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic
|
||||
)
|
||||
|
||||
// Load declarations referenced during `context` initialization
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.createTmpVariable
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irGet
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
|
||||
|
||||
class BooleanPropertyInExternalLowering(
|
||||
private val context: JsIrBackendContext
|
||||
) : BodyLoweringPass {
|
||||
|
||||
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||
irBody.transformChildrenVoid(
|
||||
ExternalBooleanPropertyProcessor(
|
||||
context
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class ExternalBooleanPropertyProcessor(
|
||||
private val context: JsIrBackendContext
|
||||
) : IrElementTransformerVoid() {
|
||||
|
||||
private val safeExternalBoolean
|
||||
get() = context.safeExternalBoolean
|
||||
|
||||
private val safeExternalBooleanDiagnostic
|
||||
get() = context.safeExternalBooleanDiagnostic
|
||||
|
||||
private val booleanType
|
||||
get() = context.irBuiltIns.booleanType
|
||||
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
|
||||
val symbol = expression.symbol
|
||||
val callee = symbol.owner
|
||||
val property = callee.correspondingPropertySymbol?.owner ?: return expression
|
||||
|
||||
if (!property.isEffectivelyExternal()) return expression
|
||||
|
||||
if (callee != property.getter) return expression
|
||||
|
||||
if (callee.returnType != booleanType) return expression
|
||||
|
||||
val function = safeExternalBooleanDiagnostic?.diagnosticMethod()
|
||||
|
||||
if (!safeExternalBoolean && function == null) return expression
|
||||
|
||||
if (safeExternalBoolean && function == null) {
|
||||
return JsIrBuilder.buildCall(
|
||||
target = context.intrinsics.jsNativeBoolean
|
||||
).apply {
|
||||
putValueArgument(0, expression)
|
||||
}
|
||||
}
|
||||
|
||||
return context.createIrBuilder(symbol).irBlock {
|
||||
val tmp = createTmpVariable(expression)
|
||||
val call = JsIrBuilder.buildCall(
|
||||
target = function!!
|
||||
).apply {
|
||||
putValueArgument(0, irGet(tmp))
|
||||
}
|
||||
|
||||
+call
|
||||
|
||||
val newBooleanGet = if (safeExternalBoolean) {
|
||||
JsIrBuilder.buildCall(
|
||||
target = this@ExternalBooleanPropertyProcessor.context.intrinsics.jsNativeBoolean
|
||||
).apply {
|
||||
putValueArgument(0, irGet(tmp))
|
||||
}
|
||||
} else irGet(tmp)
|
||||
+newBooleanGet
|
||||
}
|
||||
}
|
||||
|
||||
private fun RuntimeDiagnostic.diagnosticMethod() = when (this) {
|
||||
RuntimeDiagnostic.LOG -> context.intrinsics.jsBooleanInExternalLog
|
||||
RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsBooleanInExternalException
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-15
@@ -110,7 +110,7 @@ fun translateCall(
|
||||
// @JsName-annotated external property accessors are translated as function calls
|
||||
if (function.getJsName() == null) {
|
||||
val property = function.correspondingPropertySymbol?.owner
|
||||
if (property != null && (property.isEffectivelyExternal())) {
|
||||
if (property != null && property.isEffectivelyExternal()) {
|
||||
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
|
||||
return when (function) {
|
||||
property.getter -> nameRef
|
||||
@@ -222,22 +222,23 @@ fun translateCall(
|
||||
} else {
|
||||
val defaultResult = JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments)
|
||||
|
||||
val alternativeResult = if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) {
|
||||
val property = function.correspondingPropertySymbol?.owner
|
||||
if (property != null) {
|
||||
val propertyName = context.getNameForProperty(property)
|
||||
val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident))
|
||||
val fnName = when (function) {
|
||||
property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner)
|
||||
property.setter -> {
|
||||
args += arguments
|
||||
context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner)
|
||||
val alternativeResult =
|
||||
if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) {
|
||||
val property = function.correspondingPropertySymbol?.owner
|
||||
if (property != null) {
|
||||
val propertyName = context.getNameForProperty(property)
|
||||
val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident))
|
||||
val fnName = when (function) {
|
||||
property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner)
|
||||
property.setter -> {
|
||||
args += arguments
|
||||
context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner)
|
||||
}
|
||||
else -> error("Function must be an accessor of corresponding property")
|
||||
}
|
||||
else -> error("Function must be an accessor of corresponding property")
|
||||
}
|
||||
JsInvocation(fnName.makeRef(), args)
|
||||
JsInvocation(fnName.makeRef(), args)
|
||||
} else null
|
||||
} else null
|
||||
} else null
|
||||
|
||||
alternativeResult ?: defaultResult
|
||||
}
|
||||
|
||||
+3
@@ -27,6 +27,9 @@ where advanced options include:
|
||||
-Xir-produce-klib-file Generate packed klib into file specified by -output. Disables pre-IR backend
|
||||
-Xir-property-lazy-initialization
|
||||
Perform lazy initialization for properties
|
||||
-Xir-safe-external-boolean Safe access via Boolean() to Boolean properties in externals to safely cast falsy values.
|
||||
-Xir-safe-external-boolean-diagnostic={log|exception}
|
||||
Enable runtime diagnostics when access safely to boolean in external declarations
|
||||
-Xmetadata-only Generate *.meta.js and *.kjsm files only
|
||||
-Xrepositories=<path> Paths to additional places where libraries could be found
|
||||
-Xtyped-arrays Translate primitive arrays to JS typed arrays
|
||||
|
||||
Reference in New Issue
Block a user