[FIR JS] Implement FirJsExportDeclarationChecker

This commit is contained in:
Nikolay Lunyak
2023-01-27 15:22:17 +02:00
parent 624162dc07
commit f644eb7988
38 changed files with 450 additions and 115 deletions
@@ -4987,6 +4987,28 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJsErrors.WRONG_EXPORTED_DECLARATION) { firDiagnostic ->
WrongExportedDeclarationImpl(
firDiagnostic.a,
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.NON_EXPORTABLE_TYPE) { firDiagnostic ->
NonExportableTypeImpl(
firDiagnostic.a,
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.NON_CONSUMABLE_EXPORTED_IDENTIFIER) { firDiagnostic ->
NonConsumableExportedIdentifierImpl(
firDiagnostic.a,
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.DELEGATION_BY_DYNAMIC) { firDiagnostic ->
DelegationByDynamicImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -3466,6 +3466,22 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = NestedJsExport::class
}
abstract class WrongExportedDeclaration : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = WrongExportedDeclaration::class
abstract val kind: String
}
abstract class NonExportableType : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = NonExportableType::class
abstract val kind: String
abstract val type: KtType
}
abstract class NonConsumableExportedIdentifier : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = NonConsumableExportedIdentifier::class
abstract val name: String
}
abstract class DelegationByDynamic : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = DelegationByDynamic::class
}
@@ -4195,6 +4195,25 @@ internal class NestedJsExportImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NestedJsExport(), KtAbstractFirDiagnostic<KtElement>
internal class WrongExportedDeclarationImpl(
override val kind: String,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.WrongExportedDeclaration(), KtAbstractFirDiagnostic<KtElement>
internal class NonExportableTypeImpl(
override val kind: String,
override val type: KtType,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NonExportableType(), KtAbstractFirDiagnostic<KtElement>
internal class NonConsumableExportedIdentifierImpl(
override val name: String,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NonConsumableExportedIdentifier(), KtAbstractFirDiagnostic<KtElement>
internal class DelegationByDynamicImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
@@ -98,6 +98,16 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val EXPORT by object : DiagnosticGroup("Export") {
val NESTED_JS_EXPORT by error<KtElement>()
val WRONG_EXPORTED_DECLARATION by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<String>("kind")
}
val NON_EXPORTABLE_TYPE by warning<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<String>("kind")
parameter<ConeKotlinType>("type")
}
val NON_CONSUMABLE_EXPORTED_IDENTIFIER by warning<KtElement>(PositioningStrategy.DEFAULT) {
parameter<String>("name")
}
}
val DYNAMICS by object : DiagnosticGroup("Dynamics") {
@@ -7,6 +7,7 @@ plugins {
dependencies {
api(project(":core:compiler.common.js"))
api(project(":js:js.ast"))
api(project(":compiler:fir:checkers"))
/*
@@ -81,6 +81,9 @@ object FirJsErrors {
// Export
val NESTED_JS_EXPORT by error0<KtElement>()
val WRONG_EXPORTED_DECLARATION by error1<KtElement, String>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NON_EXPORTABLE_TYPE by warning2<KtElement, String, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NON_CONSUMABLE_EXPORTED_IDENTIFIER by warning1<KtElement, String>()
// Dynamics
val DELEGATION_BY_DYNAMIC by error0<KtElement>()
@@ -46,6 +46,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_JS_EXPORT
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NON_CONSUMABLE_EXPORTED_IDENTIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NON_EXPORTABLE_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NON_EXTERNAL_DECLARATION_IN_INAPPROPRIATE_FILE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE
@@ -54,6 +56,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNO
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_BODY_OF_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_DEFAULT_VALUE_FOR_EXTERNAL_FUN_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_EXPORTED_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_INITIALIZER_OF_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_JS_QUALIFIER
@@ -169,6 +172,18 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
)
map.put(EXTERNAL_INTERFACE_AS_CLASS_LITERAL, "Can't refer to external interface from class literal")
map.put(NESTED_JS_EXPORT, "@JsExport is only allowed on files and top-level declarations")
map.put(WRONG_EXPORTED_DECLARATION, "Declaration of such kind ({0}) can''t be exported to JS", CommonRenderers.STRING)
map.put(
NON_EXPORTABLE_TYPE,
"Exported declaration uses non-exportable {0} type: {1}",
CommonRenderers.STRING,
FirDiagnosticRenderers.RENDER_TYPE,
)
map.put(
NON_CONSUMABLE_EXPORTED_IDENTIFIER,
"Exported declaration contains non-consumable identifier '${0}', that can't be represented inside TS definitions and ESM",
CommonRenderers.STRING,
)
map.checkMissingMessages(FirJsErrors)
}
@@ -8,22 +8,22 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
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.analysis.checkers.directOverriddenFunctions
import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationStringParameter
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotatedClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.providers.firProvider
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.js.PredefinedAnnotation
import org.jetbrains.kotlin.js.common.isES5IdentifierPart
import org.jetbrains.kotlin.js.common.isES5IdentifierStart
import org.jetbrains.kotlin.name.JsStandardClassIds
private val FirBasedSymbol<*>.isExternal
@@ -72,6 +72,13 @@ fun FirBasedSymbol<*>.getJsName(session: FirSession): String? {
return getAnnotationStringParameter(JsStandardClassIds.Annotations.JsName, session)
}
fun sanitizeName(name: String): String {
if (name.isEmpty()) return "_"
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("")
}
fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
if (hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session) || isEffectivelyExternal(session)) {
return true
@@ -109,8 +116,35 @@ fun FirBasedSymbol<*>.isPredefinedObject(session: FirSession): Boolean {
return false
}
fun FirBasedSymbol<*>.isExportedObject(session: FirSession): Boolean {
val declaration = fir
if (declaration is FirMemberDeclaration) {
val visibility = declaration.visibility
if (visibility != Visibilities.Public && visibility != Visibilities.Protected) {
return false
}
}
return when {
hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsExportIgnore, session) -> false
hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsExport, session) -> true
else -> getContainingFile(session)?.hasAnnotation(JsStandardClassIds.Annotations.JsExport, session) == true
}
}
private fun FirBasedSymbol<*>.getContainingFile(session: FirSession): FirFile? {
return when (this) {
is FirCallableSymbol<*> -> session.firProvider.getFirCallableContainerFile(this)
is FirClassLikeSymbol<*> -> session.firProvider.getFirClassifierContainerFileIfAny(this)
else -> return null
}
}
fun FirBasedSymbol<*>.isNativeObject(context: CheckerContext) = isNativeObject(context.session)
fun FirBasedSymbol<*>.isNativeInterface(context: CheckerContext) = isNativeInterface(context.session)
fun FirBasedSymbol<*>.isPredefinedObject(context: CheckerContext) = isPredefinedObject(context.session)
fun FirBasedSymbol<*>.isExportedObject(context: CheckerContext) = isExportedObject(context.session)
@@ -28,6 +28,7 @@ object JsDeclarationCheckers : DeclarationCheckers() {
FirJsExternalFileChecker,
FirJsNameChecker,
FirJsExportAnnotationChecker,
FirJsExportDeclarationChecker,
)
override val classCheckers: Set<FirClassChecker>
@@ -0,0 +1,224 @@
/*
* 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.KtFakeSourceElementKind
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
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.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.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.js.common.RESERVED_KEYWORDS
import org.jetbrains.kotlin.js.common.SPECIAL_KEYWORDS
import org.jetbrains.kotlin.name.JsStandardClassIds
object FirJsExportDeclarationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.symbol.isExportedObject(context) || declaration !is FirMemberDeclaration) {
return
}
fun checkTypeParameter(typeParameter: FirTypeParameterRef) {
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)
}
}
}
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 hasJsName = declaration.hasAnnotation(JsStandardClassIds.Annotations.JsName, context.session)
fun reportWrongExportedDeclaration(kind: String) {
reporter.reportOn(declaration.source, FirJsErrors.WRONG_EXPORTED_DECLARATION, kind, context)
}
if (declaration.isExpect) {
reportWrongExportedDeclaration("expect")
}
validateDeclarationOnConsumableName(declaration, context, reporter)
when (declaration) {
is FirFunction -> {
for (typeParameter in declaration.typeParameters) {
checkTypeParameter(typeParameter)
}
if (declaration.isInlineWithReified) {
reportWrongExportedDeclaration("inline function with reified type parameters")
return
}
if (declaration.isSuspend) {
reportWrongExportedDeclaration("suspend function")
return
}
if (declaration is FirConstructor && !declaration.isPrimary && !hasJsName) {
reportWrongExportedDeclaration("secondary constructor without @JsName")
}
// Properties are checked instead of property accessors
if (declaration is FirPropertyAccessor) {
return
}
for (parameter in declaration.valueParameters) {
checkValueParameter(parameter)
}
val returnType = declaration.returnTypeRef.coneType
if (!returnType.isExportable(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "return type", returnType, context)
}
}
is FirProperty -> {
if (declaration.source?.kind == KtFakeSourceElementKind.PropertyFromParameter) {
return
}
if (declaration.isExtension) {
reportWrongExportedDeclaration("extension property")
return
}
val returnType = declaration.returnTypeRef.coneType
if (!returnType.isExportable(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NON_EXPORTABLE_TYPE, "return type", returnType, context)
}
}
is FirClass -> {
for (typeParameter in declaration.typeParameters) {
checkTypeParameter(typeParameter)
}
val wrongDeclaration: String? = when (declaration.classKind) {
ClassKind.ANNOTATION_CLASS -> "annotation class"
ClassKind.CLASS -> when {
context.isInsideInterface -> "nested class inside exported interface"
declaration.isInline -> "value class"
else -> null
}
else -> if (context.isInsideInterface) {
"${if (declaration.status.isCompanion) "companion object" else "nested/inner declaration"} inside exported interface"
} else null
}
if (wrongDeclaration != null) {
reportWrongExportedDeclaration(wrongDeclaration)
}
}
else -> {}
}
}
private val CheckerContext.isInsideInterface
get(): Boolean {
val parent = containingDeclarations.lastOrNull() as? FirClass
return parent != null && parent.isInterface
}
private val FirCallableDeclaration.isInlineWithReified: Boolean
get() = when (this) {
is FirPropertyAccessor -> {
@OptIn(SymbolInternals::class)
this.propertySymbol.fir.isInlineWithReified
}
else -> typeParameters.any { it.symbol.isReified }
}
private fun ConeKotlinType.isExportableReturn(session: FirSession, currentlyProcessed: MutableSet<ConeKotlinType> = mutableSetOf()) =
isUnit || isExportable(session, currentlyProcessed)
private fun ConeKotlinType.isExportable(
session: FirSession,
currentlyProcessed: MutableSet<ConeKotlinType> = mutableSetOf(),
): 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
}
currentlyProcessed.remove(this)
if (isBasicFunctionType(session)) {
typeArguments.lastOrNull()?.type?.isExportableReturn(session, currentlyProcessed)
}
val nonNullable = withNullability(ConeNullability.NOT_NULL, session.typeContext)
val isPrimitiveExportableType = nonNullable.isAny || nonNullable.isNullableAny
|| nonNullable is ConeDynamicType || isPrimitiveExportableConeKotlinType
val symbol = toSymbol(session)
return when {
isPrimitiveExportableType -> true
@OptIn(SymbolInternals::class)
symbol?.fir is FirMemberDeclaration -> false
isEnum -> true
else -> symbol?.isEffectivelyExternal(session) == true || symbol?.isExportedObject(session) == true
}
}
private val ConeKotlinType.isPrimitiveExportableConeKotlinType: Boolean
get() = this is ConeTypeParameterType
|| isBoolean
|| isThrowableOrNullableThrowable
|| isString
|| isPrimitiveNumberOrNullableType && !isLong
|| isNothingOrNullableNothing
|| isArrayType
private fun validateDeclarationOnConsumableName(
declaration: FirMemberDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
) {
if (!context.isTopLevel || declaration.nameOrSpecialName.isSpecial) {
return
}
val jsNameArgument = declaration.symbol.getAnnotationFirstArgument(JsStandardClassIds.Annotations.JsName, context.session)
val reportTarget = jsNameArgument?.source ?: declaration.source
val name = (jsNameArgument as? FirConstExpression<*>)?.value as? String ?: declaration.nameOrSpecialName.asString()
if (name in SPECIAL_KEYWORDS || (name !in RESERVED_KEYWORDS && sanitizeName(name) == name)) {
return
}
reporter.reportOn(reportTarget, FirJsErrors.NON_CONSUMABLE_EXPORTED_IDENTIFIER, name, context)
}
}
@@ -736,9 +736,13 @@ fun FirBasedSymbol<*>.hasAnnotationOrInsideAnnotatedClass(classId: ClassId, sess
fun FirDeclaration.hasAnnotationOrInsideAnnotatedClass(classId: ClassId, session: FirSession) =
symbol.hasAnnotationOrInsideAnnotatedClass(classId, session)
fun FirBasedSymbol<*>.getAnnotationStringParameter(classId: ClassId, session: FirSession): String? {
fun FirBasedSymbol<*>.getAnnotationFirstArgument(classId: ClassId, session: FirSession): FirExpression? {
val annotation = getAnnotationByClassId(classId, session) as? FirAnnotationCall
val expression = annotation?.argumentMapping?.mapping?.values?.firstOrNull() as? FirConstExpression<*>
return annotation?.argumentMapping?.mapping?.values?.firstOrNull()
}
fun FirBasedSymbol<*>.getAnnotationStringParameter(classId: ClassId, session: FirSession): String? {
val expression = getAnnotationFirstArgument(classId, session) as? FirConstExpression<*>
return expression?.value as? String
}
@@ -26,7 +26,10 @@ val ConeKotlinType.isBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boo
val ConeKotlinType.isNullableBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, true)
val ConeKotlinType.isBooleanOrNullableBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, null)
val ConeKotlinType.isThrowableOrNullableThrowable: Boolean get() = isAnyOfBuiltinType(setOf(StandardClassIds.Throwable))
val ConeKotlinType.isChar: Boolean get() = isBuiltinType(StandardClassIds.Char, false)
val ConeKotlinType.isCharOrNullableChar: Boolean get() = isAnyOfBuiltinType(setOf(StandardClassIds.Char))
val ConeKotlinType.isString: Boolean get() = isBuiltinType(StandardClassIds.String, false)
val ConeKotlinType.isEnum: Boolean get() = isBuiltinType(StandardClassIds.Enum, false)
@@ -37,6 +40,8 @@ val ConeKotlinType.isUInt: Boolean get() = isBuiltinType(StandardClassIds.UInt,
val ConeKotlinType.isULong: Boolean get() = isBuiltinType(StandardClassIds.ULong, false)
val ConeKotlinType.isPrimitiveOrNullablePrimitive: Boolean get() = isAnyOfBuiltinType(StandardClassIds.primitiveTypes)
val ConeKotlinType.isPrimitive: Boolean get() = isPrimitiveOrNullablePrimitive && nullability == ConeNullability.NOT_NULL
val ConeKotlinType.isPrimitiveNumberOrNullableType: Boolean
get() = isPrimitiveOrNullablePrimitive && !isBooleanOrNullableBoolean && !isCharOrNullableChar
val ConeKotlinType.isArrayType: Boolean
get() {
return isBuiltinType(StandardClassIds.Array, false) ||
@@ -540,15 +540,6 @@ fun FirBasedSymbol<*>.getOwnerLookupTag(): ConeClassLikeLookupTag? {
}
}
fun FirClassLikeSymbol<*>.getContainingClassLookupTag(): ConeClassLikeLookupTag? {
return if (classId.isLocal) {
(fir as? FirRegularClass)?.containingClassForLocal()
} else {
val ownerId = classId.outerClassId
ownerId?.let { it.toLookupTag() }
}
}
fun FirBasedSymbol<*>.isVariableOrNamedFunction(): Boolean {
return this is FirVariableSymbol || this is FirNamedFunctionSymbol || this is FirPropertyAccessorSymbol
}
@@ -233,7 +233,10 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
fun constructorTypeParametersFromConstructedClass(ownerTypeParameters: List<FirTypeParameterRef>): List<FirTypeParameterRef> {
return ownerTypeParameters.mapNotNull {
val declaredTypeParameter = (it as? FirTypeParameter) ?: return@mapNotNull null
buildConstructedClassTypeParameterRef { symbol = declaredTypeParameter.symbol }
buildConstructedClassTypeParameterRef {
source = declaredTypeParameter.symbol.source?.fakeElement(KtFakeSourceElementKind.ConstructorTypeParameter)
symbol = declaredTypeParameter.symbol
}
}
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRef
import org.jetbrains.kotlin.fir.declarations.builder.buildOuterClassTypeParameterRef
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.symbols.impl.FirFileSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.name.ClassId
@@ -48,6 +49,7 @@ class Context<T> {
var forcedElementSourceKind: KtSourceElementKind? = null
val dispatchReceiverTypesStack = mutableListOf<ConeClassLikeType>()
var containerIsExpect: Boolean = false
var containingFileSymbol: FirFileSymbol? = null
fun pushFirTypeParameters(isInnerOrLocal: Boolean, parameters: List<FirTypeParameterRef>) {
capturedTypeParameters.add(StatusFirTypeParameterSymbolList(isInnerOrLocal, parameters.map { it.symbol }))
@@ -10,9 +10,11 @@ import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.declarations.utils.isSynthetic
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeIntersectionType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.toLookupTag
fun FirCallableSymbol<*>.dispatchReceiverClassTypeOrNull(): ConeClassLikeType? =
fir.dispatchReceiverClassTypeOrNull()
@@ -41,6 +43,15 @@ fun FirRegularClass.containingClassForLocal(): ConeClassLikeLookupTag? =
fun FirDanglingModifierList.containingClass(): ConeClassLikeLookupTag? =
containingClassAttr
fun FirClassLikeSymbol<*>.getContainingClassLookupTag(): ConeClassLikeLookupTag? {
return if (classId.isLocal) {
(fir as? FirRegularClass)?.containingClassForLocal()
} else {
val ownerId = classId.outerClassId
ownerId?.toLookupTag()
}
}
private object ContainingClassKey : FirDeclarationDataKey()
var FirCallableDeclaration.containingClassForStaticMemberAttr: ConeClassLikeLookupTag? by FirDeclarationDataRegistry.data(ContainingClassKey)
var FirRegularClass.containingClassForLocalAttr: ConeClassLikeLookupTag? by FirDeclarationDataRegistry.data(ContainingClassKey)
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.fir.declarations.comparators
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.fir.declarations.utils.nameOrSpecialName
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.types.FirTypeRefComparator
import org.jetbrains.kotlin.name.Name
object FirMemberDeclarationComparator : Comparator<FirMemberDeclaration> {
// Comparing different kinds of callable members by assigning distinct priorities to those members.
@@ -28,16 +28,6 @@ object FirMemberDeclarationComparator : Comparator<FirMemberDeclaration> {
is FirBackingField -> 0
}
private val FirMemberDeclaration.name: Name
get() = when (this) {
is FirCallableDeclaration ->
this.symbol.callableId.callableName
is FirClass ->
this.classId.shortClassName
is FirTypeAlias ->
this.name
}
override fun compare(a: FirMemberDeclaration, b: FirMemberDeclaration): Int {
val priorityDiff = a.priority - b.priority
if (priorityDiff != 0) {
@@ -51,7 +41,7 @@ object FirMemberDeclarationComparator : Comparator<FirMemberDeclaration> {
return 0
}
return a.name.compareTo(b.name)
return a.nameOrSpecialName.compareTo(b.nameOrSpecialName)
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
val FirTypeAlias.expandedConeType: ConeClassLikeType? get() = expandedTypeRef.coneTypeSafe()
@@ -45,3 +46,13 @@ val FirDeclaration.isNonLocal
}
val FirCallableDeclaration.isExtension get() = receiverParameter != null
val FirMemberDeclaration.nameOrSpecialName: Name
get() = when (this) {
is FirCallableDeclaration ->
this.symbol.callableId.callableName
is FirClass ->
this.classId.shortClassName
is FirTypeAlias ->
this.name
}
@@ -58,6 +58,10 @@ sealed class KtFakeSourceElementKind(final override val shouldSkipErrorTypeRepor
// with a fake source which refers to containing class
object ImplicitConstructor : KtFakeSourceElementKind()
// for constructor type parameters, because they refer to the same source
// as the class type parameters themselves
object ConstructorTypeParameter : KtFakeSourceElementKind()
// for constructors which do not have delegated constructor call the fake one is generated
// with a fake sources which refers to the original constructor
object DelegatingConstructorCall : KtFakeSourceElementKind()
@@ -3,7 +3,7 @@
fun f1(x: String) {}
fun f2(f: () -> Unit) {}
fun test1() = <!INAPPLICABLE_CANDIDATE!>f2<!>(::<!UNRESOLVED_REFERENCE!>f1<!>)
fun test1() = <!INAPPLICABLE_CANDIDATE("fun f2(f: () -> Unit): Unit")!>f2<!>(::<!UNRESOLVED_REFERENCE("f1")!>f1<!>)
@Target(AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.TYPE)
@@ -12,14 +12,14 @@ annotation class Ann
fun <@Ann R : @Ann Any> f3(a: Array<@Ann R>): Array<@Ann R?> = null!!
fun test2(a: @Ann Array<in @Ann Int>) {
val r: Array<in Int?> = f3(<!ARGUMENT_TYPE_MISMATCH!>a<!>)
val r: Array<in Int?> = f3(<!ARGUMENT_TYPE_MISMATCH("kotlin/Array<@R|Ann|() R>; @R|Ann|() kotlin/Array<in @R|Ann|() kotlin/Int>")!>a<!>)
}
var test3: Int = 0
set(s: <!WRONG_SETTER_PARAMETER_TYPE!>@Ann String<!>) {}
set(s: <!WRONG_SETTER_PARAMETER_TYPE("kotlin/Int; @R|Ann|() kotlin/String")!>@Ann String<!>) {}
fun f4(fn: (@Ann Int, @Ann Int) -> Unit) {}
val test4 = f4 <!ARGUMENT_TYPE_MISMATCH!>{ single -> }<!>
val test4 = f4 <!ARGUMENT_TYPE_MISMATCH("kotlin/Function2<@R|Ann|() kotlin/Int, @R|Ann|() kotlin/Int, kotlin/Unit>; kotlin/Function1<@R|Ann|() kotlin/Int, kotlin/Unit>")!>{ single -> }<!>
@@ -11,5 +11,5 @@ open class B1 {
}
class D1 : B1() {
<!NOTHING_TO_OVERRIDE!>override<!> fun foo() {}
<!NOTHING_TO_OVERRIDE("foo")!>override<!> fun foo() {}
}
@@ -11,5 +11,5 @@ class C<T> {
}
fun test(a: C<out CharSequence>) {
a[1] = <!ARGUMENT_TYPE_MISMATCH!>25<!>
a[1] = <!ARGUMENT_TYPE_MISMATCH("@R|A|() CapturedType(out kotlin/CharSequence); kotlin/Int")!>25<!>
}
@@ -15,7 +15,7 @@ interface B {
fun <T> a(@An arg: @An Int)
}
<!CONFLICTING_INHERITED_MEMBERS!>interface C<!> : A, B
<!CONFLICTING_INHERITED_MEMBERS("C; a, a")!>interface C<!> : A, B
@An
abstract class D {
@@ -23,8 +23,8 @@ abstract class D {
abstract val d: @An Int
}
<!ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED!>class E<!> : D(), A
<!ABSTRACT_MEMBER_NOT_IMPLEMENTED!>class F<!> : A
<!ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED("Class E; d")!>class E<!> : D(), A
<!ABSTRACT_MEMBER_NOT_IMPLEMENTED("Class F; a")!>class F<!> : A
@An
interface G {
@@ -44,5 +44,5 @@ interface GI : G {
override fun a(@An arg: @An Int) {}
}
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class AG1<!>(val a: A, val g: G) : A by a, G by g
<!MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED!>class AG2<!>() : AI, GI
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED("Class AG1; a")!>class AG1<!>(val a: A, val g: G) : A by a, G by g
<!MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED("Class AG2; a")!>class AG2<!>() : AI, GI
@@ -7,5 +7,5 @@ annotation class Ann(val s: String = "")
fun foo() {}
val bar = foo(
<!TOO_MANY_ARGUMENTS!>15<!>
<!TOO_MANY_ARGUMENTS("public final fun /foo(): R|kotlin/Unit|")!>15<!>
)
@@ -13,5 +13,5 @@ class C<T> {
class Out<out F>
fun test(a: C<out CharSequence>, y: Out<CharSequence>) {
a + <!ARGUMENT_TYPE_MISMATCH!>y<!>
a + <!ARGUMENT_TYPE_MISMATCH("Out<@R|A|() CapturedType(out kotlin/CharSequence)>; Out<kotlin/CharSequence>")!>y<!>
}
@@ -15,12 +15,12 @@ interface A {
@An
interface B : A {
override val p1: <!PROPERTY_TYPE_MISMATCH_ON_OVERRIDE!>Int<!>
override val p1: <!PROPERTY_TYPE_MISMATCH_ON_OVERRIDE("p1; @An() val p1: @R|An|() String")!>Int<!>
@An
override <!VAR_OVERRIDDEN_BY_VAL!>val<!> p2: @An String
override fun test(arg: String): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>Int<!>
override <!VAR_OVERRIDDEN_BY_VAL("public abstract override val /B.p2: R|@R|An|() kotlin/String| public get(): R|@R|An|() kotlin/String|; public abstract var /A.p2: R|@R|An|() kotlin/String| public get(): R|@R|An|() kotlin/String| public set(value: R|@R|An|() kotlin/String|): R|kotlin/Unit|")!>val<!> p2: @An String
override fun test(arg: String): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE("test; @An() fun test(@An() arg: @R|An|() String): @R|An|() String")!>Int<!>
}
interface C : A {
override var p2: <!VAR_TYPE_MISMATCH_ON_OVERRIDE!>Int<!>
override var p2: <!VAR_TYPE_MISMATCH_ON_OVERRIDE("p2; @An() var p2: @R|An|() String")!>Int<!>
}
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.NotNull;
annotation class An
class B : A {
override fun foo(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>String?<!> = null
override fun foo(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE("foo; @NotNull() @An() @MyTypeQualifier() fun foo(): @EnhancedNullability String")!>String?<!> = null
}
@An
@@ -38,5 +38,5 @@ public interface C {
}
class D : C {
override fun foo(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>String?<!> = null
override fun foo(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE("foo; @NotNull() @An() fun foo(): String")!>String?<!> = null
}
@@ -1,15 +0,0 @@
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
package foo
@JsExport
class C(val x: String) {
constructor(x: Int): this(x.toString())
}
@JsExport
class C2(val x: String) {
@JsName("JsNameProvided")
constructor(x: Int): this(x.toString())
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@@ -1,14 +0,0 @@
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@file:JsExport
package foo
class C(val x: String) {
constructor(x: Int): this(x.toString())
}
class C2(val x: String) {
@JsName("JsNameProvided")
constructor(x: Int): this(x.toString())
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@file:JsExport
@@ -4,18 +4,18 @@
package foo
@JsExport
inline fun <reified T> inlineReifiedFun(x: Any) = x is T
<!WRONG_EXPORTED_DECLARATION("inline function with reified type parameters")!>@JsExport
inline fun <reified T> inlineReifiedFun(x: Any)<!> = x is T
@JsExport
suspend fun suspendFun() { }
<!WRONG_EXPORTED_DECLARATION("suspend function")!>@JsExport
suspend fun suspendFun()<!> { }
@JsExport
val String.extensionProperty
<!WRONG_EXPORTED_DECLARATION("extension property")!>@JsExport
val String.extensionProperty<!>
get() = this.length
@JsExport
annotation class AnnotationClass
annotation class <!WRONG_EXPORTED_DECLARATION("annotation class")!>AnnotationClass<!>
@JsExport
interface SomeInterface
@@ -25,24 +25,24 @@ external interface GoodInterface
@JsExport
interface InterfaceWithCompanion {
companion object {
companion <!WRONG_EXPORTED_DECLARATION("companion object inside exported interface")!>object<!> {
fun foo() = 42
}
}
@JsExport
interface OuterInterface {
class Nested
class <!WRONG_EXPORTED_DECLARATION("nested class inside exported interface")!>Nested<!>
}
@JsExport
value class A(val a: Int)
value class <!WRONG_EXPORTED_DECLARATION("value class")!>A(val a: Int)<!>
@JsExport
inline class B(val b: Int)
inline class <!WRONG_EXPORTED_DECLARATION("value class")!>B(val b: Int)<!>
@JsExport
inline value class C(val c: Int)
inline value class <!WRONG_EXPORTED_DECLARATION("value class")!>C(val c: Int)<!>
@JsExport
value inline class D(val d: Int)
value inline class <!WRONG_EXPORTED_DECLARATION("value class")!>D(val d: Int)<!>
@@ -1,15 +0,0 @@
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@file:JsExport
package foo
inline fun <reified T> inlineReifiedFun(x: Any) = x is T
suspend fun suspendFun() { }
val String.extensionProperty
get() = this.length
annotation class AnnotationClass
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !OPT_IN: kotlin.js.ExperimentalJsExport
// !RENDER_DIAGNOSTICS_MESSAGES
@@ -43,6 +43,10 @@ object FirDiagnosticsDirectives : SimpleDirectivesContainer() {
description = "Defines which parser should be used for FIR compiler"
)
val RENDER_DIAGNOSTICS_MESSAGES by directive(
description = "Forces diagnostic arguments to be rendered"
)
val FIR_DISABLE_LAZY_RESOLVE_CHECKS by directive(
description = "Temporary disables lazy resolve checks until the lazy resolve contract violation is fixed"
)
@@ -70,6 +70,7 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
val lightTreeComparingModeEnabled = FirDiagnosticsDirectives.COMPARE_WITH_LIGHT_TREE in currentModule.directives
val lightTreeEnabled = currentModule.directives.singleValue(FirDiagnosticsDirectives.FIR_PARSER) == FirParser.LightTree
val forceRenderArguments = FirDiagnosticsDirectives.RENDER_DIAGNOSTICS_MESSAGES in currentModule.directives
for (file in currentModule.files) {
val firFile = info.mainFirFiles[file] ?: continue
@@ -84,10 +85,11 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
diagnostics.diagnosticCodeMetaInfos(
currentModule, file,
diagnosticsService, globalMetadataInfoHandler,
lightTreeEnabled, lightTreeComparingModeEnabled
lightTreeEnabled, lightTreeComparingModeEnabled,
forceRenderArguments,
)
globalMetadataInfoHandler.addMetadataInfosForFile(file, diagnosticsMetadataInfos)
collectSyntaxDiagnostics(currentModule, file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
collectSyntaxDiagnostics(currentModule, file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled, forceRenderArguments)
collectDebugInfoDiagnostics(currentModule, file, firFile, lightTreeEnabled, lightTreeComparingModeEnabled)
}
}
@@ -99,7 +101,8 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
testFile: TestFile,
firFile: FirFile,
lightTreeEnabled: Boolean,
lightTreeComparingModeEnabled: Boolean
lightTreeComparingModeEnabled: Boolean,
forceRenderArguments: Boolean,
) {
val metaInfos = if (firFile.psi != null) {
AnalyzingUtils.getSyntaxErrorRanges(firFile.psi!!).flatMap {
@@ -109,7 +112,8 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
testFile,
globalMetadataInfoHandler1 = globalMetadataInfoHandler,
lightTreeEnabled,
lightTreeComparingModeEnabled
lightTreeComparingModeEnabled,
forceRenderArguments,
)
}
} else {
@@ -120,7 +124,8 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
testFile,
globalMetadataInfoHandler1 = globalMetadataInfoHandler,
lightTreeEnabled,
lightTreeComparingModeEnabled
lightTreeComparingModeEnabled,
forceRenderArguments,
)
}
}
@@ -323,7 +328,8 @@ fun List<KtDiagnostic>.diagnosticCodeMetaInfos(
diagnosticsService: DiagnosticsService,
globalMetadataInfoHandler: GlobalMetadataInfoHandler,
lightTreeEnabled: Boolean,
lightTreeComparingModeEnabled: Boolean
lightTreeComparingModeEnabled: Boolean,
forceRenderArguments: Boolean = false,
): List<FirDiagnosticCodeMetaInfo> = flatMap { diagnostic ->
if (!diagnosticsService.shouldRenderDiagnostic(
module,
@@ -339,7 +345,8 @@ fun List<KtDiagnostic>.diagnosticCodeMetaInfos(
file,
globalMetadataInfoHandler,
lightTreeEnabled,
lightTreeComparingModeEnabled
lightTreeComparingModeEnabled,
forceRenderArguments,
)
}
@@ -42,7 +42,7 @@ object JsStandardClassIds {
val JsExport = "JsExport".jsId()
@JvmField
val JsExportIgnore = ClassId(JsExport.asSingleFqName(), Name.identifier("Ignore"))
val JsExportIgnore = JsExport.createNestedClassId(Name.identifier("Ignore"))
@JvmField
val annotationsRequiringExternal = setOf(JsModule, JsQualifier)
@@ -40,7 +40,6 @@ interface TypeSystemCommonBackendContext : TypeSystemContext {
fun KotlinTypeMarker.makeNullable(): KotlinTypeMarker =
asSimpleType()?.withNullability(true) ?: this
fun TypeConstructorMarker.getPrimitiveType(): PrimitiveType?
fun TypeConstructorMarker.getPrimitiveArrayType(): PrimitiveType?