Optimize const vals by inlining them at use sites
#KT-11734 Fixed #KT-13570 Fixed
This commit is contained in:
@@ -50,10 +50,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
import org.jetbrains.kotlin.codegen.when.SwitchCodegen;
|
||||
import org.jetbrains.kotlin.codegen.when.SwitchCodegenUtil;
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys;
|
||||
import org.jetbrains.kotlin.config.LanguageFeature;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl;
|
||||
import org.jetbrains.kotlin.config.*;
|
||||
import org.jetbrains.kotlin.coroutines.CoroutineUtilKt;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
||||
@@ -1438,14 +1435,17 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
@Override
|
||||
public StackValue visitConstantExpression(@NotNull KtConstantExpression expression, StackValue receiver) {
|
||||
ConstantValue<?> compileTimeValue = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext);
|
||||
ConstantValue<?> compileTimeValue = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext, state.getShouldInlineConstVals());
|
||||
assert compileTimeValue != null;
|
||||
return StackValue.constant(compileTimeValue.getValue(), expressionType(expression));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ConstantValue<?> getPrimitiveOrStringCompileTimeConstant(@NotNull KtExpression expression, @NotNull BindingContext bindingContext) {
|
||||
ConstantValue<?> constant = getCompileTimeConstant(expression, bindingContext, false);
|
||||
public static ConstantValue<?> getPrimitiveOrStringCompileTimeConstant(
|
||||
@NotNull KtExpression expression,
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals) {
|
||||
ConstantValue<?> constant = getCompileTimeConstant(expression, bindingContext, false, shouldInlineConstVals);
|
||||
if (constant == null || ConstantExpressionEvaluatorKt.isStandaloneOnlyConstant(constant)) {
|
||||
return null;
|
||||
}
|
||||
@@ -1453,22 +1453,26 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ConstantValue<?> getCompileTimeConstant(@NotNull KtExpression expression, @NotNull BindingContext bindingContext) {
|
||||
return getCompileTimeConstant(expression, bindingContext, false);
|
||||
public static ConstantValue<?> getCompileTimeConstant(
|
||||
@NotNull KtExpression expression,
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals) {
|
||||
return getCompileTimeConstant(expression, bindingContext, false, shouldInlineConstVals);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ConstantValue<?> getCompileTimeConstant(
|
||||
@NotNull KtExpression expression,
|
||||
@NotNull final BindingContext bindingContext,
|
||||
boolean takeUpConstValsAsConst
|
||||
boolean takeUpConstValsAsConst,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
CompileTimeConstant<?> compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, bindingContext);
|
||||
if (compileTimeValue == null || compileTimeValue.getUsesNonConstValAsConstant()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!takeUpConstValsAsConst && compileTimeValue.getUsesVariableAsConstant()) {
|
||||
if (!shouldInlineConstVals && !takeUpConstValsAsConst && compileTimeValue.getUsesVariableAsConstant()) {
|
||||
final Ref<Boolean> containsNonInlinedVals = new Ref<Boolean>(false);
|
||||
KtVisitor constantChecker = new KtVisitor() {
|
||||
@Override
|
||||
@@ -2607,14 +2611,10 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
boolean skipPropertyAccessors;
|
||||
|
||||
PropertyDescriptor originalPropertyDescriptor = DescriptorUtils.unwrapFakeOverride(propertyDescriptor);
|
||||
LanguageVersionSettings languageVersionSettings = state.getConfiguration().get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS);
|
||||
if (languageVersionSettings == null) {
|
||||
languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT;
|
||||
}
|
||||
|
||||
if (fieldAccessorKind != FieldAccessorKind.NORMAL) {
|
||||
int flags = AsmUtil.getVisibilityForBackingField(propertyDescriptor, isDelegatedProperty);
|
||||
boolean isInlinedConst = propertyDescriptor.isConst() && languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals);
|
||||
boolean isInlinedConst = propertyDescriptor.isConst() && state.getShouldInlineConstVals();
|
||||
skipPropertyAccessors = isInlinedConst || (flags & ACC_PRIVATE) == 0 || skipAccessorsForPrivateFieldInOuterClass;
|
||||
|
||||
if (!skipPropertyAccessors) {
|
||||
@@ -2635,7 +2635,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
if (!skipPropertyAccessors) {
|
||||
if (!couldUseDirectAccessToProperty(propertyDescriptor, true, isDelegatedProperty, context, languageVersionSettings)) {
|
||||
if (!couldUseDirectAccessToProperty(propertyDescriptor, true, isDelegatedProperty, context, state.getShouldInlineConstVals())) {
|
||||
propertyDescriptor = context.getAccessorForSuperCallIfNeeded(propertyDescriptor, superCallTarget);
|
||||
|
||||
propertyDescriptor = context.accessibleDescriptor(propertyDescriptor, superCallTarget);
|
||||
@@ -2649,7 +2649,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
if (propertyDescriptor.isVar()) {
|
||||
PropertySetterDescriptor setter = propertyDescriptor.getSetter();
|
||||
if (setter != null &&
|
||||
!couldUseDirectAccessToProperty(propertyDescriptor, false, isDelegatedProperty, context, languageVersionSettings) &&
|
||||
!couldUseDirectAccessToProperty(propertyDescriptor, false, isDelegatedProperty, context, state.getShouldInlineConstVals()) &&
|
||||
!isConstOrHasJvmFieldAnnotation(propertyDescriptor)) {
|
||||
callableSetter = typeMapper.mapToCallableMethod(setter, isSuper);
|
||||
}
|
||||
@@ -3523,7 +3523,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
expression.getRight(), reference);
|
||||
}
|
||||
else {
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext);
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext, state.getShouldInlineConstVals());
|
||||
if (compileTimeConstant != null) {
|
||||
return StackValue.constant(compileTimeConstant.getValue(), expressionType(expression));
|
||||
}
|
||||
@@ -3666,7 +3666,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
private boolean isIntZero(KtExpression expr, Type exprType) {
|
||||
ConstantValue<?> exprValue = getPrimitiveOrStringCompileTimeConstant(expr, bindingContext);
|
||||
ConstantValue<?> exprValue = getPrimitiveOrStringCompileTimeConstant(expr, bindingContext, state.getShouldInlineConstVals());
|
||||
return isIntPrimitive(exprType) && exprValue != null && Integer.valueOf(0).equals(exprValue.getValue());
|
||||
}
|
||||
|
||||
@@ -3796,7 +3796,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
public void invokeAppend(KtExpression expr) {
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expr, bindingContext);
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expr, bindingContext, state.getShouldInlineConstVals());
|
||||
|
||||
if (compileTimeConstant == null && expr instanceof KtBinaryExpression) {
|
||||
KtBinaryExpression binaryExpression = (KtBinaryExpression) expr;
|
||||
@@ -3839,7 +3839,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
@Override
|
||||
public StackValue visitPrefixExpression(@NotNull KtPrefixExpression expression, @NotNull StackValue receiver) {
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext);
|
||||
ConstantValue<?> compileTimeConstant = getPrimitiveOrStringCompileTimeConstant(expression, bindingContext, state.getShouldInlineConstVals());
|
||||
if (compileTimeConstant != null) {
|
||||
return StackValue.constant(compileTimeConstant.getValue(), expressionType(expression));
|
||||
}
|
||||
|
||||
@@ -649,12 +649,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
|
||||
private Type genPropertyOnStack(InstructionAdapter iv, MethodContext context, @NotNull PropertyDescriptor propertyDescriptor, int index) {
|
||||
iv.load(index, classAsmType);
|
||||
LanguageVersionSettings settings = state.getConfiguration().get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS);
|
||||
if (settings == null) {
|
||||
settings = LanguageVersionSettingsImpl.DEFAULT;
|
||||
}
|
||||
if (couldUseDirectAccessToProperty(propertyDescriptor, /* forGetter = */ true,
|
||||
/* isDelegated = */ false, context, settings)) {
|
||||
/* isDelegated = */ false, context, state.getShouldInlineConstVals())) {
|
||||
Type type = typeMapper.mapType(propertyDescriptor.getType());
|
||||
String fieldName = ((FieldOwnerContext) context.getParentContext()).getFieldName(propertyDescriptor, false);
|
||||
iv.getfield(classAsmType.getInternalName(), fieldName, type.getDescriptor());
|
||||
|
||||
@@ -185,9 +185,9 @@ public class JvmCodegenUtil {
|
||||
boolean forGetter,
|
||||
boolean isDelegated,
|
||||
@NotNull MethodContext contextBeforeInline,
|
||||
@NotNull LanguageVersionSettings languageVersionSettings
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
if (languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) && property.isConst()) return true;
|
||||
if (shouldInlineConstVals && property.isConst()) return true;
|
||||
|
||||
if (KotlinTypeMapper.isAccessor(property)) return false;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.codegen.inline.*;
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension;
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
|
||||
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl;
|
||||
@@ -461,7 +462,7 @@ public abstract class MemberCodegen<T extends KtElement/* TODO: & JetDeclaration
|
||||
KtExpression initializer = property.getInitializer();
|
||||
|
||||
ConstantValue<?> initializerValue =
|
||||
initializer != null ? ExpressionCodegen.getCompileTimeConstant(initializer, bindingContext) : null;
|
||||
initializer != null ? ExpressionCodegen.getCompileTimeConstant(initializer, bindingContext, state.getShouldInlineConstVals()) : null;
|
||||
// we must write constant values for fields in light classes,
|
||||
// because Java's completion for annotation arguments uses this information
|
||||
if (initializerValue == null) return state.getClassBuilderMode().generateBodies;
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.annotation.AnnotatedWithFakeAnnotations;
|
||||
import org.jetbrains.kotlin.codegen.context.*;
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationSplitter;
|
||||
@@ -228,7 +229,8 @@ public class PropertyCodegen {
|
||||
|
||||
KtExpression defaultValue = p.getDefaultValue();
|
||||
if (defaultValue != null) {
|
||||
ConstantValue<?> constant = ExpressionCodegen.getCompileTimeConstant(defaultValue, bindingContext, true);
|
||||
ConstantValue<?> constant = ExpressionCodegen.getCompileTimeConstant(
|
||||
defaultValue, bindingContext, true, state.getShouldInlineConstVals());
|
||||
assert !state.getClassBuilderMode().generateBodies || constant != null
|
||||
: "Default value for annotation parameter should be compile time value: " + defaultValue.getText();
|
||||
if (constant != null) {
|
||||
|
||||
@@ -30,10 +30,11 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.JavaClassProperty;
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
import org.jetbrains.kotlin.config.LanguageFeature;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor;
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi;
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor;
|
||||
import org.jetbrains.kotlin.psi.KtExpression;
|
||||
import org.jetbrains.kotlin.psi.ValueArgument;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
@@ -1199,7 +1200,7 @@ public abstract class StackValue {
|
||||
if (getter == null) {
|
||||
assert fieldName != null : "Property should have either a getter or a field name: " + descriptor;
|
||||
assert backingFieldOwner != null : "Property should have either a getter or a backingFieldOwner: " + descriptor;
|
||||
if (inlineJavaConstantIfNeeded(type, v)) return;
|
||||
if (inlineConstantIfNeeded(type, v)) return;
|
||||
|
||||
v.visitFieldInsn(isStaticPut ? GETSTATIC : GETFIELD,
|
||||
backingFieldOwner.getInternalName(), fieldName, this.type.getDescriptor());
|
||||
@@ -1227,15 +1228,24 @@ public abstract class StackValue {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean inlineJavaConstantIfNeeded(@NotNull Type type, @NotNull InstructionAdapter v) {
|
||||
if (!JvmCodegenUtil.isInlinedJavaConstProperty(descriptor)) return false;
|
||||
private boolean inlineConstantIfNeeded(@NotNull Type type, @NotNull InstructionAdapter v) {
|
||||
if (JvmCodegenUtil.isInlinedJavaConstProperty(descriptor)) {
|
||||
return inlineConstant(type, v);
|
||||
}
|
||||
|
||||
if (descriptor.isConst() && codegen.getState().getShouldInlineConstVals()) {
|
||||
return inlineConstant(type, v);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean inlineConstant(@NotNull Type type, @NotNull InstructionAdapter v) {
|
||||
assert AsmUtil.isPrimitive(this.type) || AsmTypes.JAVA_STRING_TYPE.equals(this.type) :
|
||||
"Java const property should have primitive or string type: " + descriptor;
|
||||
assert isStaticPut : "Java const property should be static" + descriptor;
|
||||
"Const property should have primitive or string type: " + descriptor;
|
||||
assert isStaticPut : "Const property should be static" + descriptor;
|
||||
|
||||
JavaPropertyDescriptor javaPropertyDescriptor = (JavaPropertyDescriptor) descriptor;
|
||||
ConstantValue<?> constantValue = javaPropertyDescriptor.getCompileTimeInitializer();
|
||||
ConstantValue<?> constantValue = descriptor.getCompileTimeInitializer();
|
||||
if (constantValue == null) return false;
|
||||
|
||||
Object value = constantValue.getValue();
|
||||
|
||||
+5
-1
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||
import org.jetbrains.kotlin.codegen.state.TypeMapperUtilsKt;
|
||||
import org.jetbrains.kotlin.codegen.when.SwitchCodegenUtil;
|
||||
import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.coroutines.CoroutineUtilKt;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
|
||||
@@ -83,6 +84,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
private final JvmRuntimeTypes runtimeTypes;
|
||||
private final JvmFileClassesProvider fileClassesProvider;
|
||||
private final TypeMappingConfiguration<Type> typeMappingConfiguration;
|
||||
private final boolean shouldInlineConstVals;
|
||||
|
||||
public CodegenAnnotatingVisitor(@NotNull GenerationState state) {
|
||||
this.bindingTrace = state.getBindingTrace();
|
||||
@@ -91,6 +93,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
this.runtimeTypes = state.getJvmRuntimeTypes();
|
||||
this.fileClassesProvider = state.getFileClassesProvider();
|
||||
this.typeMappingConfiguration = state.getTypeMapper().getTypeMappingConfiguration();
|
||||
this.shouldInlineConstVals = state.getShouldInlineConstVals();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -645,7 +648,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
|
||||
WhenByEnumsMapping mapping = new WhenByEnumsMapping(classDescriptor, currentClassName, fieldNumber);
|
||||
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getAllConstants(expression, bindingContext)) {
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getAllConstants(expression, bindingContext, shouldInlineConstVals)) {
|
||||
if (constant instanceof NullValue) continue;
|
||||
|
||||
assert constant instanceof EnumValue : "expression in when should be EnumValue";
|
||||
@@ -662,6 +665,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
SwitchCodegenUtil.checkAllItemsAreConstantsSatisfying(
|
||||
expression,
|
||||
bindingContext,
|
||||
shouldInlineConstVals,
|
||||
new Function1<ConstantValue<?>, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(@NotNull ConstantValue<?> constant) {
|
||||
|
||||
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.codegen.inline.InlineCache
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.codegen.optimization.OptimizationClassBuilderFactory
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
@@ -169,6 +166,11 @@ class GenerationState @JvmOverloads constructor(
|
||||
|
||||
val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA)
|
||||
|
||||
val shouldInlineConstVals = run {
|
||||
val settings = configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS) ?: LanguageVersionSettingsImpl.DEFAULT
|
||||
settings.supportsFeature(LanguageFeature.InlineConstVals)
|
||||
}
|
||||
|
||||
init {
|
||||
this.interceptedBuilderFactory = builderFactory
|
||||
.wrapWith(
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.when;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen;
|
||||
import org.jetbrains.kotlin.codegen.FrameMap;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.psi.KtWhenEntry;
|
||||
import org.jetbrains.kotlin.psi.KtWhenExpression;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
@@ -98,7 +99,7 @@ abstract public class SwitchCodegen {
|
||||
for (KtWhenEntry entry : expression.getEntries()) {
|
||||
Label entryLabel = new Label();
|
||||
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) {
|
||||
if (constant instanceof NullValue) continue;
|
||||
processConstant(constant, entryLabel);
|
||||
}
|
||||
@@ -156,7 +157,7 @@ abstract public class SwitchCodegen {
|
||||
private int findNullEntryIndex(@NotNull KtWhenExpression expression) {
|
||||
int entryIndex = 0;
|
||||
for (KtWhenEntry entry : expression.getEntries()) {
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
|
||||
for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) {
|
||||
if (constant instanceof NullValue) {
|
||||
return entryIndex;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public class SwitchCodegenUtil {
|
||||
public static boolean checkAllItemsAreConstantsSatisfying(
|
||||
@NotNull KtWhenExpression expression,
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals,
|
||||
Function1<ConstantValue<?>, Boolean> predicate
|
||||
) {
|
||||
for (KtWhenEntry entry : expression.getEntries()) {
|
||||
@@ -49,7 +50,7 @@ public class SwitchCodegenUtil {
|
||||
|
||||
if (patternExpression == null) return false;
|
||||
|
||||
ConstantValue<?> constant = ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext);
|
||||
ConstantValue<?> constant = ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext, shouldInlineConstVals);
|
||||
if (constant == null || !predicate.invoke(constant)) {
|
||||
return false;
|
||||
}
|
||||
@@ -62,12 +63,13 @@ public class SwitchCodegenUtil {
|
||||
@NotNull
|
||||
public static Iterable<ConstantValue<?>> getAllConstants(
|
||||
@NotNull KtWhenExpression expression,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
List<ConstantValue<?>> result = new ArrayList<ConstantValue<?>>();
|
||||
|
||||
for (KtWhenEntry entry : expression.getEntries()) {
|
||||
addConstantsFromEntry(result, entry, bindingContext);
|
||||
addConstantsFromEntry(result, entry, bindingContext, shouldInlineConstVals);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -76,7 +78,8 @@ public class SwitchCodegenUtil {
|
||||
private static void addConstantsFromEntry(
|
||||
@NotNull List<ConstantValue<?>> result,
|
||||
@NotNull KtWhenEntry entry,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
for (KtWhenCondition condition : entry.getConditions()) {
|
||||
if (!(condition instanceof KtWhenConditionWithExpression)) continue;
|
||||
@@ -84,17 +87,18 @@ public class SwitchCodegenUtil {
|
||||
KtExpression patternExpression = ((KtWhenConditionWithExpression) condition).getExpression();
|
||||
|
||||
assert patternExpression != null : "expression in when should not be null";
|
||||
result.add(ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext));
|
||||
result.add(ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext, shouldInlineConstVals));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Iterable<ConstantValue<?>> getConstantsFromEntry(
|
||||
@NotNull KtWhenEntry entry,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
List<ConstantValue<?>> result = new ArrayList<ConstantValue<?>>();
|
||||
addConstantsFromEntry(result, entry, bindingContext);
|
||||
addConstantsFromEntry(result, entry, bindingContext, shouldInlineConstVals);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -106,7 +110,8 @@ public class SwitchCodegenUtil {
|
||||
@NotNull ExpressionCodegen codegen
|
||||
) {
|
||||
BindingContext bindingContext = codegen.getBindingContext();
|
||||
if (!isThereConstantEntriesButNulls(expression, bindingContext)) {
|
||||
boolean shouldInlineConstVals = codegen.getState().getShouldInlineConstVals();
|
||||
if (!isThereConstantEntriesButNulls(expression, bindingContext, shouldInlineConstVals)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,11 +123,11 @@ public class SwitchCodegenUtil {
|
||||
return new EnumSwitchCodegen(expression, isStatement, isExhaustive, codegen, mapping);
|
||||
}
|
||||
|
||||
if (isIntegralConstantsSwitch(expression, subjectType, bindingContext)) {
|
||||
if (isIntegralConstantsSwitch(expression, subjectType, bindingContext, shouldInlineConstVals)) {
|
||||
return new IntegralConstantsSwitchCodegen(expression, isStatement, isExhaustive, codegen);
|
||||
}
|
||||
|
||||
if (isStringConstantsSwitch(expression, subjectType, bindingContext)) {
|
||||
if (isStringConstantsSwitch(expression, subjectType, bindingContext, shouldInlineConstVals)) {
|
||||
return new StringSwitchCodegen(expression, isStatement, isExhaustive, codegen);
|
||||
}
|
||||
|
||||
@@ -131,9 +136,10 @@ public class SwitchCodegenUtil {
|
||||
|
||||
private static boolean isThereConstantEntriesButNulls(
|
||||
@NotNull KtWhenExpression expression,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
for (ConstantValue<?> constant : getAllConstants(expression, bindingContext)) {
|
||||
for (ConstantValue<?> constant : getAllConstants(expression, bindingContext, shouldInlineConstVals)) {
|
||||
if (constant != null && !(constant instanceof NullValue)) return true;
|
||||
}
|
||||
|
||||
@@ -143,7 +149,8 @@ public class SwitchCodegenUtil {
|
||||
private static boolean isIntegralConstantsSwitch(
|
||||
@NotNull KtWhenExpression expression,
|
||||
@NotNull Type subjectType,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
int typeSort = subjectType.getSort();
|
||||
|
||||
@@ -151,7 +158,7 @@ public class SwitchCodegenUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkAllItemsAreConstantsSatisfying(expression, bindingContext, new Function1<ConstantValue<?>, Boolean>() {
|
||||
return checkAllItemsAreConstantsSatisfying(expression, bindingContext, shouldInlineConstVals, new Function1<ConstantValue<?>, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(
|
||||
@NotNull ConstantValue<?> constant
|
||||
@@ -164,14 +171,15 @@ public class SwitchCodegenUtil {
|
||||
private static boolean isStringConstantsSwitch(
|
||||
@NotNull KtWhenExpression expression,
|
||||
@NotNull Type subjectType,
|
||||
@NotNull BindingContext bindingContext
|
||||
@NotNull BindingContext bindingContext,
|
||||
boolean shouldInlineConstVals
|
||||
) {
|
||||
|
||||
if (!subjectType.getClassName().equals(String.class.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkAllItemsAreConstantsSatisfying(expression, bindingContext, new Function1<ConstantValue<?>, Boolean>() {
|
||||
return checkAllItemsAreConstantsSatisfying(expression, bindingContext, shouldInlineConstVals, new Function1<ConstantValue<?>, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(
|
||||
@NotNull ConstantValue<?> constant
|
||||
|
||||
+2
-1
@@ -89,7 +89,8 @@ class FunctionCodegen(val irFunction: IrFunction, val classCodegen: ClassCodegen
|
||||
val source = directMember.source
|
||||
(source.getPsi() as? KtParameter)?.defaultValue?.apply {
|
||||
val defaultValue = this
|
||||
val constant = org.jetbrains.kotlin.codegen.ExpressionCodegen.getCompileTimeConstant(defaultValue, state.bindingContext, true)
|
||||
val constant = org.jetbrains.kotlin.codegen.ExpressionCodegen.getCompileTimeConstant(
|
||||
defaultValue, state.bindingContext, true, state.shouldInlineConstVals)
|
||||
assert(!state.classBuilderMode.generateBodies || constant != null) { "Default value for annotation parameter should be compile time value: " + defaultValue.getText() }
|
||||
if (constant != null) {
|
||||
val annotationCodegen = AnnotationCodegen.forAnnotationDefaultValue(methodVisitor, classCodegen, state.typeMapper)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
//WITH_RUNTIME
|
||||
//FULL_JDK
|
||||
//NOTE this test should be removed if Kotlin const properties will became inlined
|
||||
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
object ThingTemplate {
|
||||
const val prop = 0
|
||||
}
|
||||
|
||||
class ThingVal {
|
||||
val prop = ThingTemplate.prop
|
||||
}
|
||||
|
||||
class ThingVar {
|
||||
var prop = ThingTemplate.prop
|
||||
}
|
||||
|
||||
|
||||
fun box() : String {
|
||||
val template = ThingTemplate;
|
||||
val javaClass = ThingTemplate::class.java
|
||||
val field = javaClass.getDeclaredField("prop")!!
|
||||
field.isAccessible = true
|
||||
|
||||
val modifiersField = Field::class.java!!.getDeclaredField("modifiers")
|
||||
modifiersField.isAccessible = true
|
||||
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
|
||||
|
||||
field.set(null, 1)
|
||||
|
||||
val thingVal = ThingVal()
|
||||
if (thingVal.prop != 1) return "fail 1"
|
||||
|
||||
val thingVar = ThingVar()
|
||||
if (thingVar.prop != 1) return "fail 2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// LANGUAGE_VERSION: 1.0
|
||||
|
||||
const val z = 0
|
||||
|
||||
fun a() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// LANGUAGE_VERSION: 1.0
|
||||
|
||||
const val z = 0
|
||||
|
||||
fun a() {
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
const val one = 1
|
||||
const val two = 2
|
||||
|
||||
fun test1() {
|
||||
if (!(one < two)) {
|
||||
val p = 1
|
||||
}
|
||||
}
|
||||
// 0 IF
|
||||
@@ -1,3 +1,5 @@
|
||||
// LANGUAGE_VERSION: 1.0
|
||||
|
||||
const val one = 1
|
||||
const val two = 2
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
const val A = 10
|
||||
private const val B = 20
|
||||
|
||||
object Constants {
|
||||
const val C = 30
|
||||
}
|
||||
|
||||
fun foo(state: Int) {
|
||||
when (state) {
|
||||
A -> return
|
||||
B -> return
|
||||
Constants.C -> return
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
// 1 LOOKUPSWITCH
|
||||
@@ -0,0 +1,20 @@
|
||||
object Constants {
|
||||
const val A = 30
|
||||
const val B = 40
|
||||
}
|
||||
|
||||
class ClassConstants {
|
||||
companion object {
|
||||
const val C = 50
|
||||
}
|
||||
}
|
||||
fun foo(state: Int) {
|
||||
when (state) {
|
||||
Constants.A -> return
|
||||
Constants.B -> return
|
||||
ClassConstants.C -> return
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
// 1 LOOKUPSWITCH
|
||||
@@ -0,0 +1,16 @@
|
||||
const val A = 10
|
||||
private const val B = 20
|
||||
|
||||
object Constants {
|
||||
const val C = 30
|
||||
}
|
||||
|
||||
fun foo(state: Int) {
|
||||
when (state) {
|
||||
A -> return
|
||||
B -> return
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
// 1 LOOKUPSWITCH
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
const val y = "cde"
|
||||
|
||||
fun foo(x : String) : String {
|
||||
when (x) {
|
||||
"abc", "${y}" -> return "abc_cde"
|
||||
"e" + "fg", "ghi" -> return "efg_ghi"
|
||||
}
|
||||
|
||||
return "other"
|
||||
}
|
||||
|
||||
// 1 LOOKUPSWITCH
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
// LANGUAGE_VERSION: 1.0
|
||||
|
||||
const val y = "cde"
|
||||
|
||||
fun foo(x : String) : String {
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
{"visibility": "public", "declaration": "fun publicFun(): kotlin.Unit", "name": "publicFun", "desc": "()V"},
|
||||
{"visibility": "internal", "declaration": "fun internalFun(param1: kotlin.Int): kotlin.Unit", "name": "internalFun", "desc": "(I)V"},
|
||||
{"visibility": "private", "declaration": "fun privateFun(x: kotlin.Any): kotlin.Unit", "name": "privateFun$MultifileFacade__Part2Kt", "desc": "(Ljava/lang/Object;)V"},
|
||||
{"visibility": "private", "declaration": "fun privateFun(x: kotlin.Any): kotlin.Unit", "name": "access$privateFun", "desc": "(Ljava/lang/Object;)V"},
|
||||
{"visibility": "private", "declaration": "fun <get-privateConst>(): kotlin.Int", "name": "access$getPrivateConst$p", "desc": "()I"}
|
||||
{"visibility": "private", "declaration": "fun privateFun(x: kotlin.Any): kotlin.Unit", "name": "access$privateFun", "desc": "(Ljava/lang/Object;)V"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
{"visibility": "private", "declaration": "fun privateFun(x: kotlin.Any?): kotlin.Unit", "name": "access$privateFun", "desc": "(Ljava/lang/Object;)V"},
|
||||
{"visibility": "private", "declaration": "fun <get-privateVal>(): kotlin.Any?", "name": "access$getPrivateVal$p", "desc": "()Ljava/lang/Object;"},
|
||||
{"visibility": "private", "declaration": "fun <get-privateVar>(): kotlin.Any?", "name": "access$getPrivateVar$p", "desc": "()Ljava/lang/Object;"},
|
||||
{"visibility": "private", "declaration": "fun <set-privateVar>(<set-?>: kotlin.Any?): kotlin.Unit", "name": "access$setPrivateVar$p", "desc": "(Ljava/lang/Object;)V"},
|
||||
{"visibility": "private", "declaration": "fun <get-privateConst>(): kotlin.Int", "name": "access$getPrivateConst$p", "desc": "()I"}
|
||||
{"visibility": "private", "declaration": "fun <set-privateVar>(<set-?>: kotlin.Any?): kotlin.Unit", "name": "access$setPrivateVar$p", "desc": "(Ljava/lang/Object;)V"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
-6
@@ -11225,12 +11225,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt12200Const.kt")
|
||||
public void testKt12200Const() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt12200Const.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt1398.kt")
|
||||
public void testKt1398() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt1398.kt");
|
||||
|
||||
@@ -11225,12 +11225,6 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt12200Const.kt")
|
||||
public void testKt12200Const() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt12200Const.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt1398.kt")
|
||||
public void testKt1398() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt1398.kt");
|
||||
|
||||
@@ -1256,6 +1256,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/lazyCodegen"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineConstInsideComparison.kt")
|
||||
public void testInlineConstInsideComparison() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/lazyCodegen/inlineConstInsideComparison.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("negateConst.kt")
|
||||
public void testNegateConst() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/lazyCodegen/negateConst.kt");
|
||||
@@ -1562,17 +1568,35 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineConstValsInsideWhen.kt")
|
||||
public void testInlineConstValsInsideWhen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/inlineConstValsInsideWhen.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("integralWhenWithNoInlinedConstants.kt")
|
||||
public void testIntegralWhenWithNoInlinedConstants() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/integralWhenWithNoInlinedConstants.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualifiedConstValsInsideWhen.kt")
|
||||
public void testQualifiedConstValsInsideWhen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/qualifiedConstValsInsideWhen.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sealedWhenInitialization.kt")
|
||||
public void testSealedWhenInitialization() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/sealedWhenInitialization.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simpleConstValsInsideWhen.kt")
|
||||
public void testSimpleConstValsInsideWhen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/when/simpleConstValsInsideWhen.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/bytecodeText/whenEnumOptimization")
|
||||
@@ -1676,6 +1700,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineStringConstInsideWhen.kt")
|
||||
public void testInlineStringConstInsideWhen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/whenStringOptimization/inlineStringConstInsideWhen.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nonInlinedConst.kt")
|
||||
public void testNonInlinedConst() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/whenStringOptimization/nonInlinedConst.kt");
|
||||
|
||||
@@ -21,8 +21,10 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.psi.KtConstantExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
@@ -44,8 +46,12 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
|
||||
private val typeName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
|
||||
private val expectedTypeIsFloat = KotlinBuiltIns.isFloat(type)
|
||||
private val expectedTypeIsDouble = KotlinBuiltIns.isDouble(type)
|
||||
private val constValue
|
||||
= ExpressionCodegen.getPrimitiveOrStringCompileTimeConstant(element, element.analyze(BodyResolveMode.PARTIAL))?.value as? Number
|
||||
|
||||
private val constValue = run {
|
||||
val shouldInlineCosntVals = element.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
|
||||
ExpressionCodegen.getPrimitiveOrStringCompileTimeConstant(
|
||||
element, element.analyze(BodyResolveMode.PARTIAL), shouldInlineCosntVals)?.value as? Number
|
||||
}
|
||||
|
||||
private val fixedExpression = buildString {
|
||||
if (expectedTypeIsFloat || expectedTypeIsDouble) {
|
||||
|
||||
-12
@@ -13608,18 +13608,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
}
|
||||
|
||||
@TestMetadata("kt12200Const.kt")
|
||||
public void testKt12200Const() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt12200Const.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
}
|
||||
|
||||
@TestMetadata("kt1398.kt")
|
||||
public void testKt1398() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/kt1398.kt");
|
||||
|
||||
Reference in New Issue
Block a user