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.
This commit is contained in:
Mikhail Glukhikh
2015-04-30 15:08:15 +03:00
parent ea6d0b464d
commit 5723c2a077
17 changed files with 212 additions and 3 deletions
@@ -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());
}
@@ -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) {
@@ -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 = <!DEBUG_INFO_SMARTCAST!>i<!>
// k also should be Int
val k : Int = <!DEBUG_INFO_SMARTCAST!><!DEBUG_INFO_SMARTCAST!>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 = <!DEBUG_INFO_SMARTCAST!>i<!>++
// and m also
val m = ++<!DEBUG_INFO_SMARTCAST!>i<!>
return <!DEBUG_INFO_SMARTCAST!>j<!> + k + m + <!DEBUG_INFO_SMARTCAST!>i<!> + ii
}
@@ -0,0 +1,3 @@
package
public fun box(): kotlin.Int
@@ -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 = <!DEBUG_INFO_SMARTCAST!>i<!>++
<!DEBUG_INFO_SMARTCAST!>j<!>.hashCode()
return i
}
@@ -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
}
@@ -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++
<!DEBUG_INFO_SMARTCAST!>j<!>.hashCode()
return i
}
@@ -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
}
@@ -0,0 +1,8 @@
fun Int?.inc(): Int? { return this }
public fun box(arg: Int?) : Int? {
var i : Int? = arg
var j = i++
j<!UNSAFE_CALL!>.<!>toInt()
return i
}
@@ -0,0 +1,4 @@
package
public fun box(/*0*/ arg: kotlin.Int?): kotlin.Int?
internal fun kotlin.Int?.inc(): kotlin.Int?
@@ -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 = ++<!DEBUG_INFO_SMARTCAST!>i<!>
// j is null so call is unsafe
j<!UNSAFE_CALL!>.<!>hashCode()
}
@@ -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
}
@@ -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<!UNSAFE_CALL!>.<!>hashCode()
}
@@ -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
}
@@ -0,0 +1,8 @@
fun Int?.inc(): Int? { return this }
public fun box(arg: Int?) : Int? {
var i = arg
var j = ++i
j<!UNSAFE_CALL!>.<!>toInt()
return ++j
}
@@ -0,0 +1,4 @@
package
public fun box(/*0*/ arg: kotlin.Int?): kotlin.Int?
internal fun kotlin.Int?.inc(): kotlin.Int?
@@ -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");