FIR checker: add convention check for ++ and +=, etc

This commit is contained in:
Tianyu Geng
2021-08-09 18:46:08 -07:00
committed by TeamCityServer
parent 68e7476765
commit 64ebddcbc6
14 changed files with 128 additions and 23 deletions
@@ -1115,6 +1115,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
parameter<ConeKotlinType>("leftType")
parameter<ConeKotlinType>("rightType")
}
val INC_DEC_SHOULD_NOT_RETURN_UNIT by error<KtExpression>(PositioningStrategy.OPERATOR)
val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error<KtExpression>(PositioningStrategy.OPERATOR) {
parameter<FirNamedFunctionSymbol>("functionSymbol")
parameter<String>("operator")
}
}
val TYPE_ALIAS by object : DiagnosticGroup("Type alias") {
@@ -577,6 +577,8 @@ object FirErrors {
val EQUALITY_NOT_APPLICABLE by error3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
val EQUALITY_NOT_APPLICABLE_WARNING by warning3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
val INCOMPATIBLE_ENUM_COMPARISON_ERROR by error2<KtElement, ConeKotlinType, ConeKotlinType>()
val INC_DEC_SHOULD_NOT_RETURN_UNIT by error0<KtExpression>(SourceElementPositioningStrategies.OPERATOR)
val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error2<KtExpression, FirNamedFunctionSymbol, String>(SourceElementPositioningStrategies.OPERATOR)
// Type alias
val TOPLEVEL_TYPEALIASES_ONLY by error0<KtTypeAlias>()
@@ -50,7 +50,8 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirConventionFunctionCallChecker,
FirDivisionByZeroChecker,
FirConstructorCallChecker,
FirSpreadOfNullableChecker
FirSpreadOfNullableChecker,
FirAssignmentOperatorCallChecker,
)
override val tryExpressionCheckers: Set<FirTryExpressionChecker>
@@ -16,8 +16,11 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.analysis.getChild
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
@@ -578,7 +581,11 @@ fun checkTypeMismatch(
rValueType = lValueType
lValueType = tempType
}
reporter.reportOn(source, FirErrors.RESULT_TYPE_MISMATCH, lValueType, rValueType, context)
if (rValueType.isUnit) {
reporter.reportOn(source, FirErrors.INC_DEC_SHOULD_NOT_RETURN_UNIT, context)
} else {
reporter.reportOn(source, FirErrors.RESULT_TYPE_MISMATCH, lValueType, rValueType, context)
}
}
else -> {
reporter.reportOn(source, FirErrors.ASSIGNMENT_TYPE_MISMATCH, lValueType, rValueType, context)
@@ -0,0 +1,40 @@
/*
* 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.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.expressions.FirOperationNameConventions
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isUnit
object FirAssignmentOperatorCallChecker : FirFunctionCallChecker() {
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
val resolvedCallee = expression.calleeReference as? FirResolvedNamedReference ?: return
val resolvedCalleeSymbol = resolvedCallee.resolvedSymbol as? FirNamedFunctionSymbol ?: return
val resolvedCalleeName = resolvedCalleeSymbol.name
if (expression.origin != FirFunctionCallOrigin.Operator ||
resolvedCalleeName !in FirOperationNameConventions.ASSIGNMENT_NAMES
) {
return
}
if (!expression.typeRef.coneType.isUnit) {
reporter.reportOn(
expression.source,
FirErrors.ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT,
resolvedCalleeSymbol,
FirOperationNameConventions.ASSIGNMENT_NAMES[resolvedCalleeName]!!.operator,
context
)
}
}
}
@@ -64,6 +64,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARGUMENT_PASSED_T
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARGUMENT_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGNED_VALUE_IS_NEVER_READ
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGNMENT_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BACKING_FIELD_IN_INTERFACE
@@ -219,6 +220,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_TYPES_WARNING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCONSISTENT_TYPE_PARAMETER_BOUNDS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCONSISTENT_TYPE_PARAMETER_VALUES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INC_DEC_SHOULD_NOT_RETURN_UNIT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFERENCE_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFIX_MODIFIER_REQUIRED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZATION_BEFORE_DECLARATION
@@ -238,8 +240,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INNER_CLASS_INSID
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INSTANCE_ACCESS_BEFORE_SUPER_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INTERFACE_WITH_SUPERCLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_DEFAULT_FUNCTIONAL_PARAMETER_FOR_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_CHARACTERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_DEFAULT_FUNCTIONAL_PARAMETER_FOR_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_IF_AS_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVISIBLE_REFERENCE
@@ -290,9 +292,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_LOOP_LABEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_YET_SUPPORTED_IN_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ACTUAL_FOR_EXPECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_YET_SUPPORTED_IN_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_COMPANION_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ELSE_IN_WHEN
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_GET_METHOD
@@ -1471,6 +1473,13 @@ class FirDefaultErrorMessages {
RENDER_TYPE,
RENDER_TYPE
)
map.put(INC_DEC_SHOULD_NOT_RETURN_UNIT, "Functions inc(), dec() shouldn't return Unit to be used by operators ++, --")
map.put(
ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT,
"Function ''{0}'' should return Unit to be used by corresponding operator ''{1}''",
SYMBOL,
TO_STRING
)
// Type alias
map.put(TOPLEVEL_TYPEALIASES_ONLY, "Nested and local type aliases are not supported")
@@ -60,6 +60,7 @@ object FirOperationNameConventions {
FirOperation.REM_ASSIGN to OperatorNameConventions.REM_ASSIGN
)
)
val ASSIGNMENT_NAMES = ASSIGNMENTS.map { (k, v) -> v to k }.toMap()
val ASSIGNMENTS_TO_SIMPLE_OPERATOR: Map<FirOperation, Name> = EnumMap(
mapOf(
+8 -8
View File
@@ -35,12 +35,12 @@ class UnitIncDec() {
fun testUnitIncDec() {
var x = UnitIncDec()
<!RESULT_TYPE_MISMATCH!>x++<!>
<!RESULT_TYPE_MISMATCH!>++x<!>
<!RESULT_TYPE_MISMATCH!>x--<!>
<!RESULT_TYPE_MISMATCH!>--x<!>
x = <!RESULT_TYPE_MISMATCH!>x++<!>
x = <!RESULT_TYPE_MISMATCH!>x--<!>
x = <!RESULT_TYPE_MISMATCH!>++x<!>
x = <!RESULT_TYPE_MISMATCH!>--x<!>
x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>x
x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>
<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>x
x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>
x = <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>x
x = <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>x
}
@@ -29,7 +29,7 @@ class Test()
fun test()
{
val control = Control()
control.MouseMoved += { it.X } // here
control.MouseMoved <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>+=<!> { it.X } // here
control.MouseMoved.plusAssign( { it.X } ) // ok
}
}
}
@@ -20,9 +20,9 @@ class B() {
fun main() {
var c = B()
c += B()
c *= B()
c /= B()
c -= B()
c %= B()
}
c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>+=<!> B()
c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>*=<!> B()
c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>/=<!> B()
c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>-=<!> B()
c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>%=<!> B()
}
@@ -4,7 +4,7 @@ class IncDec {
fun foo(): IncDec {
var x = IncDec()
x = <!RESULT_TYPE_MISMATCH!>x++<!>
<!RESULT_TYPE_MISMATCH!>x++<!>
x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
return x
}
@@ -3027,6 +3027,20 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.INC_DEC_SHOULD_NOT_RETURN_UNIT) { firDiagnostic ->
IncDecShouldNotReturnUnitImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT) { firDiagnostic ->
AssignmentOperatorShouldReturnUnitImpl(
firSymbolBuilder.functionLikeBuilder.buildFunctionSymbol(firDiagnostic.a.fir),
firDiagnostic.b,
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.TOPLEVEL_TYPEALIASES_ONLY) { firDiagnostic ->
ToplevelTypealiasesOnlyImpl(
firDiagnostic as FirPsiDiagnostic,
@@ -2113,6 +2113,16 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val rightType: KtType
}
abstract class IncDecShouldNotReturnUnit : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = IncDecShouldNotReturnUnit::class
}
abstract class AssignmentOperatorShouldReturnUnit : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = AssignmentOperatorShouldReturnUnit::class
abstract val functionSymbol: KtFunctionLikeSymbol
abstract val operator: String
}
abstract class ToplevelTypealiasesOnly : KtFirDiagnostic<KtTypeAlias>() {
override val diagnosticClass get() = ToplevelTypealiasesOnly::class
}
@@ -3401,6 +3401,22 @@ internal class IncompatibleEnumComparisonErrorImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class IncDecShouldNotReturnUnitImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.IncDecShouldNotReturnUnit(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class AssignmentOperatorShouldReturnUnitImpl(
override val functionSymbol: KtFunctionLikeSymbol,
override val operator: String,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.AssignmentOperatorShouldReturnUnit(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ToplevelTypealiasesOnlyImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,