[FIR JS] Support JS_NAME_CLASH and JS_FAKE_NAME_CLASH diagnostics

^KT-59425 Fixed
^KT-59370 Fixed
This commit is contained in:
Alexander Korepanov
2023-08-10 11:45:08 +02:00
committed by Space Team
parent 5ac277d058
commit 6bb939c6cb
50 changed files with 452 additions and 196 deletions
@@ -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
}
@@ -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)
}
}
@@ -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
)
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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)
}
}
}
}
@@ -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,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()<!> {}
@@ -1,7 +0,0 @@
package foo
class A {
fun bar() = 23
val bar = 23
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
class A {
@@ -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,3 +1,4 @@
// FIR_IDENTICAL
interface A {
@JsName("foo") fun f()
}
@@ -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
@@ -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
@@ -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
@@ -1,5 +0,0 @@
package foo
@JsName("bar") fun foo(x: Int) = x
fun bar() = 42
@@ -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,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
@@ -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()
@@ -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,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
@@ -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
@@ -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
@@ -1,9 +0,0 @@
package foo
interface I {
fun foo() = 23
}
class Sub : I {
var foo = 42
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
interface I {
@@ -1,9 +0,0 @@
package foo
open class Super {
val foo = 23
}
class Sub : Super() {
fun foo() = 42
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
open class Super {
@@ -1,7 +0,0 @@
package foo
class A(val x: String) {
@JsName("aa") constructor(x: Int) : this("int $x")
}
fun aa() {}
@@ -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()<!> {}
@@ -1,5 +0,0 @@
package foo
fun bar() = 23
val bar = 32
@@ -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