diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index cdd71ccbd85..9c9a07669ae 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -5742,6 +5742,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirJsErrors.NAMED_COMPANION_IN_EXPORTED_INTERFACE) { firDiagnostic -> + NamedCompanionInExportedInterfaceImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirWebCommonErrors.NESTED_JS_EXPORT) { firDiagnostic -> NestedJsExportImpl( firDiagnostic as KtPsiDiagnostic, @@ -5881,6 +5887,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirWebCommonErrors.NAMED_COMPANION_IN_EXTERNAL_INTERFACE) { firDiagnostic -> + NamedCompanionInExternalInterfaceImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirWebCommonErrors.JSCODE_ARGUMENT_NON_CONST_EXPRESSION) { firDiagnostic -> JscodeArgumentNonConstExpressionImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index a77b51c4c79..683f13e9781 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -3999,6 +3999,10 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { val name: String } + interface NamedCompanionInExportedInterface : KtFirDiagnostic { + override val diagnosticClass get() = NamedCompanionInExportedInterface::class + } + interface NestedJsExport : KtFirDiagnostic { override val diagnosticClass get() = NestedJsExport::class } @@ -4094,6 +4098,10 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { val typeArgument: KtType } + interface NamedCompanionInExternalInterface : KtFirDiagnostic { + override val diagnosticClass get() = NamedCompanionInExternalInterface::class + } + interface JscodeArgumentNonConstExpression : KtFirDiagnostic { override val diagnosticClass get() = JscodeArgumentNonConstExpression::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 7276c72f72a..b7b8ee1752d 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -4828,6 +4828,11 @@ internal class NonConsumableExportedIdentifierImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.NonConsumableExportedIdentifier +internal class NamedCompanionInExportedInterfaceImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.NamedCompanionInExportedInterface + internal class NestedJsExportImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, @@ -4945,6 +4950,11 @@ internal class ExternalInterfaceAsReifiedTypeArgumentImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.ExternalInterfaceAsReifiedTypeArgument +internal class NamedCompanionInExternalInterfaceImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.NamedCompanionInExternalInterface + internal class JscodeArgumentNonConstExpressionImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt index 5a77b01f955..04844d0bd08 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt @@ -113,6 +113,7 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") { val NON_CONSUMABLE_EXPORTED_IDENTIFIER by warning(PositioningStrategy.DEFAULT) { parameter("name") } + val NAMED_COMPANION_IN_EXPORTED_INTERFACE by error(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) } val DYNAMICS by object : DiagnosticGroup("Dynamics") { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWebCommonDiagnosticList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWebCommonDiagnosticList.kt index 322e7f9a6b9..4bcf0b10f30 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWebCommonDiagnosticList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirWebCommonDiagnosticList.kt @@ -44,6 +44,7 @@ object WEB_COMMON_DIAGNOSTICS_LIST : DiagnosticList("FirWebCommonErrors") { val EXTERNAL_INTERFACE_AS_REIFIED_TYPE_ARGUMENT by error(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) { parameter("typeArgument") } + val NAMED_COMPANION_IN_EXTERNAL_INTERFACE by error(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) } val EXPORT by object : DiagnosticGroup("Export") { diff --git a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt index 3ef72e81fe4..c863bae1573 100644 --- a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt +++ b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt @@ -78,6 +78,7 @@ object FirJsErrors { val WRONG_EXPORTED_DECLARATION: KtDiagnosticFactory1 by error1(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) val NON_EXPORTABLE_TYPE: KtDiagnosticFactory2 by warning2(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) val NON_CONSUMABLE_EXPORTED_IDENTIFIER: KtDiagnosticFactory1 by warning1() + val NAMED_COMPANION_IN_EXPORTED_INTERFACE: KtDiagnosticFactory0 by error0(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) // Dynamics val DELEGATION_BY_DYNAMIC: KtDiagnosticFactory0 by error0() diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt index a8a74a36a7b..0d16bd1b18a 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_ON_P import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_NAMED_NATIVE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_OVERRIDE +import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NAMED_COMPANION_IN_EXPORTED_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NAME_CONTAINS_ILLEGAL_CHARS import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NATIVE_ANNOTATIONS_ALLOWED_ONLY_ON_MEMBER_OR_EXTENSION_FUN import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE @@ -189,5 +190,6 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() { "Exported declaration contains non-consumable identifier ''{0}'', which cannot be represented inside TS definitions and ESM.", CommonRenderers.STRING, ) + map.put(NAMED_COMPANION_IN_EXPORTED_INTERFACE, "Named companions are not allowed inside exported interfaces.") } } diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt index f7a09055f9a..41ef5209f86 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExportDeclarationChecker.kt @@ -30,6 +30,7 @@ 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 +import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT object FirJsExportDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKind.Common) { override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { @@ -135,11 +136,15 @@ object FirJsExportDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKind declaration.isInline -> "value class" else -> null } - else -> if (context.isInsideInterface) { - "${if (declaration.status.isCompanion) "companion object" else "nested/inner declaration"} inside exported interface" + else -> if (context.isInsideInterface && !declaration.status.isCompanion) { + "nested/inner declaration inside exported interface" } else null } + if (context.isInsideInterface && declaration.status.isCompanion && declaration.nameOrSpecialName != DEFAULT_NAME_FOR_COMPANION_OBJECT) { + reporter.reportOn(declaration.source, FirJsErrors.NAMED_COMPANION_IN_EXPORTED_INTERFACE, context) + } + if (wrongDeclaration != null) { reportWrongExportedDeclaration(wrongDeclaration) } diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalChecker.kt index 80de3c130b9..1e2570bbd5c 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalChecker.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/declaration/FirJsExternalChecker.kt @@ -31,7 +31,7 @@ import org.jetbrains.kotlin.name.JsStandardClassIds import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsNative import org.jetbrains.kotlin.psi.KtParameter -object FirJsExternalChecker : FirWebCommonExternalChecker() { +object FirJsExternalChecker : FirWebCommonExternalChecker(allowCompanionInInterface = true) { override fun isNativeOrEffectivelyExternal(symbol: FirBasedSymbol<*>, session: FirSession): Boolean { return symbol.isNativeObject(session) } diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmExternalChecker.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmExternalChecker.kt index 74f3eb38066..369a1ce9821 100644 --- a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmExternalChecker.kt +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmExternalChecker.kt @@ -17,7 +17,7 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.WebCommonStandardClassIds -object FirWasmExternalChecker : FirWebCommonExternalChecker() { +object FirWasmExternalChecker : FirWebCommonExternalChecker(allowCompanionInInterface = false) { override fun isNativeOrEffectivelyExternal(symbol: FirBasedSymbol<*>, session: FirSession): Boolean { return symbol.isEffectivelyExternal(session) } diff --git a/compiler/fir/checkers/checkers.web.common/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrors.kt b/compiler/fir/checkers/checkers.web.common/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrors.kt index 69acc38ffca..960e2017bea 100644 --- a/compiler/fir/checkers/checkers.web.common/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrors.kt +++ b/compiler/fir/checkers/checkers.web.common/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrors.kt @@ -43,6 +43,7 @@ object FirWebCommonErrors { val UNCHECKED_CAST_TO_EXTERNAL_INTERFACE: KtDiagnosticFactory2 by warning2() val EXTERNAL_INTERFACE_AS_CLASS_LITERAL: KtDiagnosticFactory0 by error0() val EXTERNAL_INTERFACE_AS_REIFIED_TYPE_ARGUMENT: KtDiagnosticFactory1 by error1(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) + val NAMED_COMPANION_IN_EXTERNAL_INTERFACE: KtDiagnosticFactory0 by error0(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT) // Export val NESTED_JS_EXPORT: KtDiagnosticFactory0 by error0() diff --git a/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrorsDefaultMessages.kt index b5f726b4948..cfed3cd0406 100644 --- a/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/diagnostics/web/common/FirWebCommonErrorsDefaultMessages.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErro import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.EXTERNAL_INTERFACE_AS_REIFIED_TYPE_ARGUMENT import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.INLINE_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.JSCODE_ARGUMENT_NON_CONST_EXPRESSION +import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.NAMED_COMPANION_IN_EXTERNAL_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.NESTED_CLASS_IN_EXTERNAL_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.NESTED_EXTERNAL_DECLARATION import org.jetbrains.kotlin.fir.analysis.diagnostics.web.common.FirWebCommonErrors.NESTED_JS_EXPORT @@ -76,5 +77,6 @@ object FirWebCommonErrorsDefaultMessages : BaseDiagnosticRendererFactory() { map.put(NESTED_JS_EXPORT, "'@JsExport' is only allowed on files and top-level declarations.") map.put(JSCODE_ARGUMENT_NON_CONST_EXPRESSION, "An argument for the 'js()' function must be a constant string expression.") + map.put(NAMED_COMPANION_IN_EXTERNAL_INTERFACE, "Named companions are not allowed inside external interfaces.") } } \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/web/common/checkers/declaration/FirWebCommonExternalChecker.kt b/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/web/common/checkers/declaration/FirWebCommonExternalChecker.kt index 4312c759070..7f9ee60148c 100644 --- a/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/web/common/checkers/declaration/FirWebCommonExternalChecker.kt +++ b/compiler/fir/checkers/checkers.web.common/src/org/jetbrains/kotlin/fir/analysis/web/common/checkers/declaration/FirWebCommonExternalChecker.kt @@ -6,9 +6,7 @@ package org.jetbrains.kotlin.fir.analysis.web.common.checkers.declaration import org.jetbrains.kotlin.* -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirElement @@ -30,9 +28,10 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -abstract class FirWebCommonExternalChecker : FirBasicDeclarationChecker(MppCheckerKind.Common) { +abstract class FirWebCommonExternalChecker(private val allowCompanionInInterface: Boolean) : FirBasicDeclarationChecker(MppCheckerKind.Common) { abstract fun isNativeOrEffectivelyExternal(symbol: FirBasedSymbol<*>, session: FirSession): Boolean abstract fun reportExternalEnum(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) @@ -87,12 +86,24 @@ abstract class FirWebCommonExternalChecker : FirBasicDeclarationChecker(MppCheck if ( declaration is FirClass && - declaration.classKind != ClassKind.INTERFACE && - container is FirClass && container.classKind == ClassKind.INTERFACE + !declaration.classKind.isInterface && (!allowCompanionInInterface || !declaration.status.isCompanion) && + container is FirClass && container.classKind.isInterface ) { reporter.reportOn(declaration.source, FirWebCommonErrors.NESTED_CLASS_IN_EXTERNAL_INTERFACE, context) } + if ( + allowCompanionInInterface && + declaration is FirClass && + declaration.status.isCompanion && + container is FirClass && + container.isInterface && + declaration.nameOrSpecialName != DEFAULT_NAME_FOR_COMPANION_OBJECT + ) { + reporter.reportOn(declaration.source, FirWebCommonErrors.NAMED_COMPANION_IN_EXTERNAL_INTERFACE, context) + } + + if (declaration !is FirPropertyAccessor && declaration is FirCallableDeclaration && declaration.isExtension) { val target = when (declaration) { is FirFunction -> "extension function" diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt index c580dddaf3f..12962cd574d 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt @@ -645,6 +645,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = setOf( "JS_EXTERNAL_INHERITORS_ONLY", "JS_EXTERNAL_ARGUMENT", "WRONG_EXPORTED_DECLARATION", + "NAMED_COMPANION_IN_EXPORTED_INTERFACE", "NESTED_JS_EXPORT", "DELEGATION_BY_DYNAMIC", "PROPERTY_DELEGATION_BY_DYNAMIC", @@ -700,6 +701,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = setOf( "CANNOT_CHECK_FOR_EXTERNAL_INTERFACE", "EXTERNAL_INTERFACE_AS_CLASS_LITERAL", "EXTERNAL_INTERFACE_AS_REIFIED_TYPE_ARGUMENT", + "NAMED_COMPANION_IN_EXTERNAL_INTERFACE", "JSCODE_ARGUMENT_NON_CONST_EXPRESSION", "SYNTAX", ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 44b9eaeee92..f732862bdc5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.* import org.jetbrains.kotlin.utils.addToStdlib.runIf @@ -328,12 +329,15 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac members.addIfNotNull(exportProperty(candidate)?.withAttributesFor(candidate)) is IrClass -> { - if (klass.isInterface) continue - val ec = exportClass(candidate)?.withAttributesFor(candidate) - if (ec is ExportedClass) { - nestedClasses.add(ec) + if (klass.isInterface) { + nestedClasses.addIfNotNull(klass.companionObject()?.let { exportClass(it) as? ExportedClass }?.withAttributesFor(candidate)) } else { - members.addIfNotNull(ec) + val ec = exportClass(candidate)?.withAttributesFor(candidate) + if (ec is ExportedClass) { + nestedClasses.add(ec) + } else { + members.addIfNotNull(ec) + } } } @@ -441,7 +445,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac .map { exportType(it, false) } .memoryOptimizedFilter { it !is ExportedType.ErrorType } - val name = klass.getExportedIdentifier() + val name = klass.getExportedIdentifierForClass() return if (klass.kind == ClassKind.OBJECT) { return ExportedObject( @@ -862,12 +866,19 @@ val strictModeReservedWords = setOf( private val allReservedWords = reservedWords + strictModeReservedWords -fun ExportedDeclaration.withAttributesFor(declaration: IrDeclaration): ExportedDeclaration { +fun T.withAttributesFor(declaration: IrDeclaration): T { declaration.getDeprecated()?.let { attributes.add(ExportedAttribute.DeprecatedAttribute(it)) } return this } +fun IrClass.getExportedIdentifierForClass(): String { + val parentClass = parentClassOrNull + return if (parentClass != null && isCompanion && parentClass.isInterface) { + parentClass.getExportedIdentifierForClass() + } else getExportedIdentifier() +} + fun IrDeclarationWithName.getExportedIdentifier(): String = with(getJsNameOrKotlinName()) { if (isSpecial) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index b7badfa5cc6..fb3716bcacf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -33,7 +33,7 @@ class ExportModelToJsStatements( fun generateModuleExport( module: ExportedModule, internalModuleName: JsName?, - esModules: Boolean + esModules: Boolean, ): List { return module.declarations.flatMap { generateDeclarationExport(it, internalModuleName?.makeRef(), esModules) @@ -44,7 +44,7 @@ class ExportModelToJsStatements( declaration: ExportedDeclaration, namespace: JsExpression?, esModules: Boolean, - parentClass: IrClass? = null + parentClass: IrClass? = null, ): List { return when (declaration) { is ExportedNamespace -> { @@ -155,7 +155,9 @@ class ExportModelToJsStatements( } is ExportedRegularClass -> { - if (declaration.isInterface) return emptyList() + if (declaration.isInterface) { + return declaration.nestedClasses.flatMap { generateDeclarationExport(it, namespace, esModules, parentClass) } + } val (name, classInitialization) = declaration.getNameAndInitialization() val newNameSpace = when { namespace != null -> jsElementAccess(declaration.name, namespace) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index 6a9d0b165a9..3c018f5a5c5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -14,10 +14,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.util.hasAnnotation -import org.jetbrains.kotlin.ir.util.isObject -import org.jetbrains.kotlin.ir.util.parentAsClass -import org.jetbrains.kotlin.ir.util.primaryConstructor +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.js.common.isValidES5Identifier import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -106,7 +103,7 @@ class ExportModelToTsDeclarations { is ExportedConstructSignature -> generateTypeScriptString(indent) is ExportedNamespace -> generateTypeScriptString(indent, prefix) is ExportedFunction -> generateTypeScriptString(indent, prefix) - is ExportedRegularClass -> generateTypeScriptString(indent, prefix) + is ExportedRegularClass -> generateTypeScriptString(indent, prefix, esModules) is ExportedProperty -> generateTypeScriptString(indent, prefix, esModules) is ExportedObject -> generateTypeScriptString(indent, prefix, esModules) } @@ -235,7 +232,7 @@ class ExportModelToTsDeclarations { t = ExportedType.IntersectionType(t, ExportedType.InlineInterfaceType(listOf(constructor))) } - val maybeParentClass = ir.parent as? IrClass + val maybeParentClass = (ir.parent as? IrClass)?.takeIf { !it.isInterface } val propertyName = ir .takeIf { shouldRenderSeparatedAbstractClass } @@ -275,21 +272,22 @@ class ExportModelToTsDeclarations { if (esModules && !property.isMember) { property.copy(type = ExportedType.TypeOf(className), name = name) - .generateTypeScriptString(indent, prefix, esModules) + "\n${classForRender.generateTypeScriptString(indent, declare)}" + .generateTypeScriptString(indent, prefix, esModules) + "\n${classForRender.generateTypeScriptString(indent, declare, esModules)}" } else { - classForRender.generateTypeScriptString(indent, prefix) + classForRender.generateTypeScriptString(indent, prefix, esModules) } } } - private fun ExportedRegularClass.generateTypeScriptString(indent: String, prefix: String): String { + private fun ExportedRegularClass.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean): String { val keyword = if (isInterface) "interface" else "class" + val (interfaceCompanions, allNestedClasses) = nestedClasses.partition { isInterface && it.ir.isCompanion } val superInterfacesKeyword = if (isInterface) "extends" else "implements" val superClassClause = superClasses.toExtendsClause(indent) val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent) - val (memberObjects, nestedDeclarations) = nestedClasses.partition { it.couldBeProperty() } + val (memberObjects, nestedDeclarations) = allNestedClasses.partition { it.couldBeProperty() } val members = members.map { if (!ir.isInner || it !is ExportedFunction || !it.isStatic) { @@ -321,7 +319,7 @@ class ExportModelToTsDeclarations { val bodyString = privateCtorString + membersString + indent - val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() } + val realNestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() } val tsIgnoreForPrivateConstructorInheritance = if (hasSuperClassWithPrivateConstructor()) { tsIgnore("extends class with private primary constructor") + "\n$indent" } else "" @@ -329,9 +327,17 @@ class ExportModelToTsDeclarations { val klassExport = "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}" val staticsExport = - if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else "" + if (realNestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, realNestedClasses).toTypeScript(indent, prefix) else "" - return if (name.isValidES5Identifier()) tsIgnoreForPrivateConstructorInheritance + klassExport + staticsExport else "" + val interfaceCompanionsString = if (interfaceCompanions.isNotEmpty()) "\n" + interfaceCompanions.joinToString("\n") { + it.toTypeScript( + indent, + prefix, + esModules + ) + } else "" + + return if (name.isValidES5Identifier()) tsIgnoreForPrivateConstructorInheritance + klassExport + staticsExport + interfaceCompanionsString else "" } private fun ExportedRegularClass.hasSuperClassWithPrivateConstructor(): Boolean { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index bb6941b3416..3bea90648a9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind +import org.jetbrains.kotlin.js.backend.ast.metadata.constant import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.utils.memoryOptimizedMap @@ -148,9 +149,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer JsNullLiteral() - else -> context.getRefForExternalClass(obj).withSource(expression, context) + return context.getRefForExternalClass(obj).withSource(expression, context).apply { + sideEffects = SideEffectKind.PURE } } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.fir.kt index c132e0c9d9a..1806f77efea 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.fir.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.fir.kt @@ -25,7 +25,14 @@ external interface GoodInterface @JsExport interface InterfaceWithCompanion { - companion object { + companion object { + fun foo() = 42 + } +} + +@JsExport +interface InterfaceWithNamedCompanion { + companion object Named { fun foo() = 42 } } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt index dcb4984bd86..485ff351d5b 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.kt @@ -25,7 +25,14 @@ external interface GoodInterface @JsExport interface InterfaceWithCompanion { - companion object { + companion object { + fun foo() = 42 + } +} + +@JsExport +interface InterfaceWithNamedCompanion { + companion object Named { fun foo() = 42 } } diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt index 052f63134e4..361d9ab3071 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/export/wrongExportedDeclaration.txt @@ -64,6 +64,20 @@ package foo { } } + @kotlin.js.JsExport public interface InterfaceWithNamedCompanion { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Named { + private constructor Named() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + @kotlin.js.JsExport public interface OuterInterface { public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.kt b/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.kt index ce697540767..561cd133fee 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.kt @@ -9,5 +9,9 @@ external interface I { enum class E - companion object -} \ No newline at end of file + companion object +} + +external interface I2 { + companion object Named +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.txt b/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.txt index a2f926d91b0..28b4298c9d7 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/native/externalInterfaceNested.txt @@ -47,3 +47,17 @@ public external interface I { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } } + +public external interface I2 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Named { + private constructor Named() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index bc74e7ba978..e8709bd7370 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -22,7 +22,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( additionalDeclarationCheckers = listOf( NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), JsNameChecker, JsModuleChecker, JsExternalFileChecker, - JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, + JsInheritanceChecker, JsMultipleInheritanceChecker, JsExternalInheritorOnlyChecker, JsRuntimeAnnotationChecker, JsDynamicDeclarationChecker, @@ -51,7 +51,8 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( container.useInstance(ExtensionFunctionToExternalIsInlinable) container.useInstance(JsQualifierChecker) container.useInstance(JsNativeDiagnosticSuppressor) - container.useInstance(JsExportDeclarationChecker(includeUnsignedNumbers = false)) + container.useInstance(JsExternalChecker(allowCompanionInInterface = true)) + container.useInstance(JsExportDeclarationChecker(allowCompanionInInterface = true, includeUnsignedNumbers = false)) } override fun configureModuleDependentCheckers(container: StorageComponentContainer) { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index 0412442b46a..949a60f97cf 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -34,6 +34,9 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING, "Using value classes as parameter type or return type of external declarations is experimental") put(ErrorsJs.ENUM_CLASS_IN_EXTERNAL_DECLARATION_WARNING, "Using enum classes with an `external` qualifier becomes deprecated and will be an error in future releases") + put(ErrorsJs.NAMED_COMPANION_IN_EXTERNAL_INTERFACE, "Named companions are not allowed inside external interfaces") + put(ErrorsJs.NAMED_COMPANION_IN_EXPORTED_INTERFACE, "Named companions are not allowed inside exported interfaces") + put(ErrorsJs.JS_NAME_CLASH, "JavaScript name ({0}) generated for this declaration clashes with another declaration: {1}", STRING, Renderers.COMPACT) put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}", diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 13ca6de58f4..5af2dea25de 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -36,6 +36,8 @@ public interface ErrorsJs { DiagnosticFactory0 INLINE_CLASS_IN_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 ENUM_CLASS_IN_EXTERNAL_DECLARATION_WARNING = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory0 NAMED_COMPANION_IN_EXTERNAL_INTERFACE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory0 NAMED_COMPANION_IN_EXPORTED_INTERFACE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory2 JS_NAME_CLASH = DiagnosticFactory2.create( ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDefinedExternallyCallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDefinedExternallyCallChecker.kt index 45975cf90b1..342b5955a85 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDefinedExternallyCallChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDefinedExternallyCallChecker.kt @@ -17,7 +17,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.js.resolve.diagnostics.JsExternalChecker.DEFINED_EXTERNALLY_PROPERTY_NAMES +import org.jetbrains.kotlin.js.resolve.diagnostics.JsExternalChecker.Companion.DEFINED_EXTERNALLY_PROPERTY_NAMES import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt index 73e25407b28..c90b36805d0 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.js.common.SPECIAL_KEYWORDS import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration @@ -35,7 +36,10 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.isDynamic import org.jetbrains.kotlin.types.typeUtil.* -class JsExportDeclarationChecker(private val includeUnsignedNumbers: Boolean) : DeclarationChecker { +class JsExportDeclarationChecker( + private val includeUnsignedNumbers: Boolean, + private val allowCompanionInInterface: Boolean +) : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { val trace = context.trace val bindingContext = trace.bindingContext @@ -130,11 +134,15 @@ class JsExportDeclarationChecker(private val includeUnsignedNumbers: Boolean) : descriptor.isInlineClass() -> "${if (descriptor.isInline) "inline " else ""}${if (descriptor.isValue) "value " else ""}class" else -> null } - else -> if (descriptor.isInsideInterface) { + else -> if (descriptor.isInsideInterface && (!allowCompanionInInterface || !descriptor.isCompanionObject)) { "${if (descriptor.isCompanionObject) "companion object" else "nested/inner declaration"} inside exported interface" } else null } + if (allowCompanionInInterface && descriptor.isCompanionObject && descriptor.isInsideInterface && descriptor.name != DEFAULT_NAME_FOR_COMPANION_OBJECT) { + trace.report(ErrorsJs.NAMED_COMPANION_IN_EXPORTED_INTERFACE.on(declaration)) + } + if (wrongDeclaration != null) { reportWrongExportedDeclaration(wrongDeclaration) return diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt index 3aa4391f145..af5555a5621 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExternalChecker.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.js.PredefinedAnnotation import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.JsStandardClassIds +import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT import org.jetbrains.kotlin.platform.isWasm import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext @@ -28,9 +29,11 @@ import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils -object JsExternalChecker : DeclarationChecker { - val DEFINED_EXTERNALLY_PROPERTY_NAMES = JsStandardClassIds.Callables.definedExternallyPropertyNames - .map { it.asSingleFqName().toUnsafe() } +class JsExternalChecker(private val allowCompanionInInterface: Boolean) : DeclarationChecker { + companion object { + val DEFINED_EXTERNALLY_PROPERTY_NAMES = JsStandardClassIds.Callables.definedExternallyPropertyNames + .map { it.asSingleFqName().toUnsafe() } + } override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (!AnnotationsUtils.isNativeObject(descriptor)) return @@ -68,12 +71,16 @@ object JsExternalChecker : DeclarationChecker { trace.report(ErrorsJs.WRONG_EXTERNAL_DECLARATION.on(declaration, "private member of class")) } - if (descriptor is ClassDescriptor && descriptor.kind != ClassKind.INTERFACE && - descriptor.containingDeclaration.let { it is ClassDescriptor && it.kind == ClassKind.INTERFACE } - ) { + val containingDeclarationsIsInterface = descriptor.containingDeclaration.let { it is ClassDescriptor && it.kind == ClassKind.INTERFACE } + + if (descriptor is ClassDescriptor && descriptor.kind != ClassKind.INTERFACE && (!allowCompanionInInterface || !descriptor.isCompanionObject) && containingDeclarationsIsInterface) { trace.report(ErrorsJs.NESTED_CLASS_IN_EXTERNAL_INTERFACE.on(declaration)) } + if (allowCompanionInInterface && descriptor.isCompanionObject() && containingDeclarationsIsInterface && descriptor.name != DEFAULT_NAME_FOR_COMPANION_OBJECT) { + trace.report(ErrorsJs.NAMED_COMPANION_IN_EXTERNAL_INTERFACE.on(declaration)) + } + if (descriptor !is PropertyAccessorDescriptor && descriptor.isExtension) { val target = when (descriptor) { is FunctionDescriptor -> "extension function" diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt index d560acc904e..36fc47d63ab 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt @@ -17,6 +17,9 @@ package org.jetbrains.kotlin.js.inline.clean import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind +import org.jetbrains.kotlin.js.backend.ast.metadata.constant +import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.js.inline.util.collectFreeVariables @@ -55,7 +58,8 @@ internal class RedundantVariableDeclarationElimination(private val root: JsState object : JsVisitorWithContextImpl() { override fun endVisit(x: JsVars, ctx: JsContext<*>) { if (x.synthetic) { - if (x.vars.removeAll { it.initExpression == null && it.name !in usages }) { + if ( + x.vars.removeAll { it.initExpression.isPure && it.name !in usages }) { hasChanges = true } if (x.vars.isEmpty()) { @@ -65,6 +69,9 @@ internal class RedundantVariableDeclarationElimination(private val root: JsState super.endVisit(x, ctx) } + private val JsExpression?.isPure: Boolean + get() = this == null || constant || sideEffects == SideEffectKind.PURE + override fun visit(x: JsFunction, ctx: JsContext<*>) = false }.accept(root) } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java index c0771a59680..9804efcf26d 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java @@ -2605,6 +2605,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -2699,6 +2705,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("jsExternalInheritorsOnly.kt") public void testJsExternalInheritorsOnly() { @@ -7524,6 +7536,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -7660,6 +7678,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("kt39378.kt") public void testKt39378() { @@ -7836,6 +7860,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirLightTreeJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirLightTreeJsBoxTestGenerated.java index 020ae58dce5..f0d3d34ca61 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirLightTreeJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirLightTreeJsBoxTestGenerated.java @@ -2499,6 +2499,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes runTest("js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -2593,6 +2599,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("jsExternalInheritorsOnly.kt") public void testJsExternalInheritorsOnly() { @@ -7418,6 +7430,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -7554,6 +7572,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes runTest("js/js.translator/testData/box/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("kt39378.kt") public void testKt39378() { @@ -7730,6 +7754,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsBoxTestGenerated.java index 29652c8faf4..75c3de0d541 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirPsiJsBoxTestGenerated.java @@ -2499,6 +2499,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest { runTest("js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -2593,6 +2599,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("jsExternalInheritorsOnly.kt") public void testJsExternalInheritorsOnly() { @@ -7418,6 +7430,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest { runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -7554,6 +7572,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest { runTest("js/js.translator/testData/box/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("kt39378.kt") public void testKt39378() { @@ -7730,6 +7754,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest { runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index e68f33f6171..68765e024de 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -2605,6 +2605,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -2699,6 +2705,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("jsExternalInheritorsOnly.kt") public void testJsExternalInheritorsOnly() { @@ -7524,6 +7536,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -7660,6 +7678,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("kt39378.kt") public void testKt39378() { @@ -7836,6 +7860,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 86ba9ba6e91..c60a540b3f4 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -2499,6 +2499,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -2593,6 +2599,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("jsExternalInheritorsOnly.kt") public void testJsExternalInheritorsOnly() { @@ -7418,6 +7430,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt"); } + @Test + @TestMetadata("interfaceWithCompanion.kt") + public void testInterfaceWithCompanion() { + runTest("js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt"); + } + @Test @TestMetadata("jsExportInClass.kt") public void testJsExportInClass() { @@ -7554,6 +7572,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/jsModule/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("kt39378.kt") public void testKt39378() { @@ -7730,6 +7754,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt"); } + @Test + @TestMetadata("interfacesWithCompanion.kt") + public void testInterfacesWithCompanion() { + runTest("js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() { diff --git a/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt b/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt new file mode 100644 index 00000000000..fa7e53d386b --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.kt @@ -0,0 +1,24 @@ +// SKIP_MINIFICATION +// ES_MODULES + +// FILE: api.kt +package api + +@JsExport +interface A { + companion object { + fun ok() = "OK" + } +} + +// FILE: main.kt +external interface JsResult { + val res: String +} + +@JsModule("./interfaceWithCompanion.mjs") +external fun jsBox(): JsResult + +fun box(): String { + return jsBox().res +} diff --git a/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.mjs b/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.mjs new file mode 100644 index 00000000000..bfc988564ff --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsExport/interfaceWithCompanion.mjs @@ -0,0 +1,7 @@ +import * as api from "./interfaceWithCompanion_v5.mjs"; + +export default function() { + return { + "res": api.A.getInstance().ok() + }; +}; \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt b/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt new file mode 100644 index 00000000000..aafb7ff1146 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JS_IR +// TARGET_BACKEND: JS_IR_ES6 +// EXPECTED_REACHABLE_NODES: 1238 +// ES_MODULES +// FILE: bar.kt +@file:JsModule("./interfacesWithCompanion.mjs") +package bar + +external interface Bar { + companion object { + fun ok(): String + } +} + +// FILE: test.kt +import bar.Bar + +fun box(): String { + return Bar.ok() +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.mjs b/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.mjs new file mode 100644 index 00000000000..f5344ffe850 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsModule/interfacesWithCompanion.mjs @@ -0,0 +1,3 @@ +export const Bar = { + ok() { return "OK" } +}; diff --git a/js/js.translator/testData/box/jsExport/interfaceWithCompanion.js b/js/js.translator/testData/box/jsExport/interfaceWithCompanion.js new file mode 100644 index 00000000000..a32b1e03337 --- /dev/null +++ b/js/js.translator/testData/box/jsExport/interfaceWithCompanion.js @@ -0,0 +1,12 @@ +$kotlin_test_internal$.beginModule(); + +module.exports = function() { + var { A } = require("main").api + + return { + "res": A.ok() + }; +}; + +$kotlin_test_internal$.endModule("lib"); + diff --git a/js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt b/js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt new file mode 100644 index 00000000000..ab5354144a6 --- /dev/null +++ b/js/js.translator/testData/box/jsExport/interfaceWithCompanion.kt @@ -0,0 +1,24 @@ +// MODULE_KIND: COMMON_JS +// SKIP_MINIFICATION + +// FILE: api.kt +package api + +@JsExport +interface A { + companion object { + fun ok() = "OK" + } +} + +// FILE: main.kt +external interface JsResult { + val res: String +} + +@JsModule("lib") +external fun jsBox(): JsResult + +fun box(): String { + return jsBox().res +} diff --git a/js/js.translator/testData/box/jsModule/interfacesWithCompanion.js b/js/js.translator/testData/box/jsModule/interfacesWithCompanion.js new file mode 100644 index 00000000000..5d76b6229e9 --- /dev/null +++ b/js/js.translator/testData/box/jsModule/interfacesWithCompanion.js @@ -0,0 +1,7 @@ +define("bar", [], function() { + return { + Bar: { + ok() { return "OK" } + } + }; +}); diff --git a/js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt b/js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt new file mode 100644 index 00000000000..5497e57179f --- /dev/null +++ b/js/js.translator/testData/box/jsModule/interfacesWithCompanion.kt @@ -0,0 +1,23 @@ +// TARGET_BACKEND: JS_IR +// TARGET_BACKEND: JS_IR_ES6 +// EXPECTED_REACHABLE_NODES: 1238 +// MODULE_KIND: AMD +// FILE: bar.kt +@file:JsModule("bar") +package bar + +external interface Bar { + companion object { + fun ok(): String + } +} + +// FILE: test.kt +import bar.Bar + +inline fun Bar.Companion.test() = "CHECK" + +fun box(): String { + Bar.test() + return Bar.ok() +} \ No newline at end of file diff --git a/js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt b/js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt new file mode 100644 index 00000000000..abb60b591ee --- /dev/null +++ b/js/js.translator/testData/box/jsQualifier/interfacesWithCompanion.kt @@ -0,0 +1,31 @@ +// TARGET_BACKEND: JS_IR +// TARGET_BACKEND: JS_IR_ES6 +// EXPECTED_REACHABLE_NODES: 1238 +// FILE: bar.kt +@file:JsQualifier("bar") +package bar + +external interface Bar { + companion object { + fun ok(): String + } +} + +// FILE: test.kt +import bar.Bar + +fun box(): String { + return Bar.ok() +} + +// FILE: test.js +var bar = function() { + var Bar = { + ok() { + return "OK" + } + }; + return { + Bar: Bar + } +}(); diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts index 2da9c13373a..7c1324ebc4e 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.d.ts @@ -30,6 +30,15 @@ declare namespace JS_TESTS { readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"]; } function processInterface(test: foo.TestInterface): string; + interface WithTheCompanion { + readonly interfaceField: string; + readonly __doNotUseOrImplementIt: { + readonly "foo.WithTheCompanion": unique symbol; + }; + } + const WithTheCompanion: { + companionFunction(): string; + }; function processOptionalInterface(a: foo.OptionalFieldsInterface): string; interface InterfaceWithCompanion { readonly __doNotUseOrImplementIt: { @@ -37,4 +46,4 @@ declare namespace JS_TESTS { }; } } -} \ No newline at end of file +} diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt index b7289819a34..5e848f11755 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces.kt @@ -43,6 +43,15 @@ external interface OptionalFieldsInterface { } +interface WithTheCompanion { + val interfaceField: String + + companion object { + fun companionFunction(): String = "FUNCTION" + } +} + + fun processOptionalInterface(a: OptionalFieldsInterface): String { return "${a.required}${a.notRequired ?: "unknown"}" } @@ -52,6 +61,7 @@ fun processOptionalInterface(a: OptionalFieldsInterface): String { interface InterfaceWithCompanion { // Emulate added by plugin companion like kotlinx.serialization does @Suppress("WRONG_EXPORTED_DECLARATION") + @JsExport.Ignore companion object { fun foo() = "String" } diff --git a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts index de5842b2b27..be559a4b43b 100644 --- a/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts +++ b/js/js.translator/testData/typescript-export/interfaces-in-exported-file/interfaces__main.ts @@ -2,6 +2,7 @@ import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import processInterface = JS_TESTS.foo.processInterface; import processOptionalInterface = JS_TESTS.foo.processOptionalInterface; +import WithTheCompanion = JS_TESTS.foo.WithTheCompanion; function assert(condition: boolean) { if (!condition) { @@ -20,5 +21,7 @@ function box(): string { assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown") assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45") + assert(WithTheCompanion.companionFunction() == "FUNCTION") + return "OK"; } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts b/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts index 2da9c13373a..7c1324ebc4e 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.d.ts @@ -30,6 +30,15 @@ declare namespace JS_TESTS { readonly __doNotUseOrImplementIt: foo.TestInterfaceImpl["__doNotUseOrImplementIt"] & foo.AnotherExportedInterface["__doNotUseOrImplementIt"]; } function processInterface(test: foo.TestInterface): string; + interface WithTheCompanion { + readonly interfaceField: string; + readonly __doNotUseOrImplementIt: { + readonly "foo.WithTheCompanion": unique symbol; + }; + } + const WithTheCompanion: { + companionFunction(): string; + }; function processOptionalInterface(a: foo.OptionalFieldsInterface): string; interface InterfaceWithCompanion { readonly __doNotUseOrImplementIt: { @@ -37,4 +46,4 @@ declare namespace JS_TESTS { }; } } -} \ No newline at end of file +} diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt index e63b1288391..ae5024d4fd4 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces.kt +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces.kt @@ -38,6 +38,15 @@ external interface OptionalFieldsInterface { val notRequired: Int? } +@JsExport +interface WithTheCompanion { + val interfaceField: String + + companion object { + fun companionFunction(): String = "FUNCTION" + } +} + @JsExport fun processOptionalInterface(a: OptionalFieldsInterface): String { return "${a.required}${a.notRequired ?: "unknown"}" @@ -48,6 +57,7 @@ fun processOptionalInterface(a: OptionalFieldsInterface): String { interface InterfaceWithCompanion { // Emulate added by plugin companion like kotlinx.serialization does @Suppress("WRONG_EXPORTED_DECLARATION") + @JsExport.Ignore companion object { fun foo() = "String" } diff --git a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts index de5842b2b27..be559a4b43b 100644 --- a/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts +++ b/js/js.translator/testData/typescript-export/interfaces/interfaces__main.ts @@ -2,6 +2,7 @@ import TestInterfaceImpl = JS_TESTS.foo.TestInterfaceImpl; import ChildTestInterfaceImpl = JS_TESTS.foo.ChildTestInterfaceImpl; import processInterface = JS_TESTS.foo.processInterface; import processOptionalInterface = JS_TESTS.foo.processOptionalInterface; +import WithTheCompanion = JS_TESTS.foo.WithTheCompanion; function assert(condition: boolean) { if (!condition) { @@ -20,5 +21,7 @@ function box(): string { assert(processOptionalInterface({ required: 4, notRequired: null }) == "4unknown") assert(processOptionalInterface({ required: 4, notRequired: 5 }) == "45") + assert(WithTheCompanion.companionFunction() == "FUNCTION") + return "OK"; } \ No newline at end of file diff --git a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt index 0750011a9a5..57ab4119539 100644 --- a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt +++ b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.wasm.resolve.diagnostics.* object WasmJsPlatformConfigurator : PlatformConfiguratorBase( additionalDeclarationCheckers = listOf( JsNameChecker, JsModuleChecker, JsExternalFileChecker, - JsExternalChecker, WasmExternalInheritanceChecker, + WasmExternalInheritanceChecker, JsRuntimeAnnotationChecker, JsExportAnnotationChecker, WasmExternalDeclarationChecker, @@ -50,7 +50,8 @@ object WasmJsPlatformConfigurator : PlatformConfiguratorBase( container.useInstance(ExtensionFunctionToExternalIsInlinable) container.useInstance(JsQualifierChecker) container.useInstance(WasmDiagnosticSuppressor) - container.useInstance(JsExportDeclarationChecker(includeUnsignedNumbers = true)) + container.useInstance(JsExternalChecker(allowCompanionInInterface = false)) + container.useInstance(JsExportDeclarationChecker(allowCompanionInInterface = false, includeUnsignedNumbers = true)) } override fun configureModuleDependentCheckers(container: StorageComponentContainer) {