[LL FIR] dependent analyze: support different numbers of statements

Previously, the different number of statements has led to
some unexpected behavior. E.g., the resulting script can have
duplicated declarations – one from the original script instead of
some statement and one from the copy

^KT-60987
This commit is contained in:
Dmitrii Gridin
2023-08-10 15:37:20 +02:00
committed by Space Team
parent 4f4b30ee28
commit b0b2e76e13
3 changed files with 98 additions and 27 deletions
@@ -72,15 +72,26 @@ object LowLevelFirApiFacadeForResolveOnAir {
fake.originalDeclaration = original
}
/**
* We assume that [targetDeclaration] should have the same number of declarations
*
* @see restoreOriginalDeclarationsInScript
*/
if (targetDeclaration is KtScript && originalDeclaration is KtScript) {
originalDeclaration.blockExpression.statements.zip(targetDeclaration.blockExpression.statements) { original, fake ->
if (original is KtDeclaration && fake is KtDeclaration) {
fake.originalDeclaration = original
}
val originalStatements = originalDeclaration.declarationSequence()
val newStatements = targetDeclaration.declarationSequence()
originalStatements.zip(newStatements) { original, fake ->
fake.originalDeclaration = original
}
}
}
private fun KtScript.declarationSequence(): Sequence<KtDeclaration> {
val sequence = blockExpression.statements.asSequence().filter { it !is KtScriptInitializer && it is KtDeclaration }
@Suppress("UNCHECKED_CAST")
return sequence as Sequence<KtDeclaration>
}
fun <T : KtElement> onAirResolveElement(
firResolveSession: LLFirResolveSession,
place: T,
@@ -338,11 +349,7 @@ object LowLevelFirApiFacadeForResolveOnAir {
* We shouldn't touch script declarations because they're independent of a script
*/
if (newFirDeclaration is FirScript && originalFirDeclaration is FirScript) {
val newStatements = originalFirDeclaration.statements.zip(newFirDeclaration.statements).map { (original, copied) ->
original.takeUnless(FirStatement::isScriptStatement) ?: copied
}
newFirDeclaration.replaceStatements(newStatements)
restoreOriginalDeclarationsInScript(originalScript = originalFirDeclaration, newScript = newFirDeclaration)
}
session.moduleComponents.firModuleLazyDeclarationResolver.runLazyDesignatedOnAirResolve(
@@ -354,6 +361,59 @@ object LowLevelFirApiFacadeForResolveOnAir {
return newFirDeclaration
}
/**
* We assume that [newScript] has the same declarations as [originalScript]
*/
private fun restoreOriginalDeclarationsInScript(originalScript: FirScript, newScript: FirScript) {
val updatedStatements = ArrayList<FirStatement>(newScript.statements.size)
val originalDeclarations = originalScript.statements.iterator()
for (recreatedStatement in newScript.statements) {
updatedStatements += if (recreatedStatement.isScriptStatement) {
recreatedStatement
} else {
originalDeclarations.nextDeclaration() ?: scriptDeclarationInconsistencyError(originalScript, newScript)
}
}
val nextDeclaration = originalDeclarations.nextDeclaration()
if (nextDeclaration != null) {
scriptDeclarationInconsistencyError(originalScript, newScript)
}
newScript.replaceStatements(updatedStatements)
}
private fun scriptDeclarationInconsistencyError(originalScript: FirScript, newScript: FirScript): Nothing {
val originalDeclarations = originalScript.statements.filterNot(FirStatement::isScriptStatement)
val newDeclarations = newScript.statements.filterNot(FirStatement::isScriptStatement)
errorWithAttachment("New script has ${if (newDeclarations.size > originalDeclarations.size) "more" else "less"} declarations") {
withFirEntry("originalScript", originalScript)
withFirEntry("newScript", newScript)
withEntryGroup("originalDeclarations") {
for ((index, declaration) in originalDeclarations.withIndex()) {
withFirEntry(index.toString(), declaration)
}
}
withEntryGroup("newDeclarations") {
for ((index, declaration) in newDeclarations.withIndex()) {
withFirEntry(index.toString(), declaration)
}
}
}
}
private fun Iterator<FirStatement>.nextDeclaration(): FirStatement? {
while (hasNext()) {
val statement = next()
if (statement.isScriptStatement) continue
return statement
}
return null
}
private fun computeDesignation(originalDeclaration: KtElement, originalFirFile: FirFile): FirElementFinder.FirDeclarationDesignation? {
if (originalDeclaration is KtCodeFragment) {
val firCodeFragment = originalFirFile.codeFragment
@@ -16,11 +16,12 @@ Tower Data Context:
Element 7
Scope: FirScriptDeclarationsScope
Functions
FirNamedFunctionSymbol public? final? fun foo(i: Int, action: ( (Int) -> Unit )): R|kotlin/Unit|
FirNamedFunctionSymbol public? final? fun foo(i: Int, action: ( (Int) -> Unit )): R|kotlin/Unit|
FirNamedFunctionSymbol public? final? fun scriptFunction(): <implicit>
FirNamedFunctionSymbol public? final? fun unusedScriptFunction(p: String): <implicit>
FirNamedFunctionSymbol public? final? fun unusedScriptFunction(p: String): <implicit>
FirNamedFunctionSymbol public final fun foo(i: R|kotlin/Int|, action: R|(kotlin/Int) -> kotlin/Unit|): R|kotlin/Unit|
FirNamedFunctionSymbol public final fun scriptFunction(): R|kotlin/Int|
FirNamedFunctionSymbol public final fun unusedScriptFunction(p: R|kotlin/String|): R|kotlin/Int|
Properties:
FirPropertySymbol public final val $$result: R|kotlin/Int|
public get(): R|kotlin/Int|
Element 8
Scope: FirLocalScope
Properties:
@@ -31,10 +32,13 @@ Tower Data Context:
SCRIPT:
lval args: R|kotlin/Array<kotlin/String>|
public? final? fun scriptFunction(): <implicit>
public? final? fun unusedScriptFunction(p: String): <implicit>
public? final? fun unusedScriptFunction(p: String): <implicit>
public? final? fun foo(i: Int, action: ( (Int) -> Unit )): R|kotlin/Unit|
public? final? fun foo(i: Int, action: ( (Int) -> Unit )): R|kotlin/Unit|
public final fun scriptFunction(): R|kotlin/Int|
R|/scriptFunction|()
public final fun unusedScriptFunction(p: R|kotlin/String|): R|kotlin/Int|
R|/scriptFunction|()
public final fun foo(i: R|kotlin/Int|, action: R|(kotlin/Int) -> kotlin/Unit|): R|kotlin/Unit|
R|/foo|(R|/scriptFunction|(), <L> = foo@fun <anonymous>(it: R|kotlin/Int|): R|kotlin/Unit| <inline=NoInline> )
public final val $$result: R|kotlin/Int|
public get(): R|kotlin/Int|
Type: kotlin/script/templates/standard/ScriptTemplateWithArgs
Label: <script>
@@ -7,16 +7,23 @@ context(<script>@R|kotlin/script/templates/standard/ScriptTemplateWithArgs|)
SCRIPT: [ResolvedTo(BODY_RESOLVE)]
[ResolvedTo(BODY_RESOLVE)] lval args: R|kotlin/Array<kotlin/String>|
public? final? [ResolvedTo(RAW_FIR)] fun scriptFunction(): <implicit> { LAZY_BLOCK }
public? final? [ResolvedTo(RAW_FIR)] fun unusedScriptFunction([ResolvedTo(RAW_FIR)] p: String): <implicit> { LAZY_BLOCK }
public? final? [ResolvedTo(RAW_FIR)] fun unusedScriptFunction([ResolvedTo(RAW_FIR)] p: String): <implicit> {
^unusedScriptFunction IntegerLiteral(22)
public final [ResolvedTo(IMPLICIT_TYPES_BODY_RESOLVE)] fun scriptFunction(): R|kotlin/Int| {
^scriptFunction Int(42)
}
public? final? [ResolvedTo(RAW_FIR)] fun foo([ResolvedTo(RAW_FIR)] i: Int, [ResolvedTo(RAW_FIR)] action: ( (Int) -> Unit )): R|kotlin/Unit| { LAZY_BLOCK }
R|/scriptFunction|()
public final [ResolvedTo(IMPLICIT_TYPES_BODY_RESOLVE)] fun unusedScriptFunction([ResolvedTo(IMPLICIT_TYPES_BODY_RESOLVE)] p: R|kotlin/String|): R|kotlin/Int| {
^unusedScriptFunction Int(22)
}
public? final? [ResolvedTo(RAW_FIR)] fun foo([ResolvedTo(RAW_FIR)] i: Int, [ResolvedTo(RAW_FIR)] action: ( (Int) -> Unit )): R|kotlin/Unit| {
R|/scriptFunction|()
public final [ResolvedTo(CONTRACTS)] fun foo([ResolvedTo(CONTRACTS)] i: R|kotlin/Int|, [ResolvedTo(CONTRACTS)] action: R|(kotlin/Int) -> kotlin/Unit|): R|kotlin/Unit| {
action#(i#)
}
R|/foo|(R|/scriptFunction|(), <L> = [ResolvedTo(BODY_RESOLVE)] [MatchingParameterFunctionTypeKey=kotlin/Function1<kotlin/Int, kotlin/Unit>] foo@fun <anonymous>([ResolvedTo(BODY_RESOLVE)] it: R|kotlin/Int|): R|kotlin/Unit| <inline=NoInline> {
R|/scriptFunction|()
}
)
public final [ResolvedTo(BODY_RESOLVE)] val $$result: R|kotlin/Int| = R|/unusedScriptFunction|(String(str))
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|