[K/JS] feat: add logic under the flag for strict implicit export generating inside d.ts files.

This commit is contained in:
Artem Kobzar
2022-08-09 16:48:59 +00:00
committed by Space
parent 8fcbe9902c
commit 87038e7d8a
23 changed files with 641 additions and 25 deletions
@@ -207,6 +207,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
)
var generateDts: Boolean by FreezableVar(false)
@Argument(
value = "-Xstrict-implicit-export-types",
description = "Generate strict types for implicitly exported entities inside d.ts files. Available in IR backend only."
)
var strictImplicitExportType: Boolean by FreezableVar(false)
@GradleOption(DefaultValues.BooleanTrueDefault::class)
@Argument(value = "-Xtyped-arrays", description = "Translate primitive arrays to JS typed arrays")
var typedArrays: Boolean by FreezableVar(true)
@@ -503,6 +503,9 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
configuration.put(JSConfigurationKeys.FRIEND_PATHS_DISABLED, arguments.friendModulesDisabled)
configuration.put(JSConfigurationKeys.GENERATE_STRICT_IMPLICIT_EXPORT, arguments.strictImplicitExportType)
val friendModules = arguments.friendModules
if (!arguments.friendModulesDisabled && friendModules != null) {
val friendPaths = friendModules
@@ -311,6 +311,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
val jsFunAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsFun"))
val jsImplicitExportAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsImplicitExport"))
// TODO move CharSequence-related stiff to IntrinsifyCallsLowering
val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence")))
val charSequenceLengthPropertyGetterSymbol by context.lazy2 {
@@ -798,6 +798,12 @@ private val escapedIdentifiersLowering = makeBodyLoweringPhase(
description = "Convert global variables with invalid names access to globalThis member expression"
)
private val implicitlyExportedDeclarationsMarkingLowering = makeDeclarationTransformerPhase(
::ImplicitlyExportedDeclarationsMarkingLowering,
name = "ImplicitlyExportedDeclarationsMarkingLowering",
description = "Add @JsImplicitExport annotation to declarations which are not exported but are used inside other exported declarations as a type"
)
private val cleanupLoweringPhase = makeBodyLoweringPhase(
{ CleanupLowering() },
name = "CleanupLowering",
@@ -920,6 +926,7 @@ val loweringList = listOf<Lowering>(
captureStackTraceInThrowablesPhase,
callsLoweringPhase,
escapedIdentifiersLowering,
implicitlyExportedDeclarationsMarkingLowering,
cleanupLoweringPhase,
// Currently broken due to static members lowering making single-open-class
// files non-recognizable as single-class files
@@ -61,7 +61,6 @@ class ExportedProperty(
val irSetter: IrFunction? = null,
) : ExportedDeclaration()
// TODO: Cover all cases with frontend and disable error declarations
class ErrorDeclaration(val message: String) : ExportedDeclaration()
@@ -22,6 +22,8 @@ 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.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@@ -53,7 +55,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? {
val candidate = getExportCandidate(declaration) ?: return null
if (!shouldDeclarationBeExported(candidate, context)) return null
if (!shouldDeclarationBeExportedImplicitlyOrExplicitly(candidate, context)) return null
return when (candidate) {
is IrSimpleFunction -> exportFunction(candidate)
@@ -74,9 +76,10 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
}
private fun exportFunction(function: IrSimpleFunction): ExportedDeclaration? {
return when (val exportability = functionExportability(function)) {
is Exportability.NotNeeded -> null
is Exportability.NotNeeded, is Exportability.Implicit -> null
is Exportability.Prohibited -> ErrorDeclaration(exportability.reason)
is Exportability.Allowed -> {
val parent = function.parent
@@ -201,18 +204,44 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
}
if (klass.isJsImplicitExport()) {
return Exportability.Implicit
}
if (klass.isSingleFieldValueClass)
return Exportability.Prohibited("Inline class ${klass.fqNameWhenAvailable}")
return Exportability.Allowed
}
private fun exportDeclarationImplicitly(klass: IrClass, superTypes: Iterable<IrType>): ExportedDeclaration {
val typeParameters = klass.typeParameters.map(::exportTypeParameter)
val superInterfaces = superTypes
.filter { (it.classifierOrFail.owner as? IrDeclaration)?.isExportedImplicitlyOrExplicitly(context) ?: false }
.map { exportType(it) }
.filter { it !is ExportedType.ErrorType }
val name = klass.getExportedIdentifier()
val (members, nestedClasses) = exportClassDeclarations(klass, superTypes)
return ExportedRegularClass(
name = name,
isInterface = true,
isAbstract = false,
superClasses = emptyList(),
superInterfaces = superInterfaces,
typeParameters = typeParameters,
members = members,
nestedClasses = nestedClasses,
ir = klass
)
}
private fun exportOrdinaryClass(klass: IrClass, superTypes: Iterable<IrType>): ExportedDeclaration? {
when (val exportability = classExportability(klass)) {
is Exportability.Prohibited -> error(exportability.reason)
is Exportability.NotNeeded -> return null
Exportability.Allowed -> {
}
Exportability.NotNeeded -> return null
Exportability.Implicit -> return exportDeclarationImplicitly(klass, superTypes)
Exportability.Allowed -> {}
}
val (members, nestedClasses) = exportClassDeclarations(klass, superTypes)
@@ -228,9 +257,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
private fun exportEnumClass(klass: IrClass, superTypes: Iterable<IrType>): ExportedDeclaration? {
when (val exportability = classExportability(klass)) {
is Exportability.Prohibited -> error(exportability.reason)
is Exportability.NotNeeded -> return null
Exportability.Allowed -> {
}
Exportability.NotNeeded -> return null
Exportability.Implicit -> return exportDeclarationImplicitly(klass, superTypes)
Exportability.Allowed -> {}
}
val enumEntries = klass
@@ -267,10 +296,12 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
): ExportedClassDeclarationsInfo {
val members = mutableListOf<ExportedDeclaration>()
val nestedClasses = mutableListOf<ExportedClass>()
val isImplicitlyExportedClass = klass.isJsImplicitExport()
for (declaration in klass.declarations) {
val candidate = getExportCandidate(declaration) ?: continue
if (!shouldDeclarationBeExported(candidate, context)) continue
if (isImplicitlyExportedClass && candidate !is IrClass) continue
if (!shouldDeclarationBeExportedImplicitlyOrExplicitly(candidate, context)) continue
val processingResult = specialProcessing(candidate)
if (processingResult != null) {
@@ -324,13 +355,13 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
private fun IrClass.shouldNotBeImplemented(): Boolean {
return isInterface && !isExternal
return isInterface && !isExternal || isJsImplicitExport()
}
private fun IrClass.shouldContainImplementationOfMagicProperty(superTypes: Iterable<IrType>): Boolean {
return !isExternal && superTypes.any {
val superClass = it.classOrNull?.owner ?: return@any false
superClass.isInterface && superClass.isExported(context)
superClass.isInterface && superClass.isExported(context) || superClass.isJsImplicitExport()
}
}
@@ -339,11 +370,13 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
private fun MutableList<ExportedDeclaration>.addMagicPropertyForInterfaceImplementation(klass: IrClass, superTypes: Iterable<IrType>) {
val superTypesToInheritanceMagicProperty = superTypes.filter { it.shouldAddMagicPropertyOfSuper() }
val allSuperTypesWithMagicProperty = superTypes.filter { it.shouldAddMagicPropertyOfSuper() }
if (superTypesToInheritanceMagicProperty.isEmpty()) return
if (allSuperTypesWithMagicProperty.isEmpty()) {
return
}
val intersectionOfTypes = superTypesToInheritanceMagicProperty
val intersectionOfTypes = allSuperTypesWithMagicProperty
.map { ExportedType.PropertyType(exportType(it), ExportedType.LiteralType.StringLiteralType(magicPropertyName)) }
.reduce(ExportedType::IntersectionType)
.let { if (klass.shouldNotBeImplemented()) ExportedType.IntersectionType(klass.generateTagType(), it) else it }
@@ -356,8 +389,11 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
private fun IrClass.isOwnMagicPropertyAdded(): Boolean {
if (isJsImplicitExport()) return true
if (!isExported(context)) return false
return isInterface && !isExternal || superTypes.any { it.classOrNull?.owner?.isOwnMagicPropertyAdded() ?: false }
return isInterface && !isExternal || superTypes.any {
it.classOrNull?.owner?.isOwnMagicPropertyAdded() == true
}
}
private fun IrClass.generateTagType(): ExportedType {
@@ -388,7 +424,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
.filter { it !is ExportedType.ErrorType }
val superInterfaces = superTypes
.filter { it.classifierOrFail.isInterface }
.filter { it.shouldPresentInsideImplementsClause() }
.map { exportType(it, false) }
.filter { it !is ExportedType.ErrorType }
@@ -419,9 +455,14 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
}
private fun IrSimpleType.collectSuperTransitiveHierarchy() =
private fun IrSimpleType.collectSuperTransitiveHierarchy(): Set<IrType> =
transitiveExportCollector.collectSuperTypesTransitiveHierarchyFor(this)
private fun IrType.shouldPresentInsideImplementsClause(): Boolean {
val classifier = classifierOrFail
return classifier.isInterface || (classifier.owner as? IrDeclaration)?.isJsImplicitExport() == true
}
private fun exportAsEnumMember(
candidate: IrDeclarationWithName,
enumEntriesToOrdinal: Map<IrEnumEntry, Int>
@@ -460,7 +501,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
}
private fun IrType.canBeUsedAsSuperTypeOfExportedClasses(): Boolean =
!this.isAny() && classifierOrNull != context.irBuiltIns.enumClass
!isAny() &&
classifierOrNull != context.irBuiltIns.enumClass &&
(classifierOrNull?.owner as? IrDeclaration)?.isJsImplicitExport() != true
private fun exportTypeArgument(type: IrTypeArgument): ExportedType {
if (type is IrTypeProjection)
@@ -541,7 +584,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
classifier is IrClassSymbol -> {
val klass = classifier.owner
val isExported = klass.isExported(context)
val isExported = klass.isExportedImplicitlyOrExplicitly(context)
val isImplicitlyExported = !isExported && !klass.isExternal
val isNonExportedExternal = klass.isExternal && !isExported
val name = klass.getFqNameWithJsNameWhenAvailable(!isNonExportedExternal && generateNamespacesForPackages).asString()
@@ -630,6 +673,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
sealed class Exportability {
object Allowed : Exportability()
object NotNeeded : Exportability()
object Implicit : Exportability()
class Prohibited(val reason: String) : Exportability()
}
@@ -670,6 +714,10 @@ private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithNam
return declaration
}
private fun shouldDeclarationBeExportedImplicitlyOrExplicitly(declaration: IrDeclarationWithName, context: JsIrBackendContext): Boolean {
return declaration.isJsImplicitExport() || shouldDeclarationBeExported(declaration, context)
}
private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName, context: JsIrBackendContext): Boolean {
if (context.additionalExportedDeclarationNames.contains(declaration.fqNameWhenAvailable))
return true
@@ -727,6 +775,11 @@ fun IrDeclaration.isExported(context: JsIrBackendContext): Boolean {
return shouldDeclarationBeExported(candidate, context)
}
fun IrDeclaration.isExportedImplicitlyOrExplicitly(context: JsIrBackendContext): Boolean {
val candidate = getExportCandidate(this) ?: return false
return shouldDeclarationBeExportedImplicitlyOrExplicitly(candidate, context)
}
private fun DescriptorVisibility.toExportedVisibility() =
when (this) {
DescriptorVisibilities.PROTECTED -> ExportedVisibility.PROTECTED
@@ -12,6 +12,10 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.backend.js.utils.isJsImplicitExport
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.superTypes
private typealias SubstitutionMap = Map<IrTypeParameterSymbol, IrType>
@@ -34,13 +38,26 @@ class TransitiveExportCollector(val context: JsIrBackendContext) {
.toSet()
}
private fun IrSimpleType.findNearestExportedClass(typeSubstitutionMap: SubstitutionMap): IrSimpleType? {
val classifier = classifier as? IrClassSymbol ?: return null
if (classifier.owner.isExported(context)) return substitute(typeSubstitutionMap) as IrSimpleType
return classifier.superTypes()
.firstNotNullOfOrNull { (it as? IrSimpleType)?.findNearestExportedClass(typeSubstitutionMap) }
}
private fun IrSimpleType.collectTransitiveHierarchy(typeSubstitutionMap: SubstitutionMap): Set<IrType> {
val owner = classifier.owner as? IrClass ?: return emptySet()
val substitutionMap = calculateTypeSubstitutionMap(typeSubstitutionMap)
return when {
isBuiltInClass(owner) || isStdLibClass(owner) -> emptySet()
owner.isExported(context) -> setOf(substitute(typeSubstitutionMap))
owner.isExported(context) -> setOf(substitute(substitutionMap))
owner.isJsImplicitExport() -> setOfNotNull(
substitute(typeSubstitutionMap),
takeIf { !owner.isInterface }?.findNearestExportedClass(substitutionMap)
)
else -> collectSuperTypesTransitiveHierarchy(substitutionMap)
}
}
@@ -62,4 +79,4 @@ class TransitiveExportCollector(val context: JsIrBackendContext) {
}
private data class ClassWithAppliedArguments(val classSymbol: IrClassSymbol, val appliedArguments: List<IrTypeArgument>)
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2021 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.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.isJsImplicitExport
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
class ImplicitlyExportedDeclarationsMarkingLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val strictImplicitExport = context.configuration.getBoolean(JSConfigurationKeys.GENERATE_STRICT_IMPLICIT_EXPORT)
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!strictImplicitExport || !declaration.isExported(context)) return null
val implicitlyExportedDeclarations = when (declaration) {
is IrFunction -> declaration.collectImplicitlyExportedDeclarations()
is IrClass -> declaration.collectImplicitlyExportedDeclarations()
is IrProperty -> declaration.collectImplicitlyExportedDeclarations()
else -> emptySet()
}
implicitlyExportedDeclarations.forEach { it.markWithJsImplicitExport() }
return null
}
private fun IrClass.collectImplicitlyExportedDeclarations(): Set<IrDeclaration> {
return typeParameters
.flatMap { it.superTypes }.toSet()
.flatMap { it.collectImplicitlyExportedDeclarations() }.toSet()
}
private fun IrFunction.collectImplicitlyExportedDeclarations(): Set<IrDeclaration> {
val types = buildSet {
add(returnType)
addAll(valueParameters.map { it.type })
addAll(typeParameters.flatMap { it.superTypes })
}
return types.flatMap { it.collectImplicitlyExportedDeclarations() }.toSet()
}
private fun IrProperty.collectImplicitlyExportedDeclarations(): Set<IrDeclaration> {
val getterImplicitlyExportedDeclarations = getter?.collectImplicitlyExportedDeclarations() ?: emptySet()
val setterImplicitlyExportedDeclarations = setter?.collectImplicitlyExportedDeclarations() ?: emptySet()
val fieldImplicitlyExportedDeclarations = backingField?.type?.collectImplicitlyExportedDeclarations() ?: emptySet()
return getterImplicitlyExportedDeclarations + setterImplicitlyExportedDeclarations + fieldImplicitlyExportedDeclarations
}
private fun IrType.collectImplicitlyExportedDeclarations(): Set<IrDeclaration> {
if (this is IrDynamicType || this !is IrSimpleType)
return emptySet()
val nonNullType = makeNotNull() as IrSimpleType
val classifier = nonNullType.classifier
return when {
nonNullType.isPrimitiveType() || nonNullType.isPrimitiveArray() || nonNullType.isAny() || nonNullType.isUnit() -> emptySet()
classifier is IrTypeParameterSymbol -> classifier.owner.superTypes.flatMap { it.collectImplicitlyExportedDeclarations() }
.toSet()
classifier is IrClassSymbol -> setOfNotNull(classifier.owner.takeIf { it.shouldBeMarkedWithImplicitExport() })
else -> emptySet()
}
}
private fun IrDeclaration.shouldBeMarkedWithImplicitExport(): Boolean {
return this is IrClass && !isExternal && !isExported(context) && !isJsImplicitExport()
}
private fun IrDeclaration.markWithJsImplicitExport() {
val jsImplicitExportCtor = context.intrinsics.jsImplicitExportAnnotationSymbol.constructors.single()
annotations += context.createIrBuilder(symbol).irCall(jsImplicitExportCtor)
parentClassOrNull?.takeIf { it.shouldBeMarkedWithImplicitExport() }?.markWithJsImplicitExport()
}
}
@@ -23,6 +23,7 @@ object JsAnnotations {
val jsNameFqn = FqName("kotlin.js.JsName")
val jsQualifierFqn = FqName("kotlin.js.JsQualifier")
val jsExportFqn = FqName("kotlin.js.JsExport")
val jsImplicitExportFqn = FqName("kotlin.js.JsImplicitExport")
val jsNativeGetter = FqName("kotlin.js.nativeGetter")
val jsNativeSetter = FqName("kotlin.js.nativeSetter")
val jsNativeInvoke = FqName("kotlin.js.nativeInvoke")
@@ -34,6 +35,10 @@ object JsAnnotations {
fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
@Suppress("UNCHECKED_CAST")
fun IrConstructorCall.getSingleConstBooleanArgument() =
(getValueArgument(0) as IrConst<Boolean>).value
fun IrAnnotationContainer.getJsModule(): String? =
getAnnotation(JsAnnotations.jsModuleFqn)?.getSingleConstStringArgument()
@@ -58,6 +63,9 @@ fun IrAnnotationContainer.getJsFunAnnotation(): String? =
fun IrAnnotationContainer.isJsExport(): Boolean =
hasAnnotation(JsAnnotations.jsExportFqn)
fun IrAnnotationContainer.isJsImplicitExport(): Boolean =
hasAnnotation(JsAnnotations.jsImplicitExportFqn)
fun IrAnnotationContainer.isJsNativeGetter(): Boolean = hasAnnotation(JsAnnotations.jsNativeGetter)
fun IrAnnotationContainer.isJsNativeSetter(): Boolean = hasAnnotation(JsAnnotations.jsNativeSetter)
+1
View File
@@ -38,6 +38,7 @@ where advanced options include:
-Xmetadata-only Generate *.meta.js and *.kjsm files only
-Xpartial-linkage Allow unlinked symbols
-Xrepositories=<path> Paths to additional places where libraries could be found
-Xstrict-implicit-export-types Generate strict types for implicitly exported entities inside d.ts files. Available in IR backend only.
-Xtyped-arrays Translate primitive arrays to JS typed arrays
-Xwasm Use experimental WebAssembly compiler backend
-Xwasm-debug-info Add debug info to WebAssembly compiled module
@@ -117,6 +117,11 @@ object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() {
applicability = DirectiveApplicability.Global
)
val GENERATE_STRICT_IMPLICIT_EXPORT by directive(
description = "enable strict implicitly exported entities types inside d.ts files",
applicability = DirectiveApplicability.Global
)
val SAFE_EXTERNAL_BOOLEAN by directive(
description = "",
applicability = DirectiveApplicability.Global
@@ -125,6 +125,11 @@ fun TargetPlatform.platformToEnvironmentConfigFiles() = when {
fun createCompilerConfiguration(module: TestModule, configurators: List<AbstractEnvironmentConfigurator>): CompilerConfiguration {
val configuration = CompilerConfiguration()
configuration[CommonConfigurationKeys.MODULE_NAME] = module.name
if (JsEnvironmentConfigurationDirectives.GENERATE_STRICT_IMPLICIT_EXPORT in module.directives) {
configuration.put(JSConfigurationKeys.GENERATE_STRICT_IMPLICIT_EXPORT, true)
}
if (module.frontendKind == FrontendKinds.FIR) {
configuration[CommonConfigurationKeys.USE_FIR] = true
}