Make @JvmOverloads work with JVM_IR
This commit is contained in:
@@ -31,6 +31,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
|
|||||||
object SYNTHETIC_ACCESSOR : IrDeclarationOriginImpl("SYNTHETIC_ACCESSOR")
|
object SYNTHETIC_ACCESSOR : IrDeclarationOriginImpl("SYNTHETIC_ACCESSOR")
|
||||||
object TO_ARRAY : IrDeclarationOriginImpl("TO_ARRAY")
|
object TO_ARRAY : IrDeclarationOriginImpl("TO_ARRAY")
|
||||||
object JVM_STATIC_WRAPPER : IrDeclarationOriginImpl("JVM_STATIC_WRAPPER")
|
object JVM_STATIC_WRAPPER : IrDeclarationOriginImpl("JVM_STATIC_WRAPPER")
|
||||||
|
object JVM_OVERLOADS_WRAPPER : IrDeclarationOriginImpl("JVM_OVERLOADS_WRAPPER")
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JvmLoweredStatementOrigin : IrStatementOrigin {
|
interface JvmLoweredStatementOrigin : IrStatementOrigin {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class JvmLower(val context: JvmBackendContext) {
|
|||||||
SingletonReferencesLowering(context).runOnFilePostfix(irFile)
|
SingletonReferencesLowering(context).runOnFilePostfix(irFile)
|
||||||
SyntheticAccessorLowering(context).lower(irFile)
|
SyntheticAccessorLowering(context).lower(irFile)
|
||||||
BridgeLowering(context).runOnFilePostfix(irFile)
|
BridgeLowering(context).runOnFilePostfix(irFile)
|
||||||
|
JvmOverloadsAnnotationLowering(context).runOnFilePostfix(irFile)
|
||||||
JvmStaticAnnotationLowering(context).lower(irFile)
|
JvmStaticAnnotationLowering(context).lower(irFile)
|
||||||
StaticDefaultFunctionLowering(context.state).runOnFilePostfix(irFile)
|
StaticDefaultFunctionLowering(context.state).runOnFilePostfix(irFile)
|
||||||
|
|
||||||
@@ -70,4 +71,4 @@ class JvmLower(val context: JvmBackendContext) {
|
|||||||
|
|
||||||
irFile.acceptVoid(PatchDeclarationParentsVisitor())
|
irFile.acceptVoid(PatchDeclarationParentsVisitor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -251,8 +251,13 @@ class ExpressionCodegen(
|
|||||||
|
|
||||||
callGenerator.beforeValueParametersStart()
|
callGenerator.beforeValueParametersStart()
|
||||||
val defaultMask = DefaultCallArgs(callable.valueParameterTypes.size)
|
val defaultMask = DefaultCallArgs(callable.valueParameterTypes.size)
|
||||||
val enumConstructorDefaultArgsShift =
|
val extraArgsShift =
|
||||||
if (expression.descriptor is ConstructorDescriptor && isEnumClass(expression.descriptor.containingDeclaration)) 2 else 0
|
when {
|
||||||
|
expression.descriptor is ConstructorDescriptor && isEnumClass(expression.descriptor.containingDeclaration) -> 2
|
||||||
|
expression.descriptor is ConstructorDescriptor &&
|
||||||
|
(expression.descriptor.containingDeclaration as ClassDescriptor).isInner -> 1 // skip the `$outer` parameter
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
expression.descriptor.valueParameters.forEachIndexed { i, parameterDescriptor ->
|
expression.descriptor.valueParameters.forEachIndexed { i, parameterDescriptor ->
|
||||||
val arg = expression.getValueArgument(i)
|
val arg = expression.getValueArgument(i)
|
||||||
val parameterType = callable.valueParameterTypes[i]
|
val parameterType = callable.valueParameterTypes[i]
|
||||||
@@ -268,7 +273,7 @@ class ExpressionCodegen(
|
|||||||
i,
|
i,
|
||||||
this@ExpressionCodegen
|
this@ExpressionCodegen
|
||||||
)
|
)
|
||||||
defaultMask.mark(i - enumConstructorDefaultArgsShift/*TODO switch to separate lower*/)
|
defaultMask.mark(i - extraArgsShift/*TODO switch to separate lower*/)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
assert(parameterDescriptor.varargElementType != null)
|
assert(parameterDescriptor.varargElementType != null)
|
||||||
@@ -1123,4 +1128,4 @@ fun DefaultCallArgs.generateOnStackIfNeeded(callGenerator: IrCallGenerator, isCo
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return toInts.isNotEmpty()
|
return toInts.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -92,8 +92,16 @@ class JvmDescriptorsFactory(
|
|||||||
val newValueParameters =
|
val newValueParameters =
|
||||||
listOf(outerThisValueParameter) +
|
listOf(outerThisValueParameter) +
|
||||||
oldDescriptor.valueParameters.map { it.copy(newDescriptor, it.name, it.index + 1) }
|
oldDescriptor.valueParameters.map { it.copy(newDescriptor, it.name, it.index + 1) }
|
||||||
newDescriptor.initialize(newValueParameters, oldDescriptor.visibility)
|
// Call the long version of `initialize()`, because otherwise default implementation inserts
|
||||||
newDescriptor.returnType = oldDescriptor.returnType
|
// an unwanted `dispatchReceiverParameter`
|
||||||
|
newDescriptor.initialize(
|
||||||
|
oldDescriptor.extensionReceiverParameter?.type,
|
||||||
|
null,
|
||||||
|
oldDescriptor.typeParameters,
|
||||||
|
newValueParameters,
|
||||||
|
oldDescriptor.returnType,
|
||||||
|
oldDescriptor.modality,
|
||||||
|
oldDescriptor.visibility)
|
||||||
return IrConstructorSymbolImpl(newDescriptor)
|
return IrConstructorSymbolImpl(newDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. 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.backend.jvm.lower
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.impl.createFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.util.createParameterDeclarations
|
||||||
|
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmOverloadsAnnotation
|
||||||
|
|
||||||
|
|
||||||
|
class JvmOverloadsAnnotationLowering(val context: JvmBackendContext) : ClassLoweringPass {
|
||||||
|
|
||||||
|
override fun lower(irClass: IrClass) {
|
||||||
|
val functions = irClass.declarations.filterIsInstance<IrFunction>().filter {
|
||||||
|
it.descriptor.findJvmOverloadsAnnotation() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
functions.forEach {
|
||||||
|
generateWrappers(it, irClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateWrappers(target: IrFunction, irClass: IrClass) {
|
||||||
|
val numDefaultParameters = target.symbol.descriptor.valueParameters.count { it.hasDefaultValue() }
|
||||||
|
for (i in 0 until numDefaultParameters) {
|
||||||
|
val wrapper = generateWrapper(target, i)
|
||||||
|
irClass.addMember(wrapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateWrapper(target: IrFunction, numDefaultParametersToExpect: Int): IrFunction {
|
||||||
|
val wrapperSymbol = generateWrapperSymbol(target.symbol, numDefaultParametersToExpect)
|
||||||
|
val wrapperIrFunction = when (wrapperSymbol) {
|
||||||
|
is IrConstructorSymbol -> IrConstructorImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER,
|
||||||
|
wrapperSymbol
|
||||||
|
)
|
||||||
|
is IrSimpleFunctionSymbol -> IrFunctionImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER,
|
||||||
|
wrapperSymbol
|
||||||
|
)
|
||||||
|
else -> error("expected IrConstructorSymbol or IrSimpleFunctionSymbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapperIrFunction.returnType = target.returnType
|
||||||
|
wrapperIrFunction.createParameterDeclarations()
|
||||||
|
|
||||||
|
val call = if (target is IrConstructor)
|
||||||
|
IrDelegatingConstructorCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, target.returnType, target.symbol, target.descriptor)
|
||||||
|
else
|
||||||
|
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, target.returnType, target.symbol)
|
||||||
|
call.dispatchReceiver = wrapperIrFunction.dispatchReceiverParameter?.let { dispatchReceiver ->
|
||||||
|
IrGetValueImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
dispatchReceiver.symbol
|
||||||
|
)
|
||||||
|
}
|
||||||
|
call.extensionReceiver = wrapperIrFunction.extensionReceiverParameter?.let { extensionReceiver ->
|
||||||
|
IrGetValueImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
extensionReceiver.symbol
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var parametersCopied = 0
|
||||||
|
var defaultParametersCopied = 0
|
||||||
|
for ((i, valueParameter) in target.valueParameters.withIndex()) {
|
||||||
|
if ((valueParameter.descriptor as ValueParameterDescriptor).hasDefaultValue()) {
|
||||||
|
if (defaultParametersCopied < numDefaultParametersToExpect) {
|
||||||
|
defaultParametersCopied++
|
||||||
|
call.putValueArgument(
|
||||||
|
i,
|
||||||
|
IrGetValueImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
wrapperIrFunction.valueParameters[parametersCopied++].symbol
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
call.putValueArgument(i, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
call.putValueArgument(
|
||||||
|
i,
|
||||||
|
IrGetValueImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||||
|
wrapperIrFunction.valueParameters[parametersCopied++].symbol
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapperIrFunction.body = IrExpressionBodyImpl(
|
||||||
|
UNDEFINED_OFFSET, UNDEFINED_OFFSET, call
|
||||||
|
)
|
||||||
|
|
||||||
|
target.annotations.mapTo(wrapperIrFunction.annotations) { it.deepCopyWithSymbols() }
|
||||||
|
|
||||||
|
return wrapperIrFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateWrapperSymbol(oldSymbol: IrFunctionSymbol, numDefaultParametersToExpect: Int): IrFunctionSymbol {
|
||||||
|
val oldDescriptor = oldSymbol.descriptor
|
||||||
|
val newDescriptor = if (oldDescriptor is ClassConstructorDescriptor) {
|
||||||
|
oldDescriptor.copyWithModifiedParameters(numDefaultParametersToExpect)
|
||||||
|
} else {
|
||||||
|
val newParameters = generateNewValueParameters(oldDescriptor, numDefaultParametersToExpect)
|
||||||
|
oldDescriptor.newCopyBuilder()
|
||||||
|
.setValueParameters(newParameters)
|
||||||
|
.setOriginal(null)
|
||||||
|
.setKind(CallableMemberDescriptor.Kind.SYNTHESIZED)
|
||||||
|
.build()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
return createFunctionSymbol(newDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ClassConstructorDescriptor.copyWithModifiedParameters(numDefaultParametersToExpect: Int): ClassConstructorDescriptor {
|
||||||
|
val result = ClassConstructorDescriptorImpl.createSynthesized(
|
||||||
|
this.containingDeclaration,
|
||||||
|
annotations,
|
||||||
|
/* isPrimary = */ false,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
// Call the long version of `initialize()`, because otherwise default implementation inserts
|
||||||
|
// an unwanted `dispatchReceiverParameter`.
|
||||||
|
result.initialize(
|
||||||
|
extensionReceiverParameter?.type,
|
||||||
|
dispatchReceiverParameter,
|
||||||
|
typeParameters,
|
||||||
|
generateNewValueParameters(this, numDefaultParametersToExpect),
|
||||||
|
returnType,
|
||||||
|
modality,
|
||||||
|
visibility
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateNewValueParameters(
|
||||||
|
oldDescriptor: FunctionDescriptor,
|
||||||
|
numDefaultParametersToExpect: Int
|
||||||
|
): List<ValueParameterDescriptor> {
|
||||||
|
var parametersCopied = 0
|
||||||
|
var defaultParametersCopied = 0
|
||||||
|
val result = mutableListOf<ValueParameterDescriptor>()
|
||||||
|
for (oldValueParameter in oldDescriptor.valueParameters) {
|
||||||
|
if (oldValueParameter.hasDefaultValue() &&
|
||||||
|
defaultParametersCopied < numDefaultParametersToExpect
|
||||||
|
) {
|
||||||
|
defaultParametersCopied++
|
||||||
|
result.add(
|
||||||
|
ValueParameterDescriptorImpl(
|
||||||
|
oldDescriptor, // to be substituted with newDescriptor
|
||||||
|
null,
|
||||||
|
parametersCopied++,
|
||||||
|
oldValueParameter.annotations,
|
||||||
|
oldValueParameter.name,
|
||||||
|
oldValueParameter.type,
|
||||||
|
declaresDefaultValue = false,
|
||||||
|
isCrossinline = false,
|
||||||
|
isNoinline = false,
|
||||||
|
varargElementType = oldValueParameter.varargElementType,
|
||||||
|
source = oldValueParameter.source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (!oldValueParameter.hasDefaultValue()) {
|
||||||
|
result.add(oldValueParameter.copy(oldDescriptor, oldValueParameter.name, parametersCopied++))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -99,6 +99,7 @@ private class CompanionObjectJvmStaticLowering(val context: JvmBackendContext) :
|
|||||||
proxyIrFunction.createParameterDeclarations()
|
proxyIrFunction.createParameterDeclarations()
|
||||||
|
|
||||||
proxyIrFunction.body = createProxyBody(target, proxyIrFunction, companion)
|
proxyIrFunction.body = createProxyBody(target, proxyIrFunction, companion)
|
||||||
|
target.annotations.mapTo(proxyIrFunction.annotations) { it.deepCopyWithSymbols() }
|
||||||
return proxyIrFunction
|
return proxyIrFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
Reference in New Issue
Block a user