From 5723c2a07758a8c2c5d681208f1cb44ec9201a3c Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 30 Apr 2015 15:08:15 +0300 Subject: [PATCH] Front-end fix: result type corrected for increment / decrement expressions. Smart cast allowed on a postfix increment result. See KT-7561. Now result type is receiver type for postfix increment, or increment result type for prefix increment. A set of relevant tests. --- .../smartcasts/DataFlowValueFactory.java | 18 ++++++++ .../BasicExpressionTypingVisitor.java | 18 ++++++-- .../diagnostics/tests/smartCasts/kt7561.kt | 15 +++++++ .../diagnostics/tests/smartCasts/kt7561.txt | 3 ++ .../postfixNotnullClassIncrement.kt | 13 ++++++ .../postfixNotnullClassIncrement.txt | 11 +++++ .../postfixNullableClassIncrement.kt | 12 ++++++ .../postfixNullableClassIncrement.txt | 11 +++++ .../varnotnull/postfixNullableIncrement.kt | 8 ++++ .../varnotnull/postfixNullableIncrement.txt | 4 ++ .../varnotnull/prefixNotnullClassIncrement.kt | 13 ++++++ .../prefixNotnullClassIncrement.txt | 11 +++++ .../prefixNullableClassIncrement.kt | 13 ++++++ .../prefixNullableClassIncrement.txt | 11 +++++ .../varnotnull/prefixNullableIncrement.kt | 8 ++++ .../varnotnull/prefixNullableIncrement.txt | 4 ++ .../checkers/JetDiagnosticsTestGenerated.java | 42 +++++++++++++++++++ 17 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/kt7561.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/kt7561.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java index cb66d462583..6e26160ae09 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactory.java @@ -17,11 +17,13 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts; import com.intellij.openapi.util.Pair; +import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.JetNodeTypes; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; +import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContextUtils; @@ -190,6 +192,14 @@ public class DataFlowValueFactory { false); } + @NotNull + private static IdentifierInfo createPostfixInfo(@NotNull JetPostfixExpression expression, @NotNull IdentifierInfo argumentInfo) { + if (argumentInfo == NO_IDENTIFIER_INFO) { + return NO_IDENTIFIER_INFO; + } + return createInfo(Pair.create(expression, argumentInfo.id), argumentInfo.isStable, argumentInfo.isLocal); + } + @NotNull private static IdentifierInfo getIdForStableIdentifier( @Nullable JetExpression expression, @@ -220,6 +230,14 @@ public class DataFlowValueFactory { return getIdForThisReceiver(declarationDescriptor); } + else if (expression instanceof JetPostfixExpression) { + JetPostfixExpression postfixExpression = (JetPostfixExpression) expression; + IElementType operationType = postfixExpression.getOperationReference().getReferencedNameElementType(); + if (operationType == JetTokens.PLUSPLUS || operationType == JetTokens.MINUSMINUS) { + return createPostfixInfo(postfixExpression, + getIdForStableIdentifier(postfixExpression.getBaseExpression(), bindingContext, containingDeclarationOrModule)); + } + } else if (expression instanceof JetRootPackageExpression) { //todo return createPackageInfo()); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 63d39612f06..822a62e530e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -51,6 +51,7 @@ import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory; import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability; import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind; import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate; @@ -914,8 +915,17 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { JetExpression stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(baseExpression.getProject(), context.trace, "$e", type); checkLValue(context.trace, context, baseExpression, stubExpression); } - // TODO : Maybe returnType? - result = receiverType; + // x++ type is x type, but ++x type is x.inc() type + DataFlowValue receiverValue = DataFlowValueFactory.createDataFlowValue(call.getExplicitReceiver(), contextWithExpectedType); + if (expression instanceof JetPrefixExpression) { + result = returnType; + } + else { + result = receiverType; + // Also record data flow information for x++ value (= x) + DataFlowValue returnValue = DataFlowValueFactory.createDataFlowValue(expression, receiverType, contextWithExpectedType); + typeInfo = typeInfo.replaceDataFlowInfo(typeInfo.getDataFlowInfo().assign(returnValue, receiverValue)); + } } } else { @@ -928,7 +938,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { return createCompileTimeConstantTypeInfo(value, expression, contextWithExpectedType, components.builtIns); } - return DataFlowUtils.checkType(typeInfo.replaceType(result), expression, contextWithExpectedType); + return DataFlowUtils.checkType(typeInfo.replaceType(result), + expression, + contextWithExpectedType.replaceDataFlowInfo(typeInfo.getDataFlowInfo())); } private JetTypeInfo visitExclExclExpression(@NotNull JetUnaryExpression expression, @NotNull ExpressionTypingContext context) { diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt7561.kt b/compiler/testData/diagnostics/tests/smartCasts/kt7561.kt new file mode 100644 index 00000000000..b27f051059e --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/kt7561.kt @@ -0,0 +1,15 @@ +// Test for a potential byte code mistake for a postfix operation on a smart casted variable +public fun box() : Int { + var i : Int? + i = 10 + val ii: Int = i + // k also should be Int + val k : Int = i++ + // KT-7561: both i and i++ should be Int, otherwise VerifyError can arise here + // VerifyError reason: byte code tries to store (i++) result which is Int (smart cast) + // into a j which is Int? + val j = i++ + // and m also + val m = ++i + return j + k + m + i + ii +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt7561.txt b/compiler/testData/diagnostics/tests/smartCasts/kt7561.txt new file mode 100644 index 00000000000..a18aad882c7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/kt7561.txt @@ -0,0 +1,3 @@ +package + +public fun box(): kotlin.Int diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.kt new file mode 100644 index 00000000000..375c9579e7c --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.kt @@ -0,0 +1,13 @@ +class MyClass + +// In principle it is not correct, MyClass? is not a subtype of MyClass +fun MyClass.inc(): MyClass? { return null } + +public fun box() : MyClass? { + var i : MyClass? + i = MyClass() + // type of j can be inferred as MyClass() + var j = i++ + j.hashCode() + return i +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.txt new file mode 100644 index 00000000000..2d8b3e9ced5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.txt @@ -0,0 +1,11 @@ +package + +public fun box(): MyClass? +internal fun MyClass.inc(): MyClass? + +internal final class MyClass { + public constructor MyClass() + 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.kt new file mode 100644 index 00000000000..d1df0830ba0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.kt @@ -0,0 +1,12 @@ +class MyClass + +// Correct at compile time but wrong at run-time +fun MyClass?.inc(): MyClass? { return null } + +public fun box() : MyClass? { + var i : MyClass? + i = MyClass() + var j = i++ + j.hashCode() + return i +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.txt new file mode 100644 index 00000000000..b96900f11e8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.txt @@ -0,0 +1,11 @@ +package + +public fun box(): MyClass? +internal fun MyClass?.inc(): MyClass? + +internal final class MyClass { + public constructor MyClass() + 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.kt new file mode 100644 index 00000000000..fa1b5d96fbc --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.kt @@ -0,0 +1,8 @@ +fun Int?.inc(): Int? { return this } + +public fun box(arg: Int?) : Int? { + var i : Int? = arg + var j = i++ + j.toInt() + return i +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.txt new file mode 100644 index 00000000000..af52f58195a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.txt @@ -0,0 +1,4 @@ +package + +public fun box(/*0*/ arg: kotlin.Int?): kotlin.Int? +internal fun kotlin.Int?.inc(): kotlin.Int? diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.kt new file mode 100644 index 00000000000..88355dc61f1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.kt @@ -0,0 +1,13 @@ +class MyClass + +// In principle it is not correct, MyClass? is not a subtype of MyClass +fun MyClass.inc(): MyClass? { return null } + +public fun box() { + var i : MyClass? + i = MyClass() + // Type of j should be inferred as MyClass? + var j = ++i + // j is null so call is unsafe + j.hashCode() +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.txt new file mode 100644 index 00000000000..7910d2a4cc7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.txt @@ -0,0 +1,11 @@ +package + +public fun box(): kotlin.Unit +internal fun MyClass.inc(): MyClass? + +internal final class MyClass { + public constructor MyClass() + 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.kt new file mode 100644 index 00000000000..d1dab95d697 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.kt @@ -0,0 +1,13 @@ +class MyClass + +// Correct at compile time but wrong at run-time +fun MyClass?.inc(): MyClass? { return null } + +public fun box() { + var i : MyClass? + i = MyClass() + // type of j should be MyClass? + var j = ++i + // j is null so call should be unsafe + j.hashCode() +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.txt new file mode 100644 index 00000000000..e337575b2a5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.txt @@ -0,0 +1,11 @@ +package + +public fun box(): kotlin.Unit +internal fun MyClass?.inc(): MyClass? + +internal final class MyClass { + public constructor MyClass() + 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.kt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.kt new file mode 100644 index 00000000000..a5781bcefc0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.kt @@ -0,0 +1,8 @@ +fun Int?.inc(): Int? { return this } + +public fun box(arg: Int?) : Int? { + var i = arg + var j = ++i + j.toInt() + return ++j +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.txt b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.txt new file mode 100644 index 00000000000..af52f58195a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.txt @@ -0,0 +1,4 @@ +package + +public fun box(/*0*/ arg: kotlin.Int?): kotlin.Int? +internal fun kotlin.Int?.inc(): kotlin.Int? diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 6f010c292d8..df909e446e0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -11763,6 +11763,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("kt7561.kt") + public void testKt7561() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/kt7561.kt"); + doTest(fileName); + } + @TestMetadata("noErrorCheckForPackageLevelVal.kt") public void testNoErrorCheckForPackageLevelVal() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt"); @@ -12677,6 +12683,42 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("postfixNotnullClassIncrement.kt") + public void testPostfixNotnullClassIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNotnullClassIncrement.kt"); + doTest(fileName); + } + + @TestMetadata("postfixNullableClassIncrement.kt") + public void testPostfixNullableClassIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableClassIncrement.kt"); + doTest(fileName); + } + + @TestMetadata("postfixNullableIncrement.kt") + public void testPostfixNullableIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/postfixNullableIncrement.kt"); + doTest(fileName); + } + + @TestMetadata("prefixNotnullClassIncrement.kt") + public void testPrefixNotnullClassIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNotnullClassIncrement.kt"); + doTest(fileName); + } + + @TestMetadata("prefixNullableClassIncrement.kt") + public void testPrefixNullableClassIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableClassIncrement.kt"); + doTest(fileName); + } + + @TestMetadata("prefixNullableIncrement.kt") + public void testPrefixNullableIncrement() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/prefixNullableIncrement.kt"); + doTest(fileName); + } + @TestMetadata("unnecessary.kt") public void testUnnecessary() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/unnecessary.kt");