diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.kt new file mode 100644 index 00000000000..6a9fb53452e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.kt @@ -0,0 +1,18 @@ +interface I { + fun foo() +} + +interface J { + @JsName("bar") + fun foo() +} + +interface K : I, J { + override fun foo() +} + +interface L : K { + override fun foo() + + fun bar() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.txt new file mode 100644 index 00000000000..4d495cbd2d1 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.txt @@ -0,0 +1,30 @@ +package + +public interface I { + 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 + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface J { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "bar") public abstract fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface K : I, J { + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface L : K { + public abstract fun bar(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ 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/name/nameSwapInOverride.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.kt new file mode 100644 index 00000000000..6de82abc7aa --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.kt @@ -0,0 +1,21 @@ +interface I { + @JsName("bar") + fun foo() + + @JsName("foo") + fun bar() +} + +interface J { + fun foo() + + fun bar() +} + +class A : I, J { + // Duplicate diagnostics are expected here, since `bar()` function gets both `foo` and `bar` names and clashes with both + // names of `foo()` function. + override fun bar() {} + + override fun foo() {} +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.txt new file mode 100644 index 00000000000..d5ac7be2757 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.txt @@ -0,0 +1,26 @@ +package + +public final class A : I, J { + public constructor A() + public open override /*2*/ fun bar(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I { + @kotlin.js.JsName(name = "foo") public abstract fun bar(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "bar") public abstract fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface J { + 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 + 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 8a92d12fac9..0e62b5bdb38 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -467,6 +467,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithJsStdLib/name"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("bridgeClash.kt") + public void testBridgeClash() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/bridgeClash.kt"); + doTest(fileName); + } + @TestMetadata("classAndFunction.kt") public void testClassAndFunction() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt"); @@ -587,6 +593,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("nameSwapInOverride.kt") + public void testNameSwapInOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/nameSwapInOverride.kt"); + doTest(fileName); + } + @TestMetadata("overrideOverloadedNativeFunction.kt") public void testOverrideOverloadedNativeFunction() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/overrideOverloadedNativeFunction.kt"); 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 da62c9b60f8..71c48a391d8 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 @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.js.naming.NameSuggestion +import org.jetbrains.kotlin.js.naming.SuggestedName import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.BindingContext @@ -40,7 +41,7 @@ class JsNameClashChecker : SimpleDeclarationChecker { private val nameSuggestion = NameSuggestion() private val scopes = mutableMapOf>() private val clashedFakeOverrides = mutableMapOf>() - private val clashedDescriptors = mutableSetOf() + private val clashedDescriptors = mutableSetOf>() override fun check( declaration: KtDeclaration, @@ -61,28 +62,29 @@ class JsNameClashChecker : SimpleDeclarationChecker { ) { if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return - val suggested = nameSuggestion.suggest(descriptor)!! - if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) { - val scope = getScope(suggested.scope) - val name = suggested.names.last() - val existing = scope[name] - if (existing != null && - existing != descriptor && - existing.isActual == descriptor.isActual && - existing.isExpect == descriptor.isExpect && - !bindingContext.isCommonDiagnosticReported(declaration) - ) { - diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing)) - val existingDeclaration = existing.findPsi() - if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) { - diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor)) + for (suggested in nameSuggestion.suggestAllPossibleNames(descriptor)) { + if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) { + val scope = getScope(suggested.scope) + val name = suggested.names.last() + val existing = scope[name] + if (existing != null && + existing != descriptor && + existing.isActual == descriptor.isActual && + existing.isExpect == descriptor.isExpect && + !bindingContext.isCommonDiagnosticReported(declaration) + ) { + diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing)) + val existingDeclaration = existing.findPsi() + if (clashedDescriptors.add(existing to name) && existingDeclaration is KtDeclaration && + existingDeclaration != declaration) { + diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor)) + } } } } - val fqnDescriptor = suggested.descriptor - if (fqnDescriptor is ClassDescriptor) { - val fakeOverrides = fqnDescriptor.defaultType.memberScope.getContributedDescriptors().asSequence() + if (descriptor is ClassDescriptor) { + val fakeOverrides = descriptor.unsubstitutedMemberScope.getContributedDescriptors().asSequence() .mapNotNull { it as? CallableMemberDescriptor } .filter { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE } for (override in fakeOverrides) { @@ -105,6 +107,25 @@ class JsNameClashChecker : SimpleDeclarationChecker { } } + private fun NameSuggestion.suggestAllPossibleNames(descriptor: DeclarationDescriptor): Collection = + if (descriptor is CallableMemberDescriptor) { + val primary = suggest(descriptor) + if (primary != null) { + val overriddenNames = descriptor.overriddenDescriptors.flatMap { + suggestAllPossibleNames(it).map { overridden -> + SuggestedName(overridden.names, overridden.stable, primary.descriptor, primary.scope) + } + } + (overriddenNames + primary).distinctBy { it.names } + } + else { + emptyList() + } + } + else { + listOfNotNull(suggest(descriptor)) + } + private fun BindingContext.isCommonDiagnosticReported(declaration: KtDeclaration): Boolean { return diagnostics.forElement(declaration).any { it.factory in COMMON_DIAGNOSTICS } } @@ -149,10 +170,11 @@ class JsNameClashChecker : SimpleDeclarationChecker { } } - val fqn = nameSuggestion.suggest(descriptor) ?: return - if (fqn.stable && presentsInGeneratedCode(fqn.descriptor)) { - target[fqn.names.last()] = fqn.descriptor - (fqn.descriptor as? CallableMemberDescriptor)?.let { checkOverrideClashes(it, target) } + for (fqn in nameSuggestion.suggestAllPossibleNames(descriptor)) { + if (fqn.stable && presentsInGeneratedCode(fqn.descriptor)) { + target[fqn.names.last()] = fqn.descriptor + (fqn.descriptor as? CallableMemberDescriptor)?.let { checkOverrideClashes(it, target) } + } } }