KT-2752: add diagnostics that reports about conflicting overridden declarations

This commit is contained in:
Alexey Andreev
2016-06-03 16:02:39 +03:00
parent 16c172f053
commit c5087779e4
14 changed files with 333 additions and 19 deletions
@@ -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
}
@@ -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<KtExpression> JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT);
DiagnosticFactory0<KtExpression> NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<KtElement, String, DeclarationDescriptor> JS_NAME_CLASH = DiagnosticFactory2.create(ERROR);
DiagnosticFactory3<KtElement, String, DeclarationDescriptor, DeclarationDescriptor> JS_NAME_CLASH_SYNTHETIC =
DiagnosticFactory3.create(ERROR);
DiagnosticFactory0<KtElement> JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtElement> JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> JS_NAME_PROHIBITED_FOR_OVERRIDE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, DeclarationDescriptor, DeclarationDescriptor> JS_NAME_OVERRIDE_CLASH = DiagnosticFactory2.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -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) {
@@ -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))
}
}
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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");