JS: prohibit non-abstract members of external interfaces, except for nullable properties. See KT-15308

This commit is contained in:
Alexey Andreev
2016-12-19 14:39:26 +03:00
parent 403753f5b5
commit 55d4c0e439
10 changed files with 81 additions and 5 deletions
@@ -0,0 +1,21 @@
external interface I {
<!NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE!>fun foo(): Unit<!> = noImpl
val a: Int?
get() = noImpl
var b: String?
get() = noImpl
set(value) = noImpl
<!NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE!>val c: Int<!>
get() = noImpl
<!NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE!>var d: String<!>
get() = noImpl
set(value) = noImpl
var e: dynamic
get() = noImpl
set(value) = noImpl
}
@@ -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
}
@@ -11,7 +11,7 @@ external interface T {
val baz: Int
fun foo()
fun bar() {}
fun bar()
companion object {
val baz: Int
@@ -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
@@ -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?
}
@@ -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
@@ -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");
@@ -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
}
@@ -78,6 +78,8 @@ public interface ErrorsJs {
ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<KtDeclaration> INLINE_EXTERNAL_DECLARATION = DiagnosticFactory0.create(
ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<KtDeclaration> NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE = DiagnosticFactory0.create(
ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -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()
}
}