diff --git a/platform/ml-impl/resources/META-INF/ml.xml b/platform/ml-impl/resources/META-INF/ml.xml
index 9baa148ed76d..0bd706b54600 100644
--- a/platform/ml-impl/resources/META-INF/ml.xml
+++ b/platform/ml-impl/resources/META-INF/ml.xml
@@ -16,7 +16,11 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporter.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporter.kt
new file mode 100644
index 000000000000..f2a92aa81f19
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporter.kt
@@ -0,0 +1,19 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness
+
+import com.intellij.lang.Language
+import com.intellij.platform.ml.impl.correctness.autoimport.ImportFixer
+import com.intellij.platform.ml.impl.correctness.checker.CorrectnessChecker
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+interface MLCompletionCorrectnessSupporter {
+ val correctnessChecker: CorrectnessChecker
+ val importFixer: ImportFixer
+
+ companion object {
+ fun getInstance(language: Language): MLCompletionCorrectnessSupporter? {
+ return MLCompletionCorrectnessSupporterEP.EP_NAME.lazySequence().firstOrNull { it.language == language.id }?.instance
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterBase.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterBase.kt
new file mode 100644
index 000000000000..78f2d45d2f9f
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterBase.kt
@@ -0,0 +1,12 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness
+
+import com.intellij.platform.ml.impl.correctness.autoimport.ImportFixer
+import com.intellij.platform.ml.impl.correctness.checker.CorrectnessCheckerBase
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+abstract class MLCompletionCorrectnessSupporterBase : MLCompletionCorrectnessSupporter {
+ override val correctnessChecker = CorrectnessCheckerBase()
+ override val importFixer: ImportFixer = ImportFixer.EMPTY
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterEP.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterEP.kt
new file mode 100644
index 000000000000..45521152aa03
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/MLCompletionCorrectnessSupporterEP.kt
@@ -0,0 +1,44 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness
+
+import com.intellij.openapi.extensions.CustomLoadingExtensionPointBean
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.extensions.RequiredElement
+import com.intellij.util.KeyedLazyInstance
+import com.intellij.util.xmlb.annotations.Attribute
+import org.jetbrains.annotations.ApiStatus
+import org.jetbrains.annotations.TestOnly
+
+@ApiStatus.Internal
+class MLCompletionCorrectnessSupporterEP : CustomLoadingExtensionPointBean,
+ KeyedLazyInstance {
+ @RequiredElement
+ @Attribute("language")
+ var language: String? = null
+
+ @RequiredElement
+ @Attribute("implementationClass")
+ var implementationClass: String? = null
+
+ @Suppress("unused")
+ constructor() : super()
+
+ @Suppress("unused")
+ @TestOnly
+ constructor(
+ language: String?,
+ implementationClass: String?,
+ instance: MLCompletionCorrectnessSupporter,
+ ) : super(instance) {
+ this.language = language
+ this.implementationClass = implementationClass
+ }
+
+ override fun getImplementationClassName(): String? = this.implementationClass
+ override fun getKey(): String = language!!
+
+ companion object {
+ val EP_NAME: ExtensionPointName = ExtensionPointName.create(
+ "com.intellij.mlCompletionCorrectnessSupporter")
+ }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/ImportFixer.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/ImportFixer.kt
new file mode 100644
index 000000000000..2846e333369f
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/ImportFixer.kt
@@ -0,0 +1,48 @@
+package com.intellij.platform.ml.impl.correctness.autoimport
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.readAction
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.progress.blockingContextToIndicator
+import com.intellij.openapi.project.DumbService
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiFile
+import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
+import com.intellij.util.concurrency.annotations.RequiresBlockingContext
+import com.intellij.util.concurrency.annotations.RequiresReadLock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+interface ImportFixer {
+ @RequiresReadLock
+ @RequiresBackgroundThread
+ @RequiresBlockingContext
+ fun runAutoImport(file: PsiFile, editor: Editor, suggestionRange: TextRange)
+
+ object EMPTY : ImportFixer {
+ override fun runAutoImport(file: PsiFile, editor: Editor, suggestionRange: TextRange) {}
+ }
+}
+
+@RequiresBlockingContext
+@ApiStatus.Internal
+fun ImportFixer.runAutoImportAsync(scope: CoroutineScope, file: PsiFile, editor: Editor, suggestionRange: TextRange) {
+ val autoImportAction = {
+ if (!DumbService.getInstance(file.project).isDumb) {
+ runAutoImport(file, editor, suggestionRange)
+ }
+ }
+
+ if (ApplicationManager.getApplication().isUnitTestMode) {
+ autoImportAction()
+ }
+ else {
+ scope.launch {
+ readAction {
+ blockingContextToIndicator(autoImportAction)
+ }
+ }
+ }
+}
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/InspectionBasedImportFixer.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/InspectionBasedImportFixer.kt
new file mode 100644
index 000000000000..929f055d6c7a
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/autoimport/InspectionBasedImportFixer.kt
@@ -0,0 +1,67 @@
+package com.intellij.platform.ml.impl.correctness.autoimport
+
+import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator
+import com.intellij.codeInspection.InspectionEngine
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.LocalQuickFixOnPsiElement
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ex.LocalInspectionToolWrapper
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.SyntaxTraverser
+import com.intellij.util.PairProcessor
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+abstract class InspectionBasedImportFixer : ImportFixer {
+
+ protected abstract fun getAutoImportInspections(element: PsiElement?): List
+ protected abstract fun filterApplicableFixes(fixes: List): List
+ override fun runAutoImport(file: PsiFile, editor: Editor, suggestionRange: TextRange) {
+ val elements = SyntaxTraverser.psiTraverser(file)
+ .onRange(suggestionRange)
+ .toList()
+ val indicator = when (ApplicationManager.getApplication().isUnitTestMode) {
+ false -> ProgressManager.getInstance().progressIndicator
+ true -> DaemonProgressIndicator()
+ }
+
+ val fixes = InspectionEngine.inspectElements(
+ getAutoImportInspections(file).map { LocalInspectionToolWrapper(it) },
+ file,
+ file.textRange,
+ true,
+ true,
+ indicator,
+ elements,
+ PairProcessor.alwaysTrue()
+ ).values.flatMap { problemDescriptors ->
+ problemDescriptors.flatMap { it.fixes.orEmpty().toList() }
+ }.filterIsInstance()
+
+ applyFixes(editor, filterApplicableFixes(fixes))
+ }
+
+ fun areFixableByAutoImport(problems: List): Boolean {
+ return problems.all {
+ val fixes = it.fixes.orEmpty().filterIsInstance()
+ filterApplicableFixes(fixes).isNotEmpty()
+ }
+ }
+
+ open fun applyFixes(editor: Editor, fixes: List) {
+ val fixToApply = fixes.firstOrNull() ?: return // To avoid layering of some import popups on others
+ val lastModified = editor.document.modificationStamp
+ fun action() = ApplicationManager.getApplication().runWriteAction {
+ fixToApply.applyFix()
+ }
+ ApplicationManager.getApplication().invokeLater(::action, ModalityState.defaultModalityState()) {
+ editor.document.modificationStamp != lastModified
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectness.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectness.kt
new file mode 100644
index 000000000000..c02614aeb9d7
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectness.kt
@@ -0,0 +1,14 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import org.jetbrains.annotations.ApiStatus
+
+
+@ApiStatus.Internal
+data class CodeCorrectness(val syntaxState: ErrorsState, val semanticState: ErrorsState) {
+ fun allErrors(): List = syntaxState.errors() + semanticState.errors()
+
+ companion object {
+ fun empty(): CodeCorrectness = CodeCorrectnessBuilder().build()
+ }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectnessBuilder.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectnessBuilder.kt
new file mode 100644
index 000000000000..401147815727
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CodeCorrectnessBuilder.kt
@@ -0,0 +1,40 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import com.intellij.platform.ml.impl.correctness.checker.ErrorsState.Unknown.UnknownReason
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+class CodeCorrectnessBuilder {
+ private var syntaxState: ErrorsState = ErrorsState.Unknown(UnknownReason.NOT_STARTED)
+ private var semanticState: ErrorsState = ErrorsState.Unknown(UnknownReason.NOT_STARTED)
+ fun syntaxCorrectness(block: () -> List) {
+ syntaxState = ErrorsState.Unknown(UnknownReason.IN_PROGRESS)
+ val errors = block()
+ syntaxState = selectState(errors)
+ }
+
+ fun semanticCorrectness(block: () -> List) {
+ semanticState = ErrorsState.Unknown(UnknownReason.IN_PROGRESS)
+ val errors = block()
+ semanticState = selectState(errors)
+ }
+
+ private fun selectState(errors: List): ErrorsState = when {
+ errors.isEmpty() -> ErrorsState.Correct
+ else -> ErrorsState.Incorrect(errors)
+ }
+
+ fun timeLimitExceeded() {
+ if ((syntaxState as? ErrorsState.Unknown)?.reason == UnknownReason.IN_PROGRESS) {
+ syntaxState = ErrorsState.Unknown(UnknownReason.TIME_LIMIT_EXCEEDED)
+ }
+ if ((semanticState as? ErrorsState.Unknown)?.reason == UnknownReason.IN_PROGRESS) {
+ semanticState = ErrorsState.Unknown(UnknownReason.TIME_LIMIT_EXCEEDED)
+ }
+ }
+
+ fun build(): CodeCorrectness {
+ return CodeCorrectness(syntaxState, semanticState)
+ }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessChecker.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessChecker.kt
new file mode 100644
index 000000000000..47aa5e191842
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessChecker.kt
@@ -0,0 +1,23 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import com.intellij.psi.PsiFile
+import com.intellij.util.concurrency.annotations.RequiresBlockingContext
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+interface CorrectnessChecker {
+ @RequiresBlockingContext
+ @ApiStatus.ScheduledForRemoval
+ @Deprecated("Do not use it")
+ fun checkSyntax(file: PsiFile,
+ suggestion: String,
+ offset: Int,
+ prefix: String, ignoreSyntaxErrorsBeforeSuggestionLen: Int): List
+
+ @RequiresBlockingContext
+ fun checkSemantic(file: PsiFile,
+ suggestion: String,
+ offset: Int,
+ prefix: String): List
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessCheckerBase.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessCheckerBase.kt
new file mode 100644
index 000000000000..afee35e05980
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessCheckerBase.kt
@@ -0,0 +1,78 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import com.intellij.codeInspection.InspectionEngine
+import com.intellij.openapi.diagnostic.thisLogger
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiFile
+import com.intellij.psi.SyntaxTraverser
+import com.intellij.util.PairProcessor
+import com.intellij.platform.ml.impl.correctness.finalizer.SuggestionFinalizer
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+open class CorrectnessCheckerBase(open val semanticCheckers: List = emptyList()) : CorrectnessChecker {
+ @Suppress("PropertyName")
+ protected val LOG = thisLogger()
+
+ protected open val suggestionFinalizer: SuggestionFinalizer? = null
+
+ final override fun checkSyntax(file: PsiFile,
+ suggestion: String,
+ offset: Int,
+ prefix: String,
+ ignoreSyntaxErrorsBeforeSuggestionLen: Int): List {
+ // todo: consider using length in leaves instead of plain offset
+ val isSyntaxCorrect = suggestionFinalizer
+ ?.getFinalization(file, suggestion, offset, prefix)
+ ?.hasNoErrorsStartingFrom(offset - ignoreSyntaxErrorsBeforeSuggestionLen) ?: true
+ return if (isSyntaxCorrect) {
+ emptyList()
+ }
+ else {
+ listOf(CorrectnessError(TextRange.EMPTY_RANGE, Severity.CRITICAL)) // todo specify error location
+ }
+ }
+
+ protected open fun buildPsiForSemanticChecks(file: PsiFile, suggestion: String, offset: Int, prefix: String): PsiFile {
+ return file
+ }
+
+ private val toolWrappers = semanticCheckers.filterIsInstance()
+ .map { it.toolWrapper }
+
+ private val toolNameToSemanticChecker = semanticCheckers.filterIsInstance()
+ .associateBy { it.toolWrapper.id }
+
+ private val customSemanticCheckers = semanticCheckers.filterIsInstance()
+
+ final override fun checkSemantic(file: PsiFile, suggestion: String, offset: Int, prefix: String): List {
+ if (semanticCheckers.isEmpty()) {
+ return emptyList()
+ }
+ val fullPsi = buildPsiForSemanticChecks(file, suggestion, offset, prefix)
+
+ val range = TextRange(offset - prefix.length, offset + suggestion.length - prefix.length)
+
+ val elements = SyntaxTraverser.psiTraverser(fullPsi)
+ .onRange(range)
+ .toList()
+
+ return InspectionEngine.inspectElements(
+ toolWrappers,
+ fullPsi,
+ fullPsi.textRange,
+ true,
+ true,
+ ProgressManager.getInstance().progressIndicator,
+ elements,
+ PairProcessor.alwaysTrue()
+ ).flatMap {
+ val semanticChecker = checkNotNull(toolNameToSemanticChecker[it.key.id])
+ semanticChecker.convertInspectionsResults(file, it.value, offset, prefix, suggestion)
+ } + customSemanticCheckers.flatMap { analyzer ->
+ elements.flatMap { element -> analyzer.findErrors(file, element, offset, prefix, suggestion) }
+ }
+ }
+}
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessError.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessError.kt
new file mode 100644
index 000000000000..8f6d95e0f487
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/CorrectnessError.kt
@@ -0,0 +1,8 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import com.intellij.openapi.util.TextRange
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+data class CorrectnessError(val location: TextRange, val severity: Severity)
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/ErrorsState.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/ErrorsState.kt
new file mode 100644
index 000000000000..c57671715c12
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/ErrorsState.kt
@@ -0,0 +1,35 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+sealed class ErrorsState {
+ object Correct : ErrorsState() {
+ override fun toString(): String = "Correct"
+
+ override fun errors() = emptyList()
+ }
+
+ data class Incorrect(val errors: List) : ErrorsState() {
+ init {
+ require(errors.isNotEmpty())
+ }
+
+ override fun errors() = errors
+ }
+
+ data class Unknown(val reason: UnknownReason) : ErrorsState() {
+ enum class UnknownReason {
+ TIME_LIMIT_EXCEEDED,
+ NOT_STARTED,
+ IN_PROGRESS
+ }
+
+ override fun errors() = emptyList()
+ }
+
+ abstract fun errors(): List
+
+ fun List.hasCriticalErrors(): Boolean = any { it.severity == Severity.CRITICAL }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/SemanticChecker.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/SemanticChecker.kt
new file mode 100644
index 000000000000..4b9552fc24fe
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/SemanticChecker.kt
@@ -0,0 +1,52 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ex.LocalInspectionToolWrapper
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+sealed interface SemanticChecker {
+ fun getLocationInSuggestion(errorRangeInFile: TextRange, offset: Int, prefix: String, suggestion: String): TextRange? {
+ val shift = offset - prefix.length
+ val suggestionLocationInFile = TextRange(0, suggestion.length).shiftRight(shift)
+ if (!suggestionLocationInFile.intersects(errorRangeInFile)) {
+ return null
+ }
+ return suggestionLocationInFile.intersection(errorRangeInFile)!!.shiftLeft(shift)
+ }
+}
+
+@ApiStatus.Internal
+abstract class InspectionBasedSemanticChecker(localInspectionTool: LocalInspectionTool) : SemanticChecker {
+ abstract fun convertInspectionsResults(
+ originalPsi: PsiFile,
+ problemDescriptors: List,
+ offset: Int,
+ prefix: String,
+ suggestion: String
+ ): List
+
+ val toolWrapper: LocalInspectionToolWrapper = LocalInspectionToolWrapper(localInspectionTool)
+
+ protected fun getLocationInSuggestion(problemDescriptor: ProblemDescriptor, offset: Int, prefix: String, suggestion: String): TextRange? =
+ getLocationInSuggestion(getErrorRangeInFile(problemDescriptor), offset, prefix, suggestion)
+
+ protected fun getErrorRangeInFile(problemDescriptor: ProblemDescriptor): TextRange {
+ val rangeInElement = problemDescriptor.textRangeInElement ?: TextRange(0, problemDescriptor.psiElement.textLength)
+ return rangeInElement.shiftRight(problemDescriptor.psiElement.textRange.startOffset)
+ }
+}
+
+@ApiStatus.Internal
+abstract class CustomSemanticChecker : SemanticChecker {
+ abstract fun findErrors(originalPsi: PsiFile,
+ element: PsiElement,
+ offset: Int,
+ prefix: String,
+ suggestion: String): List
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/Severity.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/Severity.kt
new file mode 100644
index 000000000000..539c934408fe
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/checker/Severity.kt
@@ -0,0 +1,10 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ml.impl.correctness.checker
+
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+enum class Severity {
+ CRITICAL,
+ ACCEPTABLE,
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/FinalizedFile.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/FinalizedFile.kt
new file mode 100644
index 000000000000..a04e8ecb83c7
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/FinalizedFile.kt
@@ -0,0 +1,29 @@
+package com.intellij.platform.ml.impl.correctness.finalizer
+
+import com.intellij.psi.PsiErrorElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.SyntaxTraverser
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.ScheduledForRemoval
+@Deprecated("Do not use it")
+class FinalizedFile(finalizedPsi: PsiFile) {
+ private val errorElementsRanges = SyntaxTraverser.psiTraverser(finalizedPsi)
+ .filter(PsiErrorElement::class.java)
+ .map { it.textRange }
+ .toList()
+
+ /**
+ * This function checks that the finalized file does not contain errors starting with [offset] or later.
+ *
+ * Since when finalizing a file, we only add code after suggestion,
+ * if you run it with [offset] = 0
+ * it cannot return true if suggestion is syntactically incorrect.
+ * This is an important property of this function, it must be preserved.
+ *
+ * Of course, if you run this function with a large offset, it will always say that there are no errors.
+ * So you should run it with an offset less than the offset of the completion call.
+ * But how much less depends on how many errors you need to ignore before completion.
+ */
+ fun hasNoErrorsStartingFrom(offset: Int): Boolean = errorElementsRanges.none { it.startOffset >= offset }
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizer.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizer.kt
new file mode 100644
index 000000000000..f9fd550eac14
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizer.kt
@@ -0,0 +1,33 @@
+package com.intellij.platform.ml.impl.correctness.finalizer
+
+import com.intellij.platform.ml.impl.correctness.finalizer.FinalizedFile
+import com.intellij.psi.PsiFile
+import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
+
+@ScheduledForRemoval
+@Deprecated("Do not use it")
+interface SuggestionFinalizer {
+ /**
+ * This function takes the text up to the caret, inserts the [suggestion] into it
+ * and tries to finalize the file so that the inserted [suggestion] does not create syntax errors
+ * (due to unfinished code in the [suggestion]).
+ *
+ * Example:
+ * If we have a context
+ * ```
+ * for i
+ * ```
+ * And suggestion
+ * ```
+ * in range(
+ * ```
+ * Then the [FinalizedFile] likely will be
+ * ```
+ * for i in range():
+ * pass
+ * ```
+ *
+ * Please see [FinalizedFile] if you are going to change the semantics of this function.
+ */
+ fun getFinalization(originalPsi: PsiFile, suggestion: String, offset: Int, prefix: String): FinalizedFile
+}
\ No newline at end of file
diff --git a/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizerBase.kt b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizerBase.kt
new file mode 100644
index 000000000000..cccff71269ea
--- /dev/null
+++ b/platform/ml-impl/src/com/intellij/platform/ml/impl/correctness/finalizer/SuggestionFinalizerBase.kt
@@ -0,0 +1,39 @@
+package com.intellij.platform.ml.impl.correctness.finalizer
+
+import com.intellij.lang.Language
+import com.intellij.openapi.application.runReadAction
+import com.intellij.psi.*
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.ScheduledForRemoval
+@Deprecated("Do not use it")
+abstract class SuggestionFinalizerBase(val language: Language) : SuggestionFinalizer {
+
+ /**
+ * Returns the order in which the elements will be traversed to finalize.
+ */
+ protected abstract fun getTraverseOrder(insertedElement: PsiElement): Sequence
+
+ /**
+ * Returns the finalization for the element.
+ */
+ protected abstract fun getFinalizationCandidate(element: PsiElement): String?
+
+
+ /**
+ * This implementation traverse tree with the inserted [suggestion] according to [getTraverseOrder]
+ * and appends text from [getFinalizationCandidate].
+ */
+ final override fun getFinalization(originalPsi: PsiFile, suggestion: String, offset: Int, prefix: String): FinalizedFile = runReadAction {
+ require(suggestion.isNotBlank())
+ val psi = PsiFileFactory.getInstance(originalPsi.project)
+ .createFileFromText(language, originalPsi.text.take(offset - prefix.length) + suggestion)
+ val insertedElement = psi.findElementAt(psi.textLength - 1 - suggestion.takeLastWhile { it == ' ' }.length)!!
+ val finalization = getTraverseOrder(insertedElement).joinToString(separator = "") {
+ getFinalizationCandidate(it).orEmpty()
+ }
+ val finalizedText = psi.text + finalization
+ val finalizedPsi = PsiFileFactory.getInstance(originalPsi.project).createFileFromText(language, finalizedText)
+ FinalizedFile(finalizedPsi)
+ }
+}
\ No newline at end of file
diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml
index 64c7fcf45178..9aca195d0083 100644
--- a/python/intellij.python.community.impl.iml
+++ b/python/intellij.python.community.impl.iml
@@ -111,7 +111,6 @@
-
@@ -143,5 +142,7 @@
+
+
\ No newline at end of file
diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml
index 4c52d112d1c2..d3ee445b8b90 100644
--- a/python/src/META-INF/python-core-common.xml
+++ b/python/src/META-INF/python-core-common.xml
@@ -264,6 +264,8 @@
implementationClass="com.jetbrains.python.codeInsight.mlcompletion.PyContextFeatureProvider"/>
+