diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt index 27715890d1c..f534d8b36ec 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt @@ -57,9 +57,8 @@ object OperatorModifierChecker { val checkResult = OperatorChecks.checkOperator(functionDescriptor) if (checkResult.isSuccess) { - if (functionDescriptor.name == OperatorNameConventions.COROUTINE_HANDLE_RESULT - && !languageFeatureSettings.supportsFeature(LanguageFeature.Coroutines) - ) { + if (functionDescriptor.name in COROUTINE_OPERATOR_NAMES + && !languageFeatureSettings.supportsFeature(LanguageFeature.Coroutines)) { diagnosticHolder.report(Errors.UNSUPPORTED_FEATURE.on(modifier, LanguageFeature.Coroutines)) } return @@ -73,3 +72,6 @@ object OperatorModifierChecker { diagnosticHolder.report(Errors.INAPPLICABLE_OPERATOR_MODIFIER.on(modifier, errorDescription)) } } + +private val COROUTINE_OPERATOR_NAMES = + setOf(OperatorNameConventions.COROUTINE_HANDLE_RESULT, OperatorNameConventions.COROUTINE_HANDLE_EXCEPTION) diff --git a/compiler/testData/diagnostics/tests/coroutines/unsupported.kt b/compiler/testData/diagnostics/tests/coroutines/unsupported.kt index 139519b1f1c..0e4837f0cb6 100644 --- a/compiler/testData/diagnostics/tests/coroutines/unsupported.kt +++ b/compiler/testData/diagnostics/tests/coroutines/unsupported.kt @@ -7,6 +7,9 @@ class Controller { } operator fun handleResult(x: String, y: Continuation) {} + + operator fun handleException(x: Throwable, y: Continuation) { + } } fun builder(coroutine c: Controller.() -> Continuation) { diff --git a/compiler/testData/diagnostics/tests/coroutines/unsupported.txt b/compiler/testData/diagnostics/tests/coroutines/unsupported.txt index c554e40fa6d..15887f61bca 100644 --- a/compiler/testData/diagnostics/tests/coroutines/unsupported.txt +++ b/compiler/testData/diagnostics/tests/coroutines/unsupported.txt @@ -6,6 +6,7 @@ public fun builder(/*0*/ coroutine c: Controller.() -> kotlin.coroutines.Continu public final class Controller { public constructor Controller() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit public final operator fun handleResult(/*0*/ x: kotlin.String, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public final suspend fun suspendHere(/*0*/ x: kotlin.coroutines.Continuation): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.kt b/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.kt new file mode 100644 index 00000000000..e9aca72775b --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.kt @@ -0,0 +1,35 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +class A1 { + operator fun handleException(x: Throwable) { + } +} + +class A2 { + operator fun handleException(x: Throwable, y: Int) { + } +} + +class A3 { + operator fun handleException(x: Throwable, y: Continuation) { + } +} + +class A4 { + operator fun handleException(x: Throwable, y: Continuation) { + } +} + +class A5 { + // TODO: Allow? + operator fun handleException(x: Throwable, y: Continuation): Int = 1 +} + +class A6 { + operator fun handleException(x: Throwable = Exception(), y: Continuation) {} + operator fun handleException(x: Throwable, vararg y: Continuation) {} +} + +class A7 { + operator fun handleException(x: Throwable, y: Continuation) { + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.txt b/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.txt new file mode 100644 index 00000000000..3e9ec24ec3a --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/wrongHandleException.txt @@ -0,0 +1,58 @@ +package + +public final class A1 { + public constructor A1() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A2 { + public constructor A2() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A3 { + public constructor A3() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A4 { + public constructor A4() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A5 { + public constructor A5() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A6 { + public constructor A6() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ vararg y: kotlin.coroutines.Continuation /*kotlin.Array>*/): kotlin.Unit + public final operator fun handleException(/*0*/ x: kotlin.Throwable = ..., /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A7 { + public constructor A7() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleException(/*0*/ x: kotlin.Throwable, /*1*/ y: kotlin.coroutines.Continuation): kotlin.Unit + 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/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 7f75f914c1e..e11f2b4984d 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3957,6 +3957,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("wrongHandleException.kt") + public void testWrongHandleException() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/wrongHandleException.kt"); + doTest(fileName); + } + @TestMetadata("wrongHandleResult.kt") public void testWrongHandleResult() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/wrongHandleResult.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 96ea85fe2ca..564acbfc672 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -19,8 +19,11 @@ package org.jetbrains.kotlin.types.typeUtil import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.inference.isCaptured +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import java.util.* @@ -56,6 +59,10 @@ fun KotlinType.isNullableAny(): Boolean = KotlinBuiltIns.isNullableAny(this) fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this) fun KotlinType.isPrimitiveNumberType(): Boolean = KotlinBuiltIns.isPrimitiveType(this) && !isBoolean() fun KotlinType.isBooleanOrNullableBoolean(): Boolean = KotlinBuiltIns.isBooleanOrNullableBoolean(this) +fun KotlinType.isThrowable(): Boolean = isConstructedFromClassWithGivenFqName(KotlinBuiltIns.FQ_NAMES.throwable) && !isMarkedNullable + +fun KotlinType.isConstructedFromClassWithGivenFqName(fqName: FqName) = + (constructor.declarationDescriptor as? ClassDescriptor)?.fqNameUnsafe == fqName.toUnsafe() fun KotlinType.isTypeParameter(): Boolean = TypeUtils.isTypeParameter(this) diff --git a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt b/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt index cdb18eab35f..e11c38c3049 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/util/OperatorChecker.kt @@ -23,11 +23,12 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.typeUtil.isNothing -import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf -import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.types.typeUtil.* import org.jetbrains.kotlin.util.MemberKindCheck.Member import org.jetbrains.kotlin.util.MemberKindCheck.MemberOrExtension import org.jetbrains.kotlin.util.OperatorNameConventions.ASSIGNMENT_OPERATIONS @@ -35,6 +36,7 @@ import org.jetbrains.kotlin.util.OperatorNameConventions.BINARY_OPERATION_NAMES import org.jetbrains.kotlin.util.OperatorNameConventions.COMPARE_TO import org.jetbrains.kotlin.util.OperatorNameConventions.COMPONENT_REGEX import org.jetbrains.kotlin.util.OperatorNameConventions.CONTAINS +import org.jetbrains.kotlin.util.OperatorNameConventions.COROUTINE_HANDLE_EXCEPTION import org.jetbrains.kotlin.util.OperatorNameConventions.COROUTINE_HANDLE_RESULT import org.jetbrains.kotlin.util.OperatorNameConventions.DEC import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS @@ -188,19 +190,29 @@ object OperatorChecks { }, Checks(ASSIGNMENT_OPERATIONS, MemberOrExtension, ReturnsUnit, SingleValueParameter, NoDefaultAndVarargsCheck), Checks(COMPONENT_REGEX, MemberOrExtension, NoValueParameters), - Checks( - COROUTINE_HANDLE_RESULT, Member, ValueParameterCountCheck.Equals(2), ReturnsUnit, - NoDefaultAndVarargsCheck, NoTypeParametersCheck) { - val secondParameter = valueParameters[1] - ensure( - secondParameter.type.constructor.declarationDescriptor?.fqNameUnsafe == DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME.toUnsafe() - && secondParameter.type.arguments[0].type.isNothing() - ) { - "Second parameter should be Continuation" + Checks(COROUTINE_HANDLE_RESULT, Member, ValueParameterCountCheck.Equals(2), ReturnsUnit, NoDefaultAndVarargsCheck, + NoTypeParametersCheck) { + checkHandleSecondParameter() + }, + Checks(COROUTINE_HANDLE_EXCEPTION, Member, ValueParameterCountCheck.Equals(2), ReturnsUnit, NoDefaultAndVarargsCheck, + NoTypeParametersCheck) { + checkHandleSecondParameter() + ?: ensure(valueParameters[0].type.isThrowable()) { + "First parameter should be 'Throwable'" } } ) + private fun FunctionDescriptor.checkHandleSecondParameter(): String? { + val secondParameter = valueParameters[1] + return ensure( + secondParameter.type.isConstructedFromClassWithGivenFqName(DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME) + && secondParameter.type.arguments[0].type.isNothing() + ) { + "Second parameter should be Continuation" + } + } + fun checkOperator(functionDescriptor: FunctionDescriptor): CheckResult { for (check in CHECKS) { if (!check.isApplicable(functionDescriptor)) continue diff --git a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorNameConventions.kt b/core/descriptors/src/org/jetbrains/kotlin/util/OperatorNameConventions.kt index ac423719a28..3c33a2394cd 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/util/OperatorNameConventions.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/util/OperatorNameConventions.kt @@ -57,6 +57,7 @@ object OperatorNameConventions { @JvmField val PLUS_ASSIGN = Name.identifier("plusAssign") @JvmField val MINUS_ASSIGN = Name.identifier("minusAssign") @JvmField val COROUTINE_HANDLE_RESULT = Name.identifier("handleResult") + @JvmField val COROUTINE_HANDLE_EXCEPTION = Name.identifier("handleException") // If you add new unary, binary or assignment operators, add it to OperatorConventions as well