Support smart casts to Type after x as? Type null check #KT-4565 Fixed
This commit is contained in:
+13
@@ -190,6 +190,19 @@ object DataFlowValueFactory {
|
|||||||
IdentifierInfo.qualified(receiverInfo, bindingContext.getType(receiverExpression),
|
IdentifierInfo.qualified(receiverInfo, bindingContext.getType(receiverExpression),
|
||||||
selectorInfo, expression.operationSign === KtTokens.SAFE_ACCESS)
|
selectorInfo, expression.operationSign === KtTokens.SAFE_ACCESS)
|
||||||
}
|
}
|
||||||
|
is KtBinaryExpressionWithTypeRHS -> {
|
||||||
|
val subjectExpression = expression.left
|
||||||
|
val targetTypeReference = expression.right
|
||||||
|
val operationToken = expression.operationReference.getReferencedNameElementType()
|
||||||
|
if (operationToken == KtTokens.IS_KEYWORD || operationToken == KtTokens.AS_KEYWORD) {
|
||||||
|
IdentifierInfo.NO
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IdentifierInfo.SafeCast(getIdForStableIdentifier(subjectExpression, bindingContext, containingDeclarationOrModule),
|
||||||
|
bindingContext.getType(subjectExpression),
|
||||||
|
bindingContext[BindingContext.TYPE, targetTypeReference])
|
||||||
|
}
|
||||||
|
}
|
||||||
is KtSimpleNameExpression ->
|
is KtSimpleNameExpression ->
|
||||||
getIdForSimpleNameExpression(expression, bindingContext, containingDeclarationOrModule)
|
getIdForSimpleNameExpression(expression, bindingContext, containingDeclarationOrModule)
|
||||||
is KtThisExpression -> {
|
is KtThisExpression -> {
|
||||||
|
|||||||
+37
-22
@@ -90,11 +90,14 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
nullabilityInfo[key] ?: parent?.getCollectedNullability(key) ?: key.immanentNullability
|
nullabilityInfo[key] ?: parent?.getCollectedNullability(key) ?: key.immanentNullability
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun putNullability(map: MutableMap<DataFlowValue, Nullability>,
|
private fun putNullabilityAndTypeInfo(
|
||||||
value: DataFlowValue,
|
map: MutableMap<DataFlowValue, Nullability>,
|
||||||
nullability: Nullability,
|
value: DataFlowValue,
|
||||||
languageVersionSettings: LanguageVersionSettings,
|
nullability: Nullability,
|
||||||
affectReceiver: Boolean = true): Boolean {
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
|
typeInfo: SetMultimap<DataFlowValue, KotlinType>? = null,
|
||||||
|
affectReceiver: Boolean = true
|
||||||
|
): Boolean {
|
||||||
map.put(value, nullability)
|
map.put(value, nullability)
|
||||||
|
|
||||||
val identifierInfo = value.identifierInfo
|
val identifierInfo = value.identifierInfo
|
||||||
@@ -104,11 +107,23 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
is IdentifierInfo.Qualified -> {
|
is IdentifierInfo.Qualified -> {
|
||||||
val receiverType = identifierInfo.receiverType
|
val receiverType = identifierInfo.receiverType
|
||||||
if (identifierInfo.safe && receiverType != null) {
|
if (identifierInfo.safe && receiverType != null) {
|
||||||
putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability, languageVersionSettings)
|
val receiverValue = DataFlowValue(identifierInfo.receiverInfo, receiverType)
|
||||||
|
putNullabilityAndTypeInfo(map, receiverValue, nullability, languageVersionSettings, typeInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierInfo.SafeCast -> {
|
||||||
|
val targetType = identifierInfo.targetType
|
||||||
|
val subjectType = identifierInfo.subjectType
|
||||||
|
if (targetType != null && subjectType != null &&
|
||||||
|
languageVersionSettings.supportsFeature(LanguageFeature.SafeCastCheckBoundSmartCasts)) {
|
||||||
|
|
||||||
|
val subjectValue = DataFlowValue(identifierInfo.subjectInfo, subjectType)
|
||||||
|
putNullabilityAndTypeInfo(map, subjectValue, nullability, languageVersionSettings, typeInfo)
|
||||||
|
typeInfo?.put(subjectValue, targetType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierInfo.Variable -> identifierInfo.bound?.let {
|
is IdentifierInfo.Variable -> identifierInfo.bound?.let {
|
||||||
putNullability(map, it, nullability, languageVersionSettings)
|
putNullabilityAndTypeInfo(map, it, nullability, languageVersionSettings, typeInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,14 +164,14 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
*/
|
*/
|
||||||
override fun clearValueInfo(value: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo {
|
override fun clearValueInfo(value: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo {
|
||||||
val resultNullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
val resultNullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
||||||
putNullability(resultNullabilityInfo, value, value.immanentNullability, languageVersionSettings)
|
putNullabilityAndTypeInfo(resultNullabilityInfo, value, value.immanentNullability, languageVersionSettings)
|
||||||
return create(this, resultNullabilityInfo, EMPTY_TYPE_INFO, value)
|
return create(this, resultNullabilityInfo, EMPTY_TYPE_INFO, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun assign(a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo {
|
override fun assign(a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo {
|
||||||
val nullability = hashMapOf<DataFlowValue, Nullability>()
|
val nullability = hashMapOf<DataFlowValue, Nullability>()
|
||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
putNullability(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false)
|
putNullabilityAndTypeInfo(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false)
|
||||||
|
|
||||||
val newTypeInfo = newTypeInfo()
|
val newTypeInfo = newTypeInfo()
|
||||||
var typesForB = getStableTypes(b)
|
var typesForB = getStableTypes(b)
|
||||||
@@ -179,11 +194,12 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val nullabilityOfA = getStableNullability(a)
|
val nullabilityOfA = getStableNullability(a)
|
||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
|
|
||||||
var changed = putNullability(resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB), languageVersionSettings) or
|
val newTypeInfo = newTypeInfo()
|
||||||
putNullability(resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA), languageVersionSettings)
|
var changed =
|
||||||
|
putNullabilityAndTypeInfo(resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB), languageVersionSettings, newTypeInfo) or
|
||||||
|
putNullabilityAndTypeInfo(resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA), languageVersionSettings, newTypeInfo)
|
||||||
|
|
||||||
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
||||||
val newTypeInfo = newTypeInfo()
|
|
||||||
if (identityEquals || !nullabilityOfA.canBeNonNull() || !nullabilityOfB.canBeNonNull()) {
|
if (identityEquals || !nullabilityOfA.canBeNonNull() || !nullabilityOfB.canBeNonNull()) {
|
||||||
newTypeInfo.putAll(a, getStableTypes(b, false))
|
newTypeInfo.putAll(a, getStableTypes(b, false))
|
||||||
newTypeInfo.putAll(b, getStableTypes(a, false))
|
newTypeInfo.putAll(b, getStableTypes(a, false))
|
||||||
@@ -199,12 +215,7 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
changed = changed or !newTypeInfo.isEmpty
|
changed = changed or !newTypeInfo.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (!changed) {
|
return if (changed) create(this, resultNullabilityInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo) else this
|
||||||
this
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
create(this, resultNullabilityInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectTypesFromMeAndParents(value: DataFlowValue): Set<KotlinType> {
|
private fun collectTypesFromMeAndParents(value: DataFlowValue): Set<KotlinType> {
|
||||||
@@ -232,9 +243,13 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val nullabilityOfA = getStableNullability(a)
|
val nullabilityOfA = getStableNullability(a)
|
||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
|
|
||||||
val changed = putNullability(resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB.invert()), languageVersionSettings) or
|
val newTypeInfo = newTypeInfo()
|
||||||
putNullability(resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA.invert()), languageVersionSettings)
|
val changed =
|
||||||
return if (changed) create(this, resultNullabilityInfo, EMPTY_TYPE_INFO) else this
|
putNullabilityAndTypeInfo(resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB.invert()), languageVersionSettings, newTypeInfo) or
|
||||||
|
putNullabilityAndTypeInfo(resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA.invert()), languageVersionSettings, newTypeInfo)
|
||||||
|
|
||||||
|
return if (changed) create(this, resultNullabilityInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo) else this
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun establishSubtyping(
|
override fun establishSubtyping(
|
||||||
@@ -247,7 +262,7 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
newTypeInfo.put(value, type)
|
newTypeInfo.put(value, type)
|
||||||
val nullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
val nullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
||||||
if (!type.isMarkedNullable) {
|
if (!type.isMarkedNullable) {
|
||||||
putNullability(nullabilityInfo, value, NOT_NULL, languageVersionSettings)
|
putNullabilityAndTypeInfo(nullabilityInfo, value, NOT_NULL, languageVersionSettings)
|
||||||
}
|
}
|
||||||
return create(this, if (type.isMarkedNullable) emptyMap() else nullabilityInfo, newTypeInfo)
|
return create(this, if (type.isMarkedNullable) emptyMap() else nullabilityInfo, newTypeInfo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ interface IdentifierInfo {
|
|||||||
override fun toString() = "$receiverInfo${if (safe) "?." else "."}$selectorInfo"
|
override fun toString() = "$receiverInfo${if (safe) "?." else "."}$selectorInfo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SafeCast(
|
||||||
|
val subjectInfo: IdentifierInfo,
|
||||||
|
val subjectType: KotlinType?,
|
||||||
|
val targetType: KotlinType?
|
||||||
|
) : IdentifierInfo {
|
||||||
|
override val kind get() = OTHER
|
||||||
|
|
||||||
|
override val canBeBound get() = subjectInfo.canBeBound
|
||||||
|
|
||||||
|
override fun toString() = "$subjectInfo as? ${targetType ?: "???"}"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun qualified(
|
fun qualified(
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// !LANGUAGE: -SafeCastCheckBoundSmartCasts
|
||||||
|
|
||||||
|
interface SomeClass {
|
||||||
|
val data: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SomeSubClass : SomeClass {
|
||||||
|
val foo: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun g(a: SomeClass?) {
|
||||||
|
if (a as? SomeSubClass != null) {
|
||||||
|
// 'a' can be cast to SomeSubClass
|
||||||
|
a<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
a.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(a as SomeSubClass).foo
|
||||||
|
}
|
||||||
|
val b = (a as? SomeSubClass)?.foo
|
||||||
|
if (b != null) {
|
||||||
|
// 'a' can be cast to SomeSubClass
|
||||||
|
a<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
a.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(a as SomeSubClass).foo
|
||||||
|
}
|
||||||
|
val c = a as? SomeSubClass
|
||||||
|
if (c != null) {
|
||||||
|
// 'a' and 'c' can be cast to SomeSubClass
|
||||||
|
a<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
a.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.foo
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun g(/*0*/ a: SomeClass?): kotlin.Unit
|
||||||
|
|
||||||
|
public interface SomeClass {
|
||||||
|
public abstract val data: kotlin.Any?
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SomeSubClass : SomeClass {
|
||||||
|
public abstract override /*1*/ /*fake_override*/ val data: kotlin.Any?
|
||||||
|
public abstract val foo: kotlin.Any?
|
||||||
|
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,36 @@
|
|||||||
|
// !LANGUAGE: +SafeCastCheckBoundSmartCasts
|
||||||
|
|
||||||
|
interface SomeClass {
|
||||||
|
val data: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SomeSubClass : SomeClass {
|
||||||
|
val foo: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun g(a: SomeClass?) {
|
||||||
|
if (a as? SomeSubClass != null) {
|
||||||
|
// 'a' can be cast to SomeSubClass
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.foo
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(a <!USELESS_CAST!>as SomeSubClass<!>).foo
|
||||||
|
}
|
||||||
|
val b = (a as? SomeSubClass)?.foo
|
||||||
|
if (b != null) {
|
||||||
|
// 'a' can be cast to SomeSubClass
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.foo
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(a <!USELESS_CAST!>as SomeSubClass<!>).foo
|
||||||
|
}
|
||||||
|
val c = a as? SomeSubClass
|
||||||
|
if (c != null) {
|
||||||
|
// 'a' and 'c' can be cast to SomeSubClass
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>a<!>.foo
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.foo
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun g(/*0*/ a: SomeClass?): kotlin.Unit
|
||||||
|
|
||||||
|
public interface SomeClass {
|
||||||
|
public abstract val data: kotlin.Any?
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SomeSubClass : SomeClass {
|
||||||
|
public abstract override /*1*/ /*fake_override*/ val data: kotlin.Any?
|
||||||
|
public abstract val foo: kotlin.Any?
|
||||||
|
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,13 @@
|
|||||||
|
// !LANGUAGE: +SafeCastCheckBoundSmartCasts
|
||||||
|
// See KT-19007
|
||||||
|
|
||||||
|
// Stub
|
||||||
|
fun String.indexOf(arg: String) = this.length - arg.length
|
||||||
|
|
||||||
|
// Stub
|
||||||
|
fun String.toLowerCase() = this
|
||||||
|
|
||||||
|
fun foo(a: Any) {
|
||||||
|
// Should compile in 1.2
|
||||||
|
(a as? String)?.indexOf(<!DEBUG_INFO_SMARTCAST!>a<!>.toLowerCase())
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun foo(/*0*/ a: kotlin.Any): kotlin.Unit
|
||||||
|
public fun kotlin.String.indexOf(/*0*/ arg: kotlin.String): kotlin.Int
|
||||||
|
public fun kotlin.String.toLowerCase(): kotlin.String
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// !LANGUAGE: +SafeCastCheckBoundSmartCasts
|
||||||
|
interface SomeClass {
|
||||||
|
val data: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SomeSubClass : SomeClass {
|
||||||
|
val foo: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
object Impl : SomeSubClass {
|
||||||
|
override val data = ""
|
||||||
|
override val foo = 42
|
||||||
|
}
|
||||||
|
|
||||||
|
fun g(a: SomeClass?) {
|
||||||
|
var b = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>(a as? SomeSubClass)?.foo<!>
|
||||||
|
b = "Hello"
|
||||||
|
if (<!SENSELESS_COMPARISON!>b != null<!>) {
|
||||||
|
// 'a' cannot be cast to SomeSubClass!
|
||||||
|
a<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
a.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(a as SomeSubClass).foo
|
||||||
|
}
|
||||||
|
var c = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>a as? SomeSubClass<!>
|
||||||
|
c = Impl
|
||||||
|
if (<!SENSELESS_COMPARISON!>c != null<!>) {
|
||||||
|
// 'a' cannot be cast to SomeSubClass
|
||||||
|
a<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
a.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(a as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.foo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun f(a: SomeClass?) {
|
||||||
|
var aa = a
|
||||||
|
|
||||||
|
if (aa as? SomeSubClass != null) {
|
||||||
|
aa = null
|
||||||
|
// 'aa' cannot be cast to SomeSubClass
|
||||||
|
<!DEBUG_INFO_CONSTANT!>aa<!><!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
aa.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(<!DEBUG_INFO_CONSTANT!>aa<!> as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(<!ALWAYS_NULL!>aa<!> as SomeSubClass).foo
|
||||||
|
}
|
||||||
|
val b = (aa as? SomeSubClass)?.foo
|
||||||
|
aa = null
|
||||||
|
if (b != null) {
|
||||||
|
// 'aa' cannot be cast to SomeSubClass
|
||||||
|
<!DEBUG_INFO_CONSTANT!>aa<!><!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
aa.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(<!DEBUG_INFO_CONSTANT!>aa<!> as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
(<!ALWAYS_NULL!>aa<!> as SomeSubClass).foo
|
||||||
|
}
|
||||||
|
aa = a
|
||||||
|
val c = aa as? SomeSubClass
|
||||||
|
if (c != null) {
|
||||||
|
// 'c' can be cast to SomeSubClass
|
||||||
|
aa<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
aa.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||||
|
(aa as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.hashCode()
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>c<!>.foo
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun f(/*0*/ a: SomeClass?): kotlin.Unit
|
||||||
|
public fun g(/*0*/ a: SomeClass?): kotlin.Unit
|
||||||
|
|
||||||
|
public object Impl : SomeSubClass {
|
||||||
|
private constructor Impl()
|
||||||
|
public open override /*1*/ val data: kotlin.String = ""
|
||||||
|
public open override /*1*/ val foo: kotlin.Int = 42
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SomeClass {
|
||||||
|
public abstract val data: kotlin.Any?
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SomeSubClass : SomeClass {
|
||||||
|
public abstract override /*1*/ /*fake_override*/ val data: kotlin.Any?
|
||||||
|
public abstract val foo: kotlin.Any?
|
||||||
|
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
|
||||||
|
}
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// !LANGUAGE: -SafeCastCheckBoundSmartCasts
|
||||||
// A set of examples for
|
// A set of examples for
|
||||||
// "If the result of a safe call is not null, understand that its receiver is not null"
|
// "If the result of a safe call is not null, understand that its receiver is not null"
|
||||||
// and some other improvements for nullability detection
|
// and some other improvements for nullability detection
|
||||||
|
|||||||
@@ -20266,6 +20266,39 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/castchecks")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class Castchecks extends AbstractDiagnosticsTest {
|
||||||
|
public void testAllFilesPresentInCastchecks() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/castchecks"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basicOff.kt")
|
||||||
|
public void testBasicOff() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/castchecks/basicOff.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basicOn.kt")
|
||||||
|
public void testBasicOn() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/castchecks/basicOn.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("insideCall.kt")
|
||||||
|
public void testInsideCall() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/castchecks/insideCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("variables.kt")
|
||||||
|
public void testVariables() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/castchecks/variables.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/inference")
|
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/inference")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ enum class LanguageFeature(
|
|||||||
InlineDefaultFunctionalParameters(KOTLIN_1_2),
|
InlineDefaultFunctionalParameters(KOTLIN_1_2),
|
||||||
SoundSmartCastsAfterTry(KOTLIN_1_2),
|
SoundSmartCastsAfterTry(KOTLIN_1_2),
|
||||||
DeprecatedFieldForInvisibleCompanionObject(KOTLIN_1_2),
|
DeprecatedFieldForInvisibleCompanionObject(KOTLIN_1_2),
|
||||||
|
SafeCastCheckBoundSmartCasts(KOTLIN_1_2),
|
||||||
|
|
||||||
// Experimental features
|
// Experimental features
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user