diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt index e2ea71b6666..607bed0ba67 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplCodeAnalyzer.kt @@ -280,7 +280,7 @@ open class ReplCodeAnalyzerBase( // create scope that wraps previous line lexical scope and adds imports from this line val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope - val scopesForThisLine = fileScopeFactory.createScopesForFile(linePsi, lastLineImports) + val scopesForThisLine = fileScopeFactory.createScopesForFile(linePsi, lastLineImports, false) val combinedLexicalScopes = if (hasImports) lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope) else diff --git a/plugins/scripting/scripting-ide-services-test/build.gradle.kts b/plugins/scripting/scripting-ide-services-test/build.gradle.kts index e589ee589a4..6c4f6431fcb 100644 --- a/plugins/scripting/scripting-ide-services-test/build.gradle.kts +++ b/plugins/scripting/scripting-ide-services-test/build.gradle.kts @@ -20,6 +20,8 @@ dependencies { allTestsRuntime(commonDep("junit")) testCompile(project(":kotlin-scripting-ide-services-unshaded")) testCompile(project(":kotlin-scripting-compiler")) + testCompile(project(":kotlin-scripting-dependencies")) + testCompile(project(":kotlin-main-kts")) testCompile(project(":compiler:cli-common")) testRuntimeOnly(project(":kotlin-compiler")) diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt index 3c2597c04c5..51290644aa3 100644 --- a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/JvmReplTest.kt @@ -7,15 +7,19 @@ package org.jetbrains.kotlin.scripting.ide_services import junit.framework.TestCase import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocationWithRange +import org.jetbrains.kotlin.scripting.ide_services.test_util.* import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl import java.io.File import kotlin.script.experimental.api.* import kotlin.script.experimental.jvm.impl.KJvmCompiledScript +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.util.LinkedSnippet import kotlin.script.experimental.util.get import kotlin.script.experimental.jvm.util.isError import kotlin.script.experimental.jvm.util.isIncomplete +import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext // Adapted form GenericReplTest @@ -268,6 +272,50 @@ class JvmIdeServicesTest : TestCase() { ) } } + + fun testDependency() { + val resolver = ScriptDependenciesResolver() + + val conf = ScriptCompilationConfiguration { + jvm { + updateClasspath(scriptCompilationClasspathFromContext("test", classLoader = DependsOn::class.java.classLoader)) + } + defaultImports(DependsOn::class) + refineConfiguration { + onAnnotations(DependsOn::class, handler = { configureMavenDepsOnAnnotations(it, resolver) }) + } + } + + JvmTestRepl(conf) + .use { repl -> + /* + The only source file in test.jar contains following code: + + package example.dependency + infix fun String.to(that: String) = this + that + */ + assertEvalUnit( + repl, """ + @file:DependsOn("plugins/scripting/scripting-ide-services-test/testData/KT-35651-test.jar") + import example.dependency.* + + val x = listOf() + """.trimIndent() + ) + + // This snippet is needed to be evaluated to ensure that importing scopes were created + // (but default ones were not) + assertEvalUnit( + repl, """ + import kotlin.math.* + + val y = listOf() + """.trimIndent() + ) + + assertEvalResult(repl, """ "a" to "a" """, "aa") + } + } } class LegacyReplTestLong : TestCase() { diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt index 591d89579ec..181ee208b94 100644 --- a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/ReplCompletionAndErrorsAnalysisTest.kt @@ -332,6 +332,27 @@ class ReplCompletionAndErrorsAnalysisTest : TestCase() { run(setupDefaultImportsCompletionRun) } + @Test + fun testLongCompilationsWithImport() = test { + // This test normally completes in about 5-10s + // Log should show slow _linear_ compilation time growth + + val compileWriter = System.out.writer() + + for (i in 1..120) { + run { + code = """ + import kotlin.math.* + val dataFrame = mapOf("x" to sin(3.0)) + val e = "str" + """.trimIndent() + doCompile + + loggingInfo = CSVLoggingInfo(compile = CSVLoggingInfoItem(compileWriter, i, "compile;")) + } + } + } + @Test fun testLongRunningCompilationWithReceiver() = test { // This test normally completes in about 8-13s diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/resolverUtil.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/resolverUtil.kt new file mode 100644 index 00000000000..cb6937ba08a --- /dev/null +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/resolverUtil.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2020 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.scripting.ide_services.test_util + +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.mainKts.impl.IvyResolver +import java.io.File +import kotlin.script.dependencies.ScriptContents +import kotlin.script.experimental.api.* +import kotlin.script.experimental.dependencies.* +import kotlin.script.experimental.jvm.withUpdatedClasspath + +// in case of flat or direct resolvers the value should be a direct path or file name of a jar respectively +// in case of maven resolver the maven coordinates string is accepted (resolved with com.jcabi.aether library) +@Target(AnnotationTarget.FILE) +@Repeatable +@Retention(AnnotationRetention.SOURCE) +annotation class DependsOn(val value: String = "") + +open class ScriptDependenciesResolver { + + private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), IvyResolver()) + private val addedClasspath = mutableListOf() + + fun resolveFromAnnotations(script: ScriptContents): ResultWithDiagnostics> { + val scriptDiagnostics = mutableListOf() + val classpath = mutableListOf() + + script.annotations.forEach { annotation -> + when (annotation) { + is DependsOn -> { + try { + when (val result = runBlocking { resolver.resolve(annotation.value) }) { + is ResultWithDiagnostics.Failure -> { + val diagnostics = ScriptDiagnostic( + ScriptDiagnostic.unspecifiedError, + "Failed to resolve ${annotation.value}:\n" + result.reports.joinToString("\n") { it.message }) + scriptDiagnostics.add(diagnostics) + } + is ResultWithDiagnostics.Success -> { + addedClasspath.addAll(result.value) + classpath.addAll(result.value) + } + } + } catch (e: Exception) { + val diagnostic = + ScriptDiagnostic(ScriptDiagnostic.unspecifiedError, "Unhandled exception during resolve", exception = e) + scriptDiagnostics.add(diagnostic) + } + } + else -> throw Exception("Unknown annotation ${annotation.javaClass}") + } + } + return if (scriptDiagnostics.isEmpty()) classpath.asSuccess() + else makeFailureResult(scriptDiagnostics) + } +} + +fun configureMavenDepsOnAnnotations( + context: ScriptConfigurationRefinementContext, + resolver: ScriptDependenciesResolver +): ResultWithDiagnostics { + val annotations = context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() } + ?: return context.compilationConfiguration.asSuccess() + val scriptContents = object : ScriptContents { + override val annotations: Iterable = annotations + override val file: File? = null + override val text: CharSequence? = null + } + return try { + resolver.resolveFromAnnotations(scriptContents) + .onSuccess { classpath -> + context.compilationConfiguration + .let { if (classpath.isEmpty()) it else it.withUpdatedClasspath(classpath) } + .asSuccess() + } + } catch (e: Throwable) { + ResultWithDiagnostics.Failure(e.asDiagnostics(path = context.script.locationId)) + } +} diff --git a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt index 7223a5255c9..37d958f88a5 100644 --- a/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt +++ b/plugins/scripting/scripting-ide-services-test/test/org/jetbrains/kotlin/scripting/ide_services/test_util/testUtil.kt @@ -9,20 +9,17 @@ import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices import java.io.Closeable import java.util.concurrent.atomic.AtomicInteger -import kotlin.script.experimental.api.CompiledSnippet -import kotlin.script.experimental.api.ResultWithDiagnostics -import kotlin.script.experimental.api.SourceCode -import kotlin.script.experimental.api.valueOrNull +import kotlin.script.experimental.api.* import kotlin.script.experimental.jvm.BasicJvmReplEvaluator import kotlin.script.experimental.util.LinkedSnippet import kotlin.script.experimental.jvm.util.toSourceCodePosition -internal class JvmTestRepl : Closeable { +internal class JvmTestRepl ( + private val compileConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, + private val evalConfiguration: ScriptEvaluationConfiguration = simpleScriptEvaluationConfiguration, +) : Closeable { private val currentLineCounter = AtomicInteger(0) - private val compileConfiguration = simpleScriptCompilationConfiguration - private val evalConfiguration = simpleScriptEvaluationConfiguration - fun nextCodeLine(code: String): SourceCode = SourceCodeTestImpl( currentLineCounter.getAndIncrement(), diff --git a/plugins/scripting/scripting-ide-services-test/testData/KT-35651-test.jar b/plugins/scripting/scripting-ide-services-test/testData/KT-35651-test.jar new file mode 100644 index 00000000000..abc942025f3 Binary files /dev/null and b/plugins/scripting/scripting-ide-services-test/testData/KT-35651-test.jar differ