Diagnostics on 'infix' (declaration&use site)

This commit is contained in:
Yan Zhulanow
2015-09-29 19:22:28 +03:00
parent fff434d377
commit 1238e93b9f
11 changed files with 240 additions and 5 deletions
@@ -534,8 +534,10 @@ public interface Errors {
DiagnosticFactory0<PsiElement> UNDERSCORE_IS_DEPRECATED = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> INAPPLICABLE_OPERATOR_MODIFIER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, FunctionDescriptor, String> OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<JetOperationReferenceExpression, FunctionDescriptor, String> INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(WARNING);
// Labels
@@ -362,8 +362,10 @@ public class DefaultErrorMessages {
MAP.put(UNDERSCORE_IS_DEPRECATED, "Names _, __, ___, ..., are deprecated");
MAP.put(INAPPLICABLE_OPERATOR_MODIFIER, "'operator' modifier is inapplicable on this function");
MAP.put(INAPPLICABLE_INFIX_MODIFIER, "'infix' modifier is inapplicable on this function");
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(RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY,
"Returns are not allowed for functions with expression body. Use block body in '{...}'");
@@ -20,6 +20,7 @@ import com.intellij.lang.ASTNode
import org.jetbrains.kotlin.parsing.JetExpressionParsing
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.TreeElement
import org.jetbrains.kotlin.lexer.JetSingleValueToken
import org.jetbrains.kotlin.lexer.JetToken
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.expressions.OperatorConventions
@@ -32,4 +33,6 @@ public class JetOperationReferenceExpression(node: ASTNode) : JetSimpleNameExpre
return OperatorConventions.getNameForOperationSymbol(operator)
}
fun isPredefinedOperator() = (firstChild as? TreeElement)?.elementType is JetSingleValueToken
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
public class InfixModifierChecker : DeclarationChecker {
override fun check(
declaration: JetDeclaration,
descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext
) {
val functionDescriptor = descriptor as? FunctionDescriptor ?: return
if (!functionDescriptor.isInfix) return
val modifier = declaration.modifierList?.getModifier(JetTokens.INFIX_KEYWORD) ?: return
if (!isApplicable(functionDescriptor)) {
diagnosticHolder.report(Errors.INAPPLICABLE_INFIX_MODIFIER.on(modifier))
}
}
private fun isApplicable(descriptor: FunctionDescriptor): Boolean {
if (descriptor.dispatchReceiverParameter == null && descriptor.extensionReceiverParameter == null) return false
val paramCount = descriptor.valueParameters.size()
return when (paramCount) {
0 -> false
1 -> true
else -> {
val params = descriptor.valueParameters
for (i in 1..params.lastIndex) {
if (!params[i].hasDefaultValue()) {
return false
}
}
true
}
}
}
}
@@ -24,9 +24,7 @@ import org.jetbrains.kotlin.descriptors.ModuleParameters
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.checkers.*
import org.jetbrains.kotlin.resolve.validation.DeprecatedSymbolValidator
import org.jetbrains.kotlin.resolve.validation.OperatorValidator
import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator
import org.jetbrains.kotlin.resolve.validation.*
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.types.DynamicTypesSettings
@@ -49,10 +47,16 @@ abstract class TargetPlatform(
}
}
private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker(), ConstModifierChecker, UnderscoreChecker, OperatorModifierChecker())
private val DEFAULT_DECLARATION_CHECKERS = listOf(
DataClassAnnotationChecker(),
ConstModifierChecker,
UnderscoreChecker,
OperatorModifierChecker(),
InfixModifierChecker())
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker(), SafeCallChecker())
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator())
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator())
open class PlatformConfigurator(
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.validation
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.psi.JetOperationReferenceExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.types.ErrorUtils
public class InfixValidator : SymbolUsageValidator {
override fun validateCall(targetDescriptor: CallableDescriptor, trace: BindingTrace, element: PsiElement) {
val functionDescriptor = targetDescriptor as? FunctionDescriptor ?: return
if (functionDescriptor.isDynamic() || ErrorUtils.isError(functionDescriptor)) return
if (isInfixCall(element) && !functionDescriptor.isInfix) {
val operationRefExpression = element as? JetOperationReferenceExpression ?: return
val containingDeclarationName = functionDescriptor.containingDeclaration.fqNameUnsafe.asString()
trace.report(Errors.INFIX_MODIFIER_REQUIRED.on(operationRefExpression, functionDescriptor, containingDeclarationName))
}
}
companion object {
fun isInfixCall(element: PsiElement?): Boolean {
val operationRefExpression = element as? JetOperationReferenceExpression ?: return false
val binaryExpression = operationRefExpression.parent as? JetBinaryExpression ?: return false
return binaryExpression.operationReference === operationRefExpression && !operationRefExpression.isPredefinedOperator()
}
}
}
+26
View File
@@ -0,0 +1,26 @@
class Pair<out A, out B>(val first: A, val second: B)
class Example {
infix fun to(other: Example) = Pair(this, other)
fun toNonInfix(other: Example) = Pair(this, other)
}
infix fun Example.toExt(other: Example) = Pair(this, other)
fun Example.toExtNonInfix(other: Example) = Pair(this, other)
fun Example.withLambda(f: () -> Unit) = Pair(this, f)
fun test() {
Example() to Example()
Example() <!INFIX_MODIFIER_REQUIRED!>toNonInfix<!> Example()
Example().toNonInfix(Example())
val a = Example()
val b = Example()
a toExt b
a <!INFIX_MODIFIER_REQUIRED!>toExtNonInfix<!> b
a.toExtNonInfix(b)
a <!INFIX_MODIFIER_REQUIRED!>withLambda<!> { }
}
+24
View File
@@ -0,0 +1,24 @@
package
public fun test(): kotlin.Unit
public infix fun Example.toExt(/*0*/ other: Example): Pair<Example, Example>
public fun Example.toExtNonInfix(/*0*/ other: Example): Pair<Example, Example>
public fun Example.withLambda(/*0*/ f: () -> kotlin.Unit): Pair<Example, () -> kotlin.Unit>
public final class Example {
public constructor Example()
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 to(/*0*/ other: Example): Pair<Example, Example>
public final fun toNonInfix(/*0*/ other: Example): Pair<Example, Example>
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Pair</*0*/ out A, /*1*/ out B> {
public constructor Pair</*0*/ out A, /*1*/ out B>(/*0*/ first: A, /*1*/ second: B)
public final val first: A
public final val second: B
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 toString(): kotlin.String
}
@@ -0,0 +1,21 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
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
infix fun String.o2(o: String, o2: String? = null) = o
<!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) {}
class Example {
infix fun c1(s: String) {}
infix fun c2(s: String, a: Int = 0) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun cw1(s: String, a: Int) {}
<!INAPPLICABLE_INFIX_MODIFIER!>infix<!> fun sw2() {}
}
@@ -0,0 +1,30 @@
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 </*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 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 Pair</*0*/ A, /*1*/ B> {
public constructor Pair</*0*/ A, /*1*/ B>(/*0*/ a: A, /*1*/ b: B)
public final val a: A
public final val b: B
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 toString(): kotlin.String
}
@@ -271,6 +271,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("Infix.kt")
public void testInfix() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/Infix.kt");
doTest(fileName);
}
@TestMetadata("InfixModifierApplicability.kt")
public void testInfixModifierApplicability() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/InfixModifierApplicability.kt");
doTest(fileName);
}
@TestMetadata("IsExpressions.kt")
public void testIsExpressions() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/IsExpressions.kt");