[FIR2IR] Generate IR for unbound symbols in allowNonCachedDeclarations mode

`allowNonCachedDeclarations` mode allows referring source declaration which
  does not belong to the set of sources passed to fir2ir (e.g. for debugger
  support). So if code refers to such declaration, fir2ir creates symbol
  for it but not the IR declaration itself
This commit is contained in:
Dmitriy Novozhilov
2023-11-15 14:08:11 +02:00
committed by Space Team
parent c9ff0c41fc
commit 3dff232710
19 changed files with 208 additions and 27 deletions
@@ -208,6 +208,12 @@ public class FirIdeNormalAnalysisLibrarySourceModuleCompilerFacilityTestGenerate
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/local.kt");
}
@Test
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localDelegatedProperty.kt");
}
@Test
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
@@ -262,6 +268,12 @@ public class FirIdeNormalAnalysisLibrarySourceModuleCompilerFacilityTestGenerate
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localMutated.kt");
}
@Test
@TestMetadata("localVariable.kt")
public void testLocalVariable() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localVariable.kt");
}
@Test
@TestMetadata("multipleClassAndFunctionContextReceivers.kt")
public void testMultipleClassAndFunctionContextReceivers() throws Exception {
@@ -208,6 +208,12 @@ public class FirIdeNormalAnalysisSourceModuleCompilerFacilityTestGenerated exten
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/local.kt");
}
@Test
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localDelegatedProperty.kt");
}
@Test
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
@@ -262,6 +268,12 @@ public class FirIdeNormalAnalysisSourceModuleCompilerFacilityTestGenerated exten
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localMutated.kt");
}
@Test
@TestMetadata("localVariable.kt")
public void testLocalVariable() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localVariable.kt");
}
@Test
@TestMetadata("multipleClassAndFunctionContextReceivers.kt")
public void testMultipleClassAndFunctionContextReceivers() throws Exception {
@@ -0,0 +1,3 @@
LocalDelegate[name: x; isMutated: true; displayText: x$delegate]
lvar x: R|kotlin/String|
R|kotlin/String|
@@ -0,0 +1,16 @@
MODULE_FRAGMENT
FILE fqName:<root> fileName:fragment.kt
CLASS CLASS name:CodeFragment modality:FINAL visibility:public superTypes:[kotlin.Any]
$this: VALUE_PARAMETER INSTANCE_RECEIVER name:<this> type:<root>.CodeFragment
CONSTRUCTOR visibility:public <> () returnType:<root>.CodeFragment [primary]
BLOCK_BODY
DELEGATING_CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in kotlin.Any'
FUN name:run visibility:public modality:FINAL <> (p0:kotlin.String) returnType:kotlin.String
VALUE_PARAMETER SHARED_VARIABLE_IN_EVALUATOR_FRAGMENT name:p0 index:0 type:kotlin.String [assignable]
EXPRESSION_BODY
BLOCK type=kotlin.String origin=null
SET_VAR 'p0: kotlin.String [assignable] declared in <root>.CodeFragment.run' type=kotlin.Unit origin=null
CONST String type=kotlin.String value="O"
CALL 'public final fun plus (other: kotlin.Any?): kotlin.String [operator] declared in kotlin.String' type=kotlin.String origin=PLUS
$this: GET_VAR 'p0: kotlin.String [assignable] declared in <root>.CodeFragment.run' type=kotlin.String origin=null
other: CONST String type=kotlin.String value="K"
@@ -0,0 +1,16 @@
import kotlin.reflect.KProperty
class Delegate(private var value: String) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value
}
}
fun test() {
var x by Delegate("a")
<caret>x
}
@@ -0,0 +1,5 @@
public final class CodeFragment {
// source: 'fragment.kt'
public method <init>(): void
public final static method run(p0: java.lang.String): java.lang.String
}
@@ -0,0 +1,3 @@
Local[name: x; isMutated: true; displayText: x]
lvar x: R|kotlin/String|
R|kotlin/String|
@@ -0,0 +1,16 @@
MODULE_FRAGMENT
FILE fqName:<root> fileName:fragment.kt
CLASS CLASS name:CodeFragment modality:FINAL visibility:public superTypes:[kotlin.Any]
$this: VALUE_PARAMETER INSTANCE_RECEIVER name:<this> type:<root>.CodeFragment
CONSTRUCTOR visibility:public <> () returnType:<root>.CodeFragment [primary]
BLOCK_BODY
DELEGATING_CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in kotlin.Any'
FUN name:run visibility:public modality:FINAL <> (p0:kotlin.String) returnType:kotlin.String
VALUE_PARAMETER SHARED_VARIABLE_IN_EVALUATOR_FRAGMENT name:p0 index:0 type:kotlin.String [assignable]
EXPRESSION_BODY
BLOCK type=kotlin.String origin=null
SET_VAR 'p0: kotlin.String [assignable] declared in <root>.CodeFragment.run' type=kotlin.Unit origin=null
CONST String type=kotlin.String value="O"
CALL 'public final fun plus (other: kotlin.Any?): kotlin.String [operator] declared in kotlin.String' type=kotlin.String origin=PLUS
$this: GET_VAR 'p0: kotlin.String [assignable] declared in <root>.CodeFragment.run' type=kotlin.String origin=null
other: CONST String type=kotlin.String value="K"
@@ -0,0 +1,4 @@
fun test() {
var x = "a"
<caret>x
}
@@ -0,0 +1,5 @@
public final class CodeFragment {
// source: 'fragment.kt'
public method <init>(): void
public final static method run(p0: java.lang.String): java.lang.String
}
@@ -84,6 +84,12 @@ public class CodeFragmentCapturingTestGenerated extends AbstractCodeFragmentCapt
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/local.kt");
}
@Test
@TestMetadata("localDelegatedProperty.kt")
public void testLocalDelegatedProperty() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localDelegatedProperty.kt");
}
@Test
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
@@ -138,6 +144,12 @@ public class CodeFragmentCapturingTestGenerated extends AbstractCodeFragmentCapt
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localMutated.kt");
}
@Test
@TestMetadata("localVariable.kt")
public void testLocalVariable() throws Exception {
runTest("analysis/analysis-api/testData/components/compilerFacility/compilation/codeFragments/capturing/localVariable.kt");
}
@Test
@TestMetadata("multipleClassAndFunctionContextReceivers.kt")
public void testMultipleClassAndFunctionContextReceivers() throws Exception {
@@ -122,6 +122,12 @@ class Fir2IrConverter(
declarationStorage.generateUnboundFakeOverrides()
}
if (configuration.allowNonCachedDeclarations) {
// See the comment to fillUnboundSymbols function itself
@OptIn(LeakedDeclarationCaches::class)
declarationStorage.fillUnboundSymbols()
}
evaluateConstants(irModuleFragment, components)
}
@@ -9,14 +9,12 @@ import org.jetbrains.kotlin.builtins.StandardNames.BUILT_INS_PACKAGE_FQ_NAMES
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.backend.generators.isExternalParent
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.builder.buildProperty
import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isStatic
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.descriptors.FirBuiltInsPackageFragment
import org.jetbrains.kotlin.fir.descriptors.FirModuleDescriptor
import org.jetbrains.kotlin.fir.java.symbols.FirJavaOverriddenSyntheticPropertySymbol
@@ -1177,22 +1175,75 @@ class Fir2IrDeclarationStorage(
for ((identifier, symbol) in irForFirSessionDependantDeclarationMap) {
if (symbol.isBound) continue
val (originalSymbol, dispatchReceiverLookupTag, _) = identifier
val irParent = findIrParent(originalSymbol.fir, dispatchReceiverLookupTag)
when (originalSymbol) {
is FirPropertySymbol -> createAndCacheIrProperty(
originalSymbol.fir,
irParent,
fakeOverrideOwnerLookupTag = dispatchReceiverLookupTag
)
generateDeclaration(originalSymbol, dispatchReceiverLookupTag)
}
}
is FirNamedFunctionSymbol -> createAndCacheIrFunction(
originalSymbol.fir,
irParent,
fakeOverrideOwnerLookupTag = dispatchReceiverLookupTag
)
/**
* This function iterates over all non f/o callable symbols created in declaration storage and binds all unbound symbols
*
* Usually all symbols are bound after fir2ir conversion is over, but it's not true for `allowNonCachedDeclarations`, when
* we convert to IR only part of sources from code fragments.
*
* ```
* // Original code
* fun foo(x: Int) {} // (1)
*
* fun bar() {
* 1.let { // (2)
* <context of code fragment>
* }
* }
*
* // Code fragment
* foo(this@let)
*
* Here in the body of the code fragment we reference function (1) and lambda (2), which leads to creation of their symbols,
* but not to generation of their IR. And since the original code won't be processed by fir2ir, we need to manually create
* IR for all symbols from it, to avoid publication of unbound symbols after fir2ri conversion is over
*
* Note that in the code fragment we may capture even local functions and lambdas, which are stored not in global caches,
* but in `localStorage`, which is getting cleared after leaving from corresponding scope. So to generate IR for them we need
* to call this function not only after fir2ir conversion, but also after leaving each local scope (see `leaveScope` function)
*/
@LeakedDeclarationCaches
internal fun fillUnboundSymbols() {
fillUnboundSymbols(functionCache)
fillUnboundSymbols(propertyCache)
}
else -> error("Unexpected declaration: $originalSymbol")
}
@LeakedDeclarationCaches
private fun fillUnboundSymbols(cache: Map<out FirCallableDeclaration, IrSymbol>) {
for ((firDeclaration, irSymbol) in cache) {
if (irSymbol.isBound) continue
generateDeclaration(firDeclaration.symbol, dispatchReceiverLookupTag = null)
}
}
private fun generateDeclaration(
originalSymbol: FirBasedSymbol<*>,
dispatchReceiverLookupTag: ConeClassLikeLookupTag?,
) {
val irParent = findIrParent(
originalSymbol.packageFqName(),
dispatchReceiverLookupTag ?: originalSymbol.getContainingClassSymbol(session)?.toLookupTag(),
originalSymbol,
originalSymbol.origin
)
when (originalSymbol) {
is FirPropertySymbol -> createAndCacheIrProperty(
originalSymbol.fir,
irParent,
fakeOverrideOwnerLookupTag = dispatchReceiverLookupTag
)
is FirNamedFunctionSymbol -> createAndCacheIrFunction(
originalSymbol.fir,
irParent,
fakeOverrideOwnerLookupTag = dispatchReceiverLookupTag
)
else -> error("Unexpected declaration: $originalSymbol")
}
}
@@ -1240,6 +1291,11 @@ class Fir2IrDeclarationStorage(
symbol is IrEnumEntrySymbol ||
symbol is IrScriptSymbol
) {
if (configuration.allowNonCachedDeclarations) {
// See KDoc to `fillUnboundSymbols` function
@OptIn(LeakedDeclarationCaches::class)
fillUnboundSymbols(localStorage.lastCache.localFunctions)
}
localStorage.leaveCallable()
}
symbolTable.leaveScope(symbol)
@@ -19,6 +19,10 @@ class Fir2IrLocalCallableStorage {
cacheStack += Fir2IrScopeCache()
}
@LeakedDeclarationCaches
val lastCache: Fir2IrScopeCache
get() = cacheStack.last()
fun leaveCallable() {
cacheStack.last().clear()
cacheStack.removeAt(cacheStack.size - 1)
@@ -17,6 +17,9 @@ class Fir2IrScopeCache {
private val localFunctionCache = mutableMapOf<FirFunction, IrSimpleFunctionSymbol>()
val localFunctions: Map<FirFunction, IrSimpleFunctionSymbol>
get() = localFunctionCache
private val delegatedPropertyCache = mutableMapOf<FirProperty, IrLocalDelegatedPropertySymbol>()
fun getParameter(parameter: FirValueParameter): IrValueParameterSymbol? {
@@ -464,15 +464,6 @@ abstract class FirVisibilityChecker : FirSessionComponent {
return false
}
protected fun FirBasedSymbol<*>.packageFqName(): FqName {
return when (this) {
is FirClassLikeSymbol<*> -> classId.packageFqName
is FirPropertyAccessorSymbol -> propertySymbol.packageFqName()
is FirCallableSymbol<*> -> callableId.packageName
else -> error("No package fq name for $this")
}
}
}
val FirSession.moduleVisibilityChecker: FirModuleVisibilityChecker? by FirSession.nullableSessionComponentAccessor()
@@ -20,6 +20,10 @@ import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.*
import org.jetbrains.kotlin.fir.types.impl.*
@@ -273,3 +277,12 @@ val FirExpression.isStatementLikeExpression: Boolean
private val FirExpression.isIndexedAssignment: Boolean
get() = this is FirBlock && statements.lastOrNull()?.source?.kind == KtFakeSourceElementKind.ImplicitUnit.IndexedAssignmentCoercion
fun FirBasedSymbol<*>.packageFqName(): FqName {
return when (this) {
is FirClassLikeSymbol<*> -> classId.packageFqName
is FirPropertyAccessorSymbol -> propertySymbol.packageFqName()
is FirCallableSymbol<*> -> callableId.packageName
else -> error("No package fq name for $this")
}
}