[FIR] Add CANNOT_CHECK_FOR_ERASED
This commit is contained in:
committed by
TeamCityServer
parent
e52a410599
commit
291bc74676
+7
@@ -3097,6 +3097,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
|||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(FirErrors.CANNOT_CHECK_FOR_ERASED) { firDiagnostic ->
|
||||||
|
CannotCheckForErasedImpl(
|
||||||
|
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
|
||||||
|
firDiagnostic as FirPsiDiagnostic,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
add(FirErrors.USELESS_CAST) { firDiagnostic ->
|
add(FirErrors.USELESS_CAST) { firDiagnostic ->
|
||||||
UselessCastImpl(
|
UselessCastImpl(
|
||||||
firDiagnostic as KtPsiDiagnostic,
|
firDiagnostic as KtPsiDiagnostic,
|
||||||
|
|||||||
+5
@@ -2168,6 +2168,11 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
|||||||
override val diagnosticClass get() = UselessElvisRightIsNull::class
|
override val diagnosticClass get() = UselessElvisRightIsNull::class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class CannotCheckForErased : KtFirDiagnostic<PsiElement>() {
|
||||||
|
override val diagnosticClass get() = CannotCheckForErased::class
|
||||||
|
abstract val type: KtType
|
||||||
|
}
|
||||||
|
|
||||||
abstract class UselessCast : KtFirDiagnostic<KtBinaryExpressionWithTypeRHS>() {
|
abstract class UselessCast : KtFirDiagnostic<KtBinaryExpressionWithTypeRHS>() {
|
||||||
override val diagnosticClass get() = UselessCast::class
|
override val diagnosticClass get() = UselessCast::class
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -2610,6 +2610,14 @@ internal class UselessElvisRightIsNullImpl(
|
|||||||
override val token: ValidityToken,
|
override val token: ValidityToken,
|
||||||
) : KtFirDiagnostic.UselessElvisRightIsNull(), KtAbstractFirDiagnostic<KtBinaryExpression>
|
) : KtFirDiagnostic.UselessElvisRightIsNull(), KtAbstractFirDiagnostic<KtBinaryExpression>
|
||||||
|
|
||||||
|
internal class CannotCheckForErasedImpl(
|
||||||
|
override val type: KtType,
|
||||||
|
firDiagnostic: FirPsiDiagnostic,
|
||||||
|
override val token: ValidityToken,
|
||||||
|
) : KtFirDiagnostic.CannotCheckForErased(), KtAbstractFirDiagnostic<PsiElement> {
|
||||||
|
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
internal class UselessCastImpl(
|
internal class UselessCastImpl(
|
||||||
override val firDiagnostic: KtPsiDiagnostic,
|
override val firDiagnostic: KtPsiDiagnostic,
|
||||||
override val token: ValidityToken,
|
override val token: ValidityToken,
|
||||||
|
|||||||
+1
@@ -132,6 +132,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
|
|||||||
override val typeOperatorCallCheckers: Set<FirTypeOperatorCallChecker>
|
override val typeOperatorCallCheckers: Set<FirTypeOperatorCallChecker>
|
||||||
get() = setOf(
|
get() = setOf(
|
||||||
FirUselessTypeOperationCallChecker,
|
FirUselessTypeOperationCallChecker,
|
||||||
|
FirCannotCheckForErasedChecker
|
||||||
)
|
)
|
||||||
|
|
||||||
override val resolvedQualifierCheckers: Set<FirResolvedQualifierChecker>
|
override val resolvedQualifierCheckers: Set<FirResolvedQualifierChecker>
|
||||||
|
|||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.platformClassMapper
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.typeContext
|
||||||
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
|
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
|
||||||
|
import org.jetbrains.kotlin.types.model.typeConstructor
|
||||||
|
|
||||||
|
fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: CheckerContext): Boolean {
|
||||||
|
val typeContext = context.session.typeContext
|
||||||
|
|
||||||
|
val isNonReifiedTypeParameter = subtype.isNonReifiedTypeParameter()
|
||||||
|
val isUpcast = isUpcast(context, 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 = !subtype.canBeNull && supertype.withNullability(ConeNullability.NOT_NULL, typeContext) == subtype
|
||||||
|
if (!nullableToDefinitelyNotNull) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cast between T and T? is always OK
|
||||||
|
if (supertype.isMarkedNullable || subtype.isMarkedNullable) {
|
||||||
|
return isCastErased(
|
||||||
|
supertype.withNullability(ConeNullability.NOT_NULL, typeContext),
|
||||||
|
subtype.withNullability(ConeNullability.NOT_NULL, typeContext),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is a upcast, it's never erased
|
||||||
|
if (isUpcast) return false
|
||||||
|
|
||||||
|
// downcasting to a non-reified type parameter is always erased
|
||||||
|
if (isNonReifiedTypeParameter) return true
|
||||||
|
|
||||||
|
// Check that we are actually casting to a generic type
|
||||||
|
// NOTE: this does not account for 'as Array<List<T>>'
|
||||||
|
if (subtype.allParameterReified()) return false
|
||||||
|
|
||||||
|
val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype, context).first ?: return true
|
||||||
|
|
||||||
|
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
||||||
|
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
||||||
|
|
||||||
|
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
||||||
|
// If not, it's wrong to use it
|
||||||
|
return !AbstractTypeChecker.isSubtypeOf(context.session.typeContext, staticallyKnownSubtype, subtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ConeKotlinType.allParameterReified(): Boolean {
|
||||||
|
return typeArguments.all { (it.type as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol?.isReified == true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remember that we are trying to cast something of type `supertype` to `subtype`.
|
||||||
|
|
||||||
|
* Since at runtime we can only check the class (type constructor), the rest of the subtype should be known statically, from supertype.
|
||||||
|
* This method reconstructs all static information that can be obtained from supertype.
|
||||||
|
|
||||||
|
* Example 1:
|
||||||
|
* supertype = Collection
|
||||||
|
* subtype = List<...>
|
||||||
|
* result = List, all arguments are inferred
|
||||||
|
|
||||||
|
* Example 2:
|
||||||
|
* supertype = Any
|
||||||
|
* subtype = List<...>
|
||||||
|
* result = List<*>, some arguments were not inferred, replaced with '*'
|
||||||
|
*/
|
||||||
|
fun findStaticallyKnownSubtype(
|
||||||
|
supertype: ConeKotlinType,
|
||||||
|
subtype: ConeKotlinType,
|
||||||
|
context: CheckerContext
|
||||||
|
): Pair<ConeKotlinType?, Boolean> {
|
||||||
|
assert(!supertype.isMarkedNullable) { "This method only makes sense for non-nullable types" }
|
||||||
|
|
||||||
|
val session = context.session
|
||||||
|
val typeContext = session.typeContext
|
||||||
|
|
||||||
|
// Assume we are casting an expression of type Collection<Foo> to List<Bar>
|
||||||
|
// First, let's make List<T>, where T is a type variable
|
||||||
|
val subtypeWithVariables = subtype.toRegularClassSymbol(session)!!
|
||||||
|
val subtypeWithVariablesType = subtypeWithVariables.defaultType()
|
||||||
|
|
||||||
|
// Now, let's find a supertype of List<T> that is a Collection of something,
|
||||||
|
// in this case it will be Collection<T>
|
||||||
|
val typeCheckerState = context.session.typeContext.newTypeCheckerState(
|
||||||
|
errorTypesEqualToAnything = false,
|
||||||
|
stubTypesEqualToAnything = true
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFirstNotIntersectedType(type: ConeKotlinType): ConeKotlinType? {
|
||||||
|
if (type is ConeIntersectionType) {
|
||||||
|
for (intersectionType in type.intersectedTypes) {
|
||||||
|
val result = getFirstNotIntersectedType(intersectionType)
|
||||||
|
if (result != null) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtaining not intersected type to get not null typeConstructor.
|
||||||
|
// Not sure if it's correct
|
||||||
|
val notIntersectedSupertype = getFirstNotIntersectedType(supertype) ?: supertype
|
||||||
|
val supertypeWithVariables =
|
||||||
|
findCorrespondingSupertypes(
|
||||||
|
typeCheckerState,
|
||||||
|
subtypeWithVariablesType,
|
||||||
|
notIntersectedSupertype.typeConstructor(typeContext)
|
||||||
|
).firstOrNull()
|
||||||
|
|
||||||
|
val variables = subtypeWithVariables.typeParameterSymbols
|
||||||
|
|
||||||
|
val substitution = if (supertypeWithVariables != null) {
|
||||||
|
// Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
|
||||||
|
val typeUnifier = TypeUnifier(session, variables)
|
||||||
|
val unificationResult = typeUnifier.unify(supertype, supertypeWithVariables as ConeKotlinTypeProjection)
|
||||||
|
unificationResult.substitution.toMutableMap()
|
||||||
|
} else {
|
||||||
|
mutableMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If some of the parameters are not determined by unification, it means that these parameters are lost,
|
||||||
|
// let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
||||||
|
var allArgumentsInferred = true
|
||||||
|
for (variable in variables) {
|
||||||
|
val value = substitution[variable]
|
||||||
|
if (value == null) {
|
||||||
|
substitution[variable] = context.session.builtinTypes.nullableAnyType.type
|
||||||
|
allArgumentsInferred = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we have values for all type parameters of List
|
||||||
|
// Let's make a type by substituting them: List<T> -> List<Foo>
|
||||||
|
val substitutor = ConeSubstitutorByMap(substitution, session)
|
||||||
|
val substituted = substitutor.substituteOrSelf(subtypeWithVariablesType)
|
||||||
|
|
||||||
|
return Pair(substituted, allArgumentsInferred)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ConeKotlinType.isNonReifiedTypeParameter(): Boolean {
|
||||||
|
return this is ConeTypeParameterType && !this.lookupTag.typeParameterSymbol.isReified
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
fun shouldCheckForExactType(expression: FirTypeOperatorCall, context: CheckerContext): Boolean {
|
||||||
|
return when (expression.operation) {
|
||||||
|
FirOperation.IS, FirOperation.NOT_IS -> false
|
||||||
|
// TODO: differentiate if this expression defines the enclosing thing's type
|
||||||
|
// e.g.,
|
||||||
|
// val c1 get() = 1 as Number
|
||||||
|
// val c2: Number get() = 1 <!USELESS_CAST!>as Number<!>
|
||||||
|
FirOperation.AS, FirOperation.SAFE_AS -> true
|
||||||
|
else -> throw AssertionError("Should not be here: ${expression.operation}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isRefinementUseless(
|
||||||
|
context: CheckerContext,
|
||||||
|
candidateType: ConeKotlinType,
|
||||||
|
targetType: ConeKotlinType,
|
||||||
|
shouldCheckForExactType: Boolean,
|
||||||
|
arg: FirExpression,
|
||||||
|
): Boolean {
|
||||||
|
return if (shouldCheckForExactType) {
|
||||||
|
if (arg is FirFunctionCall) {
|
||||||
|
val functionSymbol = arg.toResolvedCallableSymbol() as? FirFunctionSymbol<*>
|
||||||
|
if (functionSymbol != null && functionSymbol.isFunctionForExpectTypeFromCastFeature()) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isExactTypeCast(context, candidateType, targetType)
|
||||||
|
} else {
|
||||||
|
isUpcast(context, candidateType, targetType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isExactTypeCast(context: CheckerContext, candidateType: ConeKotlinType, targetType: ConeKotlinType): Boolean {
|
||||||
|
if (!AbstractTypeChecker.equalTypes(context.session.typeContext, candidateType, targetType, stubTypesEqualToAnything = false))
|
||||||
|
return false
|
||||||
|
// See comments at [isUpcast] why we need to check the existence of @ExtensionFunctionType
|
||||||
|
return candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isUpcast(context: CheckerContext, candidateType: ConeKotlinType, targetType: ConeKotlinType): Boolean {
|
||||||
|
if (!AbstractTypeChecker.isSubtypeOf(context.session.typeContext, candidateType, targetType, stubTypesEqualToAnything = false))
|
||||||
|
return false
|
||||||
|
|
||||||
|
// E.g., foo(p1: (X) -> Y), where p1 has a functional type whose receiver type is X and return type is Y.
|
||||||
|
// For bar(p2: X.() -> Y), p2 has the same functional type (with same receiver and return types).
|
||||||
|
// The only difference is the existence of type annotation, @ExtensionFunctionType,
|
||||||
|
// which indicates that the annotated type represents an extension function.
|
||||||
|
// If one casts p1 to p2 (or vice versa), it is _not_ up cast, i.e., not redundant, yet meaningful.
|
||||||
|
return candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
|
|
||||||
|
class TypeUnifier(private val session: FirSession, private val typeParameterSymbols: List<FirTypeParameterSymbol>) {
|
||||||
|
/**
|
||||||
|
* Finds a substitution S that turns {@code projectWithVariables} to {@code knownProjection}.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* known = List<String>
|
||||||
|
* withVariables = List<X>
|
||||||
|
* variables = {X}
|
||||||
|
*
|
||||||
|
* result = X -> String
|
||||||
|
*
|
||||||
|
* Only types accepted by {@code isVariable} are considered variables.
|
||||||
|
*/
|
||||||
|
fun unify(knownProjection: ConeKotlinTypeProjection, projectWithVariables: ConeKotlinTypeProjection): UnificationResult {
|
||||||
|
val result = UnificationResult()
|
||||||
|
doUnify(knownProjection, projectWithVariables, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doUnify(knownProjection: ConeTypeProjection, projectWithVariables: ConeTypeProjection, result: UnificationResult) {
|
||||||
|
val firstType = knownProjection.type
|
||||||
|
|
||||||
|
if (firstType is ConeIntersectionType) {
|
||||||
|
val intersectionResult = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
|
||||||
|
|
||||||
|
for (intersectedType in firstType.intersectedTypes) {
|
||||||
|
val localResult = UnificationResult()
|
||||||
|
doUnify(intersectedType, projectWithVariables, localResult)
|
||||||
|
|
||||||
|
for ((typeParameterSymbol, typeParameterType) in localResult.substitution) {
|
||||||
|
val existingTypeParameterType = intersectionResult[typeParameterSymbol]
|
||||||
|
if (existingTypeParameterType == null ||
|
||||||
|
AbstractTypeChecker.isSubtypeOf(session.typeContext, typeParameterType, existingTypeParameterType)
|
||||||
|
) {
|
||||||
|
intersectionResult[typeParameterSymbol] = typeParameterType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((typeParameterSymbol, typeParameterType) in intersectionResult) {
|
||||||
|
result.put(typeParameterSymbol, typeParameterType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val known = firstType?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type
|
||||||
|
val withVariables = projectWithVariables.type?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type
|
||||||
|
|
||||||
|
// in Foo ~ in X => Foo ~ X
|
||||||
|
val knownProjectionKind = knownProjection.kind
|
||||||
|
val withVariablesProjectionKind = projectWithVariables.kind
|
||||||
|
if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != ProjectionKind.INVARIANT) {
|
||||||
|
doUnify(known, withVariables, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo? ~ X? => Foo ~ X
|
||||||
|
if (known.isMarkedNullable && withVariables.isMarkedNullable) {
|
||||||
|
doUnify(
|
||||||
|
known.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection(
|
||||||
|
knownProjectionKind
|
||||||
|
) as ConeKotlinTypeProjection,
|
||||||
|
withVariables.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection(
|
||||||
|
withVariablesProjectionKind
|
||||||
|
) as ConeKotlinTypeProjection,
|
||||||
|
result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// in Foo ~ out X => fail
|
||||||
|
// in Foo ~ X => may be OK
|
||||||
|
if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != ProjectionKind.INVARIANT) {
|
||||||
|
result.fail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo ~ X? => fail
|
||||||
|
if (!known.isMarkedNullable && withVariables.isMarkedNullable) {
|
||||||
|
result.fail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo ~ X => x |-> Foo
|
||||||
|
// * ~ X => x |-> *
|
||||||
|
val maybeVariable = withVariables.toSymbol(session)
|
||||||
|
if (maybeVariable is FirTypeParameterSymbol && typeParameterSymbols.contains(maybeVariable)) {
|
||||||
|
result.put(maybeVariable, known)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar
|
||||||
|
val structuralMismatch = known.isMarkedNullable != withVariables.isMarkedNullable ||
|
||||||
|
knownProjectionKind != withVariablesProjectionKind ||
|
||||||
|
known.toSymbol(session) != maybeVariable
|
||||||
|
if (structuralMismatch) {
|
||||||
|
result.fail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo<A> ~ Foo<B, C>
|
||||||
|
if (known.typeArguments.size != withVariables.typeArguments.size) {
|
||||||
|
result.fail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo ~ Foo
|
||||||
|
if (known.typeArguments.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo<...> ~ Foo<...>
|
||||||
|
val knownArguments = known.typeArguments
|
||||||
|
val withVariablesArguments = withVariables.typeArguments
|
||||||
|
for (index in knownArguments.indices) {
|
||||||
|
val knownArg = knownArguments[index]
|
||||||
|
val withVariablesArg = withVariablesArguments[index]
|
||||||
|
doUnify(knownArg, withVariablesArg, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnificationResult {
|
||||||
|
private var success: Boolean = true
|
||||||
|
private var failedVariables: MutableSet<FirTypeParameterSymbol> = mutableSetOf()
|
||||||
|
private val _substitution: MutableMap<FirTypeParameterSymbol, ConeKotlinType> = mutableMapOf()
|
||||||
|
val substitution: Map<FirTypeParameterSymbol, ConeKotlinType>
|
||||||
|
get() = _substitution
|
||||||
|
|
||||||
|
fun fail() {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun put(key: FirTypeParameterSymbol, value: ConeKotlinType) {
|
||||||
|
if (failedVariables.contains(key)) return
|
||||||
|
|
||||||
|
if (substitution.containsKey(key)) {
|
||||||
|
_substitution.remove(key)
|
||||||
|
failedVariables.add(key)
|
||||||
|
fail()
|
||||||
|
} else {
|
||||||
|
_substitution[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.isCastErased
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirOperation
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
|
import org.jetbrains.kotlin.fir.types.coneType
|
||||||
|
|
||||||
|
object FirCannotCheckForErasedChecker : FirTypeOperatorCallChecker() {
|
||||||
|
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
|
if (expression.operation != FirOperation.IS) return
|
||||||
|
|
||||||
|
val session = context.session
|
||||||
|
val subjectType = expression.argumentList.arguments[0].typeRef.coneType.fullyExpandedType(session)
|
||||||
|
val conversionTypeRef = expression.conversionTypeRef
|
||||||
|
val targetType = conversionTypeRef.coneType.fullyExpandedType(session)
|
||||||
|
|
||||||
|
if (isCastErased(subjectType, targetType, context)) {
|
||||||
|
reporter.reportOn(conversionTypeRef.source, FirErrors.CANNOT_CHECK_FOR_ERASED, targetType, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.CastingType
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.isCastErased
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.checkCasting
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirOperation
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
|
import org.jetbrains.kotlin.fir.types.coneType
|
||||||
|
|
||||||
|
object FirCastOperatorsChecker : FirTypeOperatorCallChecker() {
|
||||||
|
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
|
val session = context.session
|
||||||
|
val firstArgument = expression.argumentList.arguments[0]
|
||||||
|
val actualType = (if (firstArgument is FirExpressionWithSmartcast) {
|
||||||
|
firstArgument.originalType.coneType
|
||||||
|
} else {
|
||||||
|
firstArgument.typeRef.coneType
|
||||||
|
}).fullyExpandedType(session)
|
||||||
|
val conversionTypeRef = expression.conversionTypeRef
|
||||||
|
val targetType = conversionTypeRef.coneType.fullyExpandedType(session)
|
||||||
|
|
||||||
|
val isSafeAs = expression.operation == FirOperation.SAFE_AS
|
||||||
|
if (expression.operation == FirOperation.AS || isSafeAs) {
|
||||||
|
val castType = checkCasting(actualType, targetType, isSafeAs, context)
|
||||||
|
if (castType == CastingType.Impossible) {
|
||||||
|
reporter.reportOn(expression.source, FirErrors.CAST_NEVER_SUCCEEDS, context)
|
||||||
|
} else if (castType == CastingType.Always) {
|
||||||
|
reporter.reportOn(expression.source, FirErrors.USELESS_CAST, context)
|
||||||
|
} else if (isCastErased(actualType, targetType, context)) {
|
||||||
|
reporter.reportOn(expression.source, FirErrors.UNCHECKED_CAST, actualType, targetType, context)
|
||||||
|
}
|
||||||
|
} else if (expression.operation == FirOperation.IS) {
|
||||||
|
if (isCastErased(actualType, targetType, context)) {
|
||||||
|
reporter.reportOn(conversionTypeRef.source, FirErrors.CANNOT_CHECK_FOR_ERASED, targetType, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-54
@@ -6,15 +6,14 @@
|
|||||||
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.isFunctionForExpectTypeFromCastFeature
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.isRefinementUseless
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.shouldCheckForExactType
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
|
||||||
|
|
||||||
// See .../types/CastDiagnosticsUtil.kt for counterparts, including isRefinementUseless, isExactTypeCast, isUpcast.
|
// See .../types/CastDiagnosticsUtil.kt for counterparts, including isRefinementUseless, isExactTypeCast, isUpcast.
|
||||||
object FirUselessTypeOperationCallChecker : FirTypeOperatorCallChecker() {
|
object FirUselessTypeOperationCallChecker : FirTypeOperatorCallChecker() {
|
||||||
@@ -44,55 +43,4 @@ object FirUselessTypeOperationCallChecker : FirTypeOperatorCallChecker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
private fun shouldCheckForExactType(expression: FirTypeOperatorCall, context: CheckerContext): Boolean {
|
|
||||||
return when (expression.operation) {
|
|
||||||
FirOperation.IS, FirOperation.NOT_IS -> false
|
|
||||||
// TODO: differentiate if this expression defines the enclosing thing's type
|
|
||||||
// e.g.,
|
|
||||||
// val c1 get() = 1 as Number
|
|
||||||
// val c2: Number get() = 1 <!USELESS_CAST!>as Number<!>
|
|
||||||
FirOperation.AS, FirOperation.SAFE_AS -> true
|
|
||||||
else -> throw AssertionError("Should not be here: ${expression.operation}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isRefinementUseless(
|
|
||||||
context: CheckerContext,
|
|
||||||
candidateType: ConeKotlinType,
|
|
||||||
targetType: ConeKotlinType,
|
|
||||||
shouldCheckForExactType: Boolean,
|
|
||||||
arg: FirExpression,
|
|
||||||
): Boolean {
|
|
||||||
return if (shouldCheckForExactType) {
|
|
||||||
if (arg is FirFunctionCall) {
|
|
||||||
val functionSymbol = arg.toResolvedCallableSymbol() as? FirFunctionSymbol<*>
|
|
||||||
if (functionSymbol != null && functionSymbol.isFunctionForExpectTypeFromCastFeature()) return false
|
|
||||||
}
|
|
||||||
|
|
||||||
isExactTypeCast(context, candidateType, targetType)
|
|
||||||
} else {
|
|
||||||
isUpcast(context, candidateType, targetType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isExactTypeCast(context: CheckerContext, candidateType: ConeKotlinType, targetType: ConeKotlinType): Boolean {
|
|
||||||
if (!AbstractTypeChecker.equalTypes(context.session.typeContext, candidateType, targetType, stubTypesEqualToAnything = false))
|
|
||||||
return false
|
|
||||||
// See comments at [isUpcast] why we need to check the existence of @ExtensionFunctionType
|
|
||||||
return candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isUpcast(context: CheckerContext, candidateType: ConeKotlinType, targetType: ConeKotlinType): Boolean {
|
|
||||||
if (!AbstractTypeChecker.isSubtypeOf(context.session.typeContext, candidateType, targetType, stubTypesEqualToAnything = false))
|
|
||||||
return false
|
|
||||||
|
|
||||||
// E.g., foo(p1: (X) -> Y), where p1 has a functional type whose receiver type is X and return type is Y.
|
|
||||||
// For bar(p2: X.() -> Y), p2 has the same functional type (with same receiver and return types).
|
|
||||||
// The only difference is the existence of type annotation, @ExtensionFunctionType,
|
|
||||||
// which indicates that the annotated type represents an extension function.
|
|
||||||
// If one casts p1 to p2 (or vice versa), it is _not_ up cast, i.e., not redundant, yet meaningful.
|
|
||||||
return candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,15 @@ fun ConeKotlinType.toTypeProjection(variance: Variance): ConeTypeProjection =
|
|||||||
Variance.OUT_VARIANCE -> ConeKotlinTypeProjectionOut(this)
|
Variance.OUT_VARIANCE -> ConeKotlinTypeProjectionOut(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ConeKotlinType.toTypeProjection(projectionKind: ProjectionKind): ConeTypeProjection {
|
||||||
|
return when (projectionKind) {
|
||||||
|
ProjectionKind.INVARIANT -> this
|
||||||
|
ProjectionKind.IN -> ConeKotlinTypeProjectionIn(this)
|
||||||
|
ProjectionKind.OUT -> ConeKotlinTypeProjectionOut(this)
|
||||||
|
ProjectionKind.STAR -> ConeStarProjection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ConeClassLikeType.replaceArgumentsWithStarProjections(): ConeClassLikeType {
|
fun ConeClassLikeType.replaceArgumentsWithStarProjections(): ConeClassLikeType {
|
||||||
if (typeArguments.isEmpty()) return this
|
if (typeArguments.isEmpty()) return this
|
||||||
val newArguments = Array(typeArguments.size) { ConeStarProjection }
|
val newArguments = Array(typeArguments.size) { ConeStarProjection }
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
interface A
|
||||||
|
interface B: A
|
||||||
|
interface D
|
||||||
|
|
||||||
|
interface BaseSuper<out T>
|
||||||
|
interface BaseImpl: BaseSuper<D>
|
||||||
|
interface DerivedSuper<out S>: <!INCONSISTENT_TYPE_PARAMETER_VALUES!>BaseSuper<S>, BaseImpl<!>
|
||||||
|
|
||||||
|
fun test(t: BaseSuper<B>) = t is <!CANNOT_CHECK_FOR_ERASED!>DerivedSuper<A><!>
|
||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// FIR_IDENTICAL
|
|
||||||
interface A
|
interface A
|
||||||
interface B: A
|
interface B: A
|
||||||
interface D
|
interface D
|
||||||
|
|||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
fun f(a: MutableList<String>) = a is MutableList<CharSequence>
|
|
||||||
+1
@@ -1 +1,2 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun f(a: MutableList<String>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<CharSequence><!>
|
fun f(a: MutableList<String>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<CharSequence><!>
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
open class A
|
|
||||||
open class B: A()
|
|
||||||
|
|
||||||
open class Base<out T>
|
|
||||||
open class SubBase<T> : Base<T>()
|
|
||||||
|
|
||||||
|
|
||||||
fun ff(l: Base<B>) = l is SubBase<A>
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
open class A
|
open class A
|
||||||
open class B: A()
|
open class B: A()
|
||||||
|
|
||||||
|
|||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
interface A
|
|
||||||
interface B: A
|
|
||||||
|
|
||||||
interface Base<T>
|
|
||||||
|
|
||||||
fun <T> test(a: Base<B>) where T: Base<A> = a is T
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
interface A
|
interface A
|
||||||
interface B: A
|
interface B: A
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
fun ff(l: Any) = l is MutableList<String>
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
|
|
||||||
fun ff(l: Any) = l is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>
|
fun ff(l: Any) = l is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
fun f(a: MutableList<in String>) = a is MutableList<CharSequence>
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun f(a: MutableList<in String>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<CharSequence><!>
|
fun f(a: MutableList<in String>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<CharSequence><!>
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
fun f(a : MutableList<out Any>) = a is MutableList<out Int>
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
|
|
||||||
fun f(a : MutableList<out Any>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<out Int><!>
|
fun f(a : MutableList<out Any>) = a is <!CANNOT_CHECK_FOR_ERASED!>MutableList<out Int><!>
|
||||||
|
|||||||
-1
@@ -1 +0,0 @@
|
|||||||
fun f(a: List<Any>) = a is List<Number>
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun f(a: List<Any>) = a is <!CANNOT_CHECK_FOR_ERASED!>List<Number><!>
|
fun f(a: List<Any>) = a is <!CANNOT_CHECK_FOR_ERASED!>List<Number><!>
|
||||||
|
|||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
open class A
|
|
||||||
open class B: A()
|
|
||||||
open class D
|
|
||||||
|
|
||||||
open class Base<out T, out U>
|
|
||||||
open class Derived<out S>: Base<S, S>()
|
|
||||||
|
|
||||||
fun test(a: Base<D, B>) = a is Derived<A>
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
open class A
|
open class A
|
||||||
open class B: A()
|
open class B: A()
|
||||||
open class D
|
open class D
|
||||||
|
|||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
open class A
|
|
||||||
open class B: A()
|
|
||||||
open class D
|
|
||||||
|
|
||||||
open class Base<out T, out U>
|
|
||||||
open class Derived<out S>: Base<S, S>()
|
|
||||||
|
|
||||||
fun test(a: Base<B, D>) = a is Derived<A>
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
open class A
|
open class A
|
||||||
open class B: A()
|
open class B: A()
|
||||||
open class D
|
open class D
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fun <T, S : T> test(x: T?, y: S, z: T) {
|
fun <T, S : T> test(x: T?, y: S, z: T) {
|
||||||
x is T
|
x is <!CANNOT_CHECK_FOR_ERASED!>T<!>
|
||||||
<!USELESS_IS_CHECK!>x is T?<!>
|
<!USELESS_IS_CHECK!>x is T?<!>
|
||||||
|
|
||||||
<!USELESS_IS_CHECK!>y is T<!>
|
<!USELESS_IS_CHECK!>y is T<!>
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ open class RecA<T>: <!CYCLIC_INHERITANCE_HIERARCHY!>RecB<T><!>()
|
|||||||
open class RecB<T>: <!CYCLIC_INHERITANCE_HIERARCHY!>RecA<T><!>()
|
open class RecB<T>: <!CYCLIC_INHERITANCE_HIERARCHY!>RecA<T><!>()
|
||||||
open class SelfR<T>: <!CYCLIC_INHERITANCE_HIERARCHY!>SelfR<T><!>()
|
open class SelfR<T>: <!CYCLIC_INHERITANCE_HIERARCHY!>SelfR<T><!>()
|
||||||
|
|
||||||
fun test(f: SelfR<String>) = f is RecA<String>
|
fun test(f: SelfR<String>) = f is <!CANNOT_CHECK_FOR_ERASED!>RecA<String><!>
|
||||||
fun test(f: RecB<String>) = f is RecA<String>
|
fun test(f: RecB<String>) = f is <!CANNOT_CHECK_FOR_ERASED!>RecA<String><!>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
fun ff(l: Any) = when(l) {
|
|
||||||
is MutableList<String> -> 1
|
|
||||||
else <!SYNTAX!>2<!>
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
|
|
||||||
fun ff(l: Any) = when(l) {
|
fun ff(l: Any) = when(l) {
|
||||||
is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!> -> 1
|
is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!> -> 1
|
||||||
|
|||||||
Vendored
+1
-1
@@ -9,7 +9,7 @@ public class A {
|
|||||||
// FILE: main.kt
|
// FILE: main.kt
|
||||||
|
|
||||||
fun main(a: A, ml: Any) {
|
fun main(a: A, ml: Any) {
|
||||||
if (ml is MutableList<String>) {
|
if (ml is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>) {
|
||||||
a.foo(<!JAVA_TYPE_MISMATCH!>ml<!>)
|
a.foo(<!JAVA_TYPE_MISMATCH!>ml<!>)
|
||||||
a.foo(ml as List<Any>)
|
a.foo(ml as List<Any>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// KT-398 Internal error when property initializes with function
|
|
||||||
|
|
||||||
class X<T>() {
|
|
||||||
val check = { a : Any -> a is T }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun box() : String {
|
|
||||||
if(X<String>().check(10)) return "fail"
|
|
||||||
if(!X<String>().check("lala")) return "fail"
|
|
||||||
return "OK"
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// KT-398 Internal error when property initializes with function
|
// KT-398 Internal error when property initializes with function
|
||||||
|
|
||||||
class X<T>() {
|
class X<T>() {
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// KT-399 Type argument inference not implemented for CALL_EXPRESSION
|
|
||||||
|
|
||||||
fun <T> getSameTypeChecker(obj: T) : Function1<Any,Boolean> {
|
|
||||||
return { a : Any -> a is T }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun box() : String {
|
|
||||||
if(getSameTypeChecker<String>("lala")(10)) return "fail"
|
|
||||||
if(!getSameTypeChecker<String>("mama")("lala")) return "fail"
|
|
||||||
return "OK"
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// KT-399 Type argument inference not implemented for CALL_EXPRESSION
|
// KT-399 Type argument inference not implemented for CALL_EXPRESSION
|
||||||
|
|
||||||
fun <T> getSameTypeChecker(obj: T) : Function1<Any,Boolean> {
|
fun <T> getSameTypeChecker(obj: T) : Function1<Any,Boolean> {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
fun f(a: Array<out Number>) = a.isArrayOf<Int>()
|
|
||||||
|
|
||||||
fun f1(a: Array<out Number>) = <!USELESS_IS_CHECK!>a is Array<*><!>
|
|
||||||
|
|
||||||
fun f2(a: Array<out Number>) = a is Array<Int>
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun f(a: Array<out Number>) = a.isArrayOf<Int>()
|
fun f(a: Array<out Number>) = a.isArrayOf<Int>()
|
||||||
|
|
||||||
fun f1(a: Array<out Number>) = <!USELESS_IS_CHECK!>a is Array<*><!>
|
fun f1(a: Array<out Number>) = <!USELESS_IS_CHECK!>a is Array<*><!>
|
||||||
|
|||||||
+2
-2
@@ -9,14 +9,14 @@ class Case1<AT>(val x: AT) {
|
|||||||
|
|
||||||
inner class C() {
|
inner class C() {
|
||||||
fun case1a(x: Any) {
|
fun case1a(x: Any) {
|
||||||
if (x is AT) {
|
if (x is <!CANNOT_CHECK_FOR_ERASED!>AT<!>) {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun case1b(x: Any) {
|
fun case1b(x: Any) {
|
||||||
when (x) {
|
when (x) {
|
||||||
is AT -> println("at")
|
is <!CANNOT_CHECK_FOR_ERASED!>AT<!> -> println("at")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -9,13 +9,13 @@ class Case1<AT: CharSequence>(val x: AT) {
|
|||||||
|
|
||||||
inner class C() {
|
inner class C() {
|
||||||
fun case1a(x: Any) {
|
fun case1a(x: Any) {
|
||||||
if (x is AT) {
|
if (x is <!CANNOT_CHECK_FOR_ERASED!>AT<!>) {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun case1b(x: Any) = when (x) {
|
fun case1b(x: Any) = when (x) {
|
||||||
is AT -> println("at")
|
is <!CANNOT_CHECK_FOR_ERASED!>AT<!> -> println("at")
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,14 +28,14 @@ class Case2<AT: CharSequence>(val x: AT) {
|
|||||||
|
|
||||||
inner class C() {
|
inner class C() {
|
||||||
fun case2a(x: CharSequence) {
|
fun case2a(x: CharSequence) {
|
||||||
if (x is AT) {
|
if (x is <!CANNOT_CHECK_FOR_ERASED!>AT<!>) {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun case2b(x: CharSequence) {
|
fun case2b(x: CharSequence) {
|
||||||
when (x) {
|
when (x) {
|
||||||
is AT -> ""
|
is <!CANNOT_CHECK_FOR_ERASED!>AT<!> -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user