JS: improve name clash checker to handle complicated cases
See KT-18010
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
interface I {
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface J {
|
||||||
|
@JsName("bar")
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface K : I, J {
|
||||||
|
override fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface L : K {
|
||||||
|
<!JS_NAME_CLASH!>override fun foo()<!>
|
||||||
|
|
||||||
|
<!JS_NAME_CLASH!>fun bar()<!>
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
<!JS_NAME_CLASH, JS_NAME_CLASH!>override fun bar()<!> {}
|
||||||
|
|
||||||
|
<!JS_NAME_CLASH, JS_NAME_CLASH!>override fun foo()<!> {}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+12
@@ -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);
|
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")
|
@TestMetadata("classAndFunction.kt")
|
||||||
public void testClassAndFunction() throws Exception {
|
public void testClassAndFunction() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt");
|
||||||
@@ -587,6 +593,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("overrideOverloadedNativeFunction.kt")
|
||||||
public void testOverrideOverloadedNativeFunction() throws Exception {
|
public void testOverrideOverloadedNativeFunction() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/overrideOverloadedNativeFunction.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/overrideOverloadedNativeFunction.kt");
|
||||||
|
|||||||
+45
-23
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.*
|
|||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.js.naming.NameSuggestion
|
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.js.translate.utils.AnnotationsUtils
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
@@ -40,7 +41,7 @@ class JsNameClashChecker : SimpleDeclarationChecker {
|
|||||||
private val nameSuggestion = NameSuggestion()
|
private val nameSuggestion = NameSuggestion()
|
||||||
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
|
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
|
||||||
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
|
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
|
||||||
private val clashedDescriptors = mutableSetOf<DeclarationDescriptor>()
|
private val clashedDescriptors = mutableSetOf<Pair<DeclarationDescriptor, String>>()
|
||||||
|
|
||||||
override fun check(
|
override fun check(
|
||||||
declaration: KtDeclaration,
|
declaration: KtDeclaration,
|
||||||
@@ -61,28 +62,29 @@ class JsNameClashChecker : SimpleDeclarationChecker {
|
|||||||
) {
|
) {
|
||||||
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return
|
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return
|
||||||
|
|
||||||
val suggested = nameSuggestion.suggest(descriptor)!!
|
for (suggested in nameSuggestion.suggestAllPossibleNames(descriptor)) {
|
||||||
if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) {
|
if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) {
|
||||||
val scope = getScope(suggested.scope)
|
val scope = getScope(suggested.scope)
|
||||||
val name = suggested.names.last()
|
val name = suggested.names.last()
|
||||||
val existing = scope[name]
|
val existing = scope[name]
|
||||||
if (existing != null &&
|
if (existing != null &&
|
||||||
existing != descriptor &&
|
existing != descriptor &&
|
||||||
existing.isActual == descriptor.isActual &&
|
existing.isActual == descriptor.isActual &&
|
||||||
existing.isExpect == descriptor.isExpect &&
|
existing.isExpect == descriptor.isExpect &&
|
||||||
!bindingContext.isCommonDiagnosticReported(declaration)
|
!bindingContext.isCommonDiagnosticReported(declaration)
|
||||||
) {
|
) {
|
||||||
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing))
|
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing))
|
||||||
val existingDeclaration = existing.findPsi()
|
val existingDeclaration = existing.findPsi()
|
||||||
if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) {
|
if (clashedDescriptors.add(existing to name) && existingDeclaration is KtDeclaration &&
|
||||||
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor))
|
existingDeclaration != declaration) {
|
||||||
|
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fqnDescriptor = suggested.descriptor
|
if (descriptor is ClassDescriptor) {
|
||||||
if (fqnDescriptor is ClassDescriptor) {
|
val fakeOverrides = descriptor.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
|
||||||
val fakeOverrides = fqnDescriptor.defaultType.memberScope.getContributedDescriptors().asSequence()
|
|
||||||
.mapNotNull { it as? CallableMemberDescriptor }
|
.mapNotNull { it as? CallableMemberDescriptor }
|
||||||
.filter { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
|
.filter { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
|
||||||
for (override in fakeOverrides) {
|
for (override in fakeOverrides) {
|
||||||
@@ -105,6 +107,25 @@ class JsNameClashChecker : SimpleDeclarationChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun NameSuggestion.suggestAllPossibleNames(descriptor: DeclarationDescriptor): Collection<SuggestedName> =
|
||||||
|
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 {
|
private fun BindingContext.isCommonDiagnosticReported(declaration: KtDeclaration): Boolean {
|
||||||
return diagnostics.forElement(declaration).any { it.factory in COMMON_DIAGNOSTICS }
|
return diagnostics.forElement(declaration).any { it.factory in COMMON_DIAGNOSTICS }
|
||||||
}
|
}
|
||||||
@@ -149,10 +170,11 @@ class JsNameClashChecker : SimpleDeclarationChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fqn = nameSuggestion.suggest(descriptor) ?: return
|
for (fqn in nameSuggestion.suggestAllPossibleNames(descriptor)) {
|
||||||
if (fqn.stable && presentsInGeneratedCode(fqn.descriptor)) {
|
if (fqn.stable && presentsInGeneratedCode(fqn.descriptor)) {
|
||||||
target[fqn.names.last()] = fqn.descriptor
|
target[fqn.names.last()] = fqn.descriptor
|
||||||
(fqn.descriptor as? CallableMemberDescriptor)?.let { checkOverrideClashes(it, target) }
|
(fqn.descriptor as? CallableMemberDescriptor)?.let { checkOverrideClashes(it, target) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user