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;
|
package org.jetbrains.kotlin.resolve.calls.smartcasts;
|
||||||
|
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
|
import com.intellij.psi.tree.IElementType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.JetNodeTypes;
|
import org.jetbrains.kotlin.JetNodeTypes;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
||||||
|
import org.jetbrains.kotlin.lexer.JetTokens;
|
||||||
import org.jetbrains.kotlin.psi.*;
|
import org.jetbrains.kotlin.psi.*;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
||||||
@@ -190,6 +192,14 @@ public class DataFlowValueFactory {
|
|||||||
false);
|
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
|
@NotNull
|
||||||
private static IdentifierInfo getIdForStableIdentifier(
|
private static IdentifierInfo getIdForStableIdentifier(
|
||||||
@Nullable JetExpression expression,
|
@Nullable JetExpression expression,
|
||||||
@@ -220,6 +230,14 @@ public class DataFlowValueFactory {
|
|||||||
|
|
||||||
return getIdForThisReceiver(declarationDescriptor);
|
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) {
|
else if (expression instanceof JetRootPackageExpression) {
|
||||||
//todo return createPackageInfo());
|
//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.results.OverloadResolutionResultsUtil;
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
|
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.smartcasts.Nullability;
|
||||||
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind;
|
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind;
|
||||||
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate;
|
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);
|
JetExpression stubExpression = ExpressionTypingUtils.createFakeExpressionOfType(baseExpression.getProject(), context.trace, "$e", type);
|
||||||
checkLValue(context.trace, context, baseExpression, stubExpression);
|
checkLValue(context.trace, context, baseExpression, stubExpression);
|
||||||
}
|
}
|
||||||
// TODO : Maybe returnType?
|
// x++ type is x type, but ++x type is x.inc() type
|
||||||
result = receiverType;
|
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 {
|
else {
|
||||||
@@ -928,7 +938,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
return createCompileTimeConstantTypeInfo(value, expression, contextWithExpectedType, components.builtIns);
|
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) {
|
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);
|
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")
|
@TestMetadata("noErrorCheckForPackageLevelVal.kt")
|
||||||
public void testNoErrorCheckForPackageLevelVal() throws Exception {
|
public void testNoErrorCheckForPackageLevelVal() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt");
|
||||||
@@ -12677,6 +12683,42 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("unnecessary.kt")
|
||||||
public void testUnnecessary() throws Exception {
|
public void testUnnecessary() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/unnecessary.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull/unnecessary.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user