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);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
+45
-23
@@ -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<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
|
||||
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
|
||||
private val clashedDescriptors = mutableSetOf<DeclarationDescriptor>()
|
||||
private val clashedDescriptors = mutableSetOf<Pair<DeclarationDescriptor, String>>()
|
||||
|
||||
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<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 {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user