[FIR] Report wrong modifiers in function type arguments and types of value parameters

#KT-59955
This commit is contained in:
Evgeniy.Zhelenskiy
2023-10-05 18:41:56 +02:00
committed by Space Team
parent 43c97077eb
commit feed3a57d0
18 changed files with 183 additions and 124 deletions
@@ -2,7 +2,7 @@ class A<in T, out K>
class B
fun test() {
val a1 = A<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>in Int<!>, <!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out B<!>>()
val a1 = A<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>in<!> Int, <!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out<!> B>()
val a2 = A<Int, B>()
val a3 = A<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>*<!>, <!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>*<!>>()
}
@@ -42,6 +42,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirAbstractSuperCallChecker,
FirQualifiedSupertypeExtendedByOtherSupertypeChecker,
FirProjectionsOnNonClassTypeArgumentChecker,
FirIncompatibleProjectionsOnTypeArgumentChecker,
FirUpperBoundViolatedExpressionChecker,
FirTypeArgumentsNotAllowedExpressionChecker,
FirTypeParameterInQualifiedAccessChecker,
@@ -17,6 +17,7 @@ object CommonTypeCheckers : TypeCheckers() {
FirUnsupportedDefaultValueInFunctionTypeParameterChecker,
FirUnsupportedModifiersInFunctionTypeParameterChecker,
FirStarProjectionModifierChecker,
FirInOutProjectionModifierChecker,
FirDuplicateParameterNameInFunctionTypeChecker,
FirOptionalExpectationTypeChecker,
FirIncompatibleClassTypeChecker,
@@ -0,0 +1,121 @@
/*
* Copyright 2010-2023 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.KtRealSourceElementKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.types.FirTypeProjection
import org.jetbrains.kotlin.resolve.Compatibility
import org.jetbrains.kotlin.resolve.compatibility
internal fun checkCompatibilityType(
firstModifier: FirModifier<*>,
secondModifier: FirModifier<*>,
reporter: DiagnosticReporter,
reportedNodes: MutableSet<FirModifier<*>>,
owner: FirElement?,
context: CheckerContext
) {
val firstModifierToken = firstModifier.token
val secondModifierToken = secondModifier.token
when (val compatibilityType = compatibility(firstModifierToken, secondModifierToken)) {
Compatibility.COMPATIBLE -> {
}
Compatibility.REPEATED ->
if (reportedNodes.add(secondModifier)) {
reporter.reportOn(secondModifier.source, FirErrors.REPEATED_MODIFIER, secondModifierToken, context)
}
Compatibility.REDUNDANT -> {
reporter.reportOn(
secondModifier.source,
FirErrors.REDUNDANT_MODIFIER,
secondModifierToken,
firstModifierToken,
context
)
}
Compatibility.REVERSE_REDUNDANT -> {
reporter.reportOn(
firstModifier.source,
FirErrors.REDUNDANT_MODIFIER,
firstModifierToken,
secondModifierToken,
context
)
}
Compatibility.DEPRECATED -> {
reporter.reportOn(
firstModifier.source,
FirErrors.DEPRECATED_MODIFIER_PAIR,
firstModifierToken,
secondModifierToken,
context
)
reporter.reportOn(
secondModifier.source,
FirErrors.DEPRECATED_MODIFIER_PAIR,
secondModifierToken,
firstModifierToken,
context
)
}
Compatibility.INCOMPATIBLE, Compatibility.COMPATIBLE_FOR_CLASSES_ONLY -> {
if (compatibilityType == Compatibility.COMPATIBLE_FOR_CLASSES_ONLY && owner is FirClass) {
return
}
if (reportedNodes.add(firstModifier)) {
reporter.reportOn(
firstModifier.source,
FirErrors.INCOMPATIBLE_MODIFIERS,
firstModifierToken,
secondModifierToken,
context
)
}
if (reportedNodes.add(secondModifier)) {
reporter.reportOn(
secondModifier.source,
FirErrors.INCOMPATIBLE_MODIFIERS,
secondModifierToken,
firstModifierToken,
context
)
}
}
}
}
private fun checkModifiersCompatibility(
owner: FirElement,
modifierList: FirModifierList,
reporter: DiagnosticReporter,
reportedNodes: MutableSet<FirModifier<*>>,
context: CheckerContext,
) {
val modifiers = modifierList.modifiers
for ((secondIndex, secondModifier) in modifiers.withIndex()) {
for (firstIndex in 0..<secondIndex) {
checkCompatibilityType(modifiers[firstIndex], secondModifier, reporter, reportedNodes, owner, context)
}
}
}
fun checkModifiersCompatibility(typeArgument: FirTypeProjection, context: CheckerContext, reporter: DiagnosticReporter) {
val source = typeArgument.source?.takeIf { it.kind is KtRealSourceElementKind } ?: return
val modifierList = source.getModifierList() ?: return
// general strategy: report no more than one error and any number of warnings
// therefore, a track of nodes with already reported errors should be kept
val reportedNodes = hashSetOf<FirModifier<*>>()
checkModifiersCompatibility(typeArgument, modifierList, reporter, reportedNodes, context)
}
@@ -10,16 +10,17 @@ import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.Companion.classActualTargets
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifier
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
import org.jetbrains.kotlin.fir.analysis.checkers.checkCompatibilityType
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
import org.jetbrains.kotlin.fir.analysis.checkers.getActualTargetList
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.utils.*
@@ -89,83 +90,6 @@ object FirModifierChecker : FirBasicDeclarationChecker() {
}
}
private fun checkCompatibilityType(
firstModifier: FirModifier<*>,
secondModifier: FirModifier<*>,
reporter: DiagnosticReporter,
reportedNodes: MutableSet<FirModifier<*>>,
owner: FirDeclaration?,
context: CheckerContext
) {
val firstModifierToken = firstModifier.token
val secondModifierToken = secondModifier.token
when (val compatibilityType = compatibility(firstModifierToken, secondModifierToken)) {
Compatibility.COMPATIBLE -> {
}
Compatibility.REPEATED ->
if (reportedNodes.add(secondModifier)) {
reporter.reportOn(secondModifier.source, FirErrors.REPEATED_MODIFIER, secondModifierToken, context)
}
Compatibility.REDUNDANT -> {
reporter.reportOn(
secondModifier.source,
FirErrors.REDUNDANT_MODIFIER,
secondModifierToken,
firstModifierToken,
context
)
}
Compatibility.REVERSE_REDUNDANT -> {
reporter.reportOn(
firstModifier.source,
FirErrors.REDUNDANT_MODIFIER,
firstModifierToken,
secondModifierToken,
context
)
}
Compatibility.DEPRECATED -> {
reporter.reportOn(
firstModifier.source,
FirErrors.DEPRECATED_MODIFIER_PAIR,
firstModifierToken,
secondModifierToken,
context
)
reporter.reportOn(
secondModifier.source,
FirErrors.DEPRECATED_MODIFIER_PAIR,
secondModifierToken,
firstModifierToken,
context
)
}
Compatibility.INCOMPATIBLE, Compatibility.COMPATIBLE_FOR_CLASSES_ONLY -> {
if (compatibilityType == Compatibility.COMPATIBLE_FOR_CLASSES_ONLY && owner is FirClass) {
return
}
if (reportedNodes.add(firstModifier)) {
reporter.reportOn(
firstModifier.source,
FirErrors.INCOMPATIBLE_MODIFIERS,
firstModifierToken,
secondModifierToken,
context
)
}
if (reportedNodes.add(secondModifier)) {
reporter.reportOn(
secondModifier.source,
FirErrors.INCOMPATIBLE_MODIFIERS,
secondModifierToken,
firstModifierToken,
context
)
}
}
}
}
private fun checkTarget(
modifierSource: KtSourceElement,
modifierToken: KtModifierKeywordToken,
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2023 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.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.checkers.checkModifiersCompatibility
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
object FirIncompatibleProjectionsOnTypeArgumentChecker : FirQualifiedAccessExpressionChecker() {
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
for (it in expression.typeArguments) {
checkModifiersCompatibility(it, context, reporter)
}
}
}
@@ -5,10 +5,11 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.types.FirStarProjection
import org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance
@@ -21,7 +22,8 @@ object FirProjectionsOnNonClassTypeArgumentChecker : FirQualifiedAccessExpressio
is FirStarProjection -> reporter.reportOn(it.source, FirErrors.PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, context)
is FirTypeProjectionWithVariance -> {
if (it.variance != Variance.INVARIANT) {
reporter.reportOn(it.source, FirErrors.PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, context)
val modifierSource = it.source.getModifierList()?.modifiers?.firstOrNull()?.source
reporter.reportOn(modifierSource ?: it.source, FirErrors.PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, context)
}
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2023 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.type
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.checkers.checkModifiersCompatibility
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.types.*
object FirInOutProjectionModifierChecker : FirTypeRefChecker() {
override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) {
if (typeRef !is FirResolvedTypeRef) return
val delegatedTypeRef = typeRef.delegatedTypeRef as? FirUserTypeRef ?: return
for (part in delegatedTypeRef.qualifier) {
for (typeArgument in part.typeArgumentList.typeArguments) {
checkModifiersCompatibility(typeArgument, context, reporter)
}
}
}
}
@@ -1,4 +0,0 @@
fun test() {
fun <T> foo(){}
foo<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>in Int<!>>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun test() {
fun <T> foo(){}
foo<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>in<!> Int>()
@@ -1,6 +0,0 @@
fun <T> Array<T>.foo() {}
fun test(array: Array<out Int>) {
array.foo()
array.<!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>foo<!><<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out Int<!>>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun <T> Array<T>.foo() {}
fun test(array: Array<out Int>) {
@@ -1,22 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
interface Foo<T>
interface Foo2<in <!REPEATED_MODIFIER!>in<!> T>
fun test1(foo: Foo<in out Int>) = foo
fun test2(): Foo<in in Int> = throw Exception()
fun test3() {
val f: Foo<out out out out Int>
class Bzz<in <!REPEATED_MODIFIER!>in<!> T>
}
class A {
fun <<!VARIANCE_ON_TYPE_PARAMETER_NOT_ALLOWED!>out<!> <!REPEATED_MODIFIER!>out<!> T> bar() {
}
}
fun test4(a: A) {
a.bar<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out out Int<!>>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE
interface Foo<T>
@@ -1 +1 @@
val unwrapped = <!UNRESOLVED_REFERENCE!>some<!><<!UNRESOLVED_REFERENCE!>sdf<!>()()<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out Any<!>>::unwrap
val unwrapped = <!UNRESOLVED_REFERENCE!>some<!><<!UNRESOLVED_REFERENCE!>sdf<!>()()<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out<!> Any>::unwrap
@@ -1,3 +1,3 @@
// NI_EXPECTED_FILE
val unwrapped = <!UNRESOLVED_REFERENCE!>some<!>.<!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>cabc<!><!SYNTAX!><!SYNTAX!>$Wrapper<!><<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out Any<!>><!>::<!UNRESOLVED_REFERENCE!>unwrap<!>
val unwrapped = <!UNRESOLVED_REFERENCE!>some<!>.<!SYNTAX!><<!><!UNRESOLVED_REFERENCE!>cabc<!><!SYNTAX!><!SYNTAX!>$Wrapper<!><<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out<!> Any><!>::<!UNRESOLVED_REFERENCE!>unwrap<!>
@@ -1,6 +0,0 @@
class In<in T>(val x: Any)
typealias InAlias<T> = In<T>
val test1 = In<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out String<!>>("")
val test2 = InAlias<<!PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT!>out String<!>>("")
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class In<in T>(val x: Any)
typealias InAlias<T> = In<T>