diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 16c762b1616..7ddfee6cec8 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -1115,6 +1115,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { parameter("leftType") parameter("rightType") } + val INC_DEC_SHOULD_NOT_RETURN_UNIT by error(PositioningStrategy.OPERATOR) + val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error(PositioningStrategy.OPERATOR) { + parameter("functionSymbol") + parameter("operator") + } } val TYPE_ALIAS by object : DiagnosticGroup("Type alias") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 49266fcf60b..f98113eca18 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -577,6 +577,8 @@ object FirErrors { val EQUALITY_NOT_APPLICABLE by error3() val EQUALITY_NOT_APPLICABLE_WARNING by warning3() val INCOMPATIBLE_ENUM_COMPARISON_ERROR by error2() + val INC_DEC_SHOULD_NOT_RETURN_UNIT by error0(SourceElementPositioningStrategies.OPERATOR) + val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT by error2(SourceElementPositioningStrategies.OPERATOR) // Type alias val TOPLEVEL_TYPEALIASES_ONLY by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt index da8b7013fc0..6f98eaab0a6 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt @@ -50,7 +50,8 @@ object CommonExpressionCheckers : ExpressionCheckers() { FirConventionFunctionCallChecker, FirDivisionByZeroChecker, FirConstructorCallChecker, - FirSpreadOfNullableChecker + FirSpreadOfNullableChecker, + FirAssignmentOperatorCallChecker, ) override val tryExpressionCheckers: Set diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 870490fae0f..e3c582a4f0d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -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) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirAssignmentOperatorCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirAssignmentOperatorCallChecker.kt new file mode 100644 index 00000000000..d6810ecd244 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirAssignmentOperatorCallChecker.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 8bc8c489322..e2f5617847e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -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") diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirOperation.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirOperation.kt index 48ca42b448f..3d03504c3cf 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirOperation.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/FirOperation.kt @@ -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 = EnumMap( mapOf( diff --git a/compiler/testData/diagnostics/tests/IncDec.fir.kt b/compiler/testData/diagnostics/tests/IncDec.fir.kt index ed80b43471c..00153790c68 100644 --- a/compiler/testData/diagnostics/tests/IncDec.fir.kt +++ b/compiler/testData/diagnostics/tests/IncDec.fir.kt @@ -35,12 +35,12 @@ class UnitIncDec() { fun testUnitIncDec() { var x = UnitIncDec() - x++ - ++x - x-- - --x - x = x++ - x = x-- - x = ++x - x = --x + x++ + ++x + x-- + --x + x = x++ + x = x-- + x = ++x + x = --x } diff --git a/compiler/testData/diagnostics/tests/operatorsOverloading/kt1028.fir.kt b/compiler/testData/diagnostics/tests/operatorsOverloading/kt1028.fir.kt index 9455690baf2..e4f9c170868 100644 --- a/compiler/testData/diagnostics/tests/operatorsOverloading/kt1028.fir.kt +++ b/compiler/testData/diagnostics/tests/operatorsOverloading/kt1028.fir.kt @@ -29,7 +29,7 @@ class Test() fun test() { val control = Control() - control.MouseMoved += { it.X } // here + control.MouseMoved += { it.X } // here control.MouseMoved.plusAssign( { it.X } ) // ok } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/regressions/kt618.fir.kt b/compiler/testData/diagnostics/tests/regressions/kt618.fir.kt index 5c823fef416..b631c9dee80 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt618.fir.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt618.fir.kt @@ -20,9 +20,9 @@ class B() { fun main() { var c = B() - c += B() - c *= B() - c /= B() - c -= B() - c %= B() -} \ No newline at end of file + c += B() + c *= B() + c /= B() + c -= B() + c %= B() +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/incDecToNull.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/incDecToNull.fir.kt index db4cd7bc2c9..d693218c103 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/incDecToNull.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/incDecToNull.fir.kt @@ -4,7 +4,7 @@ class IncDec { fun foo(): IncDec { var x = IncDec() - x = x++ - x++ + x = x++ + x++ return x } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 28138755d2b..b6f82d672b4 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -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, diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index dacb46a7361..c58ded98b86 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -2113,6 +2113,16 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val rightType: KtType } + abstract class IncDecShouldNotReturnUnit : KtFirDiagnostic() { + override val diagnosticClass get() = IncDecShouldNotReturnUnit::class + } + + abstract class AssignmentOperatorShouldReturnUnit : KtFirDiagnostic() { + override val diagnosticClass get() = AssignmentOperatorShouldReturnUnit::class + abstract val functionSymbol: KtFunctionLikeSymbol + abstract val operator: String + } + abstract class ToplevelTypealiasesOnly : KtFirDiagnostic() { override val diagnosticClass get() = ToplevelTypealiasesOnly::class } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 2e6aa4b72da..67d5f7a1b6a 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -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 { + 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 { + override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) +} + internal class ToplevelTypealiasesOnlyImpl( firDiagnostic: FirPsiDiagnostic, override val token: ValidityToken,