JS: improve name clash checker to handle complicated cases

See KT-18010
This commit is contained in:
Alexey Andreev
2017-09-19 12:53:35 +03:00
parent 95566b1374
commit ca014468ee
6 changed files with 152 additions and 23 deletions
@@ -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
}
@@ -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");
@@ -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) }
}
}
}