diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 49b4bde62c2..7688522303d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -621,7 +621,7 @@ public interface Errors { DiagnosticFactory1 INVALID_CHARACTERS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 INAPPLICABLE_OPERATOR_MODIFIER = DiagnosticFactory1.create(ERROR); - DiagnosticFactory0 INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory1.create(ERROR); DiagnosticFactory2 OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index d8416c2e6be..b726e8979bb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -379,7 +379,7 @@ public class DefaultErrorMessages { MAP.put(INVALID_CHARACTERS, "Name {0}", STRING); MAP.put(INAPPLICABLE_OPERATOR_MODIFIER, "''operator'' modifier is inapplicable on this function: {0}", STRING); - MAP.put(INAPPLICABLE_INFIX_MODIFIER, "'infix' modifier is inapplicable on this function"); + MAP.put(INAPPLICABLE_INFIX_MODIFIER, "'infix' modifier is inapplicable on this function: {0}", STRING); MAP.put(OPERATOR_MODIFIER_REQUIRED, "''operator'' modifier is required on ''{0}'' in ''{1}''", NAME, STRING); MAP.put(INFIX_MODIFIER_REQUIRED, "''infix'' modifier is required on ''{0}'' in ''{1}''", NAME, STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt index 5b014886045..d0ef195331b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt @@ -22,7 +22,8 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue +import org.jetbrains.kotlin.util.CheckResult +import org.jetbrains.kotlin.util.InfixChecks class InfixModifierChecker : SimpleDeclarationChecker { @@ -36,17 +37,9 @@ class InfixModifierChecker : SimpleDeclarationChecker { if (!functionDescriptor.isInfix) return val modifier = declaration.modifierList?.getModifier(KtTokens.INFIX_KEYWORD) ?: return - if (!isApplicable(functionDescriptor)) { - diagnosticHolder.report(Errors.INAPPLICABLE_INFIX_MODIFIER.on(modifier)) - } + val checkResult = InfixChecks.check(functionDescriptor) + if (checkResult !is CheckResult.IllegalSignature) return + + diagnosticHolder.report(Errors.INAPPLICABLE_INFIX_MODIFIER.on(modifier, checkResult.error)) } - - private fun isApplicable(descriptor: FunctionDescriptor): Boolean { - if (descriptor.dispatchReceiverParameter == null && descriptor.extensionReceiverParameter == null) return false - if (descriptor.valueParameters.size != 1) return false - - val singleParameter = descriptor.valueParameters.first() - return !singleParameter.hasDefaultValue() && singleParameter.varargElementType == null - } - } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt index f534d8b36ec..ea807489e9a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt @@ -55,7 +55,7 @@ object OperatorModifierChecker { if (!functionDescriptor.isOperator) return val modifier = declaration.modifierList?.getModifier(KtTokens.OPERATOR_KEYWORD) ?: return - val checkResult = OperatorChecks.checkOperator(functionDescriptor) + val checkResult = OperatorChecks.check(functionDescriptor) if (checkResult.isSuccess) { if (functionDescriptor.name in COROUTINE_OPERATOR_NAMES && !languageFeatureSettings.supportsFeature(LanguageFeature.Coroutines)) { diff --git a/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt b/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt index 387c35c4121..c8df26ed36b 100644 --- a/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt +++ b/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt @@ -3,20 +3,25 @@ class Pair(val a: A, val b: B) infix fun A.to(that: B): Pair = Pair(this, that) -infix fun String.o1(o: String) = o -infix fun String.o2(o: String, o2: String? = null) = o -infix fun String.o3(o: String = "", o2: String? = null) = o +// OK +infix fun String.ok1(o: String) {} +class OkTest { + infix fun ok2(o: String) {} + infix fun String.ok3(o: String) {} +} -infix fun w1() {} -infix fun w2(s: String) {} -infix fun String.w3() {} -infix fun String.w4(a: Int, b: Int) {} -infix fun w5(a: Int, b: Int) {} +// Errors +infix fun String.e1(o: String, o2: String? = null) = o +infix fun String.e2(o: String = "", o2: String? = null) = o + +infix fun e3() {} +infix fun e4(s: String) {} +infix fun String.e5() {} +infix fun String.e6(a: Int, b: Int) {} +infix fun e7(a: Int, b: Int) {} class Example { - infix fun c1(s: String) {} - infix fun c2(s: String, a: Int = 0) {} - - infix fun cw1(s: String, a: Int) {} - infix fun sw2() {} + infix fun e8(s: String, a: Int = 0) {} + infix fun e9(s: String, a: Int) {} + infix fun e10() {} } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt b/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt index 4c73a55a4b6..f32cb28d411 100644 --- a/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt +++ b/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt @@ -1,26 +1,34 @@ package -public infix fun w1(): kotlin.Unit -public infix fun w2(/*0*/ s: kotlin.String): kotlin.Unit -public infix fun w5(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit -public infix fun kotlin.String.o1(/*0*/ o: kotlin.String): kotlin.String -public infix fun kotlin.String.o2(/*0*/ o: kotlin.String, /*1*/ o2: kotlin.String? = ...): kotlin.String -public infix fun kotlin.String.o3(/*0*/ o: kotlin.String = ..., /*1*/ o2: kotlin.String? = ...): kotlin.String +public infix fun e3(): kotlin.Unit +public infix fun e4(/*0*/ s: kotlin.String): kotlin.Unit +public infix fun e7(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit +public infix fun kotlin.String.e1(/*0*/ o: kotlin.String, /*1*/ o2: kotlin.String? = ...): kotlin.String +public infix fun kotlin.String.e2(/*0*/ o: kotlin.String = ..., /*1*/ o2: kotlin.String? = ...): kotlin.String +public infix fun kotlin.String.e5(): kotlin.Unit +public infix fun kotlin.String.e6(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit +public infix fun kotlin.String.ok1(/*0*/ o: kotlin.String): kotlin.Unit public infix fun A.to(/*0*/ that: B): Pair -public infix fun kotlin.String.w3(): kotlin.Unit -public infix fun kotlin.String.w4(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit public final class Example { public constructor Example() - public final infix fun c1(/*0*/ s: kotlin.String): kotlin.Unit - public final infix fun c2(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Int = ...): kotlin.Unit - public final infix fun cw1(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Int): kotlin.Unit + public final infix fun e10(): kotlin.Unit + public final infix fun e8(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Int = ...): kotlin.Unit + public final infix fun e9(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Int): kotlin.Unit public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int - public final infix fun sw2(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public final class OkTest { + public constructor OkTest() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final infix fun ok2(/*0*/ o: kotlin.String): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final infix fun kotlin.String.ok3(/*0*/ o: kotlin.String): kotlin.Unit +} + public final class Pair { public constructor Pair(/*0*/ a: A, /*1*/ b: B) public final val a: A diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaMethodDescriptor.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaMethodDescriptor.java index bc0751bb13e..1ba71541930 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaMethodDescriptor.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaMethodDescriptor.java @@ -87,7 +87,7 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement SimpleFunctionDescriptorImpl descriptor = super.initialize( receiverParameterType, dispatchReceiverParameter, typeParameters, unsubstitutedValueParameters, unsubstitutedReturnType, modality, visibility); - setOperator(OperatorChecks.INSTANCE.checkOperator(descriptor).isSuccess()); + setOperator(OperatorChecks.INSTANCE.check(descriptor).isSuccess()); return descriptor; } diff --git a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt similarity index 90% rename from core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt rename to core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt index e11c38c3049..8bc8b2d2300 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt @@ -68,11 +68,11 @@ interface Check { } sealed class MemberKindCheck(override val description: String) : Check { - object MemberOrExtension : MemberKindCheck("must be a member or an extension") { + object MemberOrExtension : MemberKindCheck("must be a member or an extension function") { override fun check(functionDescriptor: FunctionDescriptor) = functionDescriptor.isExtension || functionDescriptor.containingDeclaration is ClassDescriptor } - object Member : MemberKindCheck("must be a member") { + object Member : MemberKindCheck("must be a member function") { override fun check(functionDescriptor: FunctionDescriptor) = functionDescriptor.containingDeclaration is ClassDescriptor } } @@ -92,6 +92,13 @@ sealed class ValueParameterCountCheck(override val description: String) : Check } } +private object HasDispatchOrExtensionReceiverParameter : Check { + override val description = "must be a member of an extension function" + override fun check(functionDescriptor: FunctionDescriptor): Boolean { + return functionDescriptor.dispatchReceiverParameter != null || functionDescriptor.extensionReceiverParameter != null + } +} + private object NoDefaultAndVarargsCheck : Check { override val description = "should not have varargs or parameters with default values" override fun check(functionDescriptor: FunctionDescriptor) = @@ -120,7 +127,7 @@ sealed class ReturnsCheck(val name: String, val type: KotlinBuiltIns.() -> Kotli object ReturnsUnit : ReturnsCheck("Unit", { unitType }) } -private class Checks private constructor( +internal class Checks private constructor( val name: Name?, val regex: Regex?, val nameList: Collection?, @@ -150,6 +157,8 @@ private class Checks private constructor( return CheckResult.SuccessCheck } + constructor(vararg checks: Check, additionalChecks: FunctionDescriptor.() -> String? = { null }) + : this(null, null, null, additionalChecks, *checks) constructor(name: Name, vararg checks: Check, additionalChecks: FunctionDescriptor.() -> String? = { null }) : this(name, null, null, additionalChecks, *checks) constructor(regex: Regex, vararg checks: Check, additionalChecks: FunctionDescriptor.() -> String? = { null }) @@ -158,10 +167,23 @@ private class Checks private constructor( : this(null, null, nameList, additionalChecks, *checks) } -object OperatorChecks { - private inline fun ensure(cond: Boolean, msg: () -> String) = if (!cond) msg() else null +abstract class AbstractModifierChecks { + abstract internal val checks: List + + inline fun ensure(cond: Boolean, msg: () -> String) = if (!cond) msg() else null - private val CHECKS = listOf( + fun check(functionDescriptor: FunctionDescriptor): CheckResult { + for (check in checks) { + if (!check.isApplicable(functionDescriptor)) continue + return check.checkAll(functionDescriptor) + } + + return CheckResult.IllegalFunctionName + } +} + +object OperatorChecks : AbstractModifierChecks() { + override val checks = listOf( Checks(GET, MemberOrExtension, ValueParameterCountCheck.AtLeast(1)), Checks(SET, MemberOrExtension, ValueParameterCountCheck.AtLeast(2)) { val lastIsOk = valueParameters.lastOrNull()?.let { !it.hasDefaultValue() && it.varargElementType == null } ?: false @@ -212,15 +234,11 @@ object OperatorChecks { "Second parameter should be Continuation" } } - - fun checkOperator(functionDescriptor: FunctionDescriptor): CheckResult { - for (check in CHECKS) { - if (!check.isApplicable(functionDescriptor)) continue - return check.checkAll(functionDescriptor) - } - - return CheckResult.IllegalFunctionName - } } -fun FunctionDescriptor.isValidOperator() = isOperator && OperatorChecks.checkOperator(this).isSuccess +object InfixChecks : AbstractModifierChecks() { + override val checks = listOf( + Checks(HasDispatchOrExtensionReceiverParameter, SingleValueParameter, NoDefaultAndVarargsCheck)) +} + +fun FunctionDescriptor.isValidOperator() = isOperator && OperatorChecks.check(this).isSuccess \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/AddOperatorModifierIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/AddOperatorModifierIntention.kt index 98d4cb28dbc..d7bb2e15bf7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/AddOperatorModifierIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/AddOperatorModifierIntention.kt @@ -28,7 +28,7 @@ class AddOperatorModifierIntention : SelfTargetingRangeIntentionfoo(bar = x) } diff --git a/idea/testData/intentions/toInfixCall/noExplicitReceiver.kt b/idea/testData/intentions/toInfixCall/noExplicitReceiver.kt index 2dfbd84372d..da6a69641fd 100644 --- a/idea/testData/intentions/toInfixCall/noExplicitReceiver.kt +++ b/idea/testData/intentions/toInfixCall/noExplicitReceiver.kt @@ -1,4 +1,4 @@ // IS_APPLICABLE: false -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must be a member of an extension function infix fun id(s: String) = s val x = id("0").get(0) \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/packageFunctionCall.kt b/idea/testData/intentions/toInfixCall/packageFunctionCall.kt index 5a54d511c7b..fb8097436fe 100644 --- a/idea/testData/intentions/toInfixCall/packageFunctionCall.kt +++ b/idea/testData/intentions/toInfixCall/packageFunctionCall.kt @@ -1,5 +1,5 @@ // IS_APPLICABLE: false -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must be a member of an extension function package ppp infix fun foo(p: String){} diff --git a/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt b/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt index 960f7d7a5ad..990e488b0f4 100644 --- a/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt +++ b/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt @@ -1,5 +1,5 @@ // IS_APPLICABLE: false -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must have a single value parameter class Foo { infix fun foo(x: Int = 0, y: Int = 0) { diff --git a/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt b/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt index b653d66b18e..b3df37ef240 100644 --- a/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt +++ b/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt @@ -1,5 +1,5 @@ // IS_APPLICABLE: false -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must have a single value parameter fun foo(x: Foo) { x.foo(1) { it * 2 } diff --git a/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt b/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt index c2575dec383..d041d97ca2e 100644 --- a/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt +++ b/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt @@ -1,6 +1,6 @@ // WITH_RUNTIME // IS_APPLICABLE: false -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must be a member of an extension function package demo diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt b/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt index c3dd08bcf6b..ad23af0ca14 100644 --- a/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt +++ b/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt @@ -1,5 +1,5 @@ // "Replace with 'newFun(p, this)'" "true" -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must be a member of an extension function @Deprecated("", ReplaceWith("newFun(p, this)")) infix fun String.oldFun(p: Int) { diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt.after index a66fadf01b7..0cf9e355d4c 100644 --- a/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt.after +++ b/idea/testData/quickfix/deprecatedSymbolUsage/infixCall.kt.after @@ -1,5 +1,5 @@ // "Replace with 'newFun(p, this)'" "true" -// ERROR: 'infix' modifier is inapplicable on this function +// ERROR: infix modifier is inapplicable on this function: must be a member of an extension function @Deprecated("", ReplaceWith("newFun(p, this)")) infix fun String.oldFun(p: Int) {