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>("leftType")
parameter<ConeKotlinType>("rightType") 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") { 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 by error3<KtBinaryExpression, String, ConeKotlinType, ConeKotlinType>()
val EQUALITY_NOT_APPLICABLE_WARNING by warning3<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 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 // Type alias
val TOPLEVEL_TYPEALIASES_ONLY by error0<KtTypeAlias>() val TOPLEVEL_TYPEALIASES_ONLY by error0<KtTypeAlias>()
@@ -50,7 +50,8 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirConventionFunctionCallChecker, FirConventionFunctionCallChecker,
FirDivisionByZeroChecker, FirDivisionByZeroChecker,
FirConstructorCallChecker, FirConstructorCallChecker,
FirSpreadOfNullableChecker FirSpreadOfNullableChecker,
FirAssignmentOperatorCallChecker,
) )
override val tryExpressionCheckers: Set<FirTryExpressionChecker> 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.analysis.getChild
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.* 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.impl.FirEmptyExpressionBlock
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.SessionHolder import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
@@ -578,7 +581,11 @@ fun checkTypeMismatch(
rValueType = lValueType rValueType = lValueType
lValueType = tempType 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 -> { else -> {
reporter.reportOn(source, FirErrors.ASSIGNMENT_TYPE_MISMATCH, lValueType, rValueType, context) 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.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.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.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.ASSIGNMENT_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BACKING_FIELD_IN_INTERFACE 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.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_BOUNDS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCONSISTENT_TYPE_PARAMETER_VALUES 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.INFERENCE_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFIX_MODIFIER_REQUIRED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFIX_MODIFIER_REQUIRED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZATION_BEFORE_DECLARATION 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.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.INSTANCE_ACCESS_BEFORE_SUPER_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INTERFACE_WITH_SUPERCLASS 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_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_IF_AS_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVISIBLE_REFERENCE 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_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_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_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_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.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_COMPANION_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ELSE_IN_WHEN import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ELSE_IN_WHEN
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_GET_METHOD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_GET_METHOD
@@ -1471,6 +1473,13 @@ class FirDefaultErrorMessages {
RENDER_TYPE, RENDER_TYPE,
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 // Type alias
map.put(TOPLEVEL_TYPEALIASES_ONLY, "Nested and local type aliases are not supported") 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 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( val ASSIGNMENTS_TO_SIMPLE_OPERATOR: Map<FirOperation, Name> = EnumMap(
mapOf( mapOf(
+8 -8
View File
@@ -35,12 +35,12 @@ class UnitIncDec() {
fun testUnitIncDec() { fun testUnitIncDec() {
var x = UnitIncDec() var x = UnitIncDec()
<!RESULT_TYPE_MISMATCH!>x++<!> x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
<!RESULT_TYPE_MISMATCH!>++x<!> <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>x
<!RESULT_TYPE_MISMATCH!>x--<!> x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>
<!RESULT_TYPE_MISMATCH!>--x<!> <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>x
x = <!RESULT_TYPE_MISMATCH!>x++<!> x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
x = <!RESULT_TYPE_MISMATCH!>x--<!> x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>
x = <!RESULT_TYPE_MISMATCH!>++x<!> x = <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>x
x = <!RESULT_TYPE_MISMATCH!>--x<!> x = <!INC_DEC_SHOULD_NOT_RETURN_UNIT!>--<!>x
} }
@@ -29,7 +29,7 @@ class Test()
fun test() fun test()
{ {
val control = Control() val control = Control()
control.MouseMoved += { it.X } // here control.MouseMoved <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>+=<!> { it.X } // here
control.MouseMoved.plusAssign( { it.X } ) // ok control.MouseMoved.plusAssign( { it.X } ) // ok
} }
} }
@@ -20,9 +20,9 @@ class B() {
fun main() { fun main() {
var c = B() var c = B()
c += B() c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>+=<!> B()
c *= B() c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>*=<!> B()
c /= B() c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>/=<!> B()
c -= B() c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>-=<!> B()
c %= B() c <!ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT!>%=<!> B()
} }
@@ -4,7 +4,7 @@ class IncDec {
fun foo(): IncDec { fun foo(): IncDec {
var x = IncDec() var x = IncDec()
x = <!RESULT_TYPE_MISMATCH!>x++<!> x = x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
<!RESULT_TYPE_MISMATCH!>x++<!> x<!INC_DEC_SHOULD_NOT_RETURN_UNIT!>++<!>
return x return x
} }
@@ -3027,6 +3027,20 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, 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 -> add(FirErrors.TOPLEVEL_TYPEALIASES_ONLY) { firDiagnostic ->
ToplevelTypealiasesOnlyImpl( ToplevelTypealiasesOnlyImpl(
firDiagnostic as FirPsiDiagnostic, firDiagnostic as FirPsiDiagnostic,
@@ -2113,6 +2113,16 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val rightType: KtType 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>() { abstract class ToplevelTypealiasesOnly : KtFirDiagnostic<KtTypeAlias>() {
override val diagnosticClass get() = ToplevelTypealiasesOnly::class override val diagnosticClass get() = ToplevelTypealiasesOnly::class
} }
@@ -3401,6 +3401,22 @@ internal class IncompatibleEnumComparisonErrorImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) 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( internal class ToplevelTypealiasesOnlyImpl(
firDiagnostic: FirPsiDiagnostic, firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken, override val token: ValidityToken,