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> 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);
@@ -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
@@ -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;
}
@@ -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
}
@@ -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,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 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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,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 -1
View File
@@ -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) {