diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 41bc67628e0..217130e6c53 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -534,8 +534,10 @@ public interface Errors { DiagnosticFactory0 UNDERSCORE_IS_DEPRECATED = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 INAPPLICABLE_OPERATOR_MODIFIER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 INAPPLICABLE_INFIX_MODIFIER = DiagnosticFactory0.create(ERROR); DiagnosticFactory2 OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 INFIX_MODIFIER_REQUIRED = DiagnosticFactory2.create(WARNING); // Labels 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 b3b6a3829b0..f6b4578d100 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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 '{...}'"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt index b666f643b07..c4546d00972 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt @@ -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 + } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt new file mode 100644 index 00000000000..f2309cef0fd --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InfixModifierChecker.kt @@ -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 + } + } + } + +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt index b584ebc43f9..93416aad909 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt @@ -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() -private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator()) +private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator()) open class PlatformConfigurator( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/InfixValidator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/InfixValidator.kt new file mode 100644 index 00000000000..272dcb61b0d --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/InfixValidator.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/Infix.kt b/compiler/testData/diagnostics/tests/Infix.kt new file mode 100644 index 00000000000..93e185bb85d --- /dev/null +++ b/compiler/testData/diagnostics/tests/Infix.kt @@ -0,0 +1,26 @@ +class Pair(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() toNonInfix Example() + Example().toNonInfix(Example()) + + val a = Example() + val b = Example() + + a toExt b + a toExtNonInfix b + a.toExtNonInfix(b) + + a withLambda { } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/Infix.txt b/compiler/testData/diagnostics/tests/Infix.txt new file mode 100644 index 00000000000..2f94f1a15d7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/Infix.txt @@ -0,0 +1,24 @@ +package + +public fun test(): kotlin.Unit +public infix fun Example.toExt(/*0*/ other: Example): Pair +public fun Example.toExtNonInfix(/*0*/ other: Example): Pair +public fun Example.withLambda(/*0*/ f: () -> kotlin.Unit): Pair 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 + public final fun toNonInfix(/*0*/ other: Example): Pair + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Pair { + public constructor Pair(/*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 +} diff --git a/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt b/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt new file mode 100644 index 00000000000..6a9a5ff9919 --- /dev/null +++ b/compiler/testData/diagnostics/tests/InfixModifierApplicability.kt @@ -0,0 +1,21 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +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 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) {} + +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() {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt b/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt new file mode 100644 index 00000000000..a95acbacce6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/InfixModifierApplicability.txt @@ -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 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 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 { + public constructor Pair(/*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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 799e139cd5d..1184a7e45b9 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -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");