[FIR] Implement CONFLICTING_IMPORT diagnostic

This commit is contained in:
Andrey Zinovyev
2021-06-02 15:26:19 +03:00
committed by Space
parent 14789cb9e5
commit 7c669b65c3
18 changed files with 99 additions and 284 deletions
@@ -951,6 +951,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val CANNOT_BE_IMPORTED by error<KtSimpleNameExpression>(PositioningStrategy.IMPORT_LAST_NAME) {
parameter<Name>("name")
}
val CONFLICTING_IMPORT by error<KtImportDirective>(PositioningStrategy.IMPORT_LAST_NAME) {
parameter<Name>("name")
}
}
}
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
@@ -522,5 +523,6 @@ object FirErrors {
val CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON by error1<KtSimpleNameExpression, Name>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
val PACKAGE_CANNOT_BE_IMPORTED by error0<KtSimpleNameExpression>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
val CANNOT_BE_IMPORTED by error1<KtSimpleNameExpression, Name>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
val CONFLICTING_IMPORT by error1<KtImportDirective, Name>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
}
@@ -6,11 +6,14 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.followAllAlias
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.resolve.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.name.ClassId
object FirImportsChecker : FirFileChecker() {
@@ -24,14 +27,14 @@ object FirImportsChecker : FirFileChecker() {
checkCanBeImported(import, context, reporter)
}
}
checkConflictingImports(declaration.imports, context, reporter)
}
private fun checkAllUnderFromEnumEntry(import: FirImport, context: CheckerContext, reporter: DiagnosticReporter) {
val fqName = import.importedFqName ?: return
if (fqName.isRoot || fqName.parent().isRoot) return
val classId = ClassId.topLevel(fqName.parent())
val classSymbol = context.session.symbolProvider.getClassLikeSymbolByFqName(classId) ?: return
val classFir = classSymbol.fir as? FirRegularClass ?: return
val classFir = classId.resolveToClass(context) ?: return
if (classFir.isEnumClass && classFir.collectEnumEntries().any { it.name == fqName.shortName() }) {
reporter.reportOn(import.source, FirErrors.CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON, classFir.name, context)
}
@@ -42,12 +45,12 @@ object FirImportsChecker : FirFileChecker() {
val importedName = importedFqName.shortName()
//empty name come from LT in some erroneous cases
if (importedName.isSpecial || importedName.identifier.isEmpty()) return
val classId = (import as? FirResolvedImport)?.resolvedClassId
if (classId != null) {
val classSymbol = context.session.symbolProvider.getClassLikeSymbolByFqName(classId) ?: return
val classFir = classSymbol.fir as? FirRegularClass ?: return
val classFir = classId.resolveToClass(context) ?: return
if (classFir.classKind.isSingleton) return
val illegalImport = classFir.declarations.any {
it is FirSimpleFunction && !it.isStatic && it.name == importedName ||
it is FirProperty && it.name == importedName
@@ -65,4 +68,48 @@ object FirImportsChecker : FirFileChecker() {
}
}
}
private fun checkConflictingImports(imports: List<FirImport>, context: CheckerContext, reporter: DiagnosticReporter) {
val interestingImports = imports
.filterIsInstance<FirResolvedImport>()
.filter { import ->
!import.isAllUnder
&& import.importedName?.identifierOrNullIfSpecial?.isNotEmpty() == true
&& import.resolvesToClass(context)
}
interestingImports
.groupBy { it.aliasName ?: it.importedName!! }
.values
.filter { it.size > 1 }
.forEach { conflicts ->
conflicts.forEach {
reporter.reportOn(it.source, FirErrors.CONFLICTING_IMPORT, it.importedName!!, context)
}
}
}
private fun FirResolvedImport.resolvesToClass(context: CheckerContext): Boolean {
if (resolvedClassId != null) {
if (isAllUnder) return true
val parentClass = resolvedClassId!!
val relativeClassName = this.relativeClassName ?: return false
val importedName = this.importedName ?: return false
val innerClassId = ClassId(parentClass.packageFqName, relativeClassName.child(importedName), false)
return innerClassId.resolveToClass(context) != null
} else {
val importedFqName = importedFqName ?: return false
if (importedFqName.isRoot) return false
val importedClassId = ClassId.topLevel(importedFqName)
return importedClassId.resolveToClass(context) != null
}
}
private fun ClassId.resolveToClass(context: CheckerContext): FirRegularClass? {
val classSymbol = context.session.symbolProvider.getClassLikeSymbolByFqName(this) ?: return null
return when (classSymbol) {
is FirRegularClassSymbol -> classSymbol.fir
is FirTypeAliasSymbol -> classSymbol.fir.followAllAlias(context.session) as? FirRegularClass
else -> null
}
}
}
@@ -70,6 +70,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTIO
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_ON_NULLABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONDITION_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_IMPORT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_OVERLOADS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION_IN_TYPEALIAS_EXPANSION
@@ -1174,6 +1175,11 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
"Cannot import ''{0}'', functions and properties can be imported only from packages or objects",
TO_STRING
)
map.put(
CONFLICTING_IMPORT,
"Conflicting import, imported name ''{0}'' is ambiguous",
TO_STRING
)
// Extended checkers group
map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier")
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.diagnostics
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes
@@ -891,6 +888,12 @@ object PositioningStrategies {
}
val IMPORT_LAST_NAME: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
override fun isValid(element: PsiElement): Boolean {
if (element is PsiErrorElement) return false
return !element.children.any { !isValid(it) }
}
override fun mark(element: PsiElement): List<TextRange> {
if (element is KtImportDirective) {
val importedReference = element.importedReference
@@ -1,15 +0,0 @@
//FILE:a.kt
package a
import b.O
import c.O
//FILE:b.kt
package b
object O {}
//FILE:c.kt
package c
object O {}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
//FILE:a.kt
package a
@@ -1,17 +0,0 @@
// FILE: a.kt
package a
class X
// FILE: b.kt
package b
class X
// FILE: c.kt
package c
import a.X
import b.X
class Y : <!UNRESOLVED_REFERENCE!>X<!>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: a.kt
package a
@@ -1,48 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: 1.kt
package a
class someFun() {}
fun someFun(i: Int) {}
class someVal() {}
val Int.someVal: Int get() = 3
class A
class B
// FILE: 2.kt
package b
class someFun
class someVal
class someAll
fun A() {}
class B
// FILE: 3.kt
import a.someFun
import b.someFun
import a.someVal
import b.someVal
import a.A
import b.A
// FILE: 4.kt
import b.*
import a.B
// FILE: 5.kt
package b
import a.B
// FILE: 6.kt
import a.B
import b.B
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: 1.kt
package a
@@ -1,31 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: a.kt
package a
class A {
class B
}
// FILE: b.kt
package a
class D {
class B
}
// FILE: c.kt
import a.A.B
import a.D.B
fun test(b: <!UNRESOLVED_REFERENCE!>B<!>) {
<!UNRESOLVED_REFERENCE!>B<!>()
}
// FILE: d.kt
import a.A.*
import a.D.*
// todo ambiguvity here
fun test2(b: <!UNRESOLVED_REFERENCE!>B<!>) {
<!UNRESOLVED_REFERENCE!>B<!>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: a.kt
package a
@@ -1,164 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: main1.kt
package abc1
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main2.kt
package abc2
import kotlin.jvm.Throws
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main3.kt
package abc3
import kotlin.Throws
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main4.kt
package abc4
import kotlin.Throws
import kotlin.jvm.Throws
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main5.kt
package abc5
import kotlin.jvm.*
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main6.kt
package abc6
import kotlin.*
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main7.kt
package abc7
import kotlin.*
import kotlin.jvm.*
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main8.kt
package abc8
import kotlin.*
import kotlin.jvm.Throws
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
// FILE: main9.kt
package abc9
import kotlin.jvm.*
import kotlin.Throws
@Throws(Exception::class)
fun foo1() {}
@kotlin.Throws(Exception::class)
fun foo2() {}
@kotlin.jvm.Throws(Exception::class)
fun foo3() {}
fun foo5(x: Throws) {}
fun foo6(x: kotlin.Throws) {}
fun foo7(x: kotlin.jvm.Throws) {}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: main1.kt
package abc1
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
@@ -2593,6 +2594,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.CONFLICTING_IMPORT) { firDiagnostic ->
ConflictingImportImpl(
firDiagnostic.a,
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic ->
ConflictingJvmDeclarationsImpl(
firDiagnostic as FirPsiDiagnostic<*>,
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
@@ -1819,6 +1820,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val name: Name
}
abstract class ConflictingImport : KtFirDiagnostic<KtImportDirective>() {
override val diagnosticClass get() = ConflictingImport::class
abstract val name: Name
}
abstract class ConflictingJvmDeclarations : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConflictingJvmDeclarations::class
}
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
@@ -2951,6 +2952,14 @@ internal class CannotBeImportedImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ConflictingImportImpl(
override val name: Name,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ConflictingImport(), KtAbstractFirDiagnostic<KtImportDirective> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ConflictingJvmDeclarationsImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,