diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/diagnosticProvider/FirIdeNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/diagnosticProvider/FirIdeNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java new file mode 100644 index 00000000000..5e85f0b5db9 --- /dev/null +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/diagnosticProvider/FirIdeNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java @@ -0,0 +1,66 @@ +/* + * 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.analysis.api.fir.test.cases.generated.cases.components.diagnosticProvider; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.analysis.api.fir.test.configurators.AnalysisApiFirTestConfiguratorFactory; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfiguratorFactoryData; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisSessionMode; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiMode; +import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.diagnosticProvider.AbstractCodeFragmentCollectDiagnosticsTest; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics") +@TestDataPath("$PROJECT_ROOT") +public class FirIdeNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated extends AbstractCodeFragmentCollectDiagnosticsTest { + @NotNull + @Override + public AnalysisApiTestConfigurator getConfigurator() { + return AnalysisApiFirTestConfiguratorFactory.INSTANCE.createConfigurator( + new AnalysisApiTestConfiguratorFactoryData( + FrontendKind.Fir, + TestModuleKind.Source, + AnalysisSessionMode.Normal, + AnalysisApiMode.Ide + ) + ); + } + + @Test + public void testAllFilesPresentInCodeFragmentDiagnostics() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.kt"); + } + + @Test + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.kt"); + } + + @Test + @TestMetadata("typeMismatch.kt") + public void testTypeMismatch() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.kt"); + } +} diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/diagnosticProvider/AbstractCodeFragmentCollectDiagnosticsTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/diagnosticProvider/AbstractCodeFragmentCollectDiagnosticsTest.kt new file mode 100644 index 00000000000..bcc1f7b4f02 --- /dev/null +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/diagnosticProvider/AbstractCodeFragmentCollectDiagnosticsTest.kt @@ -0,0 +1,36 @@ +/* + * 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.analysis.api.impl.base.test.cases.components.diagnosticProvider + +import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import java.io.File + +abstract class AbstractCodeFragmentCollectDiagnosticsTest : AbstractCollectDiagnosticsTest() { + override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) { + val contextElement = testServices.expressionMarkerProvider.getElementOfTypeAtCaret(ktFile) + + val fragmentText = module.files.single().originalFile + .run { File(parent, "$nameWithoutExtension.fragment.$extension") } + .readText() + + val isBlockFragment = fragmentText.any { it == '\n' } + + val project = ktFile.project + val factory = KtPsiFactory(project, markGenerated = false) + + val codeFragment = when { + isBlockFragment -> factory.createBlockCodeFragment(fragmentText, contextElement) + else -> factory.createExpressionCodeFragment(fragmentText, contextElement) + } + + super.doTestByFileStructure(codeFragment, module, testServices) + } +} \ No newline at end of file diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/diagnosticProvider/FirStandaloneNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/diagnosticProvider/FirStandaloneNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java new file mode 100644 index 00000000000..e605e55cd2e --- /dev/null +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/diagnosticProvider/FirStandaloneNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated.java @@ -0,0 +1,66 @@ +/* + * 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.analysis.api.standalone.fir.test.cases.generated.cases.components.diagnosticProvider; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.analysis.api.standalone.fir.test.AnalysisApiFirStandaloneModeTestConfiguratorFactory; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfiguratorFactoryData; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisSessionMode; +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiMode; +import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.diagnosticProvider.AbstractCodeFragmentCollectDiagnosticsTest; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics") +@TestDataPath("$PROJECT_ROOT") +public class FirStandaloneNormalAnalysisSourceModuleCodeFragmentCollectDiagnosticsTestGenerated extends AbstractCodeFragmentCollectDiagnosticsTest { + @NotNull + @Override + public AnalysisApiTestConfigurator getConfigurator() { + return AnalysisApiFirStandaloneModeTestConfiguratorFactory.INSTANCE.createConfigurator( + new AnalysisApiTestConfiguratorFactoryData( + FrontendKind.Fir, + TestModuleKind.Source, + AnalysisSessionMode.Normal, + AnalysisApiMode.Standalone + ) + ); + } + + @Test + public void testAllFilesPresentInCodeFragmentDiagnostics() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.kt"); + } + + @Test + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.kt"); + } + + @Test + @TestMetadata("typeMismatch.kt") + public void testTypeMismatch() throws Exception { + runTest("analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.kt"); + } +} diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.fragment.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.fragment.kt new file mode 100644 index 00000000000..c603f335787 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.fragment.kt @@ -0,0 +1,6 @@ +class Foo { + val a = x + val b = y +} + +Foo().b \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.kt new file mode 100644 index 00000000000..81652f5e8b9 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.kt @@ -0,0 +1,4 @@ +fun test() { + val x = 0 + val y = 0 +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.txt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.txt new file mode 100644 index 00000000000..d427d7ad242 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localClass.txt @@ -0,0 +1,4 @@ +Diagnostics from elements: + for PSI element of type KtNameReferenceExpression at (3,13-14) + UNRESOLVED_REFERENCE text ranges: [(38,39)] + PSI: KtNameReferenceExpression at (3,13-14) diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.fragment.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.fragment.kt new file mode 100644 index 00000000000..5734fb0000b --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.fragment.kt @@ -0,0 +1,5 @@ +fun foo(): Pair { + return x to y +} + +foo() \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.kt new file mode 100644 index 00000000000..77ca48738cc --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.kt @@ -0,0 +1,6 @@ +// WITH_STDLIB + +fun test() { + val x = 0 + val y = 0 +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.txt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.txt new file mode 100644 index 00000000000..ca03c1f05b4 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/localFunction.txt @@ -0,0 +1,4 @@ +Diagnostics from elements: + for PSI element of type KtNameReferenceExpression at (2,17-18) + UNRESOLVED_REFERENCE text ranges: [(44,45)] + PSI: KtNameReferenceExpression at (2,17-18) diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.fragment.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.fragment.kt new file mode 100644 index 00000000000..f2765deb7ba --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.fragment.kt @@ -0,0 +1 @@ +foo(x) \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.kt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.kt new file mode 100644 index 00000000000..23cbc219000 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.kt @@ -0,0 +1,6 @@ +fun test() { + val x = 0 + val y = 1 +} + +fun foo(a: String) {} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.txt b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.txt new file mode 100644 index 00000000000..ab4e1d10ce0 --- /dev/null +++ b/analysis/analysis-api/testData/components/diagnosticsProvider/codeFragmentDiagnostics/typeMismatch.txt @@ -0,0 +1,4 @@ +Diagnostics from elements: + for PSI element of type KtNameReferenceExpression at (1,5-6) + ARGUMENT_TYPE_MISMATCH text ranges: [(4,5)] + PSI: KtNameReferenceExpression at (1,5-6) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt index 9ac2c0a5792..4741a29c227 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt @@ -108,7 +108,7 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List { + is FirScript, is FirCodeFragment -> { return emptyList() } @@ -270,7 +270,7 @@ fun FirElementWithResolveState.tryCollectDesignation(): FirDesignation? = fun FirElementWithResolveState.tryCollectDesignationWithFile(): FirDesignationWithFile? { return when (this) { - is FirScript, is FirFileAnnotationsContainer -> { + is FirScript, is FirCodeFragment, is FirFileAnnotationsContainer -> { val firFile = getContainingFile() ?: return null FirDesignationWithFile(path = emptyList(), this, firFile) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/LLFirDiagnosticVisitor.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/LLFirDiagnosticVisitor.kt index 556351bca76..37c14516eaf 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/LLFirDiagnosticVisitor.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/LLFirDiagnosticVisitor.kt @@ -5,13 +5,21 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.resolveToFirSymbol import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled +import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider +import org.jetbrains.kotlin.analysis.utils.printer.parentsOfType import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContextForProvider import org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorComponents +import org.jetbrains.kotlin.fir.declarations.FirCodeFragment import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.psi +import org.jetbrains.kotlin.psi.KtCodeFragment +import org.jetbrains.kotlin.psi.KtDeclaration internal open class LLFirDiagnosticVisitor( context: CheckerContextForProvider, @@ -44,6 +52,34 @@ internal open class LLFirDiagnosticVisitor( } } + override fun visitCodeFragment(codeFragment: FirCodeFragment, data: Nothing?) { + val contextElement = (codeFragment.source.psi as? KtCodeFragment)?.context + if (contextElement != null) { + fun process(containingSymbols: List) { + if (containingSymbols.isEmpty()) { + super.visitCodeFragment(codeFragment, data) + } else { + withDeclaration(containingSymbols.first()) { + process(containingSymbols.subList(1, containingSymbols.size)) + } + } + } + + val project = contextElement.project + val module = ProjectStructureProvider.getModule(project, contextElement, contextualModule = null) + val resolveSession = module.getFirResolveSession(project) + + // Register containing declarations of a context element + contextElement.parentsOfType().toList().asReversed() + .map { it.resolveToFirSymbol(resolveSession).fir } + .run(::process) + + return + } + + super.visitCodeFragment(codeFragment, data) + } + /** * Some FirClassChecker may report diagnostics on class member headers. * That diagnostics should be suppressed if we have a `@Suppress` annotation on class member. diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt index 4cbaad75efc..48b7746b9e2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt @@ -16,7 +16,6 @@ import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorV import org.jetbrains.kotlin.fir.containingClass import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.classId -import org.jetbrains.kotlin.fir.renderWithType import org.jetbrains.kotlin.fir.resolve.SessionHolder import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry @@ -67,7 +66,7 @@ internal object PersistenceContextCollector { is FirCallableDeclaration -> declaration.symbol.callableId.isLocal is FirDanglingModifierList -> declaration.containingClass()?.classId?.isLocal == true is FirAnonymousInitializer -> declaration.containingClass().classId.isLocal - is FirScript -> false + is FirScript, is FirCodeFragment -> false else -> errorWithAttachment("Unsupported declaration ${declaration::class}") { withFirEntry("declaration", declaration) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt index 03f6af76514..60270d4e844 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirElementBuilder.kt @@ -262,7 +262,8 @@ internal fun PsiElement.getNonLocalContainingOrThisDeclaration(predicate: (KtDec !notNullCandidate.isPartOf(parent) || parent is KtClassInitializer || parent is KtObjectLiteralExpression || - parent is KtCallElement + parent is KtCallElement || + parent is KtCodeFragment ) { // Candidate turned out to be local. Let's find another one. candidate = null diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirFileBuilder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirFileBuilder.kt index 769194fdc8d..81470fdeba2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirFileBuilder.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirFileBuilder.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment import org.jetbrains.kotlin.fir.builder.BodyBuildingMode import org.jetbrains.kotlin.fir.builder.PsiRawFirBuilder import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtFile /** diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructure.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructure.kt index e3a08759468..68efc89368d 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructure.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructure.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.isAncestor import java.util.concurrent.ConcurrentHashMap import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.canBePartOfParentDeclaration +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.codeFragment import org.jetbrains.kotlin.fir.correspondingProperty import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment @@ -189,6 +190,13 @@ internal class FileStructure private constructor( } private fun createStructureElement(container: KtElement): FileStructureElement = when { + container is KtCodeFragment -> { + val firCodeFragment = firFile.codeFragment + firCodeFragment.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE) + + val timestamp = container.modificationStamp + ReanalyzableCodeFragmentStructureElement(firFile, container, firCodeFragment.symbol, timestamp, moduleComponents) + } container is KtFile -> { val firFile = moduleComponents.firFileBuilder.buildRawFirFileWithCaching(ktFile) firFile.lazyResolveToPhase(FirResolvePhase.IMPORTS) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt index 1a6bd018dd1..d15ca34383b 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FileStructureElement.kt @@ -9,6 +9,9 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiErrorElement import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDesignation import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.ClassDiagnosticRetriever import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.FileDiagnosticRetriever import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.FileStructureElementDiagnostics @@ -20,7 +23,9 @@ import org.jetbrains.kotlin.fir.correspondingProperty import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirErrorConstructor import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor +import org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCodeFragmentSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase @@ -106,12 +111,12 @@ internal class KtToFirMapping(firElement: FirElement, recorder: FirElementsRecor } } -internal sealed class ReanalyzableStructureElement>( +internal sealed class ReanalyzableStructureElement>( firFile: FirFile, val firSymbol: S, moduleComponents: LLFirModuleResolveComponents, ) : FileStructureElement(firFile, moduleComponents) { - abstract override val psi: KtDeclaration + abstract override val psi: KtAnnotated abstract val timestamp: Long /** @@ -132,6 +137,45 @@ internal sealed class ReanalyzableStructureElement(firFile, firSymbol, moduleComponents) { + override val mappings = KtToFirMapping(firSymbol.fir, recorder) + + override fun reanalyze(): ReanalyzableStructureElement { + val originalCodeFragment = firSymbol.fir + val originalDesignation = originalCodeFragment.collectDesignation() + + firFile.transformDeclarations(object : FirTransformer() { + override fun transformElement(element: E, data: Nothing?): E = element + + override fun transformCodeFragment(codeFragment: FirCodeFragment, data: Nothing?): FirCodeFragment { + return RawFirNonLocalDeclarationBuilder.build( + session = originalCodeFragment.moduleData.session, + scopeProvider = originalCodeFragment.moduleData.session.kotlinScopeProvider, + designation = originalDesignation, + rootNonLocalDeclaration = psi, + ) as FirCodeFragment + } + }, null) + + val newCodeFragment = firFile.codeFragment + newCodeFragment.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE) + + return ReanalyzableCodeFragmentStructureElement( + firFile, + psi, + newCodeFragment.symbol, + psi.modificationStamp, + moduleComponents + ) + } +} + internal class ReanalyzableFunctionStructureElement( firFile: FirFile, override val psi: KtNamedFunction, diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt index 61ce9a285db..868fe2fdd88 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/inBlockModification.kt @@ -8,9 +8,10 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure import com.intellij.openapi.application.ApplicationManager import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirInternals import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirFile import org.jetbrains.kotlin.analysis.low.level.api.fir.api.resolveToFirSymbol +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.codeFragment import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries -import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider import org.jetbrains.kotlin.fir.contracts.impl.FirEmptyContractDescription import org.jetbrains.kotlin.fir.declarations.* @@ -21,23 +22,44 @@ import org.jetbrains.kotlin.fir.expressions.FirLazyExpression import org.jetbrains.kotlin.fir.expressions.builder.buildLazyBlock import org.jetbrains.kotlin.fir.expressions.builder.buildLazyExpression import org.jetbrains.kotlin.fir.expressions.impl.FirContractCallBlock +import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement + +@Deprecated( + "Temporarily left for binary compatibility. Use invalidateAfterInBlockModification(KtElement) instead.", + replaceWith = ReplaceWith("invalidateAfterInBlockModification(declaration)", "org.jetbrains.kotlin.psi.KtElement"), + level = DeprecationLevel.HIDDEN, +) +@LLFirInternals +@Suppress("unused") +fun invalidateAfterInBlockModification(declaration: KtDeclaration): Boolean { + return invalidateAfterInBlockModification(declaration as KtElement) +} /** * Must be called in a write action. * @return **false** if it is not in-block modification */ @LLFirInternals -fun invalidateAfterInBlockModification(declaration: KtDeclaration): Boolean { +fun invalidateAfterInBlockModification(declaration: KtElement): Boolean { ApplicationManager.getApplication().assertIsWriteThread() val project = declaration.project val ktModule = ProjectStructureProvider.getModule(project, declaration, contextualModule = null) val resolveSession = ktModule.getFirResolveSession(project) - return when (val firDeclaration = declaration.resolveToFirSymbol(resolveSession).fir) { + + val firDeclaration = when (declaration) { + is KtCodeFragment -> declaration.getOrBuildFirFile(resolveSession).codeFragment + is KtDeclaration -> declaration.resolveToFirSymbol(resolveSession).fir + else -> errorWithFirSpecificEntries("Unexpected declaration kind: ${declaration.javaClass.simpleName}", psi = declaration) + } + + return when (firDeclaration) { is FirSimpleFunction -> firDeclaration.inBodyInvalidation() is FirPropertyAccessor -> firDeclaration.inBodyInvalidation() is FirProperty -> firDeclaration.inBodyInvalidation() + is FirCodeFragment -> firDeclaration.inBodyInvalidation() else -> errorWithFirSpecificEntries("Unknown declaration with body", fir = firDeclaration, psi = declaration) } } @@ -145,6 +167,17 @@ private fun FirPropertyAccessor.inBodyInvalidation(): Boolean { return true } +private fun FirCodeFragment.inBodyInvalidation(): Boolean { + if (block is FirLazyBlock) { + return true + } + + decreasePhase(FirResolvePhase.BODY_RESOLVE.previous) + replaceBlock(buildLazyBlock()) + + return true +} + private fun FirProperty.invalidateInitializer(): PropertyExpressionState = replaceWithLazyExpressionIfNeeded(::initializer, ::replaceInitializer) private fun FirProperty.invalidateDelegate(): PropertyExpressionState = replaceWithLazyExpressionIfNeeded(::delegate, ::replaceDelegate) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/FirLazyBodiesCalculator.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/FirLazyBodiesCalculator.kt index 6016f62ff9f..f5c41b8d983 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/FirLazyBodiesCalculator.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/FirLazyBodiesCalculator.kt @@ -87,7 +87,7 @@ private inline fun revive( session = session, scopeProvider = session.kotlinScopeProvider, designation = designation, - rootNonLocalDeclaration = psiFactory(designation) as KtDeclaration, + rootNonLocalDeclaration = psiFactory(designation) as KtElement, ) as T } @@ -265,6 +265,14 @@ private fun needCalculatingLazyBodyForProperty(firProperty: FirProperty): Boolea || firProperty.delegate is FirLazyExpression || firProperty.getExplicitBackingField()?.initializer is FirLazyExpression +private fun calculateLazyBodyForCodeFragment(designation: FirDesignation) { + val codeFragment = designation.target as FirCodeFragment + require(codeFragment.block is FirLazyBlock) + + val newCodeFragment = revive(designation) + codeFragment.replaceBlock(newCodeFragment.block) +} + private enum class FirLazyAnnotationTransformerScope { ALL_ANNOTATIONS, COMPILER_ONLY; @@ -472,4 +480,13 @@ private abstract class FirLazyBodiesCalculatorTransformer : FirTransformer): FirCodeFragment { + if (codeFragment.block is FirLazyBlock) { + val designation = FirDesignation(data, codeFragment) + calculateLazyBodyForCodeFragment(designation) + } + + return codeFragment + } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/RawFirNonLocalDeclarationBuilder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/RawFirNonLocalDeclarationBuilder.kt index 432b1dc8d55..adcd5bc1ab7 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/RawFirNonLocalDeclarationBuilder.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/lazy/resolve/RawFirNonLocalDeclarationBuilder.kt @@ -36,7 +36,7 @@ internal class RawFirNonLocalDeclarationBuilder private constructor( session: FirSession, baseScopeProvider: FirScopeProvider, private val originalDeclaration: FirDeclaration, - private val declarationToBuild: KtDeclaration, + private val declarationToBuild: KtElement, private val functionsToRebind: Set? = null, private val replacementApplier: RawFirReplacement.Applier? = null, ) : PsiRawFirBuilder(session, baseScopeProvider, bodyBuildingMode = BodyBuildingMode.NORMAL) { @@ -58,24 +58,16 @@ internal class RawFirNonLocalDeclarationBuilder private constructor( replacement: RawFirReplacement?, ): FirDeclaration { val replacementApplier = replacement?.Applier() - val builder = RawFirNonLocalDeclarationBuilder( - session = session, - baseScopeProvider = scopeProvider, - originalDeclaration = designation.target as FirDeclaration, - declarationToBuild = rootNonLocalDeclaration, - replacementApplier = replacementApplier - ) - builder.context.packageFqName = rootNonLocalDeclaration.containingKtFile.packageFqName - return builder.moveNext(designation.path.iterator(), containingClass = null).also { - replacementApplier?.ensureApplied() - } + val result = build(session, scopeProvider, designation, rootNonLocalDeclaration, replacementApplier = replacementApplier) + replacementApplier?.ensureApplied() + return result } fun buildWithFunctionSymbolRebind( session: FirSession, scopeProvider: FirScopeProvider, designation: FirDesignation, - rootNonLocalDeclaration: KtDeclaration, + rootNonLocalDeclaration: KtElement, ): FirDeclaration { val functionsToRebind = when (val originalDeclaration = designation.target) { is FirFunction -> setOf(originalDeclaration) @@ -83,12 +75,26 @@ internal class RawFirNonLocalDeclarationBuilder private constructor( else -> null } + return build(session, scopeProvider, designation, rootNonLocalDeclaration, functionsToRebind) + } + + fun build( + session: FirSession, + scopeProvider: FirScopeProvider, + designation: FirDesignation, + rootNonLocalDeclaration: KtElement, + functionsToRebind: Set? = null, + replacementApplier: RawFirReplacement.Applier? = null + ): FirDeclaration { + check(rootNonLocalDeclaration is KtDeclaration || rootNonLocalDeclaration is KtCodeFragment) + val builder = RawFirNonLocalDeclarationBuilder( session = session, baseScopeProvider = scopeProvider, originalDeclaration = designation.target as FirDeclaration, declarationToBuild = rootNonLocalDeclaration, functionsToRebind = functionsToRebind, + replacementApplier = replacementApplier ) builder.context.packageFqName = rootNonLocalDeclaration.containingKtFile.packageFqName return builder.moveNext(designation.path.iterator(), containingClass = null) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirAnnotationArgumentsLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirAnnotationArgumentsLazyResolver.kt index d77ac737f3d..057e09a75e5 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirAnnotationArgumentsLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirAnnotationArgumentsLazyResolver.kt @@ -162,7 +162,7 @@ internal fun LLFirAbstractBodyTargetResolver.transformAnnotations(target: FirEle target.transformSingle(transformer, ResolutionMode.ContextIndependent) } - target is FirFile -> {} + target is FirCodeFragment || target is FirFile -> {} else -> throwUnexpectedFirElementError(target) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt index f65d245fd19..a44452e4d99 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt @@ -5,11 +5,18 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.transformers +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirWholeFileResolveTarget +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.asResolveTarget import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError +import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirLockProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.FirLazyBodiesCalculator +import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData +import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirResolvableResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkDelegatedConstructorIsResolved import org.jetbrains.kotlin.analysis.low.level.api.fir.util.* import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry @@ -25,19 +32,28 @@ import org.jetbrains.kotlin.fir.expressions.builder.buildLazyDelegatedConstructo import org.jetbrains.kotlin.fir.expressions.builder.buildMultiDelegatedConstructorCall import org.jetbrains.kotlin.fir.expressions.impl.FirContractCallBlock import org.jetbrains.kotlin.fir.expressions.impl.FirLazyDelegatedConstructorCall +import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirSuperReference import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.references.builder.buildExplicitSuperReference import org.jetbrains.kotlin.fir.references.builder.buildExplicitThisReference +import org.jetbrains.kotlin.fir.resolve.FirCodeFragmentContext import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl +import org.jetbrains.kotlin.fir.resolve.codeFragmentContext import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirResolveContextCollector import org.jetbrains.kotlin.fir.resolve.transformers.contracts.FirContractsDslNames import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForClass +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.psi.KtCodeFragment +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.utils.exceptions.buildErrorWithAttachment import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment import org.jetbrains.kotlin.utils.findIsInstanceAnd @@ -100,6 +116,14 @@ private class LLFirBodyTargetResolver( calculateControlFlowGraph(target) } + return true + } + is FirCodeFragment -> { + resolveCodeFragmentContext(target) + performCustomResolveUnderLock(target) { + resolve(target, BodyStateKeepers.CODE_FRAGMENT) + } + return true } } @@ -136,9 +160,57 @@ private class LLFirBodyTargetResolver( } } + private fun resolveCodeFragmentContext(firCodeFragment: FirCodeFragment) { + val ktCodeFragment = firCodeFragment.psi as? KtCodeFragment + ?: errorWithAttachment("Code fragment source not found") { + withFirEntry("firCodeFragment", firCodeFragment) + } + + val module = firCodeFragment.llFirModuleData.ktModule + val resolveSession = module.getFirResolveSession(ktCodeFragment.project) as LLFirResolvableResolveSession + + val contextPsiElement = ktCodeFragment.context + val contextKtFile = contextPsiElement?.containingFile as? KtFile + + firCodeFragment.codeFragmentContext = if (contextKtFile != null) { + val contextFirFile = resolveSession.getOrBuildFirFile(contextKtFile) + val target = computeCodeFragmentContextResolveTarget(contextFirFile, contextPsiElement) + + val contextParentKtElements = contextPsiElement.parentsWithSelf.toSet() + + val contextProvider = ContextCollector.process(target, transformer.components) { candidate -> + when (candidate) { + contextPsiElement -> ContextCollector.FilterResponse.STOP + in contextParentKtElements -> ContextCollector.FilterResponse.CONTINUE + else -> ContextCollector.FilterResponse.SKIP + } + } + + val elementContext = contextProvider[contextPsiElement, ContextCollector.ContextKind.BODY] + ?: contextParentKtElements.firstNotNullOf { contextProvider[it, ContextCollector.ContextKind.SELF] } + + LLFirCodeFragmentContext(elementContext.towerDataContext, elementContext.smartCasts) + } else { + val towerDataContext = FirTowerDataContext() + LLFirCodeFragmentContext(towerDataContext, emptyMap()) + } + } + + private fun computeCodeFragmentContextResolveTarget(contextFirFile: FirFile, contextPsiElement: PsiElement): LLFirResolveTarget { + val contextKtDeclaration = contextPsiElement.getNonLocalContainingOrThisDeclaration() + if (contextKtDeclaration != null) { + val designationPath = FirElementFinder.collectDesignationPath(contextFirFile, contextKtDeclaration) + if (designationPath != null) { + return FirDesignationWithFile(designationPath.path, designationPath.target, contextFirFile).asResolveTarget() + } + } + + return LLFirWholeFileResolveTarget(contextFirFile) + } + override fun doLazyResolveUnderLock(target: FirElementWithResolveState) { when (target) { - is FirRegularClass -> error("Should have been resolved in ${::doResolveWithoutLock.name}") + is FirRegularClass, is FirCodeFragment -> error("Should have been resolved in ${::doResolveWithoutLock.name}") is FirConstructor -> resolve(target, BodyStateKeepers.CONSTRUCTOR) is FirFunction -> resolve(target, BodyStateKeepers.FUNCTION) is FirProperty -> resolve(target, BodyStateKeepers.PROPERTY) @@ -209,6 +281,10 @@ internal object BodyStateKeepers { ) } + val CODE_FRAGMENT: StateKeeper = stateKeeper { _, _ -> + add(FirCodeFragment::block, FirCodeFragment::replaceBlock, ::blockGuard) + } + val ANONYMOUS_INITIALIZER: StateKeeper = stateKeeper { _, _ -> add(FirAnonymousInitializer::body, FirAnonymousInitializer::replaceBody, ::blockGuard) add(FirAnonymousInitializer::controlFlowGraphReference, FirAnonymousInitializer::replaceControlFlowGraphReference) @@ -409,4 +485,9 @@ private fun requireSameSize(old: List, new: List) { new.forEachIndexed { index, statement -> withFirEntry("statement$index", statement) } } } -} \ No newline at end of file +} + +private class LLFirCodeFragmentContext( + override val towerDataContext: FirTowerDataContext, + override val variables: Map, Set> +) : FirCodeFragmentContext \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt index 490aa6d6df4..7d492f9ba6f 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt @@ -103,9 +103,14 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver( override fun doResolveWithoutLock(target: FirElementWithResolveState): Boolean { if (target is FirFile) return false - when { - target is FirRegularClass || target is FirScript || target.isRegularDeclarationWithAnnotation -> {} - else -> throwUnexpectedFirElementError(target) + + when (target) { + is FirRegularClass, is FirScript, is FirCodeFragment -> {} + else -> { + if (!target.isRegularDeclarationWithAnnotation) { + throwUnexpectedFirElementError(target) + } + } } requireIsInstance(target) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirContractsLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirContractsLazyResolver.kt index f33240a03ea..97feec45bf2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirContractsLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirContractsLazyResolver.kt @@ -65,6 +65,7 @@ private class LLFirContractsTargetResolver( is FirAnonymousInitializer, is FirFile, is FirScript, + is FirCodeFragment, is FirFileAnnotationsContainer, is FirDanglingModifierList, -> { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt index 81dd024e9f0..c577797ca1c 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt @@ -78,6 +78,7 @@ internal class LLFirImplicitBodyTargetResolver( is FirRegularClass, is FirTypeAlias, is FirFile, + is FirCodeFragment, is FirAnonymousInitializer, is FirDanglingModifierList, is FirFileAnnotationsContainer, diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt index 32f40700398..223b5c6b44e 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt @@ -131,7 +131,7 @@ private class LLFirTypeTargetResolver( is FirRegularClass -> { resolveClassTypes(target) } - is FirFile, is FirAnonymousInitializer -> {} + is FirFile, is FirAnonymousInitializer, is FirCodeFragment -> {} else -> errorWithAttachment("Unknown declaration ${target::class}") { withFirEntry("declaration", target) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt index 359fea5b4da..d7bfcd053ac 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt @@ -105,6 +105,15 @@ internal object FirElementFinder { continue } + subDeclaration is FirCodeFragment -> { + val codeFragmentDeclarations = subDeclaration.block.statements.asSequence().filterIsInstance() + if (find(codeFragmentDeclarations.asIterable(), classIdPathIndex)) { + return true + } + + continue + } + subDeclaration is FirRegularClass && currentClassSegment == subDeclaration.symbol.name -> { path += subDeclaration if (find(subDeclaration.declarations, classIdPathIndex + 1)) { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/containingFileUtils.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/containingFileUtils.kt index 914034689c4..d208bcdc166 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/containingFileUtils.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/containingFileUtils.kt @@ -26,7 +26,7 @@ fun FirElementWithResolveState.getContainingFile(): FirFile? { is FirCallableDeclaration -> provider.getFirCallableContainerFile(symbol) is FirClassLikeDeclaration -> provider.getFirClassifierContainerFileIfAny(symbol) is FirAnonymousInitializer -> containingClass().getContainingFile() - is FirDanglingModifierList -> { + is FirDanglingModifierList, is FirCodeFragment -> { val ktFile = psi?.containingFile as? KtFile ?: error("File for dangling modifier list cannot be null") val moduleComponents = llFirResolvableSession?.moduleComponents diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt index 913a99348d6..a22ebabdbd9 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/declarationUtils.kt @@ -196,6 +196,12 @@ private fun KtClassLikeDeclaration.findFir(provider: FirProvider): FirClassLikeD } } +val FirFile.codeFragment: FirCodeFragment + get() { + return declarations.singleOrNull() as? FirCodeFragment + ?: errorWithFirSpecificEntries("Code fragment not found in a FirFile", fir = this) + } + val FirDeclaration.isGeneratedDeclaration get() = realPsi == null diff --git a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/analysisApi.kt b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/analysisApi.kt index eebc36b3f36..cd3aa991441 100644 --- a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/analysisApi.kt +++ b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/analysisApi.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.compile import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.containingDeclarationProvider.AbstractContainingDeclarationProviderByDelegatedMemberScopeTest import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.containingDeclarationProvider.AbstractContainingDeclarationProviderByMemberScopeTest import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.containingDeclarationProvider.AbstractContainingDeclarationProviderByPsiTest +import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.diagnosticProvider.AbstractCodeFragmentCollectDiagnosticsTest import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.diagnosticProvider.AbstractCollectDiagnosticsTest import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.expressionInfoProvider.AbstractIsUsedAsExpressionTest import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.expressionInfoProvider.AbstractReturnTargetSymbolTest @@ -265,6 +266,10 @@ private fun AnalysisApiTestGroup.generateAnalysisApiComponentsTests() { test(AbstractCollectDiagnosticsTest::class) { model("diagnostics") } + + test(AbstractCodeFragmentCollectDiagnosticsTest::class, filter = frontendIs(FrontendKind.Fir)) { + model("codeFragmentDiagnostics", pattern = TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME) + } } // for K1, symbols do not have a proper equality implementation, so the tests are failing