[FIR JS] Support JS_NAME_CLASH and JS_FAKE_NAME_CLASH diagnostics
^KT-59425 Fixed ^KT-59370 Fixed
This commit is contained in:
committed by
Space Team
parent
5ac277d058
commit
6bb939c6cb
+17
-38
@@ -10,12 +10,12 @@ package org.jetbrains.kotlin.fir.analysis.js.checkers
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.effectiveVisibility
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.*
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isActual
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
|
||||
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
|
||||
@@ -101,13 +101,26 @@ fun FirBasedSymbol<*>.isNativeInterface(session: FirSession): Boolean {
|
||||
return isNativeObject(session) && (fir as? FirClass)?.isInterface == true
|
||||
}
|
||||
|
||||
private val FirBasedSymbol<*>.isExpect
|
||||
fun FirBasedSymbol<*>.isLibraryObject(session: FirSession): Boolean {
|
||||
return hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsLibrary, session)
|
||||
}
|
||||
|
||||
fun FirBasedSymbol<*>.isPresentInGeneratedCode(session: FirSession) = !isNativeObject(session) && !isLibraryObject(session)
|
||||
|
||||
internal val FirBasedSymbol<*>.isExpect
|
||||
get() = when (this) {
|
||||
is FirCallableSymbol<*> -> isExpect
|
||||
is FirClassSymbol<*> -> isExpect
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal val FirBasedSymbol<*>.isActual
|
||||
get() = when (this) {
|
||||
is FirCallableSymbol<*> -> isActual
|
||||
is FirClassSymbol<*> -> isActual
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun FirBasedSymbol<*>.isPredefinedObject(session: FirSession): Boolean {
|
||||
if (fir is FirMemberDeclaration && isExpect) return true
|
||||
if (isEffectivelyExternalMember(session)) return true
|
||||
@@ -154,6 +167,8 @@ fun FirBasedSymbol<*>.isPredefinedObject(context: CheckerContext) = isPredefined
|
||||
|
||||
fun FirBasedSymbol<*>.isExportedObject(context: CheckerContext) = isExportedObject(context.session)
|
||||
|
||||
fun FirBasedSymbol<*>.isLibraryObject(context: CheckerContext) = isLibraryObject(context.session)
|
||||
|
||||
internal fun FirClass.superClassNotAny(session: FirSession) = superConeTypes
|
||||
.filterNot { it.isAny || it.isNullableAny }
|
||||
.find { it.toSymbol(session)?.classKind == ClassKind.CLASS }
|
||||
@@ -161,39 +176,3 @@ internal fun FirClass.superClassNotAny(session: FirSession) = superConeTypes
|
||||
internal fun getRootClassLikeSymbolOrSelf(symbol: FirBasedSymbol<*>, session: FirSession): FirBasedSymbol<*> {
|
||||
return symbol.getContainingClassSymbol(session)?.let { getRootClassLikeSymbolOrSelf(it, session) } ?: symbol
|
||||
}
|
||||
|
||||
internal fun FirBasedSymbol<*>.getStableNameInJavaScript(session: FirSession): String? {
|
||||
val jsName = getJsName(session)
|
||||
if (jsName != null) {
|
||||
return jsName
|
||||
}
|
||||
val hasStableNameInJavaScript = when {
|
||||
isEffectivelyExternal(session) -> true
|
||||
isExportedObject(session) -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
// TODO: rethink in KT-60554
|
||||
val hasPublicName = when (this) {
|
||||
is FirClassLikeSymbol -> !isLocal
|
||||
is FirCallableSymbol -> {
|
||||
val parentClass = getContainingClassSymbol(session)
|
||||
if (parentClass != null) {
|
||||
when (visibility) {
|
||||
is Visibilities.Public -> true
|
||||
is Visibilities.Protected -> !parentClass.isFinal && parentClass.visibility.isPublicAPI
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
!callableId.isLocal && effectiveVisibility.publicApi
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
if (hasStableNameInJavaScript || hasPublicName) {
|
||||
return (fir as? FirMemberDeclaration)?.nameOrSpecialName?.identifierOrNullIfSpecial
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.js.checkers
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
|
||||
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
|
||||
internal data class FirJsStableName(
|
||||
val name: String,
|
||||
val symbol: FirBasedSymbol<*>,
|
||||
val canBeMangled: Boolean,
|
||||
val isPresentInGeneratedCode: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
private fun hasPublicName(symbol: FirBasedSymbol<*>, session: FirSession): Boolean {
|
||||
return when (symbol) {
|
||||
is FirClassLikeSymbol -> !symbol.isLocal
|
||||
is FirCallableSymbol -> {
|
||||
val parentClass = symbol.getContainingClassSymbol(session)
|
||||
if (parentClass != null) {
|
||||
when (symbol.visibility) {
|
||||
is Visibilities.Public -> true
|
||||
is Visibilities.Protected -> !parentClass.isFinal && parentClass.visibility.isPublicAPI
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
!symbol.callableId.isLocal && symbol.effectiveVisibility.publicApi
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
fun createStableNameOrNull(symbol: FirBasedSymbol<*>, session: FirSession): FirJsStableName? {
|
||||
val jsName = symbol.getJsName(session)
|
||||
if (jsName != null) {
|
||||
return FirJsStableName(jsName, symbol, false, symbol.isPresentInGeneratedCode(session))
|
||||
}
|
||||
|
||||
when (symbol) {
|
||||
is FirConstructorSymbol -> return null
|
||||
is FirPropertyAccessorSymbol -> return null
|
||||
}
|
||||
|
||||
val hasStableNameInJavaScript = when {
|
||||
symbol.isEffectivelyExternal(session) -> true
|
||||
symbol.isExportedObject(session) -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
if (hasStableNameInJavaScript ||
|
||||
// TODO: The behavior (using hasPublicName()) is inherited from K1 frontend checks for the Legacy backend.
|
||||
// However, it is entirely unnecessary in the IR backend.
|
||||
// This is a trying to replicate the K1 Legacy logic for the sake of consistency,
|
||||
// but we should redesign and overhaul it later: KT-60554
|
||||
// TODO: After that, FirJsStableName.canBeMangled should always be false,
|
||||
// and all related code can be removed, such as methods:
|
||||
// - FirJsStableName.hasPublicName();
|
||||
// - FirBasedSymbol<*>.doesJSManglingChangeName();
|
||||
// - FirJsStableName.shouldClashBeCaughtByCommonFrontendCheck().
|
||||
hasPublicName(symbol, session)
|
||||
) {
|
||||
val name = (symbol.fir as? FirMemberDeclaration)?.nameOrSpecialName?.identifierOrNullIfSpecial
|
||||
if (name != null) {
|
||||
return FirJsStableName(name, symbol, !hasStableNameInJavaScript, symbol.isPresentInGeneratedCode(session))
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirBasedSymbol<*>.doesJSManglingChangeName(): Boolean {
|
||||
return when (this) {
|
||||
is FirFunctionSymbol<*> -> isExtension || valueParameterSymbols.isNotEmpty() || typeParameterSymbols.isNotEmpty()
|
||||
is FirPropertySymbol -> isExtension
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldClashBeCaughtByCommonFrontendCheck(lhs: FirBasedSymbol<*>, rhs: FirBasedSymbol<*>): Boolean {
|
||||
return (lhs is FirFunctionSymbol<*> && rhs is FirFunctionSymbol<*>) ||
|
||||
(lhs is FirPropertySymbol && rhs is FirPropertySymbol) ||
|
||||
(lhs is FirClassLikeSymbol<*> && rhs is FirClassLikeSymbol<*>)
|
||||
}
|
||||
|
||||
private fun isExternalRedeclarable(): Boolean {
|
||||
return when {
|
||||
isPresentInGeneratedCode -> false
|
||||
(symbol as? FirCallableSymbol<*>)?.isFinal == true -> true
|
||||
else -> symbol is FirClassLikeSymbol<*>
|
||||
}
|
||||
}
|
||||
|
||||
fun clashesWith(other: FirJsStableName): Boolean {
|
||||
return when {
|
||||
symbol === other.symbol -> false
|
||||
name != other.name -> false
|
||||
!isPresentInGeneratedCode && !other.isPresentInGeneratedCode -> false
|
||||
isExternalRedeclarable() || other.isExternalRedeclarable() -> false
|
||||
symbol.isActual != other.symbol.isActual -> false
|
||||
symbol.isExpect != other.symbol.isExpect -> false
|
||||
canBeMangled && symbol.doesJSManglingChangeName() -> false
|
||||
other.canBeMangled && other.symbol.doesJSManglingChangeName() -> false
|
||||
canBeMangled && other.canBeMangled && shouldClashBeCaughtByCommonFrontendCheck(symbol, other.symbol) -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Collection<FirJsStableName>.collectNameClashesWith(name: FirJsStableName) = mapNotNull { next ->
|
||||
next.takeIf {
|
||||
next.clashesWith(name)
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -32,7 +32,8 @@ object JsDeclarationCheckers : DeclarationCheckers() {
|
||||
FirJsMultipleInheritanceChecker,
|
||||
FirJsDynamicDeclarationChecker,
|
||||
FirJsInheritanceClassChecker,
|
||||
FirJsExternalInheritorOnlyChecker
|
||||
FirJsExternalInheritorOnlyChecker,
|
||||
FirJsNameClashClassMembersChecker
|
||||
)
|
||||
|
||||
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
|
||||
@@ -46,4 +47,9 @@ object JsDeclarationCheckers : DeclarationCheckers() {
|
||||
get() = setOf(
|
||||
FirJsPropertyDelegationByDynamicChecker
|
||||
)
|
||||
|
||||
override val fileCheckers: Set<FirFileChecker>
|
||||
get() = setOf(
|
||||
FirJsNameClashFileTopLevelDeclarationsChecker
|
||||
)
|
||||
}
|
||||
|
||||
+4
-7
@@ -11,14 +11,11 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.getStableNameInJavaScript
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.FirJsStableName
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.isNativeObject
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.nameOrSpecialName
|
||||
|
||||
object FirJsBuiltinNameClashChecker : FirBasicDeclarationChecker() {
|
||||
private val PROHIBITED_STATIC_NAMES = setOf("prototype", "length", "\$metadata\$")
|
||||
@@ -32,12 +29,12 @@ object FirJsBuiltinNameClashChecker : FirBasicDeclarationChecker() {
|
||||
return
|
||||
}
|
||||
|
||||
val stableName = declaration.symbol.getStableNameInJavaScript(context.session) ?: return
|
||||
val stableName = FirJsStableName.createStableNameOrNull(declaration.symbol, context.session) ?: return
|
||||
|
||||
if (declaration is FirClassLikeDeclaration && stableName in PROHIBITED_STATIC_NAMES) {
|
||||
if (declaration is FirClassLikeDeclaration && stableName.name in PROHIBITED_STATIC_NAMES) {
|
||||
reporter.reportOn(declaration.source, FirJsErrors.JS_BUILTIN_NAME_CLASH, "Function.$stableName", context)
|
||||
}
|
||||
if (declaration is FirCallableDeclaration && stableName in PROHIBITED_MEMBER_NAMES) {
|
||||
if (declaration is FirCallableDeclaration && stableName.name in PROHIBITED_MEMBER_NAMES) {
|
||||
reporter.reportOn(declaration.source, FirJsErrors.JS_BUILTIN_NAME_CLASH, "Object.prototype.$stableName", context)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.FirJsStableName
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.getJsName
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.getStableNameInJavaScript
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.isExportedObject
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.sanitizeName
|
||||
import org.jetbrains.kotlin.fir.declarations.FirConstructor
|
||||
@@ -39,8 +39,8 @@ object FirJsNameCharsChecker : FirBasicDeclarationChecker() {
|
||||
return
|
||||
}
|
||||
|
||||
val stableName = declaration.symbol.getStableNameInJavaScript(context.session) ?: return
|
||||
if ((sanitizeName(stableName) != stableName)) {
|
||||
val stableName = FirJsStableName.createStableNameOrNull(declaration.symbol, context.session) ?: return
|
||||
if ((sanitizeName(stableName.name) != stableName.name)) {
|
||||
reporter.reportOn(declaration.source, FirJsErrors.NAME_CONTAINS_ILLEGAL_CHARS, context)
|
||||
}
|
||||
}
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.js.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.*
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.FirJsStableName
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
|
||||
import org.jetbrains.kotlin.fir.scopes.*
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.popLast
|
||||
|
||||
object FirJsNameClashClassMembersChecker : FirClassChecker() {
|
||||
private class StableNamesCollector {
|
||||
val jsStableNames = mutableSetOf<FirJsStableName>()
|
||||
val overrideIntersections = hashMapOf<FirCallableSymbol<*>, HashSet<FirCallableSymbol<*>>>()
|
||||
|
||||
private val allSymbols = mutableSetOf<FirCallableSymbol<*>>()
|
||||
|
||||
private fun FirTypeScope.collectOverriddenLeaves(classMemberSymbol: FirCallableSymbol<*>): Set<FirCallableSymbol<*>> {
|
||||
val visitedSymbols = hashSetOf(classMemberSymbol)
|
||||
val symbolsToProcess = mutableListOf(classMemberSymbol)
|
||||
val leaves = mutableSetOf<FirCallableSymbol<*>>()
|
||||
while (symbolsToProcess.isNotEmpty()) {
|
||||
val processingSymbol = symbolsToProcess.popLast()
|
||||
val overriddenMembers = getDirectOverriddenMembers(processingSymbol, true)
|
||||
for (overriddenMember in overriddenMembers) {
|
||||
if (visitedSymbols.add(overriddenMember)) {
|
||||
symbolsToProcess.add(overriddenMember)
|
||||
}
|
||||
}
|
||||
if (overriddenMembers.isEmpty()) {
|
||||
leaves.add(processingSymbol)
|
||||
}
|
||||
}
|
||||
return leaves
|
||||
}
|
||||
|
||||
private fun MutableSet<FirJsStableName>.addStableJavaScriptName(
|
||||
targetSymbol: FirCallableSymbol<*>?,
|
||||
overriddenSymbol: FirCallableSymbol<*>?,
|
||||
context: CheckerContext,
|
||||
) {
|
||||
val stableName = when {
|
||||
targetSymbol == null || overriddenSymbol == null -> return
|
||||
(targetSymbol as? FirConstructorSymbol)?.isPrimary == true -> return
|
||||
!overriddenSymbol.isPresentInGeneratedCode(context.session) && overriddenSymbol.isFinal -> return
|
||||
else -> FirJsStableName.createStableNameOrNull(overriddenSymbol, context.session) ?: return
|
||||
}
|
||||
|
||||
if (stableName.isPresentInGeneratedCode) {
|
||||
add(stableName.copy(symbol = targetSymbol))
|
||||
} else {
|
||||
val inheritedExternalName = stableName.copy(
|
||||
symbol = targetSymbol,
|
||||
canBeMangled = false,
|
||||
isPresentInGeneratedCode = targetSymbol.isPresentInGeneratedCode(context.session)
|
||||
)
|
||||
add(inheritedExternalName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableSet<FirJsStableName>.addAllStableJavaScriptNames(
|
||||
targetSymbol: FirCallableSymbol<*>,
|
||||
overriddenSymbol: FirCallableSymbol<*>,
|
||||
context: CheckerContext,
|
||||
) {
|
||||
addStableJavaScriptName(targetSymbol, overriddenSymbol, context)
|
||||
if (targetSymbol is FirPropertySymbol && overriddenSymbol is FirPropertySymbol) {
|
||||
addStableJavaScriptName(targetSymbol.getterSymbol, overriddenSymbol.getterSymbol, context)
|
||||
addStableJavaScriptName(targetSymbol.setterSymbol, overriddenSymbol.setterSymbol, context)
|
||||
}
|
||||
}
|
||||
|
||||
fun addAllSymbolsFrom(symbols: Collection<FirCallableSymbol<*>>) {
|
||||
for (symbol in symbols) {
|
||||
when (symbol) {
|
||||
is FirIntersectionCallableSymbol -> {
|
||||
addAllSymbolsFrom(symbol.intersections)
|
||||
for (intersectedSymbol in symbol.intersections) {
|
||||
overrideIntersections.getOrPut(intersectedSymbol) { hashSetOf() }.addAll(symbol.intersections)
|
||||
}
|
||||
}
|
||||
else -> allSymbols.add(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun processStableJavaScriptNamesForMembers(declaration: FirClass, context: CheckerContext) {
|
||||
for (classMember in declaration.declarations) {
|
||||
if (classMember is FirClassLikeDeclaration) {
|
||||
jsStableNames.addIfNotNull(FirJsStableName.createStableNameOrNull(classMember.symbol, context.session))
|
||||
}
|
||||
}
|
||||
|
||||
val scope = declaration.symbol.unsubstitutedScope(context)
|
||||
|
||||
addAllSymbolsFrom(scope.collectAllFunctions())
|
||||
addAllSymbolsFrom(scope.collectAllProperties())
|
||||
|
||||
for (callableMemberSymbol in allSymbols) {
|
||||
val overriddenLeaves = scope.collectOverriddenLeaves(callableMemberSymbol)
|
||||
for (symbol in overriddenLeaves) {
|
||||
jsStableNames.addAllStableJavaScriptNames(callableMemberSymbol, symbol, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<FirJsStableName>.filterFakeOverrideNames(declaration: FirClass, context: CheckerContext) = filterTo(mutableSetOf()) {
|
||||
it.symbol.getContainingClassSymbol(context.session) != declaration.symbol
|
||||
}
|
||||
|
||||
private data class ClashedSymbol(val symbol: FirBasedSymbol<*>, val clashedWith: List<FirBasedSymbol<*>>)
|
||||
|
||||
private fun List<FirJsStableName>.collectNonFakeOverrideClashes(isFakeOverrideName: (FirJsStableName) -> Boolean): List<ClashedSymbol> {
|
||||
return buildList {
|
||||
for (stableName in this@collectNonFakeOverrideClashes) {
|
||||
var clashed = collectNameClashesWith(stableName)
|
||||
if (isFakeOverrideName(stableName)) {
|
||||
// Do not check for clashes between fake overrides here.
|
||||
// Such clashes will be checked with JS_FAKE_NAME_CLASH.
|
||||
clashed = clashed.filter { !isFakeOverrideName(it) }
|
||||
}
|
||||
if (clashed.isNotEmpty()) {
|
||||
add(ClashedSymbol(stableName.symbol, clashed.map { it.symbol }))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Set<FirJsStableName>.findFirstFakeOverrideClash(stableNameCollector: StableNamesCollector): ClashedSymbol? {
|
||||
for (stableName in this) {
|
||||
val intersectedSymbols = stableNameCollector.overrideIntersections[stableName.symbol] ?: emptySet()
|
||||
val clashed = collectNameClashesWith(stableName).filter {
|
||||
// intersected fake override symbols are not clashed
|
||||
it.symbol !in intersectedSymbols
|
||||
}
|
||||
if (clashed.isNotEmpty()) {
|
||||
return ClashedSymbol(stableName.symbol, clashed.map { it.symbol })
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (!declaration.symbol.isPresentInGeneratedCode(context.session)) {
|
||||
return
|
||||
}
|
||||
|
||||
val stableNameCollector = StableNamesCollector()
|
||||
stableNameCollector.processStableJavaScriptNamesForMembers(declaration, context)
|
||||
|
||||
val membersGroupedByName = stableNameCollector.jsStableNames.groupBy { it.name }
|
||||
|
||||
for ((name, stableNames) in membersGroupedByName.entries) {
|
||||
val fakeOverrideStableNames = stableNames.filterFakeOverrideNames(declaration, context)
|
||||
|
||||
val nonFakeOverrideClashes = stableNames.collectNonFakeOverrideClashes { it in fakeOverrideStableNames }
|
||||
for ((symbol, clashedWith) in nonFakeOverrideClashes) {
|
||||
reporter.reportOn(symbol.source ?: declaration.source, FirJsErrors.JS_NAME_CLASH, name, clashedWith, context)
|
||||
}
|
||||
|
||||
fakeOverrideStableNames.findFirstFakeOverrideClash(stableNameCollector)?.let { (fakeOverrideSymbol, clashedWith) ->
|
||||
reporter.reportOn(declaration.source, FirJsErrors.JS_FAKE_NAME_CLASH, name, fakeOverrideSymbol, clashedWith, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.js.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFileChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.FirJsStableName
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.collectNameClashesWith
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
|
||||
object FirJsNameClashFileTopLevelDeclarationsChecker : FirFileChecker() {
|
||||
private fun MutableMap<String, MutableList<FirJsStableName>>.addStableName(
|
||||
symbol: FirBasedSymbol<*>,
|
||||
context: CheckerContext
|
||||
) {
|
||||
val stableName = FirJsStableName.createStableNameOrNull(symbol, context.session)
|
||||
if (stableName != null) {
|
||||
getOrPut(stableName.name) { mutableListOf() }.add(stableName)
|
||||
}
|
||||
if (symbol is FirPropertySymbol) {
|
||||
symbol.getterSymbol?.let { getter -> addStableName(getter, context) }
|
||||
symbol.setterSymbol?.let { setter -> addStableName(setter, context) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val topLevelDeclarationsWithStableName = mutableMapOf<String, MutableList<FirJsStableName>>()
|
||||
for (topLevelDeclaration in declaration.declarations) {
|
||||
if (topLevelDeclaration is FirTypeAlias) {
|
||||
// Skip type aliases since they cannot be external, cannot be exported to JavaScript, and cannot be marked with @JsName.
|
||||
// Furthermore, in the generated JavaScript code, all type alias declarations are removed,
|
||||
// and their usages are replaced with the aliased types.
|
||||
continue
|
||||
}
|
||||
topLevelDeclarationsWithStableName.addStableName(topLevelDeclaration.symbol, context)
|
||||
if (topLevelDeclaration is FirClass) {
|
||||
for (classConstructor in topLevelDeclaration.constructors(context.session)) {
|
||||
topLevelDeclarationsWithStableName.addStableName(classConstructor, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((name, stableNames) in topLevelDeclarationsWithStableName.entries) {
|
||||
for (symbol in stableNames) {
|
||||
val clashed = stableNames.collectNameClashesWith(symbol).takeIf { it.isNotEmpty() } ?: continue
|
||||
val source = symbol.symbol.source ?: declaration.source
|
||||
reporter.reportOn(source, FirJsErrors.JS_NAME_CLASH, name, clashed.map { it.symbol }, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
// IGNORE_BACKEND_K1: WASM
|
||||
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
|
||||
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6, WASM
|
||||
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// IGNORE_BACKEND_K1: WASM
|
||||
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
|
||||
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// IGNORE_BACKEND_K1: WASM
|
||||
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
|
||||
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// IGNORE_BACKEND_K1: WASM
|
||||
// IGNORE_BACKEND_K1: JS_IR, JS_IR_ES6
|
||||
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
interface I {
|
||||
fun foo()
|
||||
}
|
||||
|
||||
interface J {
|
||||
@JsName("bar")
|
||||
fun foo()
|
||||
}
|
||||
|
||||
interface K : I, J {
|
||||
override fun foo()
|
||||
}
|
||||
|
||||
interface L : K {
|
||||
override fun foo()
|
||||
|
||||
fun bar()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
interface I {
|
||||
fun foo()
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package foo
|
||||
|
||||
class A(val x: Int)
|
||||
|
||||
fun A() {}
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
class <!JS_NAME_CLASH!>A(val x: Int)<!>
|
||||
|
||||
<!JS_NAME_CLASH!>fun A()<!> {}
|
||||
<!JS_NAME_CLASH!>fun A()<!> {}
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package foo
|
||||
|
||||
class A {
|
||||
fun bar() = 23
|
||||
|
||||
val bar = 23
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
class A {
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
interface A {
|
||||
@JsName("foo") fun f()
|
||||
}
|
||||
|
||||
interface B {
|
||||
@JsName("foo") fun g()
|
||||
}
|
||||
|
||||
class C : A, B {
|
||||
override fun f() {}
|
||||
|
||||
override fun g() {}
|
||||
}
|
||||
|
||||
abstract class D : A, B
|
||||
|
||||
open class E {
|
||||
open fun f() {}
|
||||
|
||||
open fun g() {}
|
||||
}
|
||||
|
||||
class F : E(), A, B
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
interface A {
|
||||
@JsName("foo") fun f()
|
||||
}
|
||||
|
||||
+3
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case is only relevant for the JS Legacy BE and is not applicable to the JS IR backend,
|
||||
// as the IR BE can resolve such name collisions.
|
||||
package foo
|
||||
|
||||
class A
|
||||
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case is only relevant for the JS Legacy BE and is not applicable to the JS IR backend,
|
||||
// as the IR BE can resolve such name collisions.
|
||||
package foo
|
||||
|
||||
class A
|
||||
@@ -5,4 +8,4 @@ class A
|
||||
<!JS_NAME_CLASH!>fun A.get_bar()<!> = 23
|
||||
|
||||
val A.bar: Int
|
||||
<!JS_NAME_CLASH!>get()<!> = 42
|
||||
<!JS_NAME_CLASH!>get()<!> = 42
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package foo
|
||||
|
||||
open class Super {
|
||||
fun foo() = 23
|
||||
}
|
||||
|
||||
class Sub : Super() {
|
||||
@JsName("foo") fun bar() = 42
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
open class Super {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package foo
|
||||
|
||||
@JsName("x") fun foo(x: Int) = x
|
||||
|
||||
@JsName("x") fun bar() = 42
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
<!JS_NAME_CLASH!>@JsName("x") fun foo(x: Int)<!> = x
|
||||
|
||||
<!JS_NAME_CLASH!>@JsName("x") fun bar()<!> = 42
|
||||
<!JS_NAME_CLASH!>@JsName("x") fun bar()<!> = 42
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package foo
|
||||
|
||||
@JsName("bar") fun foo(x: Int) = x
|
||||
|
||||
fun bar() = 42
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
<!JS_NAME_CLASH!>@JsName("bar") fun foo(x: Int)<!> = x
|
||||
|
||||
<!JS_NAME_CLASH!>fun bar()<!> = 42
|
||||
<!JS_NAME_CLASH!>fun bar()<!> = 42
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@ package foo
|
||||
|
||||
class A {
|
||||
var x: Int
|
||||
@JsName("xx") get() = 0
|
||||
@JsName("xx") set(value) {}
|
||||
<!JS_NAME_CLASH!>@JsName("xx") get()<!> = 0
|
||||
<!JS_NAME_CLASH!>@JsName("xx") set(value)<!> {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case is only relevant for the JS Legacy BE and is not applicable to the JS IR backend,
|
||||
// as the IR BE can resolve such name collisions. Furthermore, the IR BE mangles names differently.
|
||||
package foo
|
||||
|
||||
fun bar(x: Int) = x
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case is only relevant for the JS Legacy BE and is not applicable to the JS IR backend,
|
||||
// as the IR BE can resolve such name collisions. Furthermore, the IR BE mangles names differently.
|
||||
package foo
|
||||
|
||||
<!JS_NAME_CLASH!>fun bar(x: Int)<!> = x
|
||||
|
||||
<!JS_NAME_CLASH!>fun `bar_za3lpa$`()<!> = 42
|
||||
<!JS_NAME_CLASH!>fun `bar_za3lpa$`()<!> = 42
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
override fun bar() {}
|
||||
|
||||
override fun foo() {}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
interface I {
|
||||
@JsName("bar")
|
||||
fun foo()
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
external open class A {
|
||||
open fun f(x: Int): Unit
|
||||
|
||||
open fun f(x: String): Unit
|
||||
}
|
||||
|
||||
class InheritClass : A() {
|
||||
override fun f(x: Int): Unit { }
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
external open class A {
|
||||
open fun f(x: Int): Unit
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case can't be checked using FIR. It is checked later on klib serialization.
|
||||
|
||||
// FILE: foo.kt
|
||||
|
||||
package foo
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case can't be checked using FIR. It is checked later on klib serialization.
|
||||
|
||||
// FILE: foo.kt
|
||||
|
||||
package foo
|
||||
|
||||
+3
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case can't be checked using FIR. It is checked later on klib serialization.
|
||||
|
||||
// FILE: foo.kt
|
||||
|
||||
package foo
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIR_DIFFERENCE
|
||||
// This case can't be checked using FIR. It is checked later on klib serialization.
|
||||
|
||||
// FILE: foo.kt
|
||||
|
||||
package foo
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package foo
|
||||
|
||||
@JsName("bar") private fun foo(x: Int) = x
|
||||
|
||||
fun bar() = 42
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
<!JS_NAME_CLASH!>@JsName("bar") private fun foo(x: Int)<!> = x
|
||||
|
||||
<!JS_NAME_CLASH!>fun bar()<!> = 42
|
||||
<!JS_NAME_CLASH!>fun bar()<!> = 42
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package foo
|
||||
|
||||
interface I {
|
||||
fun foo() = 23
|
||||
}
|
||||
|
||||
class Sub : I {
|
||||
var foo = 42
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
interface I {
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package foo
|
||||
|
||||
open class Super {
|
||||
val foo = 23
|
||||
}
|
||||
|
||||
class Sub : Super() {
|
||||
fun foo() = 42
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
open class Super {
|
||||
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
package foo
|
||||
|
||||
class A(val x: String) {
|
||||
@JsName("aa") constructor(x: Int) : this("int $x")
|
||||
}
|
||||
|
||||
fun aa() {}
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
class A(val x: String) {
|
||||
@JsName("aa") <!JS_NAME_CLASH!>constructor(x: Int)<!> : this("int $x")
|
||||
}
|
||||
|
||||
<!JS_NAME_CLASH!>fun aa()<!> {}
|
||||
<!JS_NAME_CLASH!>fun aa()<!> {}
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package foo
|
||||
|
||||
fun bar() = 23
|
||||
|
||||
val bar = 32
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
// FIR_IDENTICAL
|
||||
package foo
|
||||
|
||||
<!JS_NAME_CLASH!>fun bar()<!> = 23
|
||||
|
||||
<!JS_NAME_CLASH!>val bar<!> = 32
|
||||
<!JS_NAME_CLASH!>val bar<!> = 32
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIR_IDENTICAL
|
||||
// SKIP_KLIB_TEST
|
||||
// IGNORE_BACKEND_K1: JS_IR
|
||||
// IGNORE_BACKEND_K2: JS_IR
|
||||
// Ignore reason: there is a js name clash between function `a()` and property `a`
|
||||
package test
|
||||
|
||||
|
||||
Reference in New Issue
Block a user