From f903cb6bbc89dbade28cb7a3801e329dcdf1654d Mon Sep 17 00:00:00 2001 From: Marco Pennekamp Date: Thu, 13 Apr 2023 14:23:40 +0200 Subject: [PATCH] [JVM IR] KTIJ-25152 Reference undiscovered `expect` symbols - When the bytecode tool window opens a file containing `actual` declarations, not all the `expect` symbols reachable via the `actual` symbols' descriptors might be contained in the symbol table, because the source of the `expect` members is not included in the files to compile. This caused an issue during lowering in `ExpectDeclarationRemover.tryCopyDefaultArguments`. - The solution adds `expect` symbols reachable via `actual` declarations to the symbol table, so that these `expect` symbols can subsequently be stubbed. ^KTIJ-25152 fixed --- .../kotlin/backend/jvm/JvmIrCodegenFactory.kt | 6 ++ .../backend/jvm/UndiscoveredExpectUtils.kt | 88 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/UndiscoveredExpectUtils.kt diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt index 6aeaf4ec167..7d6441e105d 100644 --- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt @@ -98,11 +98,13 @@ open class JvmIrCodegenFactory( /** * @param shouldStubOrphanedExpectSymbols See [stubOrphanedExpectSymbols]. + * @param shouldReferenceUndiscoveredExpectSymbols See [referenceUndiscoveredExpectSymbols]. * @param shouldDeduplicateBuiltInSymbols See [SymbolTableWithBuiltInsDeduplication]. */ data class IdeCodegenSettings( val shouldStubAndNotLinkUnboundSymbols: Boolean = false, val shouldStubOrphanedExpectSymbols: Boolean = false, + val shouldReferenceUndiscoveredExpectSymbols: Boolean = false, val shouldDeduplicateBuiltInSymbols: Boolean = false, ) @@ -251,6 +253,10 @@ open class JvmIrCodegenFactory( listOf(irLinker, stubGeneratorForMissingClasses) } + if (ideCodegenSettings.shouldReferenceUndiscoveredExpectSymbols) { + symbolTable.referenceUndiscoveredExpectSymbols(input.files, input.bindingContext) + } + val irModuleFragment = psi2ir.generateModuleFragment( psi2irContext, diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/UndiscoveredExpectUtils.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/UndiscoveredExpectUtils.kt new file mode 100644 index 00000000000..3c7f16cf899 --- /dev/null +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/UndiscoveredExpectUtils.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.jvm + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.MemberDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.ir.util.referenceFunction +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.multiplatform.findCompatibleExpectsForActual +import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice + +/** + * [referenceUndiscoveredExpectSymbols] ensures that `actual` symbols declared in any of the given [files] have an associated `expect` + * symbol in the symbol table. During lowering, an `expect` symbol may be referenced from an `actual` symbol via _descriptors_, so unbound + * symbols may occur if the `expect` symbol isn't otherwise included in the symbol table and stubbed before lowering. + * + * Undiscovered `expect` symbols are not normally an issue when the source code containing the `expect` declarations is contained in the + * files to compile, but in cases such as the IDE bytecode tool window, such a source file won't be included in [files]. + */ +internal fun SymbolTable.referenceUndiscoveredExpectSymbols(files: Collection, bindingContext: BindingContext) { + val visitor = UndiscoveredExpectVisitor(this, bindingContext) + files.forEach(visitor::visitKtFile) +} + +@OptIn(ObsoleteDescriptorBasedAPI::class) +class UndiscoveredExpectVisitor( + private val symbolTable: SymbolTable, + private val bindingContext: BindingContext, +) : KtTreeVisitorVoid() { + override fun visitClassOrObject(classOrObject: KtClassOrObject) { + super.visitClassOrObject(classOrObject) + symbolTable.referenceClass(classOrObject.findExpectForActual(BindingContext.CLASS) ?: return) + } + + override fun visitTypeAlias(typeAlias: KtTypeAlias) { + super.visitTypeAlias(typeAlias) + symbolTable.referenceTypeAlias(typeAlias.findExpectForActual(BindingContext.TYPE_ALIAS) ?: return) + } + + override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) { + super.visitPrimaryConstructor(constructor) + referenceConstructor(constructor) + } + + override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) { + super.visitSecondaryConstructor(constructor) + referenceConstructor(constructor) + } + + private fun > referenceConstructor(constructor: KtConstructor) { + symbolTable.referenceFunction(constructor.findExpectForActual(BindingContext.CONSTRUCTOR) ?: return) + } + + override fun visitNamedFunction(function: KtNamedFunction) { + super.visitNamedFunction(function) + symbolTable.referenceFunction(function.findExpectForActual(BindingContext.FUNCTION) ?: return) + } + + override fun visitProperty(property: KtProperty) { + super.visitProperty(property) + symbolTable.referenceProperty( + property.findExpectForActualOfType(BindingContext.VARIABLE) ?: return + ) + } + + override fun visitPropertyAccessor(accessor: KtPropertyAccessor) { + super.visitPropertyAccessor(accessor) + symbolTable.referenceFunction(accessor.findExpectForActual(BindingContext.PROPERTY_ACCESSOR) ?: return) + } + + private inline fun K.findExpectForActual( + bindingContextKey: ReadOnlySlice, + ): V? = findExpectForActualOfType(bindingContextKey) + + private inline fun K.findExpectForActualOfType( + bindingContextKey: ReadOnlySlice, + ): D? { + val descriptor = (bindingContext[bindingContextKey, this] as? D)?.takeIf { it.isActual } ?: return null + return descriptor.findCompatibleExpectsForActual().singleOrNull() as? D + } +}