[FIR] Disallow operators not on functions

Introduce `NOT_FUNCTION_AS_OPERATOR` and use it instead of `PROPERTY_AS_OPERATOR`

^KT-65881 Fixed

Merge-request: KT-MR-14547
This commit is contained in:
Ivan Kochurkin
2024-03-08 10:07:23 +00:00
committed by Space Team
parent df9d59851d
commit 2d4f4b9bb5
31 changed files with 440 additions and 100 deletions
@@ -1023,6 +1023,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
runTest("compiler/testData/diagnostics/tests/OperatorChecks.kt");
}
@Test
@TestMetadata("OperatorConventions.kt")
public void testOperatorConventions() {
runTest("compiler/testData/diagnostics/tests/OperatorConventions.kt");
}
@Test
@TestMetadata("Operators.kt")
public void testOperators() {
@@ -1023,6 +1023,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
runTest("compiler/testData/diagnostics/tests/OperatorChecks.kt");
}
@Test
@TestMetadata("OperatorConventions.kt")
public void testOperatorConventions() {
runTest("compiler/testData/diagnostics/tests/OperatorConventions.kt");
}
@Test
@TestMetadata("Operators.kt")
public void testOperators() {
@@ -1585,8 +1585,9 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
parameter<FirNamedFunctionSymbol>("functionSymbol")
parameter<String>("operator")
}
val PROPERTY_AS_OPERATOR by error<PsiElement>(PositioningStrategy.OPERATOR) {
parameter<FirPropertySymbol>("property")
val NOT_FUNCTION_AS_OPERATOR by error<PsiElement>(PositioningStrategy.OPERATOR) {
parameter<String>("elementName")
parameter<FirBasedSymbol<*>>("elementSymbol")
}
val DSL_SCOPE_VIOLATION by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) {
parameter<FirBasedSymbol<*>>("calleeSymbol")
@@ -796,7 +796,7 @@ object FirErrors {
val IMPLICIT_BOXING_IN_IDENTITY_EQUALS: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType> by warning2<KtElement, ConeKotlinType, ConeKotlinType>()
val INC_DEC_SHOULD_NOT_RETURN_UNIT: KtDiagnosticFactory0 by error0<KtExpression>(SourceElementPositioningStrategies.OPERATOR)
val ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT: KtDiagnosticFactory2<FirNamedFunctionSymbol, String> by error2<KtExpression, FirNamedFunctionSymbol, String>(SourceElementPositioningStrategies.OPERATOR)
val PROPERTY_AS_OPERATOR: KtDiagnosticFactory1<FirPropertySymbol> by error1<PsiElement, FirPropertySymbol>(SourceElementPositioningStrategies.OPERATOR)
val NOT_FUNCTION_AS_OPERATOR: KtDiagnosticFactory2<String, FirBasedSymbol<*>> by error2<PsiElement, String, FirBasedSymbol<*>>(SourceElementPositioningStrategies.OPERATOR)
val DSL_SCOPE_VIOLATION: KtDiagnosticFactory1<FirBasedSymbol<*>> by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
// Type alias
@@ -533,7 +533,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"FORBIDDEN_IDENTITY_EQUALS",
"INC_DEC_SHOULD_NOT_RETURN_UNIT",
"ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT",
"PROPERTY_AS_OPERATOR",
"NOT_FUNCTION_AS_OPERATOR",
"DSL_SCOPE_VIOLATION",
"TOPLEVEL_TYPEALIASES_ONLY",
"RECURSIVE_TYPEALIAS_EXPANSION",
@@ -11,23 +11,21 @@ import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.expressions.unwrapSmartcastExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConePropertyAsOperator
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeNotFunctionAsOperator
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeDynamicType
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
object FirConventionFunctionCallChecker : FirFunctionCallChecker(MppCheckerKind.Common) {
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
// PROPERTY_AS_OPERATOR can only happen for function calls and it's reported on the receiver expression.
checkPropertyAsOperator(expression, expression.dispatchReceiver, context, reporter)
checkPropertyAsOperator(expression, expression.extensionReceiver, context, reporter)
checkNotFunctionAsOperator(expression, expression.dispatchReceiver, context, reporter)
checkNotFunctionAsOperator(expression, expression.extensionReceiver, context, reporter)
val calleeReference = expression.calleeReference as? FirErrorNamedReference ?: return
val diagnostic = calleeReference.diagnostic as? ConeUnresolvedNameError ?: return
@@ -39,7 +37,7 @@ object FirConventionFunctionCallChecker : FirFunctionCallChecker(MppCheckerKind.
}
}
private fun checkPropertyAsOperator(
private fun checkNotFunctionAsOperator(
callExpression: FirFunctionCall,
receiver: FirExpression?,
context: CheckerContext,
@@ -47,9 +45,27 @@ object FirConventionFunctionCallChecker : FirFunctionCallChecker(MppCheckerKind.
) {
if (callExpression.dispatchReceiver?.resolvedType is ConeDynamicType) return
// KT-61905: TODO: Return also in case of error type.
val unwrapped = receiver?.unwrapSmartcastExpression()
if (unwrapped !is FirPropertyAccessExpression) return
val diagnostic = unwrapped.nonFatalDiagnostics.firstIsInstanceOrNull<ConePropertyAsOperator>() ?: return
reporter.reportOn(callExpression.calleeReference.source, FirErrors.PROPERTY_AS_OPERATOR, diagnostic.symbol, context)
val unwrapped = receiver?.unwrapSmartcastExpression() ?: return
val nonFatalDiagnostics = when (unwrapped) {
is FirQualifiedAccessExpression -> unwrapped.nonFatalDiagnostics
is FirResolvedQualifier -> unwrapped.nonFatalDiagnostics
else -> return
}
val diagnosticSymbol = nonFatalDiagnostics.firstIsInstanceOrNull<ConeNotFunctionAsOperator>()?.symbol ?: return
when {
unwrapped.resolvedType.classId!!.shortClassName == OperatorNameConventions.ITERATOR -> {
reporter.reportOn(unwrapped.source, FirErrors.ITERATOR_MISSING, context)
}
else -> {
// NOT_FUNCTION_AS_OPERATOR can only happen for function calls and it's reported on the receiver expression.
reporter.reportOn(
callExpression.calleeReference.source,
FirErrors.NOT_FUNCTION_AS_OPERATOR,
if (diagnosticSymbol is FirPropertySymbol) "Property" else "Object",
diagnosticSymbol,
context
)
}
}
}
}
@@ -37,7 +37,6 @@ import org.jetbrains.kotlin.fir.resolve.calls.UnsafeCall
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.util.OperatorNameConventions
object FirForLoopChecker : FirBlockChecker(MppCheckerKind.Common) {
@@ -451,6 +451,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_FUNCTION_LA
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_LOOP_LABEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_MULTIPLATFORM_COMPILATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_FUNCTION_AS_OPERATOR
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_SUPPORTED_INLINE_PARAMETER_IN_INLINE_PARAMETER_DEFAULT_VALUE
@@ -521,7 +522,6 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PRIVATE_SETTER_FO
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PRIVATE_SETTER_FOR_OPEN_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROJECTION_IN_IMMEDIATE_ARGUMENT_TO_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_AS_OPERATOR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_FIELD_DECLARATION_MISSING_INITIALIZER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_INITIALIZER_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_INITIALIZER_NO_BACKING_FIELD
@@ -2410,8 +2410,9 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
TO_STRING
)
map.put(
PROPERTY_AS_OPERATOR,
"Property ''{0}'' cannot be used as operator.",
NOT_FUNCTION_AS_OPERATOR,
"{0} ''{1}'' cannot be used as operator.",
STRING,
SYMBOL
)
map.put(
@@ -124,15 +124,12 @@ class CandidateFactory private constructor(
result.addDiagnostic(NoCompanionObject)
}
if (callInfo.origin == FirFunctionCallOrigin.Operator) {
val propertySymbol = when {
symbol is FirPropertySymbol -> symbol
callInfo.candidateForCommonInvokeReceiver != null -> callInfo.candidateForCommonInvokeReceiver.symbol as? FirPropertySymbol
else -> null
}
if (propertySymbol != null) {
// Flag all property references that are resolved from an convention operator call.
result.addDiagnostic(PropertyAsOperator(propertySymbol))
val normalizedSymbol = when (symbol) {
!is FirFunctionSymbol -> symbol
else -> callInfo.candidateForCommonInvokeReceiver?.symbol?.takeIf { it !is FirFunctionSymbol }
}
// Flag all references that are resolved from an convention operator call.
normalizedSymbol?.let { result.addDiagnostic(NotFunctionAsOperator(normalizedSymbol)) }
}
if (symbol is FirPropertySymbol &&
!context.session.languageVersionSettings.supportsFeature(LanguageFeature.PrioritizedEnumEntries)
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.builder.FirPropertyAccessExpressionBuilder
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConePropertyAsOperator
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeNotFunctionAsOperator
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.runIf
internal class FirInvokeResolveTowerExtension(
private val context: ResolutionContext,
@@ -288,18 +289,26 @@ private fun BodyResolveComponents.createExplicitReceiverForInvoke(
invokeBuiltinExtensionMode: Boolean,
extensionReceiverExpression: FirExpression?
): FirExpression? {
val notFunctionAsOperatorDiagnostics = runIf (candidate.currentApplicability == CandidateApplicability.K2_NOT_FUNCTION_AS_OPERATOR) {
candidate.diagnostics.filterIsInstance<NotFunctionAsOperator>().map { ConeNotFunctionAsOperator(it.symbol) }
} ?: emptyList()
return when (val symbol = candidate.symbol) {
is FirCallableSymbol<*> -> createExplicitReceiverForInvokeByCallable(
candidate, info, invokeBuiltinExtensionMode, extensionReceiverExpression, symbol
candidate, info, invokeBuiltinExtensionMode, extensionReceiverExpression, symbol, notFunctionAsOperatorDiagnostics
)
is FirRegularClassSymbol -> buildResolvedQualifierForClass(
symbol,
sourceElement = info.fakeSourceForImplicitInvokeCallReceiver
sourceElement = info.fakeSourceForImplicitInvokeCallReceiver,
nonFatalDiagnostics = notFunctionAsOperatorDiagnostics,
)
is FirTypeAliasSymbol -> {
val type = symbol.fir.expandedTypeRef.coneTypeUnsafe<ConeClassLikeType>().fullyExpandedType(session)
val expansionRegularClassSymbol = type.lookupTag.toSymbol(session) ?: return null
buildResolvedQualifierForClass(expansionRegularClassSymbol, sourceElement = symbol.fir.source)
buildResolvedQualifierForClass(
expansionRegularClassSymbol,
sourceElement = symbol.fir.source,
nonFatalDiagnostics = notFunctionAsOperatorDiagnostics,
)
}
else -> throw AssertionError()
}
@@ -310,7 +319,8 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable(
info: CallInfo,
invokeBuiltinExtensionMode: Boolean,
extensionReceiverExpression: FirExpression?,
symbol: FirCallableSymbol<*>
symbol: FirCallableSymbol<*>,
nonFatalDiagnostics: List<ConeNotFunctionAsOperator>,
): FirExpression {
return FirPropertyAccessExpressionBuilder().apply {
val fakeSource = info.fakeSourceForImplicitInvokeCallReceiver
@@ -337,9 +347,7 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable(
explicitReceiver = info.explicitReceiver
}
if (candidate.currentApplicability == CandidateApplicability.K2_PROPERTY_AS_OPERATOR) {
nonFatalDiagnostics.add(ConePropertyAsOperator(candidate.symbol as FirPropertySymbol))
}
this.nonFatalDiagnostics.addAll(nonFatalDiagnostics)
candidate.updateSourcesOfReceivers()
@@ -170,11 +170,11 @@ class FirCallCompletionResultsWriterTransformer(
qualifiedAccessExpression.replaceContextReceiverArguments(subCandidate.contextReceiverArguments())
subCandidate.diagnostics.firstIsInstanceOrNull<PropertyAsOperator>()?.let { propertyAsOperator ->
val conePropertyAsOperator = ConePropertyAsOperator(propertyAsOperator.propertySymbol)
subCandidate.diagnostics.firstIsInstanceOrNull<NotFunctionAsOperator>()?.let { propertyAsOperator ->
val coneNotFunctionAsOperator = ConeNotFunctionAsOperator(propertyAsOperator.symbol)
val nonFatalDiagnostics: List<ConeDiagnostic> = buildList {
addAll(qualifiedAccessExpression.nonFatalDiagnostics)
add(conePropertyAsOperator)
add(coneNotFunctionAsOperator)
}
qualifiedAccessExpression.replaceNonFatalDiagnostics(nonFatalDiagnostics)
}
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariable
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
@@ -136,7 +135,7 @@ class OperatorCallOfConstructor(val constructor: FirConstructorSymbol) : Resolut
class InferenceError(val constraintError: ConstraintSystemError) : ResolutionDiagnostic(constraintError.applicability)
class Unsupported(val message: String, val source: KtSourceElement?) : ResolutionDiagnostic(K2_UNSUPPORTED)
class PropertyAsOperator(val propertySymbol: FirPropertySymbol) : ResolutionDiagnostic(K2_PROPERTY_AS_OPERATOR)
class NotFunctionAsOperator(val symbol: FirBasedSymbol<*>) : ResolutionDiagnostic(K2_NOT_FUNCTION_AS_OPERATOR)
class DslScopeViolation(val calleeSymbol: FirBasedSymbol<*>) : ResolutionDiagnostic(RESOLVED_WITH_ERROR)
@@ -350,8 +350,8 @@ class ConeLocalVariableNoTypeOrInitializer(val variable: FirVariable) : ConeDiag
override val reason: String get() = "Cannot infer variable type without initializer / getter / delegate"
}
class ConePropertyAsOperator(val symbol: FirPropertySymbol) : ConeDiagnostic {
override val reason: String get() = "Cannot use a property as an operator"
class ConeNotFunctionAsOperator(val symbol: FirBasedSymbol<*>) : ConeDiagnostic {
override val reason: String get() = "Cannot use not function as an operator"
}
class ConeUnknownLambdaParameterTypeDiagnostic : ConeDiagnostic {