JS: prohibit non-abstract members of external interfaces, except for nullable properties. See KT-15308
This commit is contained in:
+21
@@ -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
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ external interface T {
|
||||
val baz: Int
|
||||
|
||||
fun foo()
|
||||
fun bar() {}
|
||||
fun bar()
|
||||
|
||||
companion object {
|
||||
val baz: Int
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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?
|
||||
}
|
||||
+1
-1
@@ -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");
|
||||
|
||||
+2
@@ -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() {
|
||||
|
||||
+33
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user