diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 8ed40c8b466..ada4cabf99c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -524,6 +524,10 @@ public interface Errors { DiagnosticFactory0 UNDESCORE_IS_DEPRECATED = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 INAPPLICABLE_OPERATOR_MODIFIER = DiagnosticFactory0.create(ERROR); + + DiagnosticFactory2 OPERATOR_MODIFIER_REQUIRED = DiagnosticFactory2.create(WARNING); + // Labels DiagnosticFactory0 LABEL_NAME_CLASH = DiagnosticFactory0.create(WARNING); 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 fece0f14985..924f0f18e77 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -354,6 +354,10 @@ public class DefaultErrorMessages { MAP.put(UNDESCORE_IS_DEPRECATED, "Names _, __, ___, ..., are deprecated"); + MAP.put(INAPPLICABLE_OPERATOR_MODIFIER, "'operator' modifier is inapplicable on this function"); + + MAP.put(OPERATOR_MODIFIER_REQUIRED, "'operator' 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 '{...}'"); MAP.put(NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY, "A 'return' expression required in a function with a block body ('{...}')"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt index d077c122003..b666f643b07 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetOperationReferenceExpression.kt @@ -19,7 +19,17 @@ package org.jetbrains.kotlin.psi 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.JetToken +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.expressions.OperatorConventions public class JetOperationReferenceExpression(node: ASTNode) : JetSimpleNameExpressionImpl(node) { override fun getReferencedNameElement() = (findChildByType(JetExpressionParsing.ALL_OPERATIONS): PsiElement?) ?: this + + fun getNameForConventionalOperation(): Name? { + val operator = (firstChild as? TreeElement)?.elementType as? JetToken ?: return null + return OperatorConventions.getNameForOperationSymbol(operator) + } + } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt index 2f9cd3c27c8..6d0dba644ad 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.resolve.calls.checkers.* import org.jetbrains.kotlin.resolve.validation.AccessToPrivateTopLevelSymbolValidator 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.types.DynamicTypesSettings @@ -41,7 +42,7 @@ public abstract class TargetPlatform( private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker(), ConstModifierChecker, UnderscoreChecker) private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker()) private val DEFAULT_TYPE_CHECKERS = emptyList() -private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), AccessToPrivateTopLevelSymbolValidator()) +private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), AccessToPrivateTopLevelSymbolValidator(), OperatorValidator()) public open class PlatformConfigurator( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/OperatorValidator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/OperatorValidator.kt new file mode 100644 index 00000000000..631a198b8a0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/OperatorValidator.kt @@ -0,0 +1,82 @@ +/* + * 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.DiagnosticSink +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.psi.JetArrayAccessExpression +import org.jetbrains.kotlin.psi.JetElement +import org.jetbrains.kotlin.psi.JetMultiDeclarationEntry +import org.jetbrains.kotlin.psi.JetOperationReferenceExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall +import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic +import org.jetbrains.kotlin.types.ErrorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe + +public class OperatorValidator : SymbolUsageValidator { + + override fun validateCall(targetDescriptor: CallableDescriptor, trace: BindingTrace, element: PsiElement) { + val functionDescriptor = targetDescriptor as? FunctionDescriptor ?: return + if (functionDescriptor.isDynamic() || ErrorUtils.isError(functionDescriptor)) return + + val jetElement = element as? JetElement ?: return + val call = trace.bindingContext[BindingContext.CALL, jetElement] + + fun isVariableAsFunctionCall(): Boolean { + if (call == null) return false + val resolvedCall = trace.bindingContext[BindingContext.RESOLVED_CALL, call] ?: return false + return resolvedCall is VariableAsFunctionResolvedCall + } + + fun isMultiDeclaration(): Boolean { + return call?.callElement is JetMultiDeclarationEntry + } + + fun isConventionOperator(): Boolean { + if (jetElement !is JetOperationReferenceExpression) return false + return jetElement.getNameForConventionalOperation() != null + } + + fun isArrayAccessExpression() = jetElement is JetArrayAccessExpression + + if (isMultiDeclaration()) { + if (!functionDescriptor.isOperator && call != null) { + report(call.callElement, functionDescriptor, trace) + } + return + } + + if (isVariableAsFunctionCall() || isMultiDeclaration() || isConventionOperator() || isArrayAccessExpression()) { + if (!functionDescriptor.isOperator) { + report(jetElement, functionDescriptor, trace) + } + } + } + + companion object { + fun report(element: JetElement, descriptor: FunctionDescriptor, sink: DiagnosticSink) { + val containingDeclaration = descriptor.containingDeclaration + val containingDeclarationName = containingDeclaration.fqNameUnsafe.asString() + sink.report(Errors.OPERATOR_MODIFIER_REQUIRED.on(element, descriptor, containingDeclarationName)) + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/Operators.kt b/compiler/testData/diagnostics/tests/Operators.kt new file mode 100644 index 00000000000..d3770468068 --- /dev/null +++ b/compiler/testData/diagnostics/tests/Operators.kt @@ -0,0 +1,111 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +class Example { + public fun plus(o: Example) = o + public operator fun minus(o: Example) = o + + public fun get(i: Int) = "" + public operator fun get(s: String) = "" + + public fun set(i: Int, v: String) {} + public operator fun set(s: String, v: String) {} + + public fun not() = false + + public fun rangeTo(o: Example) = o + public fun contains(o: Example) = false + public fun compareTo(o: Example) = 0 + + public fun inc() = this + public fun dec() = this + + public fun invoke() {} +} + +class Example2 { + public operator fun not() = true + + public fun plusAssign(o: Example2) {} + public operator fun minusAssign(o: Example2) {} + + public operator fun rangeTo(o: Example2) = o + public operator fun contains(o: Example2) = false + public operator fun compareTo(o: Example2) = 0 + + public operator fun inc() = this + public operator fun dec() = this + + public operator fun invoke() {} +} + +fun a() { + var a = Example() + var b = Example() + var c = Example2() + var d = Example2() + + Example() == Example() + + a == b + c != d + + Example() + Example() + + a + b + a - b + a[1] + a["str"] + + a[1] = "A" + a["str"] = "str" + + a.plus(b) + a.minus(b) + a.get(1) + a.get("str") + + c += d + c -= d + + a..b + c..d + + Example()..Example() + Example2()..Example2() + + a < b + a >= b + c > d + + a in b + c in d + + a++ + a-- + c++ + c-- + + !a + !c + + a() + c() +} + +abstract class Base { + abstract operator fun plus(o: Base): Base + abstract fun minus(o: Base): Base +} + +open class Anc : Base() { + override fun plus(o: Base) = o + override fun minus(o: Base) = o +} + +class Anc2 : Anc() + +fun b() { + Anc() + Anc() + Anc() - Anc() + Anc2() + Anc2() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/Operators.txt b/compiler/testData/diagnostics/tests/Operators.txt new file mode 100644 index 00000000000..83342f63570 --- /dev/null +++ b/compiler/testData/diagnostics/tests/Operators.txt @@ -0,0 +1,67 @@ +package + +public fun a(): kotlin.Unit +public fun b(): kotlin.Unit + +public open class Anc : Base { + public constructor Anc() + 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*/ fun minus(/*0*/ o: Base): Base + public open override /*1*/ fun plus(/*0*/ o: Base): Base + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Anc2 : Anc { + public constructor Anc2() + 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 minus(/*0*/ o: Base): Base + public open override /*1*/ /*fake_override*/ fun plus(/*0*/ o: Base): Base + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class Base { + public constructor Base() + 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 abstract fun minus(/*0*/ o: Base): Base + public abstract operator fun plus(/*0*/ o: Base): Base + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Example { + public constructor Example() + public final fun compareTo(/*0*/ o: Example): kotlin.Int + public final fun contains(/*0*/ o: Example): kotlin.Boolean + public final fun dec(): Example + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun get(/*0*/ i: kotlin.Int): kotlin.String + public final operator fun get(/*0*/ s: kotlin.String): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final fun inc(): Example + public final fun invoke(): kotlin.Unit + public final operator fun minus(/*0*/ o: Example): Example + public final fun not(): kotlin.Boolean + public final fun plus(/*0*/ o: Example): Example + public final fun rangeTo(/*0*/ o: Example): Example + public final fun set(/*0*/ i: kotlin.Int, /*1*/ v: kotlin.String): kotlin.Unit + public final operator fun set(/*0*/ s: kotlin.String, /*1*/ v: kotlin.String): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Example2 { + public constructor Example2() + public final operator fun compareTo(/*0*/ o: Example2): kotlin.Int + public final operator fun contains(/*0*/ o: Example2): kotlin.Boolean + public final operator fun dec(): Example2 + 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 operator fun inc(): Example2 + public final operator fun invoke(): kotlin.Unit + public final operator fun minusAssign(/*0*/ o: Example2): kotlin.Unit + public final operator fun not(): kotlin.Boolean + public final fun plusAssign(/*0*/ o: Example2): kotlin.Unit + public final operator fun rangeTo(/*0*/ o: Example2): Example2 + 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 03dcf8817c3..34ba52c792c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -337,6 +337,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("Operators.kt") + public void testOperators() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/Operators.kt"); + doTest(fileName); + } + @TestMetadata("OverrideFunctionWithParamDefaultValue.kt") public void testOverrideFunctionWithParamDefaultValue() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/OverrideFunctionWithParamDefaultValue.kt");