From 402e8c1e3ed143a4d234ef67630a2668b70489ce Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Mon, 6 Jun 2016 12:25:52 +0200 Subject: [PATCH] Add dependency from annotation extraction mechanisms to the prototype, with tests --- ... => JvmLazyCliVirtualFileFinderFactory.kt} | 7 +- .../cli/jvm/compiler/KotlinCoreEnvironment.kt | 4 +- .../script/scriptAnnotationsPreprocessing.kt | 74 +++++++++++++++++++ .../jetbrains/kotlin/script/scriptTemplate.kt | 7 +- compiler/testData/script/fib_ext_ann.kts | 16 ++++ .../kotlin/scripts/TestScriptDefinitions.kt | 2 +- .../jetbrains/kotlin/scripts/ScriptTest2.kt | 55 ++++++++++++-- 7 files changed, 149 insertions(+), 16 deletions(-) rename compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/{JvmCliVirtualFileFinderFactory.kt => JvmLazyCliVirtualFileFinderFactory.kt} (80%) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt create mode 100644 compiler/testData/script/fib_ext_ann.kts diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmCliVirtualFileFinderFactory.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmLazyCliVirtualFileFinderFactory.kt similarity index 80% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmCliVirtualFileFinderFactory.kt rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmLazyCliVirtualFileFinderFactory.kt index 45b6813fb80..43b0432d2d4 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmCliVirtualFileFinderFactory.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmLazyCliVirtualFileFinderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinderFactory -class JvmCliVirtualFileFinderFactory(private val index: JvmDependenciesIndex) : JvmVirtualFileFinderFactory { +class JvmLazyCliVirtualFileFinderFactory(private val makeIndex: () -> JvmDependenciesIndex) : JvmVirtualFileFinderFactory { + + private val index by lazy { makeIndex() } + override fun create(scope: GlobalSearchScope): JvmVirtualFileFinder = JvmCliVirtualFileFinder(index) } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 872cb808f98..d25f0420619 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -163,9 +163,7 @@ class KotlinCoreEnvironment private constructor( } } - fillClasspath(configuration) - - project.registerService(JvmVirtualFileFinderFactory::class.java, JvmCliVirtualFileFinderFactory(index)) + project.registerService(JvmVirtualFileFinderFactory::class.java, JvmLazyCliVirtualFileFinderFactory({ fillClasspath(configuration); index } )) ExternalDeclarationsProvider.registerExtensionPoint(project) ExpressionCodegenExtension.registerExtensionPoint(project) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt new file mode 100644 index 00000000000..a3715004640 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script + +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.kotlin.psi.* + +object SimpleUntypedAst { + + sealed class Node(val name: String) { + class empty(name: String) : Node(name) + class str(name: String, val value: String) : Node(name) + class int(name: String, val value: Int) : Node(name) + class list(name: String, val value: List>) : Node>>(name) + class ann(name: String, val value: List>) : Node>>(name) + } + + class KtAnnotationWrapper(val psi: KtAnnotationEntry) { + val name: String + get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous() + + val valueArguments by lazy { + psi.valueArguments.map { + val name = it.getArgumentName()?.asName?.identifier.orAnonymous() + convert(it.getArgumentExpression()!!) + } + } + + internal fun String?.orAnonymous(kind: String = ""): String { + return this ?: "" + } + + internal fun convert(expression: KtExpression): Node = when (expression) { + is KtStringTemplateExpression -> { + if (expression.entries.isEmpty()) + SimpleUntypedAst.Node.str(name, "") + else if (expression.entries.size == 1) + convert(expression.entries[0]) + else + SimpleUntypedAst.Node.str(name, "") +// convertStringTemplateExpression(expression, parent, expression.entries.size - 1) + } + else -> Node.empty(name) + + } + + internal fun convert(entry: KtStringTemplateEntry): Node = when (entry) { + is KtStringTemplateEntryWithExpression -> convertOrEmpty(entry.expression) + is KtEscapeStringTemplateEntry -> Node.str(name, entry.unescapedValue) + else -> { + Node.str(name, StringUtil.unescapeStringCharacters(entry.text)) + } + } + + internal fun convertOrEmpty(expression: KtExpression?): Node { + return if (expression != null) convert(expression) else Node.empty(name) + } + } +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt index 5198b7bbbf1..beb495240fc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt @@ -20,6 +20,8 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.parsing.KotlinParserDefinition +import org.jetbrains.kotlin.psi.KtAnnotation +import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.types.KotlinType import kotlin.reflect.KClass @@ -34,7 +36,8 @@ interface ScriptDependencies { } interface GetScriptDependencies { - operator fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? + operator fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? = null + operator fun invoke(context: Any?): ScriptDependencies? = null } @Target(AnnotationTarget.CLASS) @@ -64,7 +67,7 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val } private val dependencies by lazy { - dependenciesExtractors.mapNotNull { it(template.annotations, context) } + dependenciesExtractors.mapNotNull { it(context) } } override fun getScriptDependenciesClasspath(): List = dependencies.flatMap { it.classpath } diff --git a/compiler/testData/script/fib_ext_ann.kts b/compiler/testData/script/fib_ext_ann.kts new file mode 100644 index 00000000000..d7001ac1aed --- /dev/null +++ b/compiler/testData/script/fib_ext_ann.kts @@ -0,0 +1,16 @@ + +// this script expected parameter num : Int + +@file:org.jetbrains.kotlin.scripts.depends("@{runtime}") + +fun fib(n: Int): Int { + val v = if(n < 2) 1 else fib(n-1) + fib(n-2) + System.out.println("fib($n)=$v") + return v +} + +val hdr = "Num".decapitalize() + +System.out.println("$hdr: $num") +val result = fib(num) + diff --git a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt index 7a75aa1e815..44c82350a5f 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt @@ -85,7 +85,7 @@ fun classpathFromProperty(): List = .map { File(it).canonicalPath } } ?: emptyList() -private fun URL.toFile() = +fun URL.toFile() = try { File(toURI().schemeSpecificPart) } diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt index 6c6470ac498..ce1483c4a77 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt @@ -22,10 +22,10 @@ import org.jetbrains.kotlin.cli.common.messages.* import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler -import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.addKotlinSourceRoot +import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.script.* import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils @@ -33,7 +33,6 @@ import org.jetbrains.kotlin.test.TestJdkKind import org.jetbrains.kotlin.utils.PathUtil import org.junit.Assert import org.junit.Test -import java.io.File import java.net.URLClassLoader import kotlin.reflect.KClass @@ -59,6 +58,13 @@ class ScriptTest2 { aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1) } + @Test + fun testScriptWithDependsAnn() { + val aClass = compileScript("fib_ext_ann.kts", ScriptWithIntParam::class, null) + Assert.assertNotNull(aClass) + aClass!!.getConstructor(Integer.TYPE).newInstance(4) + } + private fun compileScript( scriptPath: String, scriptBase: KClass, @@ -84,7 +90,6 @@ class ScriptTest2 { configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) configuration.addKotlinSourceRoot(scriptPath) configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) - scriptDefinition.getScriptDependenciesClasspath().forEach { configuration.addJvmClasspathRoot(File(it)) } val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) @@ -111,14 +116,44 @@ class ScriptTest2 { class GetTestKotlinScriptDependencies : GetScriptDependencies { - override fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? = - object : ScriptDependencies { - override val classpath = - (GetTestKotlinScriptDependencies::class.java.classLoader as? URLClassLoader)?.urLs?.map { it.file } ?: emptyList() - override val implicitImports = emptyList() + private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } + + override fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? { + if (annotations.none()) return null + val anns = annotations.map { parseAnnotation(it) }.filter { it.name == depends::class.simpleName } + val cp = anns.flatMap { + it.value.mapNotNull { + when (it) { + is SimpleUntypedAst.Node.str -> if (it.value == "@{runtime}") kotlinPaths.runtimePath.canonicalPath else it.value + else -> null + } } + } + return object : ScriptDependencies { + override val classpath = cp + override val implicitImports = emptyList() + } + } + + private fun classpathFromClassloader(): List = + (GetTestKotlinScriptDependencies::class.java.classLoader as? URLClassLoader)?.urLs + ?.mapNotNull { it.toFile()?.canonicalPath } + ?.filter { it.contains("out/test") } + ?: emptyList() + + override fun invoke(context: Any?): ScriptDependencies? { + return object : ScriptDependencies { + override val classpath = classpathFromClassloader() + override val implicitImports = emptyList() + } + } } +fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list { + val wann = SimpleUntypedAst.KtAnnotationWrapper(ann) + val vals = wann.valueArguments + return SimpleUntypedAst.Node.list(wann.name, vals) +} @ScriptFilePattern(".*\\.kts") @ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) @@ -131,3 +166,7 @@ abstract class ScriptWithClassParam(param: TestParamClass) @ScriptFilePattern(".*\\.kts") @ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough) + +@Target(AnnotationTarget.FILE) +@Retention(AnnotationRetention.RUNTIME) +annotation class depends(val path: String)