Implement correct is check for SuspendFunction
Let callable references implement SuspendFunction Do not generate CHECKCAST SuspendFunction when LV is 1.2 #KT-25825: Fixed
This commit is contained in:
@@ -4658,7 +4658,7 @@ The "returned" value of try expression with no finally is either the last expres
|
||||
return null;
|
||||
}
|
||||
|
||||
CodegenUtilKt.generateIsCheck(v, rhsKotlinType, type);
|
||||
CodegenUtilKt.generateIsCheck(v, rhsKotlinType, type, state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ class JvmRuntimeTypes(module: ModuleDescriptor, private val languageVersionSetti
|
||||
createCoroutineSuperClass("RestrictedSuspendLambda")
|
||||
}
|
||||
|
||||
private val suspendFunctionInterface by lazy {
|
||||
if (languageVersionSettings.isReleaseCoroutines())
|
||||
createClass(kotlinCoroutinesJvmInternalPackage, "SuspendFunction", ClassKind.INTERFACE)
|
||||
else null
|
||||
}
|
||||
|
||||
private fun createCoroutineSuperClass(className: String): ClassDescriptor {
|
||||
return if (languageVersionSettings.isReleaseCoroutines())
|
||||
createClass(kotlinCoroutinesJvmInternalPackage, className)
|
||||
@@ -145,7 +151,8 @@ class JvmRuntimeTypes(module: ModuleDescriptor, private val languageVersionSetti
|
||||
referencedFunction.isSuspend
|
||||
)
|
||||
|
||||
return listOf(functionReference.defaultType, functionType)
|
||||
val suspendFunctionType = if (referencedFunction.isSuspend) suspendFunctionInterface?.defaultType else null
|
||||
return listOfNotNull(functionReference.defaultType, functionType, suspendFunctionType)
|
||||
}
|
||||
|
||||
fun getSupertypeForPropertyReference(descriptor: VariableDescriptorWithAccessors, isMutable: Boolean, isBound: Boolean): KotlinType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -59,10 +59,12 @@ import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.*
|
||||
|
||||
@JvmOverloads
|
||||
fun generateIsCheck(
|
||||
v: InstructionAdapter,
|
||||
kotlinType: KotlinType,
|
||||
asmType: Type
|
||||
asmType: Type,
|
||||
isReleaseCoroutines: Boolean = false
|
||||
) {
|
||||
if (TypeUtils.isNullableType(kotlinType)) {
|
||||
val nope = Label()
|
||||
@@ -73,7 +75,7 @@ fun generateIsCheck(
|
||||
|
||||
ifnull(nope)
|
||||
|
||||
TypeIntrinsics.instanceOf(this, kotlinType, asmType)
|
||||
TypeIntrinsics.instanceOf(this, kotlinType, asmType, isReleaseCoroutines)
|
||||
|
||||
goTo(end)
|
||||
|
||||
@@ -84,15 +86,17 @@ fun generateIsCheck(
|
||||
mark(end)
|
||||
}
|
||||
} else {
|
||||
TypeIntrinsics.instanceOf(v, kotlinType, asmType)
|
||||
TypeIntrinsics.instanceOf(v, kotlinType, asmType, isReleaseCoroutines)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun generateAsCast(
|
||||
v: InstructionAdapter,
|
||||
kotlinType: KotlinType,
|
||||
asmType: Type,
|
||||
isSafe: Boolean
|
||||
isSafe: Boolean,
|
||||
isReleaseCoroutines: Boolean = false
|
||||
) {
|
||||
if (!isSafe) {
|
||||
if (!TypeUtils.isNullableType(kotlinType)) {
|
||||
@@ -101,7 +105,7 @@ fun generateAsCast(
|
||||
} else {
|
||||
with(v) {
|
||||
dup()
|
||||
TypeIntrinsics.instanceOf(v, kotlinType, asmType)
|
||||
TypeIntrinsics.instanceOf(v, kotlinType, asmType, isReleaseCoroutines)
|
||||
val ok = Label()
|
||||
ifne(ok)
|
||||
pop()
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
* 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.codegen.intrinsics
|
||||
@@ -19,8 +8,10 @@ package org.jetbrains.kotlin.codegen.intrinsics
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
@@ -28,7 +19,8 @@ import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import kotlin.text.Regex
|
||||
|
||||
object TypeIntrinsics {
|
||||
@JvmStatic fun instanceOf(v: InstructionAdapter, jetType: KotlinType, boxedAsmType: Type) {
|
||||
@JvmStatic
|
||||
fun instanceOf(v: InstructionAdapter, jetType: KotlinType, boxedAsmType: Type, isReleaseCoroutines: Boolean) {
|
||||
val functionTypeArity = getFunctionTypeArity(jetType)
|
||||
if (functionTypeArity >= 0) {
|
||||
v.iconst(functionTypeArity)
|
||||
@@ -36,6 +28,30 @@ object TypeIntrinsics {
|
||||
return
|
||||
}
|
||||
|
||||
if (isReleaseCoroutines) {
|
||||
val suspendFunctionTypeArity = getSuspendFunctionTypeArity(jetType)
|
||||
if (suspendFunctionTypeArity >= 0) {
|
||||
val notSuspendLambda = Label()
|
||||
val end = Label()
|
||||
|
||||
with(v) {
|
||||
dup()
|
||||
instanceOf(AsmTypes.SUSPEND_FUNCTION_TYPE)
|
||||
ifeq(notSuspendLambda)
|
||||
iconst(suspendFunctionTypeArity + 1)
|
||||
typeIntrinsic(IS_FUNCTON_OF_ARITY_METHOD_NAME, IS_FUNCTON_OF_ARITY_DESCRIPTOR)
|
||||
goTo(end)
|
||||
|
||||
mark(notSuspendLambda)
|
||||
pop()
|
||||
iconst(0)
|
||||
|
||||
mark(end)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val isMutableCollectionMethodName = getIsMutableCollectionMethodName(jetType)
|
||||
if (isMutableCollectionMethodName != null) {
|
||||
v.typeIntrinsic(isMutableCollectionMethodName, IS_MUTABLE_COLLECTION_METHOD_DESCRIPTOR)
|
||||
@@ -148,16 +164,26 @@ object TypeIntrinsics {
|
||||
}
|
||||
|
||||
private val KOTLIN_FUNCTION_INTERFACE_REGEX = Regex("^kotlin\\.Function([0-9]+)$")
|
||||
// TODO: move to correct package
|
||||
private val KOTLIN_SUSPEND_FUNCTION_INTERFACE_REGEX = Regex("^kotlin\\.SuspendFunction([0-9]+)$")
|
||||
|
||||
/**
|
||||
* @return function type arity (non-negative), or -1 if the given type is not a function type
|
||||
*/
|
||||
private fun getFunctionTypeArity(jetType: KotlinType): Int {
|
||||
val classFqName = getClassFqName(jetType) ?: return -1
|
||||
val match = KOTLIN_FUNCTION_INTERFACE_REGEX.find(classFqName.asString()) ?: return -1
|
||||
private fun getFunctionTypeArity(kotlinType: KotlinType): Int = getFunctionTypeArityByRegex(kotlinType, KOTLIN_FUNCTION_INTERFACE_REGEX)
|
||||
|
||||
private fun getFunctionTypeArityByRegex(kotlinType: KotlinType, regex: Regex): Int {
|
||||
val classFqName = getClassFqName(kotlinType) ?: return -1
|
||||
val match = regex.find(classFqName.asString()) ?: return -1
|
||||
return Integer.valueOf(match.groups[1]!!.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return function type arity (non-negative, counting continuation), or -1 if the given type is not a function type
|
||||
*/
|
||||
private fun getSuspendFunctionTypeArity(kotlinType: KotlinType): Int =
|
||||
getFunctionTypeArityByRegex(kotlinType, KOTLIN_SUSPEND_FUNCTION_INTERFACE_REGEX)
|
||||
|
||||
private fun typeIntrinsicNode(methodName: String, methodDescriptor: String): MethodInsnNode =
|
||||
MethodInsnNode(Opcodes.INVOKESTATIC, INTRINSICS_CLASS, methodName, methodDescriptor, false)
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
* 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.resolve.jvm;
|
||||
@@ -74,6 +63,8 @@ public class AsmTypes {
|
||||
public static final Type K_MUTABLE_PROPERTY1_TYPE = reflect("KMutableProperty1");
|
||||
public static final Type K_MUTABLE_PROPERTY2_TYPE = reflect("KMutableProperty2");
|
||||
|
||||
public static final Type SUSPEND_FUNCTION_TYPE = Type.getObjectType("kotlin/coroutines/jvm/internal/SuspendFunction");
|
||||
|
||||
public static final String REFLECTION = "kotlin/jvm/internal/Reflection";
|
||||
|
||||
public static final String REF_TYPE_PREFIX = "kotlin/jvm/internal/Ref$";
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// IGNORE_BACKEND: JS_IR, JS
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
|
||||
import helpers.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
val lambda1 = { x: Any -> } as (Any) -> Unit
|
||||
val suspendLambda0: suspend () -> Unit = {}
|
||||
|
||||
fun box(): String {
|
||||
assert(lambda1 is SuspendFunction0<*>) { "Failed: lambda1 !is SuspendFunction0<*>" }
|
||||
assert(suspendLambda0 is Function1<*, *>) { "Failed: suspendLambda0 is Function1<*, *>" }
|
||||
assert(suspendLambda0 is SuspendFunction0<*>) { "Failed: suspendLambda0 is SuspendFunction0<*>" }
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// IGNORE_BACKEND: JS_IR, JS
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
// LANGUAGE_VERSION: 1.3
|
||||
|
||||
import helpers.*
|
||||
import kotlin.coroutines.*
|
||||
|
||||
fun fn1(x: Any) {}
|
||||
suspend fun suspendFn0() {}
|
||||
|
||||
val lambda1 = { x: Any -> } as (Any) -> Unit
|
||||
val suspendLambda0: suspend () -> Unit = {}
|
||||
|
||||
fun Any.extFun(a: Any) {}
|
||||
suspend fun Any.suspendExtFun() {}
|
||||
|
||||
class A {
|
||||
fun foo(a: Any) {}
|
||||
suspend fun suspendFoo() {}
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val f1 = ::fn1 as Any
|
||||
val sf0 = ::suspendFn0 as Any
|
||||
|
||||
val ef = Any::extFun as Any
|
||||
val sef = Any::suspendExtFun as Any
|
||||
|
||||
val afoo = A::foo
|
||||
val safoo = A::suspendFoo
|
||||
|
||||
fun local1(x: Any) {}
|
||||
suspend fun suspendLocal0() {}
|
||||
|
||||
val localFun1 = ::local1 as Any
|
||||
val suspendLocalFun0 = ::suspendLocal0 as Any
|
||||
|
||||
assert(f1 !is SuspendFunction0<*>) { "Failed: f1 !is SuspendFunction0<*>" }
|
||||
assert(sf0 is SuspendFunction0<*>) { "Failed: f1 is SuspendFunction0<*>" }
|
||||
assert(sf0 is Function1<*, *>) { "Failed: suspendF0 is Function1<*, *>" }
|
||||
|
||||
assert(lambda1 !is SuspendFunction0<*>) { "Failed: lambda1 !is SuspendFunction0<*>" }
|
||||
assert(suspendLambda0 is Function1<*, *>) { "Failed: suspendLambda0 is Function1<*, *>" }
|
||||
assert(suspendLambda0 is SuspendFunction0<*>) { "Failed: suspendLambda0 is SuspendFunction0<*>" }
|
||||
|
||||
assert(localFun1 !is SuspendFunction0<*>) { "Failed: localFun1 !is SuspendFunction0<*, *>" }
|
||||
assert(suspendLocalFun0 is Function1<*, *>) { "Failed: suspendLocalFun0 is Function1<*, *>" }
|
||||
assert(suspendLocalFun0 is SuspendFunction0<*>) { "Failed: suspendLocalFun0 is SuspendFunction0<*>" }
|
||||
|
||||
assert(ef !is SuspendFunction1<*, *>) { "Failed: ef !is SuspendFunction1<*, *>" }
|
||||
assert(sef is SuspendFunction1<*, *>) { "Failed: sef is SuspendFunction1<*, *>" }
|
||||
assert(sef is Function2<*, *, *>) { "Failed: sef is Function2<*, *, *>" }
|
||||
|
||||
assert(afoo !is SuspendFunction1<*, *>) { "afoo !is SuspendFunction1<*, *>" }
|
||||
assert(safoo is Function2<*, *, *>) { "safoo is Function2<*, *, *>" }
|
||||
assert(safoo is SuspendFunction1<*, *>) { "asfoo is SuspendFunction1<*, *>" }
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+10
@@ -6505,6 +6505,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt", "kotlin.coroutines");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunction12.kt")
|
||||
public void testSuspendFunction12() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunction12.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunctionIsAs.kt")
|
||||
public void testSuspendFunctionIsAs() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunctionIsAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendOperatorPlusAssign.kt")
|
||||
public void testSuspendOperatorPlusAssign_1_2() throws Exception {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendOperatorPlusAssign.kt", "kotlin.coroutines.experimental");
|
||||
|
||||
+10
@@ -6505,6 +6505,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt", "kotlin.coroutines");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunction12.kt")
|
||||
public void testSuspendFunction12() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunction12.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunctionIsAs.kt")
|
||||
public void testSuspendFunctionIsAs() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunctionIsAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendOperatorPlusAssign.kt")
|
||||
public void testSuspendOperatorPlusAssign_1_2() throws Exception {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendOperatorPlusAssign.kt", "kotlin.coroutines.experimental");
|
||||
|
||||
+10
@@ -6505,6 +6505,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt", "kotlin.coroutines");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunction12.kt")
|
||||
public void testSuspendFunction12() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunction12.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunctionIsAs.kt")
|
||||
public void testSuspendFunctionIsAs() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunctionIsAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendOperatorPlusAssign.kt")
|
||||
public void testSuspendOperatorPlusAssign_1_2() throws Exception {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendOperatorPlusAssign.kt", "kotlin.coroutines.experimental");
|
||||
|
||||
+10
@@ -5665,6 +5665,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt", "kotlin.coroutines.experimental");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunction12.kt")
|
||||
public void testSuspendFunction12() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunction12.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunctionIsAs.kt")
|
||||
public void testSuspendFunctionIsAs() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunctionIsAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendOperatorPlusAssign.kt")
|
||||
public void testSuspendOperatorPlusAssign_1_2() throws Exception {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendOperatorPlusAssign.kt", "kotlin.coroutines.experimental");
|
||||
|
||||
+10
@@ -6220,6 +6220,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt", "kotlin.coroutines");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunction12.kt")
|
||||
public void testSuspendFunction12() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunction12.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFunctionIsAs.kt")
|
||||
public void testSuspendFunctionIsAs() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/coroutines/featureIntersection/suspendFunctionIsAs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendOperatorPlusAssign.kt")
|
||||
public void testSuspendOperatorPlusAssign_1_2() throws Exception {
|
||||
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/featureIntersection/suspendOperatorPlusAssign.kt", "kotlin.coroutines.experimental");
|
||||
|
||||
+6
-2
@@ -125,12 +125,16 @@ internal object CompletedContinuation : Continuation<Any?> {
|
||||
override fun toString(): String = "This continuation is already complete"
|
||||
}
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
// To distinguish suspend function types from ordinary function types all suspend function type shall implement this interface
|
||||
internal interface SuspendFunction
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
// Restricted suspension lambdas inherit from this class
|
||||
internal abstract class RestrictedSuspendLambda(
|
||||
private val arity: Int,
|
||||
completion: Continuation<Any?>?
|
||||
) : RestrictedContinuationImpl(completion), FunctionBase {
|
||||
) : RestrictedContinuationImpl(completion), FunctionBase, SuspendFunction {
|
||||
constructor(arity: Int) : this(arity, null)
|
||||
|
||||
public override fun getArity(): Int = arity
|
||||
@@ -147,7 +151,7 @@ internal abstract class RestrictedSuspendLambda(
|
||||
internal abstract class SuspendLambda(
|
||||
private val arity: Int,
|
||||
completion: Continuation<Any?>?
|
||||
) : ContinuationImpl(completion), FunctionBase {
|
||||
) : ContinuationImpl(completion), FunctionBase, SuspendFunction {
|
||||
constructor(arity: Int) : this(arity, null)
|
||||
|
||||
public override fun getArity(): Int = arity
|
||||
|
||||
Reference in New Issue
Block a user