[JS FIR] Fix FIR NON_EXPORTABLE_TYPE diagnostic bugs

- use a declaration source if a parameter source is null;
 - ignore constructor return type to avoid
   double-checking of the class itself;
 - ignore constructor type parameters
   obtained from the class declaration;
 - ignore enum entries property (it is not checked in K1 at all);
 - fix exportability check for function types;

 ^KT-63089 Fixed
This commit is contained in:
Alexander Korepanov
2023-11-21 15:45:23 +01:00
committed by Space Team
parent 6615b77213
commit 862be5a787
2 changed files with 49 additions and 23 deletions
@@ -13,15 +13,17 @@ import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationFirstArgument
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.isTopLevel
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.analysis.js.checkers.isExportedObject
import org.jetbrains.kotlin.fir.analysis.js.checkers.sanitizeName
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.isEnumEntries
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.js.common.RESERVED_KEYWORDS
import org.jetbrains.kotlin.js.common.SPECIAL_KEYWORDS
@@ -34,9 +36,13 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
}
fun checkTypeParameter(typeParameter: FirTypeParameterRef) {
if (typeParameter is FirConstructedClassTypeParameterRef) {
return
}
for (upperBound in typeParameter.symbol.resolvedBounds) {
if (!upperBound.type.isExportable(context.session)) {
reporter.reportOn(typeParameter.source, FirJsErrors.NON_EXPORTABLE_TYPE, "upper bound", upperBound.type, context)
val source = upperBound.source ?: typeParameter.source ?: declaration.source
reporter.reportOn(source, FirJsErrors.NON_EXPORTABLE_TYPE, "upper bound", upperBound.type, context)
}
}
}
@@ -44,7 +50,8 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
fun checkValueParameter(valueParameter: FirValueParameter) {
val type = valueParameter.returnTypeRef.coneType
if (!type.isExportable(context.session)) {
reporter.reportOn(valueParameter.source, FirJsErrors.NON_EXPORTABLE_TYPE, "parameter", type, context)
val source = valueParameter.source ?: declaration.source
reporter.reportOn(source, FirJsErrors.NON_EXPORTABLE_TYPE, "parameter", type, context)
}
}
@@ -91,8 +98,8 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
val returnType = declaration.returnTypeRef.coneType
if (!returnType.isExportable(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "return type", returnType, context)
if (declaration !is FirConstructor && !returnType.isExportableReturn(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "return", returnType, context)
}
}
@@ -106,10 +113,11 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
return
}
val containingClass = declaration.getContainingClassSymbol(context.session) as? FirClassSymbol<*>
val enumEntriesProperty = containingClass?.let(declaration::isEnumEntries) ?: false
val returnType = declaration.returnTypeRef.coneType
if (!returnType.isExportable(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "return type", returnType, context)
if (!enumEntriesProperty && !returnType.isExportable(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "property", returnType, context)
}
}
@@ -154,29 +162,43 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
else -> typeParameters.any { it.symbol.isReified }
}
private fun ConeKotlinType.isExportableReturn(session: FirSession, currentlyProcessed: MutableSet<ConeKotlinType> = mutableSetOf()) =
private fun ConeKotlinType.isExportableReturn(session: FirSession, currentlyProcessed: MutableSet<ConeKotlinType> = hashSetOf()) =
isUnit || isExportable(session, currentlyProcessed)
private fun ConeKotlinType.isExportableTypeArguments(
session: FirSession,
currentlyProcessed: MutableSet<ConeKotlinType>,
isFunctionType: Boolean
): Boolean {
if (typeArguments.isEmpty()) {
return true
}
for (i in 0 until typeArguments.lastIndex) {
if (typeArguments[i].type?.isExportable(session, currentlyProcessed) != true) {
return false
}
}
val isLastExportable = if (isFunctionType) {
typeArguments.last().type?.isExportableReturn(session, currentlyProcessed)
} else {
typeArguments.last().type?.isExportable(session, currentlyProcessed)
}
return isLastExportable == true
}
private fun ConeKotlinType.isExportable(
session: FirSession,
currentlyProcessed: MutableSet<ConeKotlinType> = mutableSetOf(),
currentlyProcessed: MutableSet<ConeKotlinType> = hashSetOf(),
): Boolean {
if (!currentlyProcessed.add(this)) {
return true
}
currentlyProcessed.add(this)
val hasNonExportableArgument = typeArguments.any { it.type?.isExportable(session, currentlyProcessed) != true }
if (hasNonExportableArgument) {
currentlyProcessed.remove(this)
return false
}
val isFunctionType = isBasicFunctionType(session)
val isExportableArgs = isExportableTypeArguments(session, currentlyProcessed, isFunctionType)
currentlyProcessed.remove(this)
if (isBasicFunctionType(session)) {
typeArguments.lastOrNull()?.type?.isExportableReturn(session, currentlyProcessed)
if (isFunctionType || !isExportableArgs) {
return isExportableArgs
}
val nonNullable = withNullability(ConeNullability.NOT_NULL, session.typeContext)
@@ -187,9 +209,9 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
return when {
isPrimitiveExportableType -> true
@OptIn(SymbolInternals::class)
symbol?.fir is FirMemberDeclaration -> false
symbol?.fir !is FirMemberDeclaration -> false
isEnum -> true
else -> symbol?.isEffectivelyExternal(session) == true || symbol?.isExportedObject(session) == true
else -> symbol.isEffectivelyExternal(session) || symbol.isExportedObject(session)
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.*
@@ -262,6 +263,9 @@ fun <T> List<T>.smartPlus(other: List<T>): List<T> = when {
// Source element may be missing if the class came from a library
fun FirVariable.isEnumEntries(containingClass: FirClass) = isStatic && name == StandardNames.ENUM_ENTRIES && containingClass.isEnumClass
fun FirVariable.isEnumEntries(containingClassSymbol: FirClassSymbol<*>): Boolean {
return isStatic && name == StandardNames.ENUM_ENTRIES && containingClassSymbol.isEnumClass
}
val FirExpression.isArraySet: Boolean
get() {