From cf8161507cdf02bf82a7438173466f51f2c8419f Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 28 Nov 2016 16:26:50 +0300 Subject: [PATCH] JS: prohibit private members of external classes (see KT-14029) --- .../native/privateMembers.kt | 55 +++++++++++++++++++ .../native/privateMembers.txt | 47 ++++++++++++++++ .../DiagnosticsTestWithJsStdLibGenerated.java | 6 ++ .../diagnostics/DefaultErrorMessagesJs.kt | 1 + .../js/resolve/diagnostics/ErrorsJs.java | 1 + .../resolve/diagnostics/JsExternalChecker.kt | 18 ++++-- 6 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.txt diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.kt b/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.kt new file mode 100644 index 00000000000..e6701d0602e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.kt @@ -0,0 +1,55 @@ +// !DIAGNOSTICS: -NOTHING_TO_INLINE +// TODO: should we disable NOTHING_TO_INLINE in JS backend? + +external class C { + private fun a(): Int + + private val b: String + + private var c: Float + + private var d: Float + get + set + + private inline fun inline_a(): Int = 23 + + private inline val inline_prop: Int + get() = 42 +} + +external object O { + private fun a(): Int + + private val b: String + + private var c: Float + + private var d: Float + get + set + + private inline fun inline_a(): Int = 23 + + private inline val inline_prop: Int + get() = 42 +} + +external class Outer { + class Inner { + private fun a(): Int + + private val b: String + + private var c: Float + + private var d: Float + get + set + + private inline fun inline_a(): Int = 23 + + private inline val inline_prop: Int + get() = 42 + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.txt b/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.txt new file mode 100644 index 00000000000..67a13a760c4 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.txt @@ -0,0 +1,47 @@ +package + +public final class C { + public constructor C() + private final val b: kotlin.String + private final var c: kotlin.Float + private final var d: kotlin.Float + private final val inline_prop: kotlin.Int + private final fun a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + private final inline fun inline_a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public object O { + private constructor O() + private final val b: kotlin.String + private final var c: kotlin.Float + private final var d: kotlin.Float + private final val inline_prop: kotlin.Int + private final fun a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + private final inline fun inline_a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Outer { + public constructor Outer() + 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 toString(): kotlin.String + + public final class Inner { + public constructor Inner() + private final val b: kotlin.String + private final var c: kotlin.Float + private final var d: kotlin.Float + private final val inline_prop: kotlin.Int + private final fun a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + private final inline fun inline_a(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index be7b2aff1d5..63f1616cf7d 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -539,6 +539,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("privateMembers.kt") + public void testPrivateMembers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/native/privateMembers.kt"); + doTest(fileName); + } + @TestMetadata("wrongTarget.kt") public void testWrongTarget() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/native/wrongTarget.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 ce2f3f091f3..645d7906aa4 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 @@ -38,6 +38,7 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED, "Callable references for builtin members are not supported yet: ''{0}''", RenderFirstLineOfElementText) put(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED, "Argument must be non-empty JavaScript code") put(ErrorsJs.NESTED_EXTERNAL_DECLARATION, "Non top-level `external` declaration") + put(ErrorsJs.EXTERNAL_CLASS_PRIVATE_MEMBER, "Private non-inline members of external classes are prohibited") put(ErrorsJs.JS_NAME_CLASH, "JavaScript name ({0}) generated for this declaration clashes with another declaration: {1}", Renderers.STRING, Renderers.COMPACT) put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}", 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 257698c0b0c..b6797e85b7f 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 @@ -41,6 +41,7 @@ public interface ErrorsJs { DiagnosticFactory1 NOT_SUPPORTED = DiagnosticFactory1.create(ERROR, DEFAULT); DiagnosticFactory1 REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED = DiagnosticFactory1.create(ERROR, DEFAULT); DiagnosticFactory0 JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT); + DiagnosticFactory0 EXTERNAL_CLASS_PRIVATE_MEMBER = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 NESTED_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory2 JS_NAME_CLASH = DiagnosticFactory2.create( ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); 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 ed016b79534..a753dcb8a6c 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 @@ -16,10 +16,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.js.PredefinedAnnotation @@ -49,6 +46,9 @@ class JsExternalChecker : SimpleDeclarationChecker { else if (descriptor is PropertyAccessorDescriptor && isDirectlyExternal(declaration, descriptor)) { diagnosticHolder.report(Errors.WRONG_MODIFIER_TARGET.on(declaration, KtTokens.EXTERNAL_KEYWORD, "property accessor")) } + else if (isPrivateNonInlineMemberOfExternalClass(descriptor)) { + diagnosticHolder.report(ErrorsJs.EXTERNAL_CLASS_PRIVATE_MEMBER.on(declaration)) + } if (descriptor !is PropertyAccessorDescriptor && descriptor.isExtension) { val target = when (descriptor) { @@ -66,4 +66,14 @@ class JsExternalChecker : SimpleDeclarationChecker { return declaration.hasModifier(KtTokens.EXTERNAL_KEYWORD) || AnnotationsUtils.hasAnnotation(descriptor, PredefinedAnnotation.NATIVE) } + + private fun isPrivateNonInlineMemberOfExternalClass(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is PropertyAccessorDescriptor) return false + if (descriptor !is MemberDescriptor || descriptor.visibility != Visibilities.PRIVATE) return false + if (descriptor is FunctionDescriptor && descriptor.isInline) return false + if (descriptor is PropertyDescriptor && descriptor.accessors.all { it.isInline }) return false + + val containingDeclaration = descriptor.containingDeclaration as? ClassDescriptor ?: return false + return AnnotationsUtils.isNativeObject(containingDeclaration) + } }