Report warning on usages of non-const vals in places where constants expected

This commit is contained in:
Denis Zharkov
2015-09-21 18:14:35 +03:00
parent 8d13f08271
commit afd4e644a3
21 changed files with 105 additions and 29 deletions
@@ -141,6 +141,8 @@ public interface Errors {
DiagnosticFactory0<PsiElement> CONST_VAL_WITHOUT_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetExpression> CONST_VAL_WITH_NON_CONST_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetExpression> NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_TARGET_ON_PROPERTY = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_FIELD_TARGET_NO_BACKING_FIELD = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_TARGET_PROPERTY_IMMUTABLE = DiagnosticFactory0.create(ERROR);
@@ -602,6 +602,7 @@ public class DefaultErrorMessages {
MAP.put(TYPE_CANT_BE_USED_FOR_CONST_VAL, "Const 'val' has type ''{0}''. Only primitives and String are allowed", RENDER_TYPE);
MAP.put(CONST_VAL_WITHOUT_INITIALIZER, "Const 'val' should have an initializer");
MAP.put(CONST_VAL_WITH_NON_CONST_INITIALIZER, "Const 'val' initializer should be a constant value");
MAP.put(NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION, "Only 'const val' can be used in constant expressions");
MAP.put(DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE, "An overriding function is not allowed to specify default values for its parameters");
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.resolve.dataClassUtils.DataClassUtilsPackage;
@@ -880,7 +881,7 @@ public class DescriptorResolver {
}
private void setConstantForVariableIfNeeded(
@NotNull VariableDescriptorWithInitializerImpl variableDescriptor,
@NotNull final VariableDescriptorWithInitializerImpl variableDescriptor,
@NotNull final LexicalScope scope,
@NotNull final JetVariableDeclaration variable,
@NotNull final DataFlowInfo dataFlowInfo,
@@ -898,7 +899,15 @@ public class DescriptorResolver {
public ConstantValue<?> invoke() {
JetExpression initializer = variable.getInitializer();
JetType initializerType = expressionTypingServices.safeGetType(scope, initializer, variableType, dataFlowInfo, trace);
return constantExpressionEvaluator.evaluateToConstantValue(initializer, trace, initializerType);
CompileTimeConstant<?> constant = constantExpressionEvaluator.evaluateExpression(initializer, trace, initializerType);
if (constant == null) return null;
if (constant.getUsesNonConstValAsConstant() && variableDescriptor.isConst()) {
trace.report(Errors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION.on(initializer));
}
return constant.toConstantValue(initializerType);
}
}, null)
);
@@ -124,6 +124,9 @@ public class ConstantExpressionEvaluator(
val constant = ConstantExpressionEvaluator.getConstant(argumentExpression, trace.getBindingContext())
if (constant != null && constant.canBeUsedInAnnotations) {
if (constant.usesNonConstValAsConstant) {
trace.report(Errors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION.on(argumentExpression))
}
return
}
@@ -284,7 +287,7 @@ private class ConstantExpressionEvaluatorVisitor(
} ?: return null
fun isLongWithSuffix() = nodeElementType == JetNodeTypes.INTEGER_CONSTANT && hasLongSuffix(text)
return createConstant(result, expectedType, CompileTimeConstant.Parameters(true, !isLongWithSuffix(), false))
return createConstant(result, expectedType, CompileTimeConstant.Parameters(true, !isLongWithSuffix(), false, usesNonConstValAsConstant = false))
}
override fun visitParenthesizedExpression(expression: JetParenthesizedExpression, expectedType: JetType?): CompileTimeConstant<*>? {
@@ -308,6 +311,7 @@ private class ConstantExpressionEvaluatorVisitor(
var interupted = false
var canBeUsedInAnnotation = true
var usesVariableAsConstant = false
var usesNonConstantVariableAsConstant = false
for (entry in expression.getEntries()) {
val constant = stringExpressionEvaluator.evaluate(entry)
if (constant == null) {
@@ -317,6 +321,7 @@ private class ConstantExpressionEvaluatorVisitor(
else {
if (!constant.canBeUsedInAnnotations) canBeUsedInAnnotation = false
if (constant.usesVariableAsConstant) usesVariableAsConstant = true
if (constant.usesNonConstValAsConstant) usesNonConstantVariableAsConstant = true
sb.append(constant.constantValue.value)
}
}
@@ -327,7 +332,8 @@ private class ConstantExpressionEvaluatorVisitor(
CompileTimeConstant.Parameters(
isPure = false,
canBeUsedInAnnotation = canBeUsedInAnnotation,
usesVariableAsConstant = usesVariableAsConstant
usesVariableAsConstant = usesVariableAsConstant,
usesNonConstValAsConstant = usesNonConstantVariableAsConstant
)
)
else null
@@ -363,7 +369,8 @@ private class ConstantExpressionEvaluatorVisitor(
CompileTimeConstant.Parameters(
canBeUsedInAnnotation = true,
isPure = false,
usesVariableAsConstant = leftConstant.usesVariableAsConstant || rightConstant.usesVariableAsConstant
usesVariableAsConstant = leftConstant.usesVariableAsConstant || rightConstant.usesVariableAsConstant,
usesNonConstValAsConstant = leftConstant.usesNonConstValAsConstant || rightConstant.usesNonConstValAsConstant
)
)
}
@@ -388,6 +395,7 @@ private class ConstantExpressionEvaluatorVisitor(
val isArgumentPure = isPureConstant(argumentForReceiver.expression)
val canBeUsedInAnnotation = canBeUsedInAnnotation(argumentForReceiver.expression)
val usesVariableAsConstant = usesVariableAsConstant(argumentForReceiver.expression)
val usesNonConstValAsConstant = usesNonConstValAsConstant(argumentForReceiver.expression)
val isNumberConversionMethod = resultingDescriptorName in OperatorConventions.NUMBER_CONVERSIONS
return createConstant(
result,
@@ -395,7 +403,7 @@ private class ConstantExpressionEvaluatorVisitor(
CompileTimeConstant.Parameters(
canBeUsedInAnnotation,
!isNumberConversionMethod && isArgumentPure,
usesVariableAsConstant)
usesVariableAsConstant, usesNonConstValAsConstant)
)
}
else if (argumentsEntrySet.size() == 1) {
@@ -415,7 +423,8 @@ private class ConstantExpressionEvaluatorVisitor(
val areArgumentsPure = isPureConstant(argumentForReceiver.expression) && isPureConstant(argumentForParameter.expression)
val canBeUsedInAnnotation = canBeUsedInAnnotation(argumentForReceiver.expression) && canBeUsedInAnnotation(argumentForParameter.expression)
val usesVariableAsConstant = usesVariableAsConstant(argumentForReceiver.expression) || usesVariableAsConstant(argumentForParameter.expression)
val parameters = CompileTimeConstant.Parameters(canBeUsedInAnnotation, areArgumentsPure, usesVariableAsConstant)
val usesNonConstValAsConstant = usesNonConstValAsConstant(argumentForReceiver.expression) || usesNonConstValAsConstant(argumentForParameter.expression)
val parameters = CompileTimeConstant.Parameters(canBeUsedInAnnotation, areArgumentsPure, usesVariableAsConstant, usesNonConstValAsConstant)
return when (resultingDescriptorName) {
OperatorConventions.COMPARE_TO -> createCompileTimeConstantForCompareTo(result, callExpression, factory)?.wrap(parameters)
OperatorConventions.EQUALS -> createCompileTimeConstantForEquals(result, callExpression, factory)?.wrap(parameters)
@@ -429,6 +438,8 @@ private class ConstantExpressionEvaluatorVisitor(
}
private fun usesVariableAsConstant(expression: JetExpression) = ConstantExpressionEvaluator.getConstant(expression, trace.getBindingContext())?.usesVariableAsConstant ?: false
private fun usesNonConstValAsConstant(expression: JetExpression)
= ConstantExpressionEvaluator.getConstant(expression, trace.bindingContext)?.usesNonConstValAsConstant ?: false
private fun canBeUsedInAnnotation(expression: JetExpression) = ConstantExpressionEvaluator.getConstant(expression, trace.getBindingContext())?.canBeUsedInAnnotations ?: false
@@ -515,7 +526,8 @@ private class ConstantExpressionEvaluatorVisitor(
CompileTimeConstant.Parameters(
canBeUsedInAnnotation = isPropertyCompileTimeConstant(callableDescriptor),
isPure = false,
usesVariableAsConstant = true
usesVariableAsConstant = true,
usesNonConstValAsConstant = !callableDescriptor.isConst
)
)
}
@@ -523,6 +535,7 @@ private class ConstantExpressionEvaluatorVisitor(
return null
}
// TODO: Should be replaced with descriptor.isConst
private fun isPropertyCompileTimeConstant(descriptor: VariableDescriptor): Boolean {
if (descriptor.isVar()) {
return false
@@ -566,7 +579,7 @@ private class ConstantExpressionEvaluatorVisitor(
val resultingDescriptor = call.getResultingDescriptor()
// array()
// arrayOf()
if (CompileTimeConstantUtils.isArrayMethodCall(call)) {
val varargType = resultingDescriptor.getValueParameters().first().getVarargElementType()!!
@@ -574,7 +587,8 @@ private class ConstantExpressionEvaluatorVisitor(
return factory.createArrayValue(arguments.map { it.toConstantValue(varargType) }, resultingDescriptor.getReturnType()!!).
wrap(
usesVariableAsConstant = arguments.any { it.usesVariableAsConstant }
usesVariableAsConstant = arguments.any { it.usesVariableAsConstant },
usesNonConstValAsConstant = arguments.any { it.usesNonConstValAsConstant }
)
}
@@ -696,9 +710,10 @@ private class ConstantExpressionEvaluatorVisitor(
private fun <T> ConstantValue<T>.wrap(
canBeUsedInAnnotation: Boolean = this !is NullValue,
isPure: Boolean = false,
usesVariableAsConstant: Boolean = false
usesVariableAsConstant: Boolean = false,
usesNonConstValAsConstant: Boolean = false
): TypedCompileTimeConstant<T>
= wrap(CompileTimeConstant.Parameters(canBeUsedInAnnotation, isPure, usesVariableAsConstant))
= wrap(CompileTimeConstant.Parameters(canBeUsedInAnnotation, isPure, usesVariableAsConstant, usesNonConstValAsConstant))
}
private fun hasLongSuffix(text: String) = text.endsWith('l') || text.endsWith('L')
@@ -1,3 +1,3 @@
// Properties can be recursively annotated
annotation class ann(val x: Int)
@ann(x) val x: Int = 1
@ann(x) const val x: Int = 1
@@ -1,6 +1,6 @@
package
@ann(x = 1) public val x: kotlin.Int = 1
@ann(x = 1) public const val x: kotlin.Int = 1
@kotlin.annotation.annotation() public final class ann : kotlin.Annotation {
public constructor ann(/*0*/ x: kotlin.Int)
@@ -11,8 +11,8 @@ annotation class Ann(vararg val i: Int)
class Test
var i1 = 1 // var
val i2 = 1 // val
const val i2 = 1 // val
val i3 = i1 // val with var in initializer
val i4 = i2 // val with val in initializer
const val i4 = i2 // val with val in initializer
var i5 = i1 // var with var in initializer
var i6 = i2 // var with val in initializer
@@ -1,9 +1,9 @@
package
public var i1: kotlin.Int
public val i2: kotlin.Int = 1
public const val i2: kotlin.Int = 1
public val i3: kotlin.Int
public val i4: kotlin.Int = 1
public const val i4: kotlin.Int = 1
public var i5: kotlin.Int
public var i6: kotlin.Int
@@ -1,7 +1,7 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
annotation class Ann(vararg val i: String)
val topLevel = "topLevel"
const val topLevel = "topLevel"
fun foo() {
val a1 = "a"
@@ -1,6 +1,6 @@
package
public val topLevel: kotlin.String = "topLevel"
public const val topLevel: kotlin.String = "topLevel"
public fun foo(): kotlin.Unit
@kotlin.annotation.annotation() public final class Ann : kotlin.Annotation {
@@ -2,7 +2,7 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
annotation class ann(val name: String)
val ok = "OK"
const val ok = "OK"
class A
@@ -2,7 +2,7 @@ package
public val extensionWithoutName: A.() -> kotlin.Unit
public val funfun: () -> () -> kotlin.Int
public val ok: kotlin.String = "OK"
public const val ok: kotlin.String = "OK"
public val parentesized: () -> kotlin.Unit
public val parentesizedWithType: () -> kotlin.Unit
public val withExpression: () -> kotlin.Int
@@ -10,7 +10,7 @@ annotation class Ann(val i: IntArray)
class Test
var i = 1
val i2 = 1
const val i2 = 1
val i3 = foo()
fun foo(): Int = 1
@@ -1,7 +1,7 @@
package
public var i: kotlin.Int
public val i2: kotlin.Int = 1
public const val i2: kotlin.Int = 1
public val i3: kotlin.Int
public val iAnn: Ann
public fun foo(): kotlin.Int
@@ -0,0 +1,16 @@
val nonConst = 1
const val constConst = <!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>nonConst * nonConst + 2<!>
annotation class Ann(val x: Int, val y: String)
@Ann(<!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>nonConst<!>, <!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>"${nonConst}"<!>)
fun foo1() {}
@Ann(<!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>nonConst + constConst<!>, "${constConst}")
fun foo2() {}
annotation class ArrayAnn(val x: IntArray)
@ArrayAnn(<!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>intArrayOf(1, constConst, <!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>nonConst<!>)<!>)
fun foo3() {}
@@ -0,0 +1,24 @@
package
public const val constConst: kotlin.Int = 3
public val nonConst: kotlin.Int = 1
@Ann(x = 1, y = "1") public fun foo1(): kotlin.Unit
@Ann(x = 4, y = "3") public fun foo2(): kotlin.Unit
@ArrayAnn(x = {1, 3, 1}) public fun foo3(): kotlin.Unit
@kotlin.annotation.annotation() public final class Ann : kotlin.Annotation {
public constructor Ann(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.String)
public final val x: kotlin.Int
public final val y: kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@kotlin.annotation.annotation() public final class ArrayAnn : kotlin.Annotation {
public constructor ArrayAnn(/*0*/ x: kotlin.IntArray)
public final val x: kotlin.IntArray
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -13,7 +13,7 @@ annotation class Ann(vararg val i: Int)
class Test
var i = 1
val i2 = 1
const val i2 = 1
val i3 = foo()
fun foo(): Int = 1
@@ -1,7 +1,7 @@
package
public var i: kotlin.Int
public val i2: kotlin.Int = 1
public const val i2: kotlin.Int = 1
public val i3: kotlin.Int
public val iAnn: Ann
public fun foo(): kotlin.Int
@@ -162,6 +162,12 @@ public class JetDiagnosticsTestWithStdLibGenerated extends AbstractJetDiagnostic
doTest(fileName);
}
@TestMetadata("useOfNonConstVal.kt")
public void testUseOfNonConstVal() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameterMustBeConstant/useOfNonConstVal.kt");
doTest(fileName);
}
@TestMetadata("vararg.kt")
public void testVararg() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameterMustBeConstant/vararg.kt");
@@ -34,12 +34,15 @@ public interface CompileTimeConstant<out T> {
public val usesVariableAsConstant: Boolean get() = parameters.usesVariableAsConstant
public val usesNonConstValAsConstant: Boolean get() = parameters.usesNonConstValAsConstant
public val isPure: Boolean get() = parameters.isPure
public class Parameters(
public val canBeUsedInAnnotation: Boolean,
public val isPure: Boolean,
public val usesVariableAsConstant: Boolean
public val usesVariableAsConstant: Boolean,
public val usesNonConstValAsConstant: Boolean
)
}
+2 -2
View File
@@ -16,8 +16,8 @@
package boo
val BAZ = "baz"
val N = 0
const val BAZ = "baz"
const val N = 0
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)