Captured value initialization is no more allowed #KT-10445 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-03-21 13:26:53 +03:00
parent e8b665f600
commit bc4ec97043
14 changed files with 188 additions and 19 deletions
@@ -391,6 +391,22 @@ public class ControlFlowInformationProvider {
}
}
private boolean isCapturedWrite(
@NotNull VariableDescriptor variableDescriptor,
@NotNull WriteValueInstruction writeValueInstruction
) {
DeclarationDescriptor containingDeclarationDescriptor = variableDescriptor.getContainingDeclaration();
if (containingDeclarationDescriptor instanceof ClassDescriptor) return false;
KtElement ownerElement = writeValueInstruction.getOwner().getCorrespondingElement();
DeclarationDescriptor writeOwnerDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, ownerElement);
if (containingDeclarationDescriptor.equals(writeOwnerDescriptor)) return false;
if (writeOwnerDescriptor instanceof ClassDescriptor) {
ClassDescriptor writeOwnerClass = (ClassDescriptor) writeOwnerDescriptor;
if (containingDeclarationDescriptor.equals(writeOwnerClass.getUnsubstitutedPrimaryConstructor())) return false;
}
return true;
}
private boolean checkValReassignment(
@NotNull VariableInitContext ctxt,
@NotNull KtExpression expression,
@@ -434,7 +450,8 @@ public class ControlFlowInformationProvider {
}
boolean isThisOrNoDispatchReceiver =
PseudocodeUtil.isThisOrNoDispatchReceiver(writeValueInstruction, trace.getBindingContext());
if ((mayBeInitializedNotHere || !hasBackingField || !isThisOrNoDispatchReceiver) && !variableDescriptor.isVar()) {
boolean captured = isCapturedWrite(variableDescriptor, writeValueInstruction);
if ((mayBeInitializedNotHere || !hasBackingField || !isThisOrNoDispatchReceiver || captured) && !variableDescriptor.isVar()) {
boolean hasReassignMethodReturningUnit = false;
KtSimpleNameExpression operationReference = null;
PsiElement parent = expression.getParent();
@@ -465,7 +482,12 @@ public class ControlFlowInformationProvider {
}
if (!hasReassignMethodReturningUnit) {
if (!isThisOrNoDispatchReceiver || !varWithValReassignErrorGenerated.contains(variableDescriptor)) {
report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor), ctxt);
if (captured && !mayBeInitializedNotHere && hasBackingField && isThisOrNoDispatchReceiver) {
report(Errors.CAPTURED_VAL_INITIALIZATION.on(expression, variableDescriptor), ctxt);
}
else {
report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor), ctxt);
}
}
if (isThisOrNoDispatchReceiver) {
// try to get rid of repeating VAL_REASSIGNMENT diagnostic only for vars with no receiver
@@ -645,6 +645,7 @@ public interface Errors {
DiagnosticFactory0<KtLambdaExpression> UNUSED_LAMBDA_EXPRESSION = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<KtExpression, DeclarationDescriptor> VAL_REASSIGNMENT = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtExpression, DeclarationDescriptor> CAPTURED_VAL_INITIALIZATION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtExpression, DeclarationDescriptor> SETTER_PROJECTED_OUT = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtExpression, DeclarationDescriptor> INITIALIZATION_BEFORE_DECLARATION = DiagnosticFactory1.create(ERROR);
@@ -268,6 +268,7 @@ public class DefaultErrorMessages {
MAP.put(UNUSED_LAMBDA_EXPRESSION, "The lambda expression is unused. If you mean a block, you can use 'run { ... }'");
MAP.put(VAL_REASSIGNMENT, "Val cannot be reassigned", NAME);
MAP.put(CAPTURED_VAL_INITIALIZATION, "Captured values initialization is forbidden due to possible reassignment", NAME);
MAP.put(SETTER_PROJECTED_OUT, "Setter for ''{0}'' is removed by type projection", NAME);
MAP.put(INVISIBLE_SETTER, "Cannot assign to ''{0}'': the setter is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
MAP.put(INITIALIZATION_BEFORE_DECLARATION, "Variable cannot be initialized before declaration", NAME);
@@ -0,0 +1,77 @@
// !DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
fun ignoreIt(<!UNUSED_PARAMETER!>f<!>: () -> Unit) {}
fun exec(f: () -> Unit) = f()
fun foo() {
var x: Int
ignoreIt() {
// Ok
x = 42
}
// Error!
<!UNINITIALIZED_VARIABLE!>x<!>.hashCode()
}
fun bar() {
val x: Int
exec {
<!CAPTURED_VAL_INITIALIZATION!>x<!> = 13
}
}
fun bar2() {
val x: Int
fun foo() {
<!CAPTURED_VAL_INITIALIZATION!>x<!> = 3
}
foo()
}
class My(val cond: Boolean) {
val y: Int
init {
val x: Int
if (cond) {
exec {
}
x = 1
}
else {
x = 2
}
y = x
}
constructor(): this(false) {
val x: Int
x = 2
exec {
x.hashCode()
}
}
}
class Your {
val y = if (true) {
val xx: Int
exec {
<!CAPTURED_VAL_INITIALIZATION!>xx<!> = 42
}
24
}
else 0
}
val z = if (true) {
val xx: Int
exec {
<!CAPTURED_VAL_INITIALIZATION!>xx<!> = 24
}
42
}
else 0
@@ -0,0 +1,26 @@
package
public val z: kotlin.Int
public fun bar(): kotlin.Unit
public fun bar2(): kotlin.Unit
public fun exec(/*0*/ f: () -> kotlin.Unit): kotlin.Unit
public fun foo(): kotlin.Unit
public fun ignoreIt(/*0*/ f: () -> kotlin.Unit): kotlin.Unit
public final class My {
public constructor My()
public constructor My(/*0*/ cond: kotlin.Boolean)
public final val cond: kotlin.Boolean
public final val y: kotlin.Int
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
}
public final class Your {
public constructor Your()
public final val y: kotlin.Int
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
}
@@ -2889,9 +2889,15 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("initializationInLambda.kt")
public void testInitializationInLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.kt");
@TestMetadata("initInLambda.kt")
public void testInitInLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/initInLambda.kt");
doTest(fileName);
}
@TestMetadata("initializationInLocalFun.kt")
public void testInitializationInLocalFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLocalFun.kt");
doTest(fileName);
}
@@ -93,16 +93,21 @@ object J2KPostProcessingRegistrar {
}
registerDiagnosticBasedProcessingFactory(
Errors.VAL_REASSIGNMENT,
fun (element: KtSimpleNameExpression, diagnostic: Diagnostic): (() -> Unit)? {
val property = element.mainReference.resolve() as? KtProperty ?: return null
return {
if (!property.isVar) {
property.valOrVarKeyword.replace(KtPsiFactory(element.project).createVarKeyword())
}
Errors.VAL_REASSIGNMENT, Errors.CAPTURED_VAL_INITIALIZATION
) {
element: KtSimpleNameExpression, diagnostic: Diagnostic ->
val property = element.mainReference.resolve() as? KtProperty
if (property == null) {
null
}
else {
{
if (!property.isVar) {
property.valOrVarKeyword.replace(KtPsiFactory(element.project).createVarKeyword())
}
}
)
}
}
}
private inline fun <reified TElement : KtElement, TIntention: SelfTargetingRangeIntention<TElement>> registerIntentionBasedProcessing(
@@ -132,20 +137,20 @@ object J2KPostProcessingRegistrar {
}
private inline fun <reified TElement : KtElement> registerDiagnosticBasedProcessing(
diagnosticFactory: DiagnosticFactory<*>,
vararg diagnosticFactory: DiagnosticFactory<*>,
crossinline fix: (TElement, Diagnostic) -> Unit
) {
registerDiagnosticBasedProcessingFactory(diagnosticFactory) { element: TElement, diagnostic: Diagnostic -> { fix(element, diagnostic) } }
registerDiagnosticBasedProcessingFactory(*diagnosticFactory) { element: TElement, diagnostic: Diagnostic -> { fix(element, diagnostic) } }
}
private inline fun <reified TElement : KtElement> registerDiagnosticBasedProcessingFactory(
diagnosticFactory: DiagnosticFactory<*>,
vararg diagnosticFactory: DiagnosticFactory<*>,
crossinline fixFactory: (TElement, Diagnostic) -> (() -> Unit)?
) {
_processings.add(object : J2kPostProcessing {
override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? {
if (!TElement::class.java.isInstance(element)) return null
val diagnostic = diagnostics.forElement(element).firstOrNull { it.factory == diagnosticFactory } ?: return null
val diagnostic = diagnostics.forElement(element).firstOrNull { it.factory in diagnosticFactory } ?: return null
return fixFactory(element as TElement, diagnostic)
}
})
@@ -20,7 +20,9 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -51,14 +53,18 @@ class ChangeVariableMutabilityFix(element: KtValVarKeywordOwner, private val mak
}
}
val VAL_REASSIGNMENT_FACTORY: KotlinSingleIntentionActionFactory = object: KotlinSingleIntentionActionFactory() {
class ReassignmentActionFactory(val factory: DiagnosticFactory1<*, DeclarationDescriptor>) : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val propertyDescriptor = Errors.VAL_REASSIGNMENT.cast(diagnostic).a
val propertyDescriptor = factory.cast(diagnostic).a
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor) as? KtValVarKeywordOwner ?: return null
return ChangeVariableMutabilityFix(declaration, true)
}
}
val VAL_REASSIGNMENT_FACTORY = ReassignmentActionFactory(Errors.VAL_REASSIGNMENT)
val CAPTURED_VAL_INITIALIZATION_FACTORY = ReassignmentActionFactory(Errors.CAPTURED_VAL_INITIALIZATION)
val VAR_OVERRIDDEN_BY_VAL_FACTORY: KotlinSingleIntentionActionFactory = object: KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val element = diagnostic.psiElement
@@ -166,6 +166,7 @@ class QuickFixRegistrar : QuickFixContributor {
VAL_WITH_SETTER.registerFactory(ChangeVariableMutabilityFix.VAL_WITH_SETTER_FACTORY)
VAL_REASSIGNMENT.registerFactory(ChangeVariableMutabilityFix.VAL_REASSIGNMENT_FACTORY)
CAPTURED_VAL_INITIALIZATION.registerFactory(ChangeVariableMutabilityFix.CAPTURED_VAL_INITIALIZATION_FACTORY)
VAR_OVERRIDDEN_BY_VAL.registerFactory(ChangeVariableMutabilityFix.VAR_OVERRIDDEN_BY_VAL_FACTORY)
VAL_OR_VAR_ON_FUN_PARAMETER.registerFactory(RemoveValVarFromParameterFix)
@@ -0,0 +1,9 @@
// "Make variable mutable" "true"
fun exec(f: () -> Unit) = f()
fun foo() {
val x: Int
exec {
<caret>x = 42
}
}
@@ -0,0 +1,9 @@
// "Make variable mutable" "true"
fun exec(f: () -> Unit) = f()
fun foo() {
var x: Int
exec {
x = 42
}
}
@@ -7728,6 +7728,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/variables/changeMutability"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("capturedValInitialization.kt")
public void testCapturedValInitialization() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/variables/changeMutability/capturedValInitialization.kt");
doTest(fileName);
}
@TestMetadata("funParameter.kt")
public void testFunParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/variables/changeMutability/funParameter.kt");