From c5087779e430bee0959ebdadba5f46f7480e3df6 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 3 Jun 2016 16:02:39 +0300 Subject: [PATCH] KT-2752: add diagnostics that reports about conflicting overridden declarations --- .../testData/codegen/box/bridges/jsNative.kt | 28 +++++++ .../name/conflictingNamesFromSuperclass.kt | 23 ++++++ .../name/conflictingNamesFromSuperclass.txt | 51 ++++++++++++ .../name/overriddenJsNameClash.kt | 23 ++++++ .../name/overriddenJsNameClash.txt | 45 +++++++++++ .../DiagnosticsTestWithJsStdLibGenerated.java | 12 +++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 ++ .../diagnostics/DefaultErrorMessagesJs.kt | 4 + .../js/resolve/diagnostics/ErrorsJs.java | 8 +- .../js/resolve/diagnostics/JsNameChecker.kt | 3 +- .../resolve/diagnostics/JsNameClashChecker.kt | 32 ++++---- .../diagnostics/OverriddenJsNameChecker.kt | 78 +++++++++++++++++++ .../diagnostics/sourceLocationUtils.kt | 33 ++++++++ .../test/semantics/BridgeTestGenerated.java | 6 ++ 14 files changed, 333 insertions(+), 19 deletions(-) create mode 100644 compiler/testData/codegen/box/bridges/jsNative.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.txt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.kt create mode 100644 compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.txt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/OverriddenJsNameChecker.kt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/sourceLocationUtils.kt diff --git a/compiler/testData/codegen/box/bridges/jsNative.kt b/compiler/testData/codegen/box/bridges/jsNative.kt new file mode 100644 index 00000000000..2c613e6b701 --- /dev/null +++ b/compiler/testData/codegen/box/bridges/jsNative.kt @@ -0,0 +1,28 @@ +// TARGET_BACKEND: JS +package foo + +@native interface A { + fun foo(value: Int): String +} + +interface B { + fun foo(value: Int): String +} + +class C : A, B { + override fun foo(value: Int) = "C.foo($value)" +} + +fun box(): String { + val a: A = C() + assertEquals("C.foo(55)", a.foo(55)) + + val b: B = C() + assertEquals("C.foo(23)", b.foo(23)) + + val d: dynamic = C() + assertEquals("C.foo(42)", d.foo(42)) + assertEquals("C.foo(99)", d.`foo_za3lpa$`(99)) + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt new file mode 100644 index 00000000000..313cbf781ee --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt @@ -0,0 +1,23 @@ +interface A { + @JsName("foo") fun f() +} + +interface B { + @JsName("foo") fun g() +} + +class C : A, B { + override fun f() {} + + override fun g() {} +} + +abstract class D : A, B + +open class E { + open fun f() {} + + open fun g() {} +} + +class F : E(), A, B diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.txt new file mode 100644 index 00000000000..5337c3c62f5 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.txt @@ -0,0 +1,51 @@ +package + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "foo") public abstract fun f(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface B { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "foo") public abstract fun g(): 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 C : A, B { + public constructor C() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun f(): kotlin.Unit + public open override /*1*/ fun g(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class D : A, B { + public constructor D() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "foo") public abstract override /*1*/ /*fake_override*/ fun f(): kotlin.Unit + @kotlin.js.JsName(name = "foo") public abstract override /*1*/ /*fake_override*/ fun g(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class E { + public constructor E() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun f(): kotlin.Unit + public open fun g(): 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 F : E, A, B { + public constructor F() + public open override /*3*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun f(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun g(): kotlin.Unit + public open override /*3*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*3*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.kt new file mode 100644 index 00000000000..d41c959ec1f --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.kt @@ -0,0 +1,23 @@ +interface A { + @JsName("f") fun foo(value: Int) +} + +interface B { + @JsName("g") fun foo(value: Int) +} + +class C : A, B { + override fun foo(value: Int) { } +} + +@native interface NA { + @JsName("f") fun foo(value: Int) +} + +@native interface NB { + @JsName("g") fun foo(value: Int) +} + +class NC : NA, NB { + override fun foo(value: Int) { } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.txt new file mode 100644 index 00000000000..924515f9243 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.txt @@ -0,0 +1,45 @@ +package + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "f") public abstract fun foo(/*0*/ value: 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 interface B { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "g") public abstract fun foo(/*0*/ value: 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 C : A, B { + public constructor C() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(/*0*/ value: kotlin.Int): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.js.native() public interface NA { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "f") public abstract fun foo(/*0*/ value: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.js.native() public interface NB { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "g") public abstract fun foo(/*0*/ value: 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 NC : NA, NB { + public constructor NC() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(/*0*/ value: kotlin.Int): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*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 d0713a12436..f2e0b4ab3b9 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -301,6 +301,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("conflictingNamesFromSuperclass.kt") + public void testConflictingNamesFromSuperclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt"); + doTest(fileName); + } + @TestMetadata("extensionPropertyAndMethod.kt") public void testExtensionPropertyAndMethod() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/extensionPropertyAndMethod.kt"); @@ -367,6 +373,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("overriddenJsNameClash.kt") + public void testOverriddenJsNameClash() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/overriddenJsNameClash.kt"); + doTest(fileName); + } + @TestMetadata("packageAndMethod.kt") public void testPackageAndMethod() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/packageAndMethod.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 36fa478aa56..651e38cde3b 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -906,6 +906,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public static class Bridges extends AbstractBlackBoxCodegenTest { + @TestMetadata("jsNative.kt") + public void ignoredJsNative() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/bridges/jsNative.kt"); + doTest(fileName); + } + public void testAllFilesPresentInBridges() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/bridges"), Pattern.compile("^(.+)\\.kt$"), true); } 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 a1a4510097e..2018159a9e4 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 @@ -39,10 +39,14 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.NATIVE_INNER_CLASS_PROHIBITED, "Native inner 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_NAME_CLASH_SYNTHETIC, "JavaScript name {0} is generated from two different inherited members: {1} and {2}", + Renderers.STRING, Renderers.COMPACT, Renderers.COMPACT) put(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors") put(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them") put(ErrorsJs.JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all of the property accessors") put(ErrorsJs.JS_NAME_PROHIBITED_FOR_OVERRIDE, "@JsName is prohibited for overridden members") + put(ErrorsJs.JS_NAME_OVERRIDE_CLASH, "Conflicting names got from overridden declarations {0} and {1}", Renderers.COMPACT, + Renderers.COMPACT) 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 ee690483485..655a37d1dc1 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 @@ -18,10 +18,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics; import com.intellij.psi.PsiElement; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; -import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.psi.KtDeclaration; import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.psi.KtExpression; @@ -46,10 +43,13 @@ public interface ErrorsJs { DiagnosticFactory0 JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT); DiagnosticFactory0 NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR); DiagnosticFactory2 JS_NAME_CLASH = DiagnosticFactory2.create(ERROR); + DiagnosticFactory3 JS_NAME_CLASH_SYNTHETIC = + DiagnosticFactory3.create(ERROR); DiagnosticFactory0 JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_PROHIBITED_FOR_OVERRIDE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory2 JS_NAME_OVERRIDE_CLASH = DiagnosticFactory2.create(ERROR); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt index 65fe5b455d0..e5a32952828 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics +import org.jetbrains.kotlin.config.LanguageFeatureSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils @@ -25,7 +26,7 @@ import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker object JsNameChecker : SimpleDeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink, - bindingContext: BindingContext) { + bindingContext: BindingContext, languageFeatureSettings: LanguageFeatureSettings) { if (descriptor is PropertyDescriptor) { val namedAccessorCount = descriptor.accessors.count { AnnotationsUtils.getJsName(it) != null } if (namedAccessorCount > 0 && namedAccessorCount < descriptor.accessors.size) { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt index 81009b2a0c1..80dba5baaf8 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt @@ -16,7 +16,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics -import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeatureSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.js.naming.FQNGenerator @@ -29,7 +29,6 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.resolve.source.getPsi class JsNameClashChecker : SimpleDeclarationChecker { private val fqnGenerator = FQNGenerator() @@ -40,7 +39,8 @@ class JsNameClashChecker : SimpleDeclarationChecker { declaration: KtDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink, - bindingContext: BindingContext + bindingContext: BindingContext, + languageFeatureSettings: LanguageFeatureSettings ) { if (declaration !is KtProperty || !descriptor.isExtension) { checkDescriptor(descriptor, declaration, diagnosticHolder) @@ -55,23 +55,27 @@ class JsNameClashChecker : SimpleDeclarationChecker { val existing = scope[name] if (existing != null && existing != fqn.descriptor) { diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing)) - val existingDeclaration = findPsi(existing) ?: declaration + val existingDeclaration = existing.findPsi() ?: declaration if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) { diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor)) } } } - } - private fun findPsi(descriptor: DeclarationDescriptor): PsiElement? { - val psi = (descriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() - return if (psi == null && descriptor is CallableMemberDescriptor && - descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE - ) { - descriptor.overriddenDescriptors.mapNotNull { findPsi(it) }.firstOrNull() - } - else { - psi + val fqnDescriptor = fqn.descriptor + if (fqnDescriptor is ClassDescriptor) { + val fakeOverrides = fqnDescriptor.defaultType.memberScope.getContributedDescriptors().asSequence() + .mapNotNull { it as? CallableMemberDescriptor } + .filter { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE } + for (override in fakeOverrides) { + val overrideFqn = fqnGenerator.generate(override) + val scope = getScope(overrideFqn.scope) + val name = overrideFqn.names.last() + val existing = scope[name] + if (existing != null && existing != overrideFqn.descriptor) { + diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH_SYNTHETIC.on(declaration, name, override, existing)) + } + } } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/OverriddenJsNameChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/OverriddenJsNameChecker.kt new file mode 100644 index 00000000000..77c366bf6a7 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/OverriddenJsNameChecker.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.resolve.diagnostics + +import org.jetbrains.kotlin.config.LanguageFeatureSettings +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.js.naming.FQNGenerator +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DeclarationChecker + +class OverriddenJsNameChecker : DeclarationChecker { + private val fqnGenerator = FQNGenerator() + + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink, + bindingContext: BindingContext, languageFeatureSettings: LanguageFeatureSettings) { + doCheck(descriptor) { first, second -> + val psi = descriptor.findPsi() ?: declaration + diagnosticHolder.report(ErrorsJs.JS_NAME_OVERRIDE_CLASH.on(psi, first, second)) + } + + if (descriptor is ClassDescriptor) { + val fakeOverrides = descriptor.defaultType.memberScope.getContributedDescriptors().asSequence() + .mapNotNull { it as? CallableMemberDescriptor } + .filter { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE } + for (override in fakeOverrides) { + val errorFound = doCheck(override) { first, second -> + val psi = descriptor.findPsi() ?: declaration + diagnosticHolder.report(ErrorsJs.JS_NAME_OVERRIDE_CLASH.on(psi, first, second)) + } + if (errorFound) break + } + } + } + + private fun doCheck(descriptor: DeclarationDescriptor, onClash: (CallableMemberDescriptor, CallableMemberDescriptor) -> Unit): + Boolean { + if (descriptor !is CallableMemberDescriptor) return false + + var knownName: String? = null + var knownDescriptor: CallableMemberDescriptor? = null + for (overridden in descriptor.overriddenDescriptors) { + val fqn = fqnGenerator.generate(overridden) + if (!fqn.shared) continue + + val name = fqnGenerator.generate(overridden).names.last() + if (knownName == null) { + knownName = name + knownDescriptor = overridden + } + else if (knownName != name && + (AnnotationsUtils.getJsName(knownDescriptor!!) != null || AnnotationsUtils.getJsName(overridden) != null)) { + onClash(knownDescriptor, overridden) + return true + } + } + + return false + } +} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/sourceLocationUtils.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/sourceLocationUtils.kt new file mode 100644 index 00000000000..a119fc0de32 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/sourceLocationUtils.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.resolve.diagnostics + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.resolve.source.getPsi + +fun DeclarationDescriptor.findPsi(): PsiElement? { + val psi = (this as? DeclarationDescriptorWithSource)?.source?.getPsi() + return if (psi == null && this is CallableMemberDescriptor && kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + overriddenDescriptors.mapNotNull { it.findPsi() }.firstOrNull() + } + else { + psi + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BridgeTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BridgeTestGenerated.java index 894f438a921..9881c0d60af 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BridgeTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BridgeTestGenerated.java @@ -131,6 +131,12 @@ public class BridgeTestGenerated extends AbstractBridgeTest { doTest(fileName); } + @TestMetadata("jsNative.kt") + public void testJsNative() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/bridges/jsNative.kt"); + doTest(fileName); + } + @TestMetadata("kt1939.kt") public void testKt1939() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/bridges/kt1939.kt");