Make infix modifier diagnostic message more informative (KT-12589)

(cherry picked from commit 2744309)
This commit is contained in:
Yan Zhulanow
2016-06-10 22:45:57 +03:00
parent a434055b55
commit 6752df189d
20 changed files with 94 additions and 70 deletions
@@ -621,7 +621,7 @@ public interface Errors {
DiagnosticFactory1<PsiElement, String> INVALID_CHARACTERS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1<PsiElement, String> INVALID_CHARACTERS = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_OPERATOR_MODIFIER = 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<PsiElement, FunctionDescriptor, String> OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<KtOperationReferenceExpression, FunctionDescriptor, String> INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR); DiagnosticFactory2<KtOperationReferenceExpression, FunctionDescriptor, String> INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(ERROR);
@@ -379,7 +379,7 @@ public class DefaultErrorMessages {
MAP.put(INVALID_CHARACTERS, "Name {0}", STRING); MAP.put(INVALID_CHARACTERS, "Name {0}", STRING);
MAP.put(INAPPLICABLE_OPERATOR_MODIFIER, "''operator'' modifier is inapplicable on this function: {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(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); 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.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtDeclaration 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 { class InfixModifierChecker : SimpleDeclarationChecker {
@@ -36,17 +37,9 @@ class InfixModifierChecker : SimpleDeclarationChecker {
if (!functionDescriptor.isInfix) return if (!functionDescriptor.isInfix) return
val modifier = declaration.modifierList?.getModifier(KtTokens.INFIX_KEYWORD) ?: return val modifier = declaration.modifierList?.getModifier(KtTokens.INFIX_KEYWORD) ?: return
if (!isApplicable(functionDescriptor)) { val checkResult = InfixChecks.check(functionDescriptor)
diagnosticHolder.report(Errors.INAPPLICABLE_INFIX_MODIFIER.on(modifier)) 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 if (!functionDescriptor.isOperator) return
val modifier = declaration.modifierList?.getModifier(KtTokens.OPERATOR_KEYWORD) ?: return val modifier = declaration.modifierList?.getModifier(KtTokens.OPERATOR_KEYWORD) ?: return
val checkResult = OperatorChecks.checkOperator(functionDescriptor) val checkResult = OperatorChecks.check(functionDescriptor)
if (checkResult.isSuccess) { if (checkResult.isSuccess) {
if (functionDescriptor.name in COROUTINE_OPERATOR_NAMES if (functionDescriptor.name in COROUTINE_OPERATOR_NAMES
&& !languageFeatureSettings.supportsFeature(LanguageFeature.Coroutines)) { && !languageFeatureSettings.supportsFeature(LanguageFeature.Coroutines)) {
@@ -3,20 +3,25 @@
class Pair<A, B>(val a: A, val b: B) 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 <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
infix fun String.o1(o: String) = o // OK
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.o2(o: String, o2: String? = null) = o infix fun String.ok1(o: String) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.o3(o: String = "", o2: String? = null) = o class OkTest {
infix fun ok2(o: String) {}
infix fun String.ok3(o: String) {}
}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w1() {} // Errors
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w2(s: String) {} <!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e1(o: String, o2: String? = null) = o
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.w3() {} <!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun String.e2(o: String = "", o2: String? = null) = o
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun String.w4(a: Int, b: Int) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun w5(a: Int, b: Int) {} <!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 { class Example {
infix fun c1(s: String) {} <!INAPPLICABLE_INFIX_MODIFIER(must have a single value parameter)!>infix<!> fun e8(s: String, a: Int = 0) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun c2(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() {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun cw1(s: String, a: Int) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun sw2() {}
} }
@@ -1,26 +1,34 @@
package package
public infix fun w1(): kotlin.Unit public infix fun e3(): kotlin.Unit
public infix fun w2(/*0*/ s: kotlin.String): kotlin.Unit public infix fun e4(/*0*/ s: kotlin.String): kotlin.Unit
public infix fun w5(/*0*/ a: kotlin.Int, /*1*/ b: kotlin.Int): kotlin.Unit public infix fun e7(/*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.e1(/*0*/ o: kotlin.String, /*1*/ o2: 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.e2(/*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 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 </*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 final class Example {
public constructor Example() public constructor Example()
public final infix fun c1(/*0*/ s: kotlin.String): kotlin.Unit public final infix fun e10(): kotlin.Unit
public final infix fun c2(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Int = ...): kotlin.Unit public final infix fun e8(/*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 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 equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int 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 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 final class Pair</*0*/ A, /*1*/ B> {
public constructor Pair</*0*/ A, /*1*/ B>(/*0*/ a: A, /*1*/ b: B) public constructor Pair</*0*/ A, /*1*/ B>(/*0*/ a: A, /*1*/ b: B)
public final val a: A public final val a: A
@@ -87,7 +87,7 @@ public class JavaMethodDescriptor extends SimpleFunctionDescriptorImpl implement
SimpleFunctionDescriptorImpl descriptor = super.initialize( SimpleFunctionDescriptorImpl descriptor = super.initialize(
receiverParameterType, dispatchReceiverParameter, typeParameters, unsubstitutedValueParameters, receiverParameterType, dispatchReceiverParameter, typeParameters, unsubstitutedValueParameters,
unsubstitutedReturnType, modality, visibility); unsubstitutedReturnType, modality, visibility);
setOperator(OperatorChecks.INSTANCE.checkOperator(descriptor).isSuccess()); setOperator(OperatorChecks.INSTANCE.check(descriptor).isSuccess());
return descriptor; return descriptor;
} }
@@ -68,11 +68,11 @@ interface Check {
} }
sealed class MemberKindCheck(override val description: String) : 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) = override fun check(functionDescriptor: FunctionDescriptor) =
functionDescriptor.isExtension || functionDescriptor.containingDeclaration is ClassDescriptor 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 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 { private object NoDefaultAndVarargsCheck : Check {
override val description = "should not have varargs or parameters with default values" override val description = "should not have varargs or parameters with default values"
override fun check(functionDescriptor: FunctionDescriptor) = override fun check(functionDescriptor: FunctionDescriptor) =
@@ -120,7 +127,7 @@ sealed class ReturnsCheck(val name: String, val type: KotlinBuiltIns.() -> Kotli
object ReturnsUnit : ReturnsCheck("Unit", { unitType }) object ReturnsUnit : ReturnsCheck("Unit", { unitType })
} }
private class Checks private constructor( internal class Checks private constructor(
val name: Name?, val name: Name?,
val regex: Regex?, val regex: Regex?,
val nameList: Collection<Name>?, val nameList: Collection<Name>?,
@@ -150,6 +157,8 @@ private class Checks private constructor(
return CheckResult.SuccessCheck 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 }) constructor(name: Name, vararg checks: Check, additionalChecks: FunctionDescriptor.() -> String? = { null })
: this(name, null, null, additionalChecks, *checks) : this(name, null, null, additionalChecks, *checks)
constructor(regex: Regex, vararg checks: Check, additionalChecks: FunctionDescriptor.() -> String? = { null }) 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) : this(null, null, nameList, additionalChecks, *checks)
} }
object OperatorChecks { abstract class AbstractModifierChecks {
private inline fun ensure(cond: Boolean, msg: () -> String) = if (!cond) msg() else null abstract internal val checks: List<Checks>
private val CHECKS = listOf( inline fun ensure(cond: Boolean, msg: () -> String) = if (!cond) msg() else null
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(GET, MemberOrExtension, ValueParameterCountCheck.AtLeast(1)),
Checks(SET, MemberOrExtension, ValueParameterCountCheck.AtLeast(2)) { Checks(SET, MemberOrExtension, ValueParameterCountCheck.AtLeast(2)) {
val lastIsOk = valueParameters.lastOrNull()?.let { !it.hasDefaultValue() && it.varargElementType == null } ?: false val lastIsOk = valueParameters.lastOrNull()?.let { !it.hasDefaultValue() && it.varargElementType == null } ?: false
@@ -212,15 +234,11 @@ object OperatorChecks {
"Second parameter should be Continuation<Nothing>" "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? { override fun applicabilityRange(element: KtNamedFunction): TextRange? {
val nameIdentifier = element.nameIdentifier ?: return null val nameIdentifier = element.nameIdentifier ?: return null
val functionDescriptor = element.resolveToDescriptor() as? FunctionDescriptor ?: 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 return nameIdentifier.textRange
} }
@@ -51,7 +51,7 @@ class ReplaceContainsIntention : SelfTargetingRangeIntention<KtDotQualifiedExpre
if (!element.isReceiverExpressionWithValue()) return null if (!element.isReceiverExpressionWithValue()) return null
val functionDescriptor = getFunctionDescriptor(element) ?: 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 return element.callExpression!!.calleeExpression!!.textRange
} }
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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 package p
+1 -1
View File
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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 { interface Foo {
infix fun foo(a: Int, b: Int) infix fun foo(a: Int, b: Int)
} }
+1 -1
View File
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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) { fun foo(x: Foo) {
x.<caret>foo(bar = x) x.<caret>foo(bar = x)
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false // 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 infix fun id(s: String) = s
val x = <caret>id("0").get(0) val x = <caret>id("0").get(0)
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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 package ppp
infix fun foo(p: String){} infix fun foo(p: String){}
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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 { class Foo {
infix fun foo(x: Int = 0, y: Int = 0) { infix fun foo(x: Int = 0, y: Int = 0) {
@@ -1,5 +1,5 @@
// IS_APPLICABLE: false // 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) { fun foo(x: Foo) {
x.<caret>foo(1) { it * 2 } x.<caret>foo(1) { it * 2 }
@@ -1,6 +1,6 @@
// WITH_RUNTIME // WITH_RUNTIME
// IS_APPLICABLE: false // 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 package demo
+1 -1
View File
@@ -1,5 +1,5 @@
// "Replace with 'newFun(p, this)'" "true" // "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)")) @Deprecated("", ReplaceWith("newFun(p, this)"))
infix fun String.oldFun(p: Int) { infix fun String.oldFun(p: Int) {
@@ -1,5 +1,5 @@
// "Replace with 'newFun(p, this)'" "true" // "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)")) @Deprecated("", ReplaceWith("newFun(p, this)"))
infix fun String.oldFun(p: Int) { infix fun String.oldFun(p: Int) {