J2K: OverloadingConflictResolver.kt - convert to Kotlin
This commit is contained in:
+152
-175
@@ -14,102 +14,82 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.calls.results;
|
package org.jetbrains.kotlin.resolve.calls.results
|
||||||
|
|
||||||
import gnu.trove.THashSet;
|
import gnu.trove.THashSet
|
||||||
import gnu.trove.TObjectHashingStrategy;
|
import gnu.trove.TObjectHashingStrategy
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||||
import org.jetbrains.kotlin.resolve.OverrideResolver;
|
import org.jetbrains.kotlin.resolve.OverrideResolver
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode;
|
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall;
|
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.*;
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
|
||||||
|
|
||||||
import java.util.List;
|
class OverloadingConflictResolver(private val builtIns: KotlinBuiltIns) {
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class OverloadingConflictResolver {
|
fun <D : CallableDescriptor> findMaximallySpecific(
|
||||||
|
candidates: Set<MutableResolvedCall<D>>,
|
||||||
private final KotlinBuiltIns builtIns;
|
discriminateGenericDescriptors: Boolean,
|
||||||
|
checkArgumentsMode: CheckArgumentTypesMode
|
||||||
public OverloadingConflictResolver(@NotNull KotlinBuiltIns builtIns) {
|
): MutableResolvedCall<D>? {
|
||||||
this.builtIns = builtIns;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public <D extends CallableDescriptor> MutableResolvedCall<D> findMaximallySpecific(
|
|
||||||
@NotNull Set<MutableResolvedCall<D>> candidates,
|
|
||||||
boolean discriminateGenericDescriptors,
|
|
||||||
@NotNull CheckArgumentTypesMode checkArgumentsMode
|
|
||||||
) {
|
|
||||||
// Different smartcasts may lead to the same candidate descriptor wrapped into different ResolvedCallImpl objects
|
// Different smartcasts may lead to the same candidate descriptor wrapped into different ResolvedCallImpl objects
|
||||||
Set<MutableResolvedCall<D>> maximallySpecific = new THashSet<MutableResolvedCall<D>>(new TObjectHashingStrategy<MutableResolvedCall<D>>() {
|
|
||||||
@Override
|
val maximallySpecific = THashSet(object : TObjectHashingStrategy<MutableResolvedCall<D>> {
|
||||||
public boolean equals(MutableResolvedCall<D> o1, MutableResolvedCall<D> o2) {
|
override fun equals(call1: MutableResolvedCall<D>?, call2: MutableResolvedCall<D>?): Boolean =
|
||||||
return o1 == null ? o2 == null : o1.getResultingDescriptor().equals(o2.getResultingDescriptor());
|
if (call1 == null) call2 == null
|
||||||
}
|
else call1.resultingDescriptor == call2!!.resultingDescriptor
|
||||||
|
|
||||||
@Override
|
override fun computeHashCode(call: MutableResolvedCall<D>?): Int =
|
||||||
public int computeHashCode(MutableResolvedCall<D> object) {
|
call?.resultingDescriptor?.hashCode() ?: 0
|
||||||
return object == null ? 0 : object.getResultingDescriptor().hashCode();
|
})
|
||||||
}
|
|
||||||
});
|
candidates.filterTo(maximallySpecific) {
|
||||||
for (MutableResolvedCall<D> candidateCall : candidates) {
|
isMaximallySpecific(it, candidates, discriminateGenericDescriptors, checkArgumentsMode)
|
||||||
if (isMaximallySpecific(candidateCall, candidates, discriminateGenericDescriptors, checkArgumentsMode)) {
|
|
||||||
maximallySpecific.add(candidateCall);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return maximallySpecific.size() == 1 ? maximallySpecific.iterator().next() : null;
|
|
||||||
|
return if (maximallySpecific.size == 1) maximallySpecific.first() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private <D extends CallableDescriptor> boolean isMaximallySpecific(
|
private fun <D : CallableDescriptor> isMaximallySpecific(
|
||||||
@NotNull MutableResolvedCall<D> candidateCall,
|
candidateCall: MutableResolvedCall<D>,
|
||||||
@NotNull Set<MutableResolvedCall<D>> candidates,
|
candidates: Set<MutableResolvedCall<D>>,
|
||||||
boolean discriminateGenericDescriptors,
|
discriminateGenericDescriptors: Boolean,
|
||||||
@NotNull CheckArgumentTypesMode checkArgumentsMode
|
checkArgumentsMode: CheckArgumentTypesMode): Boolean {
|
||||||
) {
|
val me = candidateCall.resultingDescriptor
|
||||||
D me = candidateCall.getResultingDescriptor();
|
|
||||||
|
|
||||||
boolean isInvoke = candidateCall instanceof VariableAsFunctionResolvedCall;
|
val isInvoke = candidateCall is VariableAsFunctionResolvedCall
|
||||||
VariableDescriptor variable;
|
val variable = (candidateCall as? VariableAsFunctionResolvedCall)?.variableCall?.resultingDescriptor
|
||||||
if (isInvoke) {
|
|
||||||
variable = ((VariableAsFunctionResolvedCall) candidateCall).getVariableCall().getResultingDescriptor();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
variable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (MutableResolvedCall<D> otherCall : candidates) {
|
for (otherCall in candidates) {
|
||||||
D other = otherCall.getResultingDescriptor();
|
val other = otherCall.resultingDescriptor
|
||||||
if (other == me) continue;
|
if (other === me) continue
|
||||||
|
|
||||||
if (definitelyNotMaximallySpecific(me, other, discriminateGenericDescriptors, checkArgumentsMode)) {
|
if (definitelyNotMaximallySpecific(me, other, discriminateGenericDescriptors, checkArgumentsMode)) {
|
||||||
|
if (!isInvoke) return false
|
||||||
|
|
||||||
if (!isInvoke) return false;
|
assert(otherCall is VariableAsFunctionResolvedCall) { "'invoke' candidate goes with usual one: " + candidateCall + otherCall }
|
||||||
|
|
||||||
assert otherCall instanceof VariableAsFunctionResolvedCall : "'invoke' candidate goes with usual one: " + candidateCall + otherCall;
|
val otherVariableCall = (otherCall as VariableAsFunctionResolvedCall).variableCall
|
||||||
ResolvedCall<VariableDescriptor> otherVariableCall = ((VariableAsFunctionResolvedCall) otherCall).getVariableCall();
|
if (definitelyNotMaximallySpecific(variable!!, otherVariableCall.resultingDescriptor, discriminateGenericDescriptors, checkArgumentsMode)) {
|
||||||
if (definitelyNotMaximallySpecific(variable, otherVariableCall.getResultingDescriptor(), discriminateGenericDescriptors, checkArgumentsMode)) {
|
return false
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private <D extends CallableDescriptor> boolean definitelyNotMaximallySpecific(
|
private fun <D : CallableDescriptor> definitelyNotMaximallySpecific(
|
||||||
D me,
|
me: D,
|
||||||
D other,
|
other: D,
|
||||||
boolean discriminateGenericDescriptors,
|
discriminateGenericDescriptors: Boolean,
|
||||||
@NotNull CheckArgumentTypesMode checkArgumentsMode
|
checkArgumentsMode: CheckArgumentTypesMode): Boolean {
|
||||||
) {
|
|
||||||
return !moreSpecific(me, other, discriminateGenericDescriptors, checkArgumentsMode) ||
|
return !moreSpecific(me, other, discriminateGenericDescriptors, checkArgumentsMode) ||
|
||||||
moreSpecific(other, me, discriminateGenericDescriptors, checkArgumentsMode);
|
moreSpecific(other, me, discriminateGenericDescriptors, checkArgumentsMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,85 +99,86 @@ public class OverloadingConflictResolver {
|
|||||||
* Int < Long
|
* Int < Long
|
||||||
* Int < Short < Byte
|
* Int < Short < Byte
|
||||||
*/
|
*/
|
||||||
private <Descriptor extends CallableDescriptor> boolean moreSpecific(
|
private fun <Descriptor : CallableDescriptor> moreSpecific(
|
||||||
Descriptor f,
|
f: Descriptor,
|
||||||
Descriptor g,
|
g: Descriptor,
|
||||||
boolean discriminateGenericDescriptors,
|
discriminateGenericDescriptors: Boolean,
|
||||||
@NotNull CheckArgumentTypesMode checkArgumentsMode
|
checkArgumentsMode: CheckArgumentTypesMode): Boolean {
|
||||||
) {
|
val resolvingCallableReference = checkArgumentsMode == CheckArgumentTypesMode.CHECK_CALLABLE_TYPE
|
||||||
boolean resolvingCallableReference = checkArgumentsMode == CheckArgumentTypesMode.CHECK_CALLABLE_TYPE;
|
|
||||||
|
|
||||||
if (f.getContainingDeclaration() instanceof ScriptDescriptor && g.getContainingDeclaration() instanceof ScriptDescriptor) {
|
if (f.containingDeclaration is ScriptDescriptor && g.containingDeclaration is ScriptDescriptor) {
|
||||||
ScriptDescriptor fs = (ScriptDescriptor) f.getContainingDeclaration();
|
val fs = f.containingDeclaration as ScriptDescriptor
|
||||||
ScriptDescriptor gs = (ScriptDescriptor) g.getContainingDeclaration();
|
val gs = g.containingDeclaration as ScriptDescriptor
|
||||||
|
|
||||||
if (fs.getPriority() != gs.getPriority()) {
|
if (fs.priority != gs.priority) {
|
||||||
return fs.getPriority() > gs.getPriority();
|
return fs.priority > gs.priority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isGenericF = isGeneric(f);
|
val isGenericF = isGeneric(f)
|
||||||
boolean isGenericG = isGeneric(g);
|
val isGenericG = isGeneric(g)
|
||||||
if (discriminateGenericDescriptors) {
|
if (discriminateGenericDescriptors) {
|
||||||
if (!isGenericF && isGenericG) return true;
|
if (!isGenericF && isGenericG) return true
|
||||||
if (isGenericF && !isGenericG) return false;
|
if (isGenericF && !isGenericG) return false
|
||||||
|
|
||||||
if (isGenericF && isGenericG) {
|
if (isGenericF && isGenericG) {
|
||||||
return moreSpecific(BoundsSubstitutor.substituteBounds(f), BoundsSubstitutor.substituteBounds(g), false, checkArgumentsMode);
|
return moreSpecific(BoundsSubstitutor.substituteBounds(f),
|
||||||
|
BoundsSubstitutor.substituteBounds(g),
|
||||||
|
false, checkArgumentsMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OverrideResolver.overrides(f, g)) return true;
|
if (OverrideResolver.overrides(f, g)) return true
|
||||||
if (OverrideResolver.overrides(g, f)) return false;
|
if (OverrideResolver.overrides(g, f)) return false
|
||||||
|
|
||||||
ReceiverParameterDescriptor receiverOfF = f.getExtensionReceiverParameter();
|
val receiverOfF = f.extensionReceiverParameter
|
||||||
ReceiverParameterDescriptor receiverOfG = g.getExtensionReceiverParameter();
|
val receiverOfG = g.extensionReceiverParameter
|
||||||
if (receiverOfF != null && receiverOfG != null) {
|
if (receiverOfF != null && receiverOfG != null) {
|
||||||
if (!typeMoreSpecific(receiverOfF.getType(), receiverOfG.getType())) return false;
|
if (!typeMoreSpecific(receiverOfF.type, receiverOfG.type)) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ValueParameterDescriptor> fParams = f.getValueParameters();
|
val fParams = f.valueParameters
|
||||||
List<ValueParameterDescriptor> gParams = g.getValueParameters();
|
val gParams = g.valueParameters
|
||||||
|
|
||||||
int fSize = fParams.size();
|
val fSize = fParams.size
|
||||||
int gSize = gParams.size();
|
val gSize = gParams.size
|
||||||
|
|
||||||
boolean fIsVararg = isVariableArity(fParams);
|
val fIsVararg = isVariableArity(fParams)
|
||||||
boolean gIsVararg = isVariableArity(gParams);
|
val gIsVararg = isVariableArity(gParams)
|
||||||
|
|
||||||
if (resolvingCallableReference && fIsVararg != gIsVararg) return false;
|
if (resolvingCallableReference && fIsVararg != gIsVararg) return false
|
||||||
if (!fIsVararg && gIsVararg) return true;
|
if (!fIsVararg && gIsVararg) return true
|
||||||
if (fIsVararg && !gIsVararg) return false;
|
if (fIsVararg && !gIsVararg) return false
|
||||||
|
|
||||||
if (!fIsVararg && !gIsVararg) {
|
if (!fIsVararg && !gIsVararg) {
|
||||||
if (resolvingCallableReference && fSize != gSize) return false;
|
if (resolvingCallableReference && fSize != gSize) return false
|
||||||
if (!resolvingCallableReference && fSize > gSize) return false;
|
if (!resolvingCallableReference && fSize > gSize) return false
|
||||||
|
|
||||||
for (int i = 0; i < fSize; i++) {
|
for (i in 0..fSize - 1) {
|
||||||
ValueParameterDescriptor fParam = fParams.get(i);
|
val fParam = fParams[i]
|
||||||
ValueParameterDescriptor gParam = gParams.get(i);
|
val gParam = gParams[i]
|
||||||
|
|
||||||
KotlinType fParamType = fParam.getType();
|
val fParamType = fParam.type
|
||||||
KotlinType gParamType = gParam.getType();
|
val gParamType = gParam.type
|
||||||
|
|
||||||
if (!typeMoreSpecific(fParamType, gParamType)) {
|
if (!typeMoreSpecific(fParamType, gParamType)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fIsVararg && gIsVararg) {
|
if (fIsVararg && gIsVararg) {
|
||||||
// Check matching parameters
|
// Check matching parameters
|
||||||
int minSize = Math.min(fSize, gSize);
|
val minSize = Math.min(fSize, gSize)
|
||||||
for (int i = 0; i < minSize - 1; i++) {
|
for (i in 0..minSize - 1 - 1) {
|
||||||
ValueParameterDescriptor fParam = fParams.get(i);
|
val fParam = fParams[i]
|
||||||
ValueParameterDescriptor gParam = gParams.get(i);
|
val gParam = gParams[i]
|
||||||
|
|
||||||
KotlinType fParamType = fParam.getType();
|
val fParamType = fParam.type
|
||||||
KotlinType gParamType = gParam.getType();
|
val gParamType = gParam.type
|
||||||
|
|
||||||
if (!typeMoreSpecific(fParamType, gParamType)) {
|
if (!typeMoreSpecific(fParamType, gParamType)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,81 +188,77 @@ public class OverloadingConflictResolver {
|
|||||||
// g(a : A, vararg vg : T)
|
// g(a : A, vararg vg : T)
|
||||||
// here we check that typeOf(a) < elementTypeOf(vf) and elementTypeOf(vg) < elementTypeOf(vf)
|
// here we check that typeOf(a) < elementTypeOf(vf) and elementTypeOf(vg) < elementTypeOf(vf)
|
||||||
if (fSize < gSize) {
|
if (fSize < gSize) {
|
||||||
ValueParameterDescriptor fParam = fParams.get(fSize - 1);
|
val fParam = fParams[fSize - 1]
|
||||||
KotlinType fParamType = fParam.getVarargElementType();
|
val fParamType = fParam.varargElementType
|
||||||
assert fParamType != null : "fIsVararg guarantees this";
|
assert(fParamType != null) { "fIsVararg guarantees this" }
|
||||||
for (int i = fSize - 1; i < gSize; i++) {
|
for (i in fSize - 1..gSize - 1) {
|
||||||
ValueParameterDescriptor gParam = gParams.get(i);
|
val gParam = gParams[i]
|
||||||
if (!typeMoreSpecific(fParamType, getVarargElementTypeOrType(gParam))) {
|
if (!typeMoreSpecific(fParamType!!, getVarargElementTypeOrType(gParam))) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ValueParameterDescriptor gParam = gParams.get(gSize - 1);
|
val gParam = gParams[gSize - 1]
|
||||||
KotlinType gParamType = gParam.getVarargElementType();
|
val gParamType = gParam.varargElementType
|
||||||
assert gParamType != null : "gIsVararg guarantees this";
|
assert(gParamType != null) { "gIsVararg guarantees this" }
|
||||||
for (int i = gSize - 1; i < fSize; i++) {
|
for (i in gSize - 1..fSize - 1) {
|
||||||
ValueParameterDescriptor fParam = fParams.get(i);
|
val fParam = fParams[i]
|
||||||
if (!typeMoreSpecific(getVarargElementTypeOrType(fParam), gParamType)) {
|
if (!typeMoreSpecific(getVarargElementTypeOrType(fParam), gParamType!!)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private fun getVarargElementTypeOrType(parameterDescriptor: ValueParameterDescriptor): KotlinType =
|
||||||
private static KotlinType getVarargElementTypeOrType(@NotNull ValueParameterDescriptor parameterDescriptor) {
|
parameterDescriptor.varargElementType ?: parameterDescriptor.type
|
||||||
KotlinType varargElementType = parameterDescriptor.getVarargElementType();
|
|
||||||
if (varargElementType != null) {
|
|
||||||
return varargElementType;
|
|
||||||
}
|
|
||||||
return parameterDescriptor.getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isVariableArity(List<ValueParameterDescriptor> fParams) {
|
private fun isVariableArity(fParams: List<ValueParameterDescriptor>): Boolean =
|
||||||
int fSize = fParams.size();
|
fParams.lastOrNull()?.varargElementType != null
|
||||||
return fSize > 0 && fParams.get(fSize - 1).getVarargElementType() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isGeneric(CallableDescriptor f) {
|
private fun isGeneric(f: CallableDescriptor): Boolean =
|
||||||
return !f.getOriginal().getTypeParameters().isEmpty();
|
f.original.typeParameters.isNotEmpty()
|
||||||
}
|
|
||||||
|
|
||||||
private boolean typeMoreSpecific(@NotNull KotlinType specific, @NotNull KotlinType general) {
|
private fun typeMoreSpecific(specific: KotlinType, general: KotlinType): Boolean {
|
||||||
boolean isSubtype = KotlinTypeChecker.DEFAULT.isSubtypeOf(specific, general) ||
|
val isSubtype = KotlinTypeChecker.DEFAULT.isSubtypeOf(specific, general) || numericTypeMoreSpecific(specific, general)
|
||||||
numericTypeMoreSpecific(specific, general);
|
|
||||||
|
|
||||||
if (!isSubtype) return false;
|
if (!isSubtype) return false
|
||||||
|
|
||||||
Specificity.Relation sThanG = TypeCapabilitiesKt.getSpecificityRelationTo(specific, general);
|
val sThanG = specific.getSpecificityRelationTo(general)
|
||||||
Specificity.Relation gThanS = TypeCapabilitiesKt.getSpecificityRelationTo(general, specific);
|
val gThanS = general.getSpecificityRelationTo(specific)
|
||||||
if (sThanG == Specificity.Relation.LESS_SPECIFIC && gThanS != Specificity.Relation.LESS_SPECIFIC) {
|
if (sThanG === Specificity.Relation.LESS_SPECIFIC &&
|
||||||
return false;
|
gThanS !== Specificity.Relation.LESS_SPECIFIC) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean numericTypeMoreSpecific(@NotNull KotlinType specific, @NotNull KotlinType general) {
|
private fun numericTypeMoreSpecific(specific: KotlinType, general: KotlinType): Boolean {
|
||||||
KotlinType _double = builtIns.getDoubleType();
|
val _double = builtIns.doubleType
|
||||||
KotlinType _float = builtIns.getFloatType();
|
val _float = builtIns.floatType
|
||||||
KotlinType _long = builtIns.getLongType();
|
val _long = builtIns.longType
|
||||||
KotlinType _int = builtIns.getIntType();
|
val _int = builtIns.intType
|
||||||
KotlinType _byte = builtIns.getByteType();
|
val _byte = builtIns.byteType
|
||||||
KotlinType _short = builtIns.getShortType();
|
val _short = builtIns.shortType
|
||||||
|
|
||||||
if (TypeUtils.equalTypes(specific, _double) && TypeUtils.equalTypes(general, _float)) return true;
|
when {
|
||||||
if (TypeUtils.equalTypes(specific, _int)) {
|
TypeUtils.equalTypes(specific, _double) && TypeUtils.equalTypes(general, _float) -> return true
|
||||||
if (TypeUtils.equalTypes(general, _long)) return true;
|
TypeUtils.equalTypes(specific, _int) -> {
|
||||||
if (TypeUtils.equalTypes(general, _byte)) return true;
|
when {
|
||||||
if (TypeUtils.equalTypes(general, _short)) return true;
|
TypeUtils.equalTypes(general, _long) -> return true
|
||||||
|
TypeUtils.equalTypes(general, _byte) -> return true
|
||||||
|
TypeUtils.equalTypes(general, _short) -> return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeUtils.equalTypes(specific, _short) && TypeUtils.equalTypes(general, _byte) -> return true
|
||||||
}
|
}
|
||||||
if (TypeUtils.equalTypes(specific, _short) && TypeUtils.equalTypes(general, _byte)) return true;
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user