diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConflictsHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConflictsHelpers.kt index e7260c17bf8..a4b370c4783 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConflictsHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirConflictsHelpers.kt @@ -128,220 +128,231 @@ private fun groupTopLevelByName(declarations: List): Map( + internal val context: CheckerContext, ) { - private val session: FirSession get() = context.sessionHolder.session + internal val session: FirSession get() = context.sessionHolder.session - val declarationConflictingSymbols: HashMap>> = hashMapOf() + val declarationConflictingSymbols: HashMap>> = hashMapOf() +} - fun collectClassMembers(klass: FirRegularClass) { - val otherDeclarations = mutableMapOf>() - val functionDeclarations = mutableMapOf>() +fun FirDeclarationCollector.collectClassMembers(klass: FirRegularClass) { + val otherDeclarations = mutableMapOf>() + val functionDeclarations = mutableMapOf>() - for (it in klass.declarations) { - if (!it.isCollectable()) continue + for (it in klass.declarations) { + if (!it.isCollectable()) continue - when (it) { - is FirSimpleFunction -> collect(it, FirRedeclarationPresenter.represent(it), functionDeclarations) - is FirRegularClass -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) - is FirTypeAlias -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) - is FirVariable -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) - else -> {} - } + when (it) { + is FirSimpleFunction -> collect(it, FirRedeclarationPresenter.represent(it), functionDeclarations) + is FirRegularClass -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) + is FirTypeAlias -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) + is FirVariable -> collect(it, FirRedeclarationPresenter.represent(it), otherDeclarations) + else -> {} } } +} private fun collect(declaration: FirDeclaration, representation: String, map: MutableMap>) { map.getOrPut(representation, ::mutableListOf).also { it.add(declaration) - val conflicts = SmartSet.create>() - for (otherDeclaration in it) { - if (otherDeclaration != declaration && !isOverloadable(declaration, otherDeclaration)) { - conflicts.add(otherDeclaration.symbol) - declarationConflictingSymbols.getOrPut(otherDeclaration) { SmartSet.create() }.add(declaration.symbol) +private fun FirDeclarationCollector.collect( + declaration: D, + representation: String, + map: MutableMap>, +) { + map.getOrPut(representation, ::mutableListOf).also { + it.add(declaration) + + val conflicts = SmartSet.create>() + for (otherDeclaration in it) { + if (otherDeclaration != declaration && !isOverloadable(declaration, otherDeclaration, session)) { + conflicts.add(otherDeclaration.symbol) + declarationConflictingSymbols.getOrPut(otherDeclaration) { SmartSet.create() }.add(declaration.symbol) + } + } + + declarationConflictingSymbols[declaration] = conflicts + } +} + +/** + * To check top-level declarations for redeclarations, we check multiple sources (the packageMemberScope's properties, functions + * and classifiers), redeclared classifiers from session.nameConflictsTracker and the file's declarations themselves. + * To prevent inspecting the same source multiple times, we group the declarations in the file by name and subdivide them into + * buckets (the properties of DeclarationGroup). + * + * Depending on the presence of declarations in the buckets, some checks can be omitted. + * E.g., if there are no functions and no classes with constructors in the file, we don't need to inspect functions. + * + * #### Matrix of possible conflicts between "sources" and "buckets" + * + * | | simpleFunctions | constructors | classLikes | Properties | extensionProperties | + * |-------------------------|-----------------|--------------|------------|------------|---------------------| + * | functions | X | X | | | | + * | classifiers | | | X | X | | + * | constructors of classes | X | | | | | + * | properties | | | X | X | X | + */ +@OptIn(SymbolInternals::class) +@Suppress("GrazieInspection") +fun FirDeclarationCollector.collectTopLevel(file: FirFile, packageMemberScope: FirPackageMemberScope) { + + for ((declarationName, group) in groupTopLevelByName(file.declarations)) { + val groupHasClassLikesOrProperties = group.classLikes.isNotEmpty() || group.properties.isNotEmpty() + val groupHasSimpleFunctions = group.simpleFunctions.isNotEmpty() + + fun collect( + declarations: List>, + conflictingSymbol: FirBasedSymbol<*>, + conflictingPresentation: String? = null, + conflictingFile: FirFile? = null, + ) { + for ((declaration, declarationPresentation) in declarations) { + collectTopLevelConflict( + declaration, + declarationPresentation, + file, + conflictingSymbol, + conflictingPresentation, + conflictingFile + ) + + session.lookupTracker?.recordLookup(declarationName, file.packageFqName.asString(), declaration.source, file.source) + } + } + + fun collectFromClassifierSource( + conflictingSymbol: FirClassifierSymbol<*>, + conflictingPresentation: String? = null, + conflictingFile: FirFile? = null, + ) { + collect(group.classLikes, conflictingSymbol, conflictingPresentation, conflictingFile) + collect(group.properties, conflictingSymbol, conflictingPresentation, conflictingFile) + + if (groupHasSimpleFunctions) { + if (conflictingSymbol !is FirRegularClassSymbol) return + if (conflictingSymbol.classKind == ClassKind.OBJECT || conflictingSymbol.classKind == ClassKind.ENUM_ENTRY) return + + conflictingSymbol.lazyResolveToPhase(FirResolvePhase.STATUS) + + val classWithSameName = conflictingSymbol.fir + classWithSameName.unsubstitutedScope(context).processDeclaredConstructors { constructor -> + val ctorRepresentation = FirRedeclarationPresenter.represent(constructor.fir, classWithSameName) + collect(group.simpleFunctions, conflictingSymbol = constructor, conflictingPresentation = ctorRepresentation) } } + } - declarationConflictingSymbols[declaration] = conflicts + // Check sources in the order from the table above. Skip the check if all relevant buckets are empty. + + // Function source + if (groupHasSimpleFunctions || group.constructors.isNotEmpty()) { + packageMemberScope.processFunctionsByName(declarationName) { + collect(group.simpleFunctions, it) + collect(group.constructors, it) + } + } + + // Classifier sources, collectForClassifierSource will also check constructors. + if (groupHasClassLikesOrProperties || groupHasSimpleFunctions) { + // Scope will only return one classifier per name + packageMemberScope.processClassifiersByNameWithSubstitution(declarationName) { symbol, _ -> + collectFromClassifierSource(conflictingSymbol = symbol) + } + + // session.nameConflictsTracker will contain more classifiers with the same name. + session.nameConflictsTracker?.let { it as? FirNameConflictsTracker } + ?.redeclaredClassifiers?.get(ClassId(file.packageFqName, declarationName))?.forEach { + collectFromClassifierSource(conflictingSymbol = it.classifier, conflictingFile = it.file) + } + + // session.nameConflictsTracker doesn't seem to work for LL API for redeclarations in the same file, for this reason + // we explicitly check classLikes in the same file, too. + for ((classLike, representation) in group.classLikes) { + collectFromClassifierSource(classLike.symbol, conflictingPresentation = representation, conflictingFile = file) + } + } + + // Property source + if (groupHasClassLikesOrProperties || group.extensionProperties.isNotEmpty()) { + packageMemberScope.processPropertiesByName(declarationName) { + collect(group.classLikes, conflictingSymbol = it) + collect(group.properties, conflictingSymbol = it) + collect(group.extensionProperties, conflictingSymbol = it) + } } } +} - /** - * To check top-level declarations for redeclarations, we check multiple sources (the packageMemberScope's properties, functions - * and classifiers), redeclared classifiers from session.nameConflictsTracker and the file's declarations themselves. - * To prevent inspecting the same source multiple times, we group the declarations in the file by name and subdivide them into - * buckets (the properties of DeclarationGroup). - * - * Depending on the presence of declarations in the buckets, some checks can be omitted. - * E.g., if there are no functions and no classes with constructors in the file, we don't need to inspect functions. - * - * #### Matrix of possible conflicts between "sources" and "buckets" - * - * | | simpleFunctions | constructors | classLikes | Properties | extensionProperties | - * |-------------------------|-----------------|--------------|------------|------------|---------------------| - * | functions | X | X | | | | - * | classifiers | | | X | X | | - * | constructors of classes | X | | | | | - * | properties | | | X | X | X | - */ +private fun FirDeclarationCollector.collectTopLevelConflict( + declaration: FirDeclaration, + declarationPresentation: String, + containingFile: FirFile, + conflictingSymbol: FirBasedSymbol<*>, + conflictingPresentation: String? = null, + conflictingFile: FirFile? = null, +) { + conflictingSymbol.lazyResolveToPhase(FirResolvePhase.STATUS) @OptIn(SymbolInternals::class) - @Suppress("GrazieInspection") - fun collectTopLevel(file: FirFile, packageMemberScope: FirPackageMemberScope) { - - for ((declarationName, group) in groupTopLevelByName(file.declarations)) { - val groupHasClassLikesOrProperties = group.classLikes.isNotEmpty() || group.properties.isNotEmpty() - val groupHasSimpleFunctions = group.simpleFunctions.isNotEmpty() - - fun collect( - declarations: List>, - conflictingSymbol: FirBasedSymbol<*>, - conflictingPresentation: String? = null, - conflictingFile: FirFile? = null, - ) { - for ((declaration, declarationPresentation) in declarations) { - collectTopLevelConflict( - declaration, - declarationPresentation, - file, - conflictingSymbol, - conflictingPresentation, - conflictingFile - ) - - session.lookupTracker?.recordLookup(declarationName, file.packageFqName.asString(), declaration.source, file.source) - } - } - - fun collectFromClassifierSource( - conflictingSymbol: FirClassifierSymbol<*>, - conflictingPresentation: String? = null, - conflictingFile: FirFile? = null, - ) { - collect(group.classLikes, conflictingSymbol, conflictingPresentation, conflictingFile) - collect(group.properties, conflictingSymbol, conflictingPresentation, conflictingFile) - - if (groupHasSimpleFunctions) { - if (conflictingSymbol !is FirRegularClassSymbol) return - if (conflictingSymbol.classKind == ClassKind.OBJECT || conflictingSymbol.classKind == ClassKind.ENUM_ENTRY) return - - conflictingSymbol.lazyResolveToPhase(FirResolvePhase.STATUS) - val classWithSameName = conflictingSymbol.fir - classWithSameName.unsubstitutedScope(context).processDeclaredConstructors { constructor -> - val ctorRepresentation = FirRedeclarationPresenter.represent(constructor.fir, classWithSameName) - collect(group.simpleFunctions, conflictingSymbol = constructor, conflictingPresentation = ctorRepresentation) - } - } - } - - // Check sources in the order from the table above. Skip the check if all relevant buckets are empty. - - // Function source - if (groupHasSimpleFunctions || group.constructors.isNotEmpty()) { - packageMemberScope.processFunctionsByName(declarationName) { - collect(group.simpleFunctions, it) - collect(group.constructors, it) - } - } - - // Classifier sources, collectForClassifierSource will also check constructors. - if (groupHasClassLikesOrProperties || groupHasSimpleFunctions) { - // Scope will only return one classifier per name - packageMemberScope.processClassifiersByNameWithSubstitution(declarationName) { symbol, _ -> - collectFromClassifierSource(conflictingSymbol = symbol) - } - - // session.nameConflictsTracker will contain more classifiers with the same name. - session.nameConflictsTracker?.let { it as? FirNameConflictsTracker } - ?.redeclaredClassifiers?.get(ClassId(file.packageFqName, declarationName))?.forEach { - collectFromClassifierSource(conflictingSymbol = it.classifier, conflictingFile = it.file) - } - - // session.nameConflictsTracker doesn't seem to work for LL API for redeclarations in the same file, for this reason - // we explicitly check classLikes in the same file, too. - for ((classLike, representation) in group.classLikes) { - collectFromClassifierSource(classLike.symbol, conflictingPresentation = representation, conflictingFile = file) - } - } - - // Property source - if (groupHasClassLikesOrProperties || group.extensionProperties.isNotEmpty()) { - packageMemberScope.processPropertiesByName(declarationName) { - collect(group.classLikes, conflictingSymbol = it) - collect(group.properties, conflictingSymbol = it) - collect(group.extensionProperties, conflictingSymbol = it) - } - } + val conflicting = conflictingSymbol.fir + if (conflicting == declaration || declaration.moduleData != conflicting.moduleData) return + val actualConflictingPresentation = conflictingPresentation ?: FirRedeclarationPresenter.represent(conflicting) + if (actualConflictingPresentation != declarationPresentation) return + val actualConflictingFile = + conflictingFile ?: when (conflictingSymbol) { + is FirClassLikeSymbol<*> -> session.firProvider.getFirClassifierContainerFileIfAny(conflictingSymbol) + is FirCallableSymbol<*> -> session.firProvider.getFirCallableContainerFile(conflictingSymbol) + else -> null } - } + if (!conflicting.isCollectable()) return + if (areCompatibleMainFunctions(declaration, containingFile, conflicting, actualConflictingFile, session)) return + if ( + conflicting is FirMemberDeclaration && + !session.visibilityChecker.isVisible(conflicting, session, containingFile, emptyList(), dispatchReceiver = null) + ) return + if (isOverloadable(declaration, conflicting, session)) return - private fun collectTopLevelConflict( - declaration: FirDeclaration, - declarationPresentation: String, - containingFile: FirFile, - conflictingSymbol: FirBasedSymbol<*>, - conflictingPresentation: String? = null, - conflictingFile: FirFile? = null, - ) { - conflictingSymbol.lazyResolveToPhase(FirResolvePhase.STATUS) - @OptIn(SymbolInternals::class) - val conflicting = conflictingSymbol.fir - if (conflicting == declaration || declaration.moduleData != conflicting.moduleData) return - val actualConflictingPresentation = conflictingPresentation ?: FirRedeclarationPresenter.represent(conflicting) - if (actualConflictingPresentation != declarationPresentation) return - val actualConflictingFile = - conflictingFile ?: when (conflictingSymbol) { - is FirClassLikeSymbol<*> -> session.firProvider.getFirClassifierContainerFileIfAny(conflictingSymbol) - is FirCallableSymbol<*> -> session.firProvider.getFirCallableContainerFile(conflictingSymbol) - else -> null - } - if (!conflicting.isCollectable()) return - if (areCompatibleMainFunctions(declaration, containingFile, conflicting, actualConflictingFile)) return - if ( - conflicting is FirMemberDeclaration && - !session.visibilityChecker.isVisible(conflicting, session, containingFile, emptyList(), dispatchReceiver = null) - ) return - if (isOverloadable(declaration, conflicting)) return + declarationConflictingSymbols.getOrPut(declaration) { SmartSet.create() }.add(conflictingSymbol) +} - declarationConflictingSymbols.getOrPut(declaration) { SmartSet.create() }.add(conflictingSymbol) - } +private fun FirSimpleFunction.representsMainFunctionAllowingConflictingOverloads(session: FirSession): Boolean { + if (name != StandardNames.MAIN || !symbol.callableId.isTopLevel || !hasMainFunctionStatus) return false + if (receiverParameter != null || typeParameters.isNotEmpty()) return false + if (valueParameters.isEmpty()) return true + val paramType = valueParameters.singleOrNull()?.returnTypeRef?.coneType?.fullyExpandedType(session) ?: return false + if (!paramType.isNonPrimitiveArray) return false + val typeArgument = paramType.typeArguments.singleOrNull() as? ConeKotlinTypeProjection ?: return false + // only Array and Array are accepted + if (typeArgument !is ConeKotlinType && typeArgument !is ConeKotlinTypeProjectionOut) return false + return typeArgument.type.fullyExpandedType(session).isString +} - private fun FirSimpleFunction.representsMainFunctionAllowingConflictingOverloads(): Boolean { - if (name != StandardNames.MAIN || !symbol.callableId.isTopLevel || !hasMainFunctionStatus) return false - if (receiverParameter != null || typeParameters.isNotEmpty()) return false - if (valueParameters.isEmpty()) return true - val paramType = valueParameters.singleOrNull()?.returnTypeRef?.coneType?.fullyExpandedType(session) ?: return false - if (!paramType.isNonPrimitiveArray) return false - val typeArgument = paramType.typeArguments.singleOrNull() as? ConeKotlinTypeProjection ?: return false - // only Array and Array are accepted - if (typeArgument !is ConeKotlinType && typeArgument !is ConeKotlinTypeProjectionOut) return false - return typeArgument.type.fullyExpandedType(session).isString - } +private fun areCompatibleMainFunctions( + declaration1: FirDeclaration, file1: FirFile, + declaration2: FirDeclaration, file2: FirFile?, + session: FirSession, +) = file1 != file2 + && declaration1 is FirSimpleFunction + && declaration2 is FirSimpleFunction + && declaration1.representsMainFunctionAllowingConflictingOverloads(session) + && declaration2.representsMainFunctionAllowingConflictingOverloads(session) - private fun areCompatibleMainFunctions( - declaration1: FirDeclaration, file1: FirFile, declaration2: FirDeclaration, file2: FirFile?, - ) = file1 != file2 - && declaration1 is FirSimpleFunction - && declaration2 is FirSimpleFunction - && declaration1.representsMainFunctionAllowingConflictingOverloads() - && declaration2.representsMainFunctionAllowingConflictingOverloads() +private fun isOverloadable( + declaration: FirDeclaration, + conflicting: FirDeclaration, + session: FirSession, +): Boolean { + if (isExpectAndActual(declaration, conflicting)) return true - private fun isOverloadable( - declaration: FirDeclaration, - conflicting: FirDeclaration, - ): Boolean { - if (isExpectAndActual(declaration, conflicting)) return true - - val declarationIsLowPriority = hasLowPriorityAnnotation(declaration.annotations) - val conflictingIsLowPriority = hasLowPriorityAnnotation(conflicting.annotations) - if (declarationIsLowPriority != conflictingIsLowPriority) return true - - return declaration is FirCallableDeclaration && - conflicting is FirCallableDeclaration && - session.declarationOverloadabilityHelper.isOverloadable(declaration, conflicting) - } + val declarationIsLowPriority = hasLowPriorityAnnotation(declaration.annotations) + val conflictingIsLowPriority = hasLowPriorityAnnotation(conflicting.annotations) + if (declarationIsLowPriority != conflictingIsLowPriority) return true + return declaration is FirCallableDeclaration && + conflicting is FirCallableDeclaration && + session.declarationOverloadabilityHelper.isOverloadable(declaration, conflicting) } /** Checks for redeclarations of value and type parameters, and local variables. */ diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt index d04f2f6d71d..354c630d9f3 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt @@ -9,8 +9,10 @@ import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirNameConflictsTrackerComponent -import org.jetbrains.kotlin.fir.analysis.checkers.FirDeclarationInspector +import org.jetbrains.kotlin.fir.analysis.checkers.FirDeclarationCollector import org.jetbrains.kotlin.fir.analysis.checkers.checkForLocalRedeclarations +import org.jetbrains.kotlin.fir.analysis.checkers.collectClassMembers +import org.jetbrains.kotlin.fir.analysis.checkers.collectTopLevel import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.* @@ -27,7 +29,7 @@ object FirConflictsDeclarationChecker : FirBasicDeclarationChecker() { override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { when (declaration) { is FirFile -> { - val inspector = FirDeclarationInspector(context) + val inspector = FirDeclarationCollector(context) checkFile(declaration, inspector, context) reportConflicts(reporter, context, inspector.declarationConflictingSymbols) } @@ -35,7 +37,7 @@ object FirConflictsDeclarationChecker : FirBasicDeclarationChecker() { if (declaration.source?.kind !is KtFakeSourceElementKind) { checkForLocalRedeclarations(declaration.typeParameters, context, reporter) } - val inspector = FirDeclarationInspector(context) + val inspector = FirDeclarationCollector(context) inspector.collectClassMembers(declaration) reportConflicts(reporter, context, inspector.declarationConflictingSymbols) } @@ -46,7 +48,6 @@ object FirConflictsDeclarationChecker : FirBasicDeclarationChecker() { } checkForLocalRedeclarations(declaration.typeParameters, context, reporter) } - return } } } @@ -76,7 +77,7 @@ object FirConflictsDeclarationChecker : FirBasicDeclarationChecker() { } } - private fun checkFile(file: FirFile, inspector: FirDeclarationInspector, context: CheckerContext) { + private fun checkFile(file: FirFile, inspector: FirDeclarationCollector, context: CheckerContext) { val packageMemberScope: FirPackageMemberScope = context.sessionHolder.scopeSession.getOrBuild(file.packageFqName, PACKAGE_MEMBER) { FirPackageMemberScope(file.packageFqName, context.sessionHolder.session) }