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:
+18
@@ -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());
|
||||
}
|
||||
|
||||
+15
-3
@@ -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
|
||||
+13
@@ -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
|
||||
}
|
||||
+11
@@ -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
|
||||
}
|
||||
+12
@@ -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
|
||||
}
|
||||
+11
@@ -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?
|
||||
+13
@@ -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()
|
||||
}
|
||||
+11
@@ -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
|
||||
}
|
||||
+13
@@ -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()
|
||||
}
|
||||
+11
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user