diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/autocasts/DataFlowValueFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/autocasts/DataFlowValueFactory.java index b1edec00e93..d8793b33b33 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/autocasts/DataFlowValueFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/autocasts/DataFlowValueFactory.java @@ -18,17 +18,20 @@ package org.jetbrains.jet.lang.resolve.calls.autocasts; import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.JetModuleUtil; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.scopes.receivers.*; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeUtils; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET; +import static org.jetbrains.jet.lang.resolve.BindingContext.RESOLVED_CALL; public class DataFlowValueFactory { public static final DataFlowValueFactory INSTANCE = new DataFlowValueFactory(); @@ -42,8 +45,8 @@ public class DataFlowValueFactory { if (constantExpression.getNode().getElementType() == JetNodeTypes.NULL) return DataFlowValue.NULL; } if (TypeUtils.equalTypes(type, KotlinBuiltIns.getInstance().getNullableNothingType())) return DataFlowValue.NULL; // 'null' is the only inhabitant of 'Nothing?' - Pair result = getIdForStableIdentifier(expression, bindingContext, false); - return new DataFlowValue(result.first == null ? expression : result.first, type, result.second, getImmanentNullability(type)); + IdentifierInfo result = getIdForStableIdentifier(expression, bindingContext, false); + return new DataFlowValue(result.id == null ? expression : result.id, type, result.isStable, getImmanentNullability(type)); } @NotNull @@ -104,55 +107,156 @@ public class DataFlowValueFactory { return type.isNullable() || TypeUtils.hasNullableSuperType(type) ? Nullability.UNKNOWN : Nullability.NOT_NULL; } + private static class IdentifierInfo { + public final Object id; + public final boolean isStable; + public final boolean isNamespace; + + private IdentifierInfo(Object id, boolean isStable, boolean isNamespace) { + this.id = id; + this.isStable = isStable; + this.isNamespace = isNamespace; + } + } + + private static final IdentifierInfo ERROR_IDENTIFIER_INFO = new IdentifierInfo(null, false, false); + @NotNull - private static Pair getIdForStableIdentifier(@NotNull JetExpression expression, @NotNull BindingContext bindingContext, boolean allowNamespaces) { + private static IdentifierInfo createInfo(Object id, boolean isStable) { + return new IdentifierInfo(id, isStable, false); + } + + @NotNull + private static IdentifierInfo createNamespaceInfo(Object id, boolean isStable) { + return new IdentifierInfo(id, isStable, true); + } + + @NotNull + private static IdentifierInfo combineInfo(@Nullable IdentifierInfo receiverInfo, @NotNull IdentifierInfo selectorInfo) { + if (receiverInfo == null || receiverInfo == ERROR_IDENTIFIER_INFO || receiverInfo.isNamespace) { + return selectorInfo; + } + return createInfo(Pair.create(receiverInfo.id, selectorInfo.id), receiverInfo.isStable && selectorInfo.isStable); + } + + @NotNull + private static IdentifierInfo getIdForStableIdentifier( + @Nullable JetExpression expression, + @NotNull BindingContext bindingContext, + boolean allowNamespaces + ) { if (expression instanceof JetParenthesizedExpression) { JetParenthesizedExpression parenthesizedExpression = (JetParenthesizedExpression) expression; JetExpression innerExpression = parenthesizedExpression.getExpression(); - if (innerExpression == null) { - return Pair.create(null, false); - } + return getIdForStableIdentifier(innerExpression, bindingContext, allowNamespaces); } else if (expression instanceof JetQualifiedExpression) { JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) expression; + JetExpression receiverExpression = qualifiedExpression.getReceiverExpression(); JetExpression selectorExpression = qualifiedExpression.getSelectorExpression(); - if (selectorExpression == null) { - return Pair.create(null, false); - } - Pair receiverId = getIdForStableIdentifier(qualifiedExpression.getReceiverExpression(), bindingContext, true); - Pair selectorId = getIdForStableIdentifier(selectorExpression, bindingContext, allowNamespaces); - return receiverId.second ? selectorId : Pair.create(receiverId.first, false); + IdentifierInfo receiverId = getIdForStableIdentifier(receiverExpression, bindingContext, true); + IdentifierInfo selectorId = getIdForStableIdentifier(selectorExpression, bindingContext, allowNamespaces); + + return combineInfo(receiverId, selectorId); } if (expression instanceof JetSimpleNameExpression) { - JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression; - DeclarationDescriptor declarationDescriptor = bindingContext.get(REFERENCE_TARGET, simpleNameExpression); - if (declarationDescriptor instanceof VariableDescriptor) { - return Pair.create((Object) declarationDescriptor, isStableVariable((VariableDescriptor) declarationDescriptor)); - } - if (declarationDescriptor instanceof NamespaceDescriptor) { - return Pair.create((Object) declarationDescriptor, allowNamespaces); - } - if (declarationDescriptor instanceof ClassDescriptor) { - ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; - return Pair.create((Object) classDescriptor, classDescriptor.isClassObjectAValue()); - } + return getIdForSimpleNameExpression((JetSimpleNameExpression) expression, bindingContext, allowNamespaces); } else if (expression instanceof JetThisExpression) { JetThisExpression thisExpression = (JetThisExpression) expression; DeclarationDescriptor declarationDescriptor = bindingContext.get(REFERENCE_TARGET, thisExpression.getInstanceReference()); - if (declarationDescriptor instanceof CallableDescriptor) { - return Pair.create((Object) ((CallableDescriptor) declarationDescriptor).getReceiverParameter().getValue(), true); - } - if (declarationDescriptor instanceof ClassDescriptor) { - return Pair.create((Object) ((ClassDescriptor) declarationDescriptor).getThisAsReceiverParameter().getValue(), true); - } - return Pair.create(null, true); + + return getIdForThisReceiver(declarationDescriptor); } else if (expression instanceof JetRootNamespaceExpression) { - return Pair.create((Object) JetModuleUtil.getRootNamespaceType(expression), allowNamespaces); + return createNamespaceInfo(JetModuleUtil.getRootNamespaceType(expression), allowNamespaces); } - return Pair.create(null, false); + return ERROR_IDENTIFIER_INFO; + } + + @NotNull + private static IdentifierInfo getIdForSimpleNameExpression( + @NotNull JetSimpleNameExpression simpleNameExpression, + @NotNull BindingContext bindingContext, + boolean allowNamespaces + ) { + DeclarationDescriptor declarationDescriptor = bindingContext.get(REFERENCE_TARGET, simpleNameExpression); + if (declarationDescriptor instanceof VariableDescriptor) { + ResolvedCall resolvedCall = bindingContext.get(RESOLVED_CALL, simpleNameExpression); + // todo return assert + // for now it fails for resolving 'invoke' convention, return it after 'invoke' algorithm changes + // assert resolvedCall != null : "Cannot create right identifier info if the resolved call is not known yet for " + declarationDescriptor; + + IdentifierInfo receiverInfo = resolvedCall != null ? getIdForImplicitReceiver(resolvedCall.getThisObject(), simpleNameExpression) : null; + + VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor; + return combineInfo(receiverInfo, createInfo(variableDescriptor, isStableVariable(variableDescriptor))); + } + if (declarationDescriptor instanceof NamespaceDescriptor) { + return createNamespaceInfo(declarationDescriptor, allowNamespaces); + } + if (declarationDescriptor instanceof ClassDescriptor) { + ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; + return createInfo(classDescriptor, classDescriptor.isClassObjectAValue()); + } + return ERROR_IDENTIFIER_INFO; + } + + @Nullable + private static IdentifierInfo getIdForImplicitReceiver(@NotNull ReceiverValue receiverValue, @Nullable final JetExpression expression) { + return receiverValue.accept(new ReceiverValueVisitor() { + + @Override + public IdentifierInfo visitNoReceiver(ReceiverValue noReceiver, Void data) { + return null; + } + + @Override + public IdentifierInfo visitTransientReceiver(TransientReceiver receiver, Void data) { + assert false: "Transient receiver is implicit for an explicit expression: " + expression + ". Receiver: " + receiver; + return null; + } + + @Override + public IdentifierInfo visitExtensionReceiver(ExtensionReceiver receiver, Void data) { + return getIdForThisReceiver(receiver); + } + + @Override + public IdentifierInfo visitExpressionReceiver(ExpressionReceiver receiver, Void data) { + // there is an explicit "this" expression and it was analyzed earlier + return null; + } + + @Override + public IdentifierInfo visitClassReceiver(ClassReceiver receiver, Void data) { + return getIdForThisReceiver(receiver); + } + + @Override + public IdentifierInfo visitScriptReceiver(ScriptReceiver receiver, Void data) { + return getIdForThisReceiver(receiver); + } + }, null); + } + + @NotNull + private static IdentifierInfo getIdForThisReceiver(@NotNull ThisReceiver thisReceiver) { + return getIdForThisReceiver(thisReceiver.getDeclarationDescriptor()); + } + + @NotNull + private static IdentifierInfo getIdForThisReceiver(@Nullable DeclarationDescriptor descriptorOfThisReceiver) { + if (descriptorOfThisReceiver instanceof CallableDescriptor) { + ReceiverParameterDescriptor receiverParameter = ((CallableDescriptor) descriptorOfThisReceiver).getReceiverParameter(); + assert receiverParameter != null : "'This' refers to the callable member without a receiver parameter: " + descriptorOfThisReceiver; + return createInfo(receiverParameter.getValue(), true); + } + if (descriptorOfThisReceiver instanceof ClassDescriptor) { + return createInfo(((ClassDescriptor) descriptorOfThisReceiver).getThisAsReceiverParameter().getValue(), true); + } + return ERROR_IDENTIFIER_INFO; } public static boolean isStableVariable(@NotNull VariableDescriptor variableDescriptor) { diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt2422.kt b/compiler/testData/diagnostics/tests/smartCasts/kt2422.kt new file mode 100644 index 00000000000..ea3ac5e68fd --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/kt2422.kt @@ -0,0 +1,23 @@ +package bar + +class Test { + val foo: Int? = null + fun foo(o: Test) = foo == null && o.foo == null // ERROR warning: o.test == null is always true + + fun bar(a: Test, b: Test) { + if (a.foo != null) { + useInt(b.foo) + } + if (a.foo != null) { + useInt(foo) + } + if (this.foo != null) { + useInt(foo) + } + if (foo != null) { + useInt(this.foo) + } + } + + fun useInt(i: Int) = i +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt b/compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt new file mode 100644 index 00000000000..c993a144277 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt @@ -0,0 +1,25 @@ +//FILE: bar.kt +package bar + +val i: Int? = 2 + +//FILE: foo.kt +package foo + +val i: Int? = 1 + +class A(val i: Int?) { + fun testUseFromClass() { + if (foo.i != null) { + useInt(i) + } + } +} + +fun testUseFromOtherPackage() { + if (bar.i != null) { + useInt(i) + } +} + +fun useInt(i: Int) = i \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/thisWithLabel.kt b/compiler/testData/diagnostics/tests/smartCasts/thisWithLabel.kt new file mode 100644 index 00000000000..618f5b66a69 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/thisWithLabel.kt @@ -0,0 +1,36 @@ +package foo + +class A(val i: Int?) { + fun test1() { + if (this@A.i != null) { + useInt(this.i) + useInt(i) + } + } + + inner class B { + fun test2() { + if (i != null) { + useInt(this@A.i) + } + } + } +} + +fun A.foo() { + if (this@foo.i != null) { + useInt(this.i) + useInt(i) + } +} + +fun test3() { + useFunction { + if(i != null) { + useInt(this.i) + } + } +} + +fun useInt(i: Int) = i +fun useFunction(f: A.() -> Unit) = f \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/thisWithLabelAsReceiverPart.kt b/compiler/testData/diagnostics/tests/smartCasts/thisWithLabelAsReceiverPart.kt new file mode 100644 index 00000000000..bdbc0aa44f7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/thisWithLabelAsReceiverPart.kt @@ -0,0 +1,39 @@ +package foo + +class C(val i: Int?) {} + +class A(val c: C) { + fun test1() { + if (this@A.c.i != null) { + useInt(this.c.i) + useInt(c.i) + } + } + + inner class B { + fun test2() { + if (c.i != null) { + useInt(this@A.c.i) + } + } + } +} + +fun A.foo() { + if (this@foo.c.i != null) { + useInt(this.c.i) + useInt(c.i) + } +} + +fun test3() { + useFunction { + if(c.i != null) { + useInt(this.c.i) + } + } +} + +fun useInt(i: Int) = i +fun useFunction(f: A.() -> Unit) = f + diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index 115e37c96fa..3ccf049c0d8 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -4722,6 +4722,26 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/diagnostics/tests/smartCasts/kt1461.kt"); } + @TestMetadata("kt2422.kt") + public void testKt2422() throws Exception { + doTest("compiler/testData/diagnostics/tests/smartCasts/kt2422.kt"); + } + + @TestMetadata("noErrorCheckForPackageLevelVal.kt") + public void testNoErrorCheckForPackageLevelVal() throws Exception { + doTest("compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt"); + } + + @TestMetadata("thisWithLabel.kt") + public void testThisWithLabel() throws Exception { + doTest("compiler/testData/diagnostics/tests/smartCasts/thisWithLabel.kt"); + } + + @TestMetadata("thisWithLabelAsReceiverPart.kt") + public void testThisWithLabelAsReceiverPart() throws Exception { + doTest("compiler/testData/diagnostics/tests/smartCasts/thisWithLabelAsReceiverPart.kt"); + } + } @TestMetadata("compiler/testData/diagnostics/tests/substitutions")