FIR checker: report PROPERTY_AS_OPERATOR

This commit is contained in:
Tianyu Geng
2021-08-31 16:57:18 -07:00
committed by Mikhail Glukhikh
parent 4915d8dda3
commit 05fbed68e2
21 changed files with 154 additions and 27 deletions
@@ -1145,6 +1145,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 TYPE_ALIAS by object : DiagnosticGroup("Type alias") {
@@ -596,6 +596,7 @@ object FirErrors {
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)
val PROPERTY_AS_OPERATOR by error1<PsiElement, FirPropertySymbol>(SourceElementPositioningStrategies.OPERATOR)
// Type alias
val TOPLEVEL_TYPEALIASES_ONLY by error0<KtTypeAlias>()
@@ -6,17 +6,26 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirRealSourceElementKind
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.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConePropertyAsOperator
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
object FirConventionFunctionCallChecker : FirFunctionCallChecker() {
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)
val calleeReference = expression.calleeReference as? FirErrorNamedReference ?: return
val diagnostic = calleeReference.diagnostic as? ConeUnresolvedNameError ?: return
@@ -27,4 +36,24 @@ object FirConventionFunctionCallChecker : FirFunctionCallChecker() {
}
}
}
private fun checkPropertyAsOperator(
callExpression: FirFunctionCall,
receiver: FirExpression,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val sourceKind = callExpression.source?.kind
if (sourceKind !is FirRealSourceElementKind &&
sourceKind !is FirFakeSourceElementKind.GeneratedComparisonExpression &&
sourceKind !is FirFakeSourceElementKind.DesugaredCompoundAssignment
) return
val unwrapped = when (receiver) {
is FirExpressionWithSmartcast -> receiver.originalExpression
else -> receiver
}
if (unwrapped !is FirPropertyAccessExpression) return
val diagnostic = unwrapped.nonFatalDiagnostics.firstIsInstanceOrNull<ConePropertyAsOperator>() ?: return
reporter.reportOn(callExpression.calleeReference.source, FirErrors.PROPERTY_AS_OPERATOR, diagnostic.symbol, context)
}
}
@@ -332,6 +332,7 @@ 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_INITIALIZER_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_INITIALIZER_NO_BACKING_FIELD
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.PROPERTY_TYPE_MISMATCH_BY_DELEGATION
@@ -1515,6 +1516,11 @@ class FirDefaultErrorMessages {
SYMBOL,
TO_STRING
)
map.put(
PROPERTY_AS_OPERATOR,
"Property ''{0}'' cannot be used as an operator.",
SYMBOL
)
// Type alias
map.put(TOPLEVEL_TYPEALIASES_ONLY, "Nested and local type aliases are not supported")
@@ -84,11 +84,11 @@ class FirCallResolver(
return components.context.withIncrementedQualifierPartIndex {
val name = functionCall.calleeReference.name
val result = collectCandidates(functionCall, name)
val result = collectCandidates(functionCall, name, origin = functionCall.origin)
var forceCandidates: Collection<Candidate>? = null
if (result.candidates.isEmpty()) {
val newResult = collectCandidates(functionCall, name, CallKind.VariableAccess)
val newResult = collectCandidates(functionCall, name, CallKind.VariableAccess, origin = functionCall.origin)
if (newResult.candidates.isNotEmpty()) {
forceCandidates = newResult.candidates
}
@@ -152,7 +152,8 @@ class FirCallResolver(
private fun <T : FirQualifiedAccess> collectCandidates(
qualifiedAccess: T,
name: Name,
forceCallKind: CallKind? = null
forceCallKind: CallKind? = null,
origin: FirFunctionCallOrigin = FirFunctionCallOrigin.Regular
): ResolutionResult {
val explicitReceiver = qualifiedAccess.explicitReceiver
val argumentList = (qualifiedAccess as? FirFunctionCall)?.argumentList ?: FirEmptyArgumentList
@@ -172,6 +173,7 @@ class FirCallResolver(
session,
components.file,
transformer.components.containingDeclarations,
origin = origin
)
towerResolver.reset()
val result = towerResolver.runResolver(info, transformer.resolutionContext)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirArgumentList
import org.jetbrains.kotlin.fir.expressions.FirEmptyArgumentList
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.expressions.builder.buildArgumentList
import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionStub
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
@@ -55,7 +56,8 @@ data class CallInfo(
// Four properties for callable references only
val expectedType: ConeKotlinType? = null,
val outerCSBuilder: ConstraintSystemBuilder? = null,
val lhs: DoubleColonLHS? = null
val lhs: DoubleColonLHS? = null,
val origin: FirFunctionCallOrigin = FirFunctionCallOrigin.Regular,
) {
val arguments: List<FirExpression> get() = argumentList.arguments
@@ -82,6 +82,10 @@ class CandidateFactory private constructor(
) {
result.addDiagnostic(NoCompanionObject)
}
if (callInfo.origin == FirFunctionCallOrigin.Operator && symbol is FirPropertySymbol) {
// Flag all property references that are resolved from an convention operator call.
result.addDiagnostic(PropertyAsOperator)
}
return result
}
@@ -98,4 +98,6 @@ class ManyLambdaExpressionArguments(
class InfixCallOfNonInfixFunction(val function: FirNamedFunctionSymbol) : ResolutionDiagnostic(INAPPLICABLE_MODIFIER)
class OperatorCallOfNonOperatorFunction(val function: FirNamedFunctionSymbol) : ResolutionDiagnostic(INAPPLICABLE_MODIFIER)
class Unsupported(val message: String, val source: FirSourceElement? = null) : ResolutionDiagnostic(UNSUPPORTED)
class Unsupported(val message: String, val source: FirSourceElement? = null) : ResolutionDiagnostic(UNSUPPORTED)
object PropertyAsOperator : ResolutionDiagnostic(PROPERTY_AS_OPERATOR)
@@ -11,14 +11,13 @@ 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.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConePropertyAsOperator
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.fir.types.isExtensionFunctionType
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.util.OperatorNameConventions
internal class FirInvokeResolveTowerExtension(
@@ -291,6 +290,10 @@ private fun BodyResolveComponents.createExplicitReceiverForInvokeByCallable(
// NB: this should fix problem in DFA (KT-36014)
explicitReceiver = info.explicitReceiver
}
if (candidate.currentApplicability == CandidateApplicability.PROPERTY_AS_OPERATOR) {
nonFatalDiagnostics.add(ConePropertyAsOperator(candidate.symbol as FirPropertySymbol))
}
}.build().let(::transformQualifiedAccessUsingSmartcastInfo)
}
@@ -206,6 +206,10 @@ 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"
}
private fun describeSymbol(symbol: FirBasedSymbol<*>): String {
return when (symbol) {
is FirClassLikeSymbol<*> -> symbol.classId.asString()
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isSuspend
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirPropertyAccessExpressionImpl
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildResolvedCallableReference
@@ -26,6 +27,7 @@ import org.jetbrains.kotlin.fir.resolve.createFunctionalType
import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeConstraintSystemHasContradiction
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeInapplicableCandidateError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConePropertyAsOperator
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeTypeParameterInQualifiedAccess
import org.jetbrains.kotlin.fir.resolve.firProvider
import org.jetbrains.kotlin.fir.resolve.inference.*
@@ -39,6 +41,7 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperatorCall
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.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
@@ -48,6 +51,7 @@ import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.Variance
@@ -126,6 +130,9 @@ class FirCallCompletionResultsWriterTransformer(
)
.transformDispatchReceiver(StoreReceiver, subCandidate.dispatchReceiverExpression())
.transformExtensionReceiver(StoreReceiver, subCandidate.extensionReceiverExpression()) as T
if (result is FirPropertyAccessExpressionImpl && calleeReference.candidate.currentApplicability == CandidateApplicability.PROPERTY_AS_OPERATOR) {
result.nonFatalDiagnostics.add(ConePropertyAsOperator(calleeReference.candidate.symbol as FirPropertySymbol))
}
if (result is FirQualifiedAccessExpression) {
result.replaceTypeRef(typeRef)
@@ -20,6 +20,7 @@ enum class CandidateApplicability {
UNSTABLE_SMARTCAST, // unstable smart cast
CONVENTION_ERROR, // missing infix, operator etc
RESOLVED_LOW_PRIORITY,
PROPERTY_AS_OPERATOR, // using property of functional type as an operator. From resolution perspective, this is considered successful.
RESOLVED_NEED_PRESERVE_COMPATIBILITY, // call resolved successfully, but using new features that changes resolve
RESOLVED_WITH_ERROR, // call has error, but it is still successful from resolution perspective
RESOLVED, // call success or has uncompleted inference or in other words possible successful candidate
-8
View File
@@ -1,8 +0,0 @@
// !LANGUAGE: +NewInference
val Int.plusAssign: (Int) -> Unit
get() = {}
fun main() {
1 += 2
}
+1
View File
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +NewInference
val Int.plusAssign: (Int) -> Unit
@@ -73,19 +73,44 @@ class Arifm() {
operator fun invoke(i: Int) {}
}
class Contains() {
operator fun invoke(i: Int): Boolean = true
}
class Getter() {
operator fun invoke(i: Int): Int = 1
}
class Setter() {
operator fun invoke(i: Int, j: Int) {}
}
class Unary<B>() {
operator fun invoke(): B = TODO()
}
class B() {
val plus = Arifm()
val minus = Arifm()
val compareTo = Comp()
val contains = Contains()
val get = Getter()
val set = Setter()
val unaryPlus = Unary<B>()
}
fun case3() {
var b = B()
b + 5
b - 5
b < 5
b >= 5
b <!PROPERTY_AS_OPERATOR!>+<!> 5
b <!PROPERTY_AS_OPERATOR!>-<!> 5
b <!PROPERTY_AS_OPERATOR!><<!> 5
b <!PROPERTY_AS_OPERATOR!>>=<!> 5
1 <!PROPERTY_AS_OPERATOR!>in<!> b
<!PROPERTY_AS_OPERATOR!>b[2]<!>
<!PROPERTY_AS_OPERATOR!>b[3]<!> = 4
<!PROPERTY_AS_OPERATOR!>+<!>b
}
// FILE: TestCase4.kt
/*
* TESTCASE NUMBER: 4
@@ -102,8 +127,8 @@ class B(val minusAssign: Assign = Assign()) {
fun case3() {
var b = B()
b += 2
b -= 3
b <!PROPERTY_AS_OPERATOR!>+=<!> 2
b <!PROPERTY_AS_OPERATOR!>-=<!> 3
}
// FILE: TestCase5.kt
@@ -142,7 +167,7 @@ class B(var a: Int = 0) {
val plus :E =TODO()
fun foo(b: B){
this + 1
this <!PROPERTY_AS_OPERATOR!>+<!> 1
}
operator fun invoke(value: Int) = B()
@@ -82,10 +82,30 @@ class Arifm() {
operator fun invoke(i: Int) {}
}
class Contains() {
operator fun invoke(i: Int): Boolean = true
}
class Getter() {
operator fun invoke(i: Int): Int = 1
}
class Setter() {
operator fun invoke(i: Int, j: Int) {}
}
class Unary<B>() {
operator fun invoke(): B = TODO()
}
class B() {
val plus = Arifm()
val minus = Arifm()
val compareTo = Comp()
val contains = Contains()
val get = Getter()
val set = Setter()
val unaryPlus = Unary<B>()
}
fun case3() {
@@ -94,7 +114,12 @@ fun case3() {
b <!PROPERTY_AS_OPERATOR!>-<!> 5
b <!PROPERTY_AS_OPERATOR!><<!> 5
b <!PROPERTY_AS_OPERATOR!>>=<!> 5
1 <!PROPERTY_AS_OPERATOR!>in<!> b
<!PROPERTY_AS_OPERATOR!>b[2]<!>
<!PROPERTY_AS_OPERATOR!>b[3]<!> = 4
<!PROPERTY_AS_OPERATOR!>+<!>b
}
// FILE: TestCase4.kt
/*
* TESTCASE NUMBER: 4
@@ -21,7 +21,7 @@ class B() {
fun case7(){
var b =B()
<!ASSIGN_OPERATOR_AMBIGUITY!>b+=1<!> //ASSIGN_OPERATOR_AMBIGUITY
this += 1 //ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT, PROPERTY_AS_OPERATOR
this <!PROPERTY_AS_OPERATOR!>+=<!> 1 //ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT, PROPERTY_AS_OPERATOR
}
}
@@ -14,7 +14,7 @@ class Case1() {
fun foo(e: E) {
if (e.plus != null) {
run { e + 1 }
run { e <!PROPERTY_AS_OPERATOR!>+<!> 1 }
/*
[PROPERTY_AS_OPERATOR] (ok)
@@ -22,7 +22,7 @@ class Case1() {
[UNSAFE_OPERATOR_CALL] (nok)
Operator call corresponds to a dot-qualified call 'e.plus(1)' which is not allowed on a nullable receiver 'e'.
*/
e + 1
e <!PROPERTY_AS_OPERATOR!>+<!> 1
e.plus.invoke(1) //ok
}
@@ -3131,6 +3131,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.PROPERTY_AS_OPERATOR) { firDiagnostic ->
PropertyAsOperatorImpl(
firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir),
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.TOPLEVEL_TYPEALIASES_ONLY) { firDiagnostic ->
ToplevelTypealiasesOnlyImpl(
firDiagnostic as FirPsiDiagnostic,
@@ -2186,6 +2186,11 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val operator: String
}
abstract class PropertyAsOperator : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = PropertyAsOperator::class
abstract val property: KtVariableSymbol
}
abstract class ToplevelTypealiasesOnly : KtFirDiagnostic<KtTypeAlias>() {
override val diagnosticClass get() = ToplevelTypealiasesOnly::class
}
@@ -3522,6 +3522,14 @@ internal class AssignmentOperatorShouldReturnUnitImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class PropertyAsOperatorImpl(
override val property: KtVariableSymbol,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.PropertyAsOperator(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ToplevelTypealiasesOnlyImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,