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 d5fae3d0951..1738edd5ae3 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 @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration +import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.WhenMissingCase import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.* @@ -62,7 +63,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList { val SUPER_IS_NOT_AN_EXPRESSION by error() val SUPER_NOT_AVAILABLE by error() val ABSTRACT_SUPER_CALL by error() - val INSTANCE_ACCESS_BEFORE_SUPER_CALL by error() { + val INSTANCE_ACCESS_BEFORE_SUPER_CALL by error { parameter("target") } } @@ -73,7 +74,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList { val RECURSION_IN_SUPERTYPES by error() val NOT_A_SUPERTYPE by error() val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE by error() - val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error() { + val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error { parameter>("otherSuperType") } val SUPERTYPE_INITIALIZED_IN_INTERFACE by error() @@ -155,7 +156,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList { val INAPPLICABLE_CANDIDATE by error { parameter>("candidate") } - val INAPPLICABLE_LATEINIT_MODIFIER by error() { + val INAPPLICABLE_LATEINIT_MODIFIER by error { parameter("reason") } } @@ -331,7 +332,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList { parameter>>("candidates") } - val COMPONENT_FUNCTION_ON_NULLABLE by error() { + val COMPONENT_FUNCTION_ON_NULLABLE by error { parameter("componentFunctionName") } @@ -357,11 +358,19 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList { val UNSAFE_CALL by error(PositioningStrategy.DOT_BY_SELECTOR) { parameter("receiverType") } - val UNSAFE_IMPLICIT_INVOKE_CALL by error() { + val UNSAFE_IMPLICIT_INVOKE_CALL by error { parameter("receiverType") } - // TODO: val UNSAFE_INFIX_CALL by ... - // TODO: val UNSAFE_OPERATOR_CALL by ... + val UNSAFE_INFIX_CALL by error { + parameter("lhs") + parameter("operator") + parameter("rhs") + } + val UNSAFE_OPERATOR_CALL by error { + parameter("lhs") + parameter("operator") + parameter("rhs") + } // TODO: val UNEXPECTED_SAFE_CALL by ... } 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 58f0043bd53..9d386bcdc7f 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 @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.SourceElementPositioningStr import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration +import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.WhenMissingCase import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol @@ -231,6 +232,8 @@ object FirErrors { // Nullability val UNSAFE_CALL by error1(SourceElementPositioningStrategies.DOT_BY_SELECTOR) val UNSAFE_IMPLICIT_INVOKE_CALL by error1() + val UNSAFE_INFIX_CALL by error3() + val UNSAFE_OPERATOR_CALL by error3() // When expressions val NO_ELSE_IN_WHEN by error1>(SourceElementPositioningStrategies.WHEN_EXPRESSION) 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 e8f15e5f7dc..6c4d88dc6c3 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 @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.AMBIGUOUS_CALLS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FQ_NAMES_IN_TYPES import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING @@ -158,6 +159,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_LABEL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_REFERENCE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_CALL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_IMPLICIT_INVOKE_CALL +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_INFIX_CALL +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_OPERATOR_CALL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNUSED_VARIABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER @@ -512,6 +515,21 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { "Reference has a nullable type ''{0}'', use explicit \"?.invoke\" to make a function-like call instead.", RENDER_TYPE ) + map.put( + UNSAFE_INFIX_CALL, + "Infix call corresponds to a dot-qualified call ''{0}.{1}({2})'' which is not allowed on a nullable receiver ''{0}''. " + + "Use ''?.''-qualified call instead", + FIR, + TO_STRING, + FIR + ) + map.put( + UNSAFE_OPERATOR_CALL, + "Operator call corresponds to a dot-qualified call ''{0}.{1}({2})'' which is not allowed on a nullable receiver ''{0}''. ", + FIR, + TO_STRING, + FIR + ) // When expressions map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index 52d7689207c..e0cd577497c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -5,12 +5,18 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics +import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.getChild +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.isInfix +import org.jetbrains.kotlin.fir.declarations.isOperator import org.jetbrains.kotlin.fir.diagnostics.* import org.jetbrains.kotlin.fir.resolve.calls.InapplicableWrongReceiver import org.jetbrains.kotlin.fir.resolve.diagnostics.* import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.resolve.calls.tower.isSuccess import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -58,7 +64,6 @@ private fun mapInapplicableCandidateError( source: FirSourceElement, ): FirDiagnostic<*> { // TODO: Need to distinguish SMARTCAST_IMPOSSIBLE - // TODO: handle other UNSAFE_* variants: infix, operator val rootCause = diagnostic.candidate.diagnostics.find { it.applicability == diagnostic.applicability } if (rootCause != null && rootCause is InapplicableWrongReceiver && @@ -68,6 +73,24 @@ private fun mapInapplicableCandidateError( if (diagnostic.candidate.callInfo.isImplicitInvoke) { return FirErrors.UNSAFE_IMPLICIT_INVOKE_CALL.on(source, rootCause.actualType!!) } + + val candidateFunction = diagnostic.candidate.symbol.fir as? FirSimpleFunction + val candidateFunctionName = candidateFunction?.name + val left = diagnostic.candidate.callInfo.explicitReceiver + val right = diagnostic.candidate.callInfo.argumentList.arguments.singleOrNull() + if (left != null && right != null && + source.elementType == KtNodeTypes.OPERATION_REFERENCE && + (candidateFunction?.isOperator == true || candidateFunction?.isInfix == true) + ) { + val operationToken = source.getChild(KtTokens.IDENTIFIER) + if (candidateFunction.isInfix && operationToken?.elementType == KtTokens.IDENTIFIER) { + return FirErrors.UNSAFE_INFIX_CALL.on(source, left, candidateFunctionName!!.asString(), right) + } + if (candidateFunction.isOperator && operationToken == null) { + return FirErrors.UNSAFE_OPERATOR_CALL.on(source, left, candidateFunctionName!!.asString(), right) + } + } + return FirErrors.UNSAFE_CALL.on(source, rootCause.actualType!!) } return FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol) diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/InfixCallNullability.fir.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/InfixCallNullability.fir.kt index 4f3edd28f85..0e415d6bb24 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/InfixCallNullability.fir.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/InfixCallNullability.fir.kt @@ -18,15 +18,15 @@ fun test(x : Int?, a : A?) { a.plus(1) a?.plus(1) - a plus 1 - a + 1 + a plus 1 + a + 1 -a a.unaryMinus() a?.unaryMinus() a.div(1) - a / 1 - a div 1 + a / 1 + a div 1 a?.div(1) a.times(1) @@ -34,8 +34,8 @@ fun test(x : Int?, a : A?) { a times 1 a?.times(1) - 1 in a - a contains 1 + 1 in a + a contains 1 a.contains(1) a?.contains(1) } diff --git a/compiler/testData/diagnostics/tests/regressions/kt282.fir.kt b/compiler/testData/diagnostics/tests/regressions/kt282.fir.kt index 69fae482fa7..3819249ce58 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt282.fir.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt282.fir.kt @@ -14,6 +14,6 @@ fun f(): Unit { val i : Int? = null i + 1 set + 1 - 1 in set + 1 in set 1 in 2 } diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt index 1cd64d4f5e7..4c34ecac3aa 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/open.fir.kt @@ -7,5 +7,5 @@ infix fun Int.bar(i: Int) = i fun test() { val p = A() // For open value properties, smart casts should not work - if (p.foo is Int) p.foo bar 11 + if (p.foo is Int) p.foo bar 11 }