diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt index d24a321baaa..cab325c18df 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt @@ -55,6 +55,7 @@ import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils.noExpectedType import org.jetbrains.kotlin.types.checker.JetTypeChecker +import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils import java.util.ArrayList @@ -452,8 +453,13 @@ public class CandidateResolver( val receiverArgumentType = receiverArgument.getType() val bindingContext = trace.getBindingContext() - if (!safeAccess && !receiverParameter.getType().isMarkedNullable() && receiverArgumentType.isMarkedNullable()) { - if (!smartCastManager.canBeSmartCast(receiverParameter, receiverArgument, this)) { + // We just checked isSubtypeIgnoringNullability(receiverType, parameter) + // Here we do almost the same thing as isSubtypeOf does (but pay attention only to nullability): + // - find corresponding supertype, than check it's nullability is not weaker than parameter's one + // - if latter failed, check whether value can be smart cast + val commonReceiverType = TypeCheckingProcedure.findCorrespondingSupertype(receiverArgumentType, receiverParameter.type) + if (!safeAccess && !receiverParameter.type.isMarkedNullable && (commonReceiverType?.isMarkedNullable ?: false)) { + if (!smartCastManager.recordSmartCastToNotNullIfPossible(receiverArgument, this)) { tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck) return UNSAFE_CALL_ERROR } 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 6e26160ae09..eabb3ca4c5d 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 @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.scopes.receivers.*; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.TypeUtils; +import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isNullableNothing; import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET; @@ -71,6 +72,20 @@ public class DataFlowValueFactory { if (isNullableNothing(type)) { return DataFlowValue.NULL; // 'null' is the only inhabitant of 'Nothing?' } + + if (ExpressionTypingUtils.isExclExclExpression(JetPsiUtil.deparenthesize(expression))) { + // In most cases type of `E!!`-expression is strictly not nullable and we could get proper Nullability + // by calling `getImmanentNullability` (as it happens below). + // + // But there are some problem with types built on type parameters, e.g. + // fun foo(x: T) = x!!.hashCode() // there no way in type system to denote that `x!!` is not nullable + return new DataFlowValue(expression, + type, + /* stableIdentifier = */false, + /* uncapturedLocalVariable = */false, + Nullability.NOT_NULL); + } + IdentifierInfo result = getIdForStableIdentifier(expression, bindingContext, containingDeclarationOrModule); return new DataFlowValue(result == NO_IDENTIFIER_INFO ? expression : result.id, type, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java index ffce32c10e7..ec917e6778c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java @@ -220,16 +220,44 @@ public class SmartCastManager { return canBeCast; } - public boolean canBeSmartCast( - @NotNull ReceiverParameterDescriptor receiverParameter, + public boolean recordSmartCastToNotNullIfPossible( @NotNull ReceiverValue receiver, - @NotNull ResolutionContext context) { - if (!receiver.getType().isMarkedNullable()) return true; + @NotNull ResolutionContext context + ) { + if (!TypeUtils.isNullableType(receiver.getType())) return true; - List smartCastVariants = getSmartCastVariants(receiver, context); - for (JetType smartCastVariant : smartCastVariants) { - if (JetTypeChecker.DEFAULT.isSubtypeOf(smartCastVariant, receiverParameter.getType())) return true; + DataFlowValue dataFlowValue = getDataFlowValueExcludingReceiver( + context.trace.getBindingContext(), + context.scope.getContainingDeclaration(), + receiver + ); + + if (dataFlowValue == null) return false; + + if (!context.dataFlowInfo.getNullability(dataFlowValue).canBeNull()) { + JetExpression receiverExpression = getReceiverExpression(receiver); + + // report smart cast only on predictable expressions that were not reported before + if (receiverExpression != null + && dataFlowValue.isPredictable() + && context.trace.getBindingContext().get(SMARTCAST, receiverExpression) == null) { + recordCastOrError( + receiverExpression, receiver.getType(), context.trace, + /* canBeCast = */ true, /* recordExpressionType = */ false + ); + } + + return true; } - return false; + + return !context.dataFlowInfo.getNullability(dataFlowValue).canBeNull(); + } + + @Nullable + private static JetExpression getReceiverExpression(@NotNull ReceiverValue value) { + if (value instanceof ExpressionReceiver) { + return ((ExpressionReceiver) value).getExpression(); + } + return null; } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingUtils.java index 97c9b6e9082..49d35fdbc33 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingUtils.java @@ -183,6 +183,11 @@ public class ExpressionTypingUtils { return expression.getOperationReference().getReferencedNameElementType() == JetTokens.EXCLEXCL; } + public static boolean isExclExclExpression(@Nullable JetExpression expression) { + return expression instanceof JetUnaryExpression + && ((JetUnaryExpression) expression).getOperationReference().getReferencedNameElementType() == JetTokens.EXCLEXCL; + } + @NotNull public static List getValueParametersTypes(@NotNull List valueParameters) { List parameterTypes = new ArrayList(valueParameters.size()); diff --git a/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.kt b/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.kt new file mode 100644 index 00000000000..af08791e7f9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.kt @@ -0,0 +1,45 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION,-UNUSED_VARIABLE + +fun T.bar1() {} +fun CharSequence?.bar2() {} + +fun T.bar3() {} +fun CharSequence.bar4() {} + +fun foo(x: T) { + + if (x != null) { + if (x != null) {} + + x.length() + x?.length() + + x.bar1() + x.bar2() + x.bar3() + x.bar4() + + + x?.bar1() + } + + x.length() + + if (x is String) { + x.length() + x?.length() + + x.bar1() + x.bar2() + x.bar3() + } + + if (x is CharSequence) { + x.length() + x?.length() + + x.bar1() + x.bar2() + x.bar3() + } +} diff --git a/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.txt b/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.txt new file mode 100644 index 00000000000..93d7b7a54ef --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/smartCasts.txt @@ -0,0 +1,7 @@ +package + +internal fun foo(/*0*/ x: T): kotlin.Unit +internal fun T.bar1(): kotlin.Unit +internal fun kotlin.CharSequence?.bar2(): kotlin.Unit +internal fun T.bar3(): kotlin.Unit +internal fun kotlin.CharSequence.bar4(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt new file mode 100644 index 00000000000..01a2fbdcc31 --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt @@ -0,0 +1,35 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION,-UNUSED_VARIABLE + +fun T.bar1() {} +fun CharSequence?.bar2() {} + +fun T.bar3() {} +fun CharSequence.bar4() {} + +fun T.foo() { + if (this != null) { + if (this != null) {} + + length() + this?.length() + + bar1() + bar2() + bar3() + bar4() + + + this?.bar1() + } + + length() + + if (this is String) { + length() + this?.length() + + bar1() + bar2() + bar3() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.txt b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.txt new file mode 100644 index 00000000000..6365b2fc85d --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.txt @@ -0,0 +1,7 @@ +package + +internal fun T.bar1(): kotlin.Unit +internal fun kotlin.CharSequence?.bar2(): kotlin.Unit +internal fun T.bar3(): kotlin.Unit +internal fun kotlin.CharSequence.bar4(): kotlin.Unit +internal fun T.foo(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.kt b/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.kt new file mode 100644 index 00000000000..7b56c9c09fd --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.kt @@ -0,0 +1,40 @@ +// !DIAGNOSTICS: -UNUSED_VALUE,-UNUSED_VARIABLE,-ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE,-BASE_WITH_NULLABLE_UPPER_BOUND,-VARIABLE_WITH_REDUNDANT_INITIALIZER + +class A { + fun T.bar() {} + + fun foo(x: E1, y: E2) { + x.bar() + + if (1 == 1) { + y.bar() + } + + x?.bar() + y?.bar() + + + var t: T = x + var tN: T? = y + + // condition needed to make smart cast on tN impossible + if (1 == 1) { + tN = x + } + + if (1 == 1) { + t = tN + } + + t = y + + // Could be smart-cast + if (y != null) { + t = y + } + + if (tN != null) { + t = tN + } + } +} diff --git a/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.txt b/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.txt new file mode 100644 index 00000000000..5f429c88e7c --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.txt @@ -0,0 +1,10 @@ +package + +internal final class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + internal final fun foo(/*0*/ x: E1, /*1*/ y: E2): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + internal final fun T.bar(): kotlin.Unit +} diff --git a/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.kt b/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.kt new file mode 100644 index 00000000000..73f2cfe025d --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.kt @@ -0,0 +1,28 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION,-UNUSED_VARIABLE + +fun T.bar1() {} +fun CharSequence?.bar2() {} + +fun T.bar3() {} + +fun T.let(f: (T) -> R): R = f(this) + +fun foo(x: T) { + x.length() + x?.length() + + if (1 == 1) { + x!!.length() + } + + + x.bar1() + x.bar2() + + x?.bar1() + x?.bar2() + + x.bar3() + + x?.let { it.length() } +} diff --git a/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.txt b/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.txt new file mode 100644 index 00000000000..31cdca4837e --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.txt @@ -0,0 +1,7 @@ +package + +internal fun foo(/*0*/ x: T): kotlin.Unit +internal fun T.bar1(): kotlin.Unit +internal fun kotlin.CharSequence?.bar2(): kotlin.Unit +internal fun T.bar3(): kotlin.Unit +internal fun T.let(/*0*/ f: (T) -> R): R diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 22af0ec8dc4..dec043093b3 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -6155,12 +6155,36 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("smartCasts.kt") + public void testSmartCasts() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/smartCasts.kt"); + doTest(fileName); + } + + @TestMetadata("smartCastsOnThis.kt") + public void testSmartCastsOnThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt"); + doTest(fileName); + } + @TestMetadata("tpBoundsViolation.kt") public void testTpBoundsViolation() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/tpBoundsViolation.kt"); doTest(fileName); } + @TestMetadata("tpInBounds.kt") + public void testTpInBounds() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/tpInBounds.kt"); + doTest(fileName); + } + + @TestMetadata("useAsReceiver.kt") + public void testUseAsReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/useAsReceiver.kt"); + doTest(fileName); + } + @TestMetadata("useAsValueArgument.kt") public void testUseAsValueArgument() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/generics/nullability/useAsValueArgument.kt");