diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.kt b/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.kt new file mode 100644 index 00000000000..e4ac31c9477 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.kt @@ -0,0 +1,21 @@ +external interface I { + fun foo(): Unit = noImpl + + val a: Int? + get() = noImpl + + var b: String? + get() = noImpl + set(value) = noImpl + + val c: Int + get() = noImpl + + var d: String + get() = noImpl + set(value) = noImpl + + var e: dynamic + get() = noImpl + set(value) = noImpl +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.txt b/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.txt new file mode 100644 index 00000000000..e086ced50db --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.txt @@ -0,0 +1,13 @@ +package + +public external interface I { + public open val a: kotlin.Int? + public open var b: kotlin.String? + public open val c: kotlin.Int + public open var d: kotlin.String + public open var e: dynamic + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun foo(): 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/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.kt b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.kt index 968012954f0..40ac0dc5868 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.kt @@ -11,7 +11,7 @@ external interface T { val baz: Int fun foo() - fun bar() {} + fun bar() companion object { val baz: Int diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.txt b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.txt index f68e2bd7756..e21df3b5c23 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/native.txt @@ -40,7 +40,7 @@ public external object O { public external interface T { public abstract val baz: kotlin.Int - public open fun bar(): kotlin.Unit + public abstract fun bar(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public abstract fun foo(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.kt b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.kt index 90100fabfc3..a17d5336c16 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.kt @@ -9,5 +9,5 @@ external interface T { @nativeGetter fun foo(d: Double): String? @nativeGetter - fun bar(d: Double): String? = noImpl + fun bar(d: Double): String? } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.txt b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.txt index d8e632d2e76..3619b7b3208 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/optionlBody/nativeGetter.txt @@ -4,7 +4,7 @@ package @kotlin.js.nativeGetter public fun kotlin.String.foo(/*0*/ n: kotlin.Int): kotlin.Int? public external interface T { - @kotlin.js.nativeGetter public open fun bar(/*0*/ d: kotlin.Double): kotlin.String? + @kotlin.js.nativeGetter public abstract fun bar(/*0*/ d: kotlin.Double): kotlin.String? public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean @kotlin.js.nativeGetter public abstract fun foo(/*0*/ d: kotlin.Double): kotlin.String? public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index b88d89db08f..f4bdcb8f558 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -599,6 +599,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("nonAbstractMembersOfInterface.kt") + public void testNonAbstractMembersOfInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/native/nonAbstractMembersOfInterface.kt"); + doTest(fileName); + } + @TestMetadata("overrideOptionalParam.kt") public void testOverrideOptionalParam() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/native/overrideOptionalParam.kt"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index 6e3c387c986..3e6df6c18ce 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -73,6 +73,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { "Overriding `external` function with optional parameters by declaration from superclass: {0}", Renderers.COMPACT) put(ErrorsJs.IMPLEMENTING_FUNCTION_INTERFACE, "Implementing function interface is prohibited in JavaScript") put(ErrorsJs.INLINE_EXTERNAL_DECLARATION, "Inline external declaration") + put(ErrorsJs.NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE, + "Only nullable properties of external interfaces are allowed to be non-abstract") this } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 93c5bf26d64..57c60ceb739 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -78,6 +78,8 @@ public interface ErrorsJs { ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 INLINE_EXTERNAL_DECLARATION = DiagnosticFactory0.create( ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory0 NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE = DiagnosticFactory0.create( + ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt index 9ca91125a02..be898e0eaab 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.resolve.diagnostics import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.annotations.isInlineOnlyOrReified import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.js.PredefinedAnnotation import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils @@ -80,6 +79,12 @@ object JsExternalChecker : SimpleDeclarationChecker { if (descriptor is FunctionDescriptor && descriptor.isInline) { diagnosticHolder.report(ErrorsJs.INLINE_EXTERNAL_DECLARATION.on(declaration)) } + + if (descriptor is CallableMemberDescriptor && descriptor.isNonAbstractMemberOfInterface() && + !descriptor.isNullableProperty() + ) { + diagnosticHolder.report(ErrorsJs.NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE.on(declaration)) + } } private fun isDirectlyExternal(declaration: KtDeclaration, descriptor: DeclarationDescriptor): Boolean { @@ -96,4 +101,31 @@ object JsExternalChecker : SimpleDeclarationChecker { val containingDeclaration = descriptor.containingDeclaration as? ClassDescriptor ?: return false return AnnotationsUtils.isNativeObject(containingDeclaration) } + + private fun CallableMemberDescriptor.isNonAbstractMemberOfInterface() = + modality != Modality.ABSTRACT && DescriptorUtils.isInterface(containingDeclaration) && + this !is PropertyAccessorDescriptor + + private fun CallableMemberDescriptor.isNullableProperty() = this is PropertyDescriptor && TypeUtils.isNullableType(type) + + private fun KtDeclarationWithBody.hasValidExternalBody(bindingContext: BindingContext): Boolean { + if (!hasBody()) return true + val body = bodyExpression!! + return if (!hasBlockBody()) { + body.isNoImplExpression(bindingContext) + } + else if (body is KtBlockExpression) { + val statement = body.statements.singleOrNull() ?: return false + statement.isNoImplExpression(bindingContext) + } + else { + false + } + } + + private fun KtExpression.isNoImplExpression(bindingContext: BindingContext): Boolean { + val descriptor = getResolvedCall(bindingContext)?.resultingDescriptor as? PropertyDescriptor ?: return false + val container = descriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false + return container.fqNameUnsafe == NO_IMPL_PROPERTY_NAME.parent() && descriptor.name == NO_IMPL_PROPERTY_NAME.shortName() + } }