Make infix modifier diagnostic message more informative (KT-12589)
(cherry picked from commit 2744309)
This commit is contained in:
@@ -621,7 +621,7 @@ public interface Errors {
|
||||
DiagnosticFactory1<PsiElement, String> INVALID_CHARACTERS = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_OPERATOR_MODIFIER = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory2<PsiElement, FunctionDescriptor, String> OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory2<KtOperationReferenceExpression, FunctionDescriptor, String> INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -3,20 +3,25 @@
|
||||
class Pair<A, B>(val a: A, val b: B)
|
||||
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
infix fun String.o1(o: String) = o
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.o2(o: String, o2: String? = null) = o
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>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) {}
|
||||
}
|
||||
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w1() {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w2(s: String) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.w3() {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.w4(a: Int, b: Int) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w5(a: Int, b: Int) {}
|
||||
// Errors
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e1(o: String, o2: String? = null) = o
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e2(o: String = "", o2: String? = null) = o
|
||||
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must be a member of an extension function)!>infix<!> fun e3() {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must be a member of an extension function)!>infix<!> fun e4(s: String) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e5() {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e6(a: Int, b: Int) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must be a member of an extension function)!>infix<!> fun e7(a: Int, b: Int) {}
|
||||
|
||||
class Example {
|
||||
infix fun c1(s: String) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun c2(s: String, a: Int = 0) {}
|
||||
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun cw1(s: String, a: Int) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun sw2() {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun e8(s: String, a: Int = 0) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun e9(s: String, a: Int) {}
|
||||
<!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun e10() {}
|
||||
}
|
||||
@@ -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 </*0*/ A, /*1*/ B> A.to(/*0*/ that: B): Pair<A, B>
|
||||
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</*0*/ A, /*1*/ B> {
|
||||
public constructor Pair</*0*/ A, /*1*/ B>(/*0*/ a: A, /*1*/ b: B)
|
||||
public final val a: A
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+34
-16
@@ -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<Name>?,
|
||||
@@ -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<Checks>
|
||||
|
||||
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<Nothing>"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -28,7 +28,7 @@ class AddOperatorModifierIntention : SelfTargetingRangeIntention<KtNamedFunction
|
||||
override fun applicabilityRange(element: KtNamedFunction): TextRange? {
|
||||
val nameIdentifier = element.nameIdentifier ?: return null
|
||||
val functionDescriptor = element.resolveToDescriptor() as? FunctionDescriptor ?: return null
|
||||
if (functionDescriptor.isOperator || !OperatorChecks.checkOperator(functionDescriptor).isSuccess) return null
|
||||
if (functionDescriptor.isOperator || !OperatorChecks.check(functionDescriptor).isSuccess) return null
|
||||
return nameIdentifier.textRange
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class ReplaceContainsIntention : SelfTargetingRangeIntention<KtDotQualifiedExpre
|
||||
if (!element.isReceiverExpressionWithValue()) return null
|
||||
|
||||
val functionDescriptor = getFunctionDescriptor(element) ?: return null
|
||||
if (!functionDescriptor.isOperator || !OperatorChecks.checkOperator(functionDescriptor).isSuccess) return null
|
||||
if (!functionDescriptor.isOperator || !OperatorChecks.check(functionDescriptor).isSuccess) return null
|
||||
|
||||
return element.callExpression!!.calleeExpression!!.textRange
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// IS_APPLICABLE: false
|
||||
// ERROR: 'operator' modifier is inapplicable on this function: must be a member or an extension
|
||||
// ERROR: 'operator' modifier is inapplicable on this function: must be a member or an extension function
|
||||
|
||||
package p
|
||||
|
||||
|
||||
@@ -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
|
||||
interface Foo {
|
||||
infix fun foo(a: Int, b: Int)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.<caret>foo(bar = x)
|
||||
}
|
||||
|
||||
@@ -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 = <caret>id("0").get(0)
|
||||
@@ -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){}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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.<caret>foo(1) { it * 2 }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user