diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt index 2cad5293951..add512eb03e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable object CastDiagnosticsUtil { @@ -88,16 +89,28 @@ object CastDiagnosticsUtil { */ @JvmStatic fun isCastErased(supertype: KotlinType, subtype: KotlinType, typeChecker: KotlinTypeChecker): Boolean { + val isNonReifiedTypeParameter = TypeUtils.isNonReifiedTypeParameter(subtype) + val isUpcast = typeChecker.isSubtypeOf(supertype, subtype) + + // here we want to restrict cases such as `x is T` for x = T?, when T might have nullable upper bound + if (isNonReifiedTypeParameter && !isUpcast) { + // hack to save previous behavior in case when `x is T`, where T is not nullable, see IsErasedNullableTasT.kt + val nullableToDefinitelyNotNull = !TypeUtils.isNullableType(subtype) && supertype.makeNotNullable() == subtype + if (!nullableToDefinitelyNotNull) { + return true + } + } + // cast between T and T? is always OK if (supertype.isMarkedNullable || subtype.isMarkedNullable) { return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker) } // if it is a upcast, it's never erased - if (typeChecker.isSubtypeOf(supertype, subtype)) return false + if (isUpcast) return false // downcasting to a non-reified type parameter is always erased - if (TypeUtils.isNonReifiedTypeParameter(subtype)) return true + if (isNonReifiedTypeParameter) return true // Check that we are actually casting to a generic type // NOTE: this does not account for 'as Array>' diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 6e0e980dad7..febd8f381ce 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -26,6 +26,7 @@ import kotlin.TuplesKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.KtNodeTypes; +import org.jetbrains.kotlin.builtins.FunctionTypesKt; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.config.LanguageFeature; import org.jetbrains.kotlin.config.LanguageVersionSettings; diff --git a/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.kt b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.kt new file mode 100644 index 00000000000..9fae5c4ead9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.kt @@ -0,0 +1,27 @@ +fun test(x: T?, y: S, z: T) { + x is T + x is T? + + y is T + y is S + y is T? + y is S? + + z is T + z is T? + + null as T + null as T? + null as S +} + +inline fun test(x: T?) { + x is T + null as T + null as T? +} + +fun foo(x: List, y: List?) { + x is List + y is List +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.txt b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.txt new file mode 100644 index 00000000000..ea8ecb5ab9e --- /dev/null +++ b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.txt @@ -0,0 +1,5 @@ +package + +public fun foo(/*0*/ x: kotlin.collections.List, /*1*/ y: kotlin.collections.List?): kotlin.Unit +public inline fun test(/*0*/ x: T?): kotlin.Unit +public fun test(/*0*/ x: T?, /*1*/ y: S, /*2*/ z: T): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/delegatedProperty/inference/differentDelegatedExpressions.kt b/compiler/testData/diagnostics/tests/delegatedProperty/inference/differentDelegatedExpressions.kt index 2111c5589be..84a17d38cec 100644 --- a/compiler/testData/diagnostics/tests/delegatedProperty/inference/differentDelegatedExpressions.kt +++ b/compiler/testData/diagnostics/tests/delegatedProperty/inference/differentDelegatedExpressions.kt @@ -25,7 +25,7 @@ class MyProperty { operator fun getValue(thisRef: R, desc: KProperty<*>): T { println("get $thisRef ${desc.name}") - return null as T + return null as T } operator fun setValue(thisRef: R, desc: KProperty<*>, value: T) { diff --git a/compiler/testData/diagnostics/tests/inference/upperBounds/kt2856.kt b/compiler/testData/diagnostics/tests/inference/upperBounds/kt2856.kt index 041e9ae0a40..1ddb0d17fb9 100644 --- a/compiler/testData/diagnostics/tests/inference/upperBounds/kt2856.kt +++ b/compiler/testData/diagnostics/tests/inference/upperBounds/kt2856.kt @@ -5,7 +5,7 @@ import java.util.HashMap public inline fun Map.getOrElse1(key: K, defaultValue: ()-> V1) : V1 { if (this.containsKey(key)) { - return this.get(key) as V + return this.get(key) as V } else { return defaultValue() } diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inference/kt15516.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inference/kt15516.kt index 56a174d7985..0fc666fb951 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inference/kt15516.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inference/kt15516.kt @@ -1,6 +1,6 @@ // SKIP_TXT class StateMachine internal constructor() { - fun getInputStub(): Q = null as Q + fun getInputStub(): Q = null as Q } fun stateMachine(block: suspend StateMachine.() -> Unit): StateMachine { @@ -8,7 +8,7 @@ fun stateMachine(block: suspend StateMachine.() -> } class Problem(){ - fun getInputStub(): F = null as F + fun getInputStub(): F = null as F fun createStateMachine(): StateMachine = stateMachine { val letter = getInputStub() diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 7890769ddb4..b9669d93db0 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -2914,6 +2914,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("IsErasedUpcastToNonReified.kt") + public void testIsErasedUpcastToNonReified() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.kt"); + doTest(fileName); + } + @TestMetadata("IsForTypeWithComplexUpperBound.kt") public void testIsForTypeWithComplexUpperBound() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/cast/IsForTypeWithComplexUpperBound.kt");