diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index 8461969256a..c921f582843 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -69,13 +69,6 @@ class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable get() = dependencies.flatMap { it.sources } } -fun Iterable.isSameClasspathAs(other: Iterable): Boolean { - val c1 = map { it.canonicalPath }.toHashSet() - val c2 = other.map { it.canonicalPath }.toHashSet() - return c1.size == c2.size && - c1.zip(c2).all { it.first == it.second } -} - data class ScriptParameter(val name: Name, val type: KotlinType) fun getFileName(file: TF): String = when (file) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt index 2f5b8ce5ae3..80b7606e6f6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt @@ -24,6 +24,7 @@ import kotlin.concurrent.read import kotlin.concurrent.write class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) { + private val cacheLock = ReentrantReadWriteLock() private val cache = hashMapOf() private val cacheOfNulls = hashSetOf() @@ -82,7 +83,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri val deps = scriptDef.getDependenciesFor(file, project, oldDeps) when { deps != null && (oldDeps == null || - !deps.classpath.isSameClasspathAs(oldDeps.classpath) || !deps.sources.isSameClasspathAs(oldDeps.sources)) -> { + !deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> { // changed or new cache.put(path, deps) cacheOfNulls.remove(path) @@ -104,9 +105,8 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri fun invalidateCaches() { cacheLock.write { - cache.keys.toList().apply { - cache.clear() - } + cache.clear() + cacheOfNulls.clear() } } @@ -136,4 +136,11 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri fun getInstance(project: Project): KotlinScriptExternalImportsProvider? = ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java) } -} \ No newline at end of file +} + +internal fun Iterable.isSamePathListAs(other: Iterable): Boolean { + val c1 = asSequence().map { it.canonicalPath } + val c2 = other.asSequence().map { it.canonicalPath } + return c1 == c2 +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt index cf4865ea615..db892b1a798 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt @@ -16,54 +16,27 @@ package org.jetbrains.kotlin.script -import com.intellij.openapi.util.text.StringUtil -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtUserType +import org.jetbrains.kotlin.resolve.BindingTraceContext +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator +import org.jetbrains.kotlin.types.TypeUtils internal class KtAnnotationWrapper(val psi: KtAnnotationEntry) { val name: String get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous() - val valueArguments by lazy { + val valueArguments: List> by lazy { psi.valueArguments.map { - Pair(it.getArgumentName()?.toString(), convert(it.getArgumentExpression()!!)) + val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance) + val trace = BindingTraceContext() + val result = evaluator.evaluateToConstantValue(it.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE) + it.getArgumentName()?.asName.toString() to result?.value + // TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.) } } - internal fun String?.orAnonymous(kind: String = ""): String { - return this ?: "" - } - - internal fun convert(expression: KtExpression): Any? = when (expression) { - is KtStringTemplateExpression -> { - if (expression.entries.isEmpty()) - "" - else if (expression.entries.size == 1) - convert(expression.entries[0]) - else - "" - // TODO: parse expressions, etc. e.g.: - // convertStringTemplateExpression(expression, parent, expression.entries.size - 1) - } - else -> null - - } - - internal fun convert(entry: KtStringTemplateEntry): Any? = when (entry) { - is KtStringTemplateEntryWithExpression -> convertOrEmpty(entry.expression) - is KtEscapeStringTemplateEntry -> entry.unescapedValue - else -> { - StringUtil.unescapeStringCharacters(entry.text) - } - } - - internal fun convertOrEmpty(expression: KtExpression?): Any? { - return if (expression != null) convert(expression) else null - } + internal fun String?.orAnonymous(kind: String = ""): String = + this ?: "" } - -fun parseAnnotation(ann: KtAnnotationEntry): Pair> { - val wann = KtAnnotationWrapper(ann) - val vals = wann.valueArguments - return Pair(wann.name, vals) -} - diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt index 4b2c2917cca..55e08e549ee 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt @@ -28,14 +28,12 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.io.File import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy -import java.util.* -import kotlin.reflect.KClass -import kotlin.reflect.memberFunctions -import kotlin.reflect.memberProperties +import kotlin.reflect.* @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @@ -56,12 +54,12 @@ annotation class AcceptedAnnotations(vararg val supportedAnnotationClasses: KCla data class KotlinScriptDefinitionFromTemplate(val template: KClass, val environment: Map?) : KotlinScriptDefinition { - private val definitionAnnotation by lazy { template.annotations.mapNotNull { it as? ScriptTemplateDefinition }.firstOrNull() } + private val definitionAnnotation by lazy { template.annotations.firstIsInstanceOrNull() } override val name = template.simpleName!! override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = - template.constructors.first().parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) } + template.primaryConstructor!!.parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) } override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List = listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!)) @@ -76,14 +74,19 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) private class ResolverWithAcceptedAnnotations(val resolverClass: KClass) { - val resolver by lazy { resolverClass.constructors.first().call() } + val resolver by lazy { resolverClass.primaryConstructor!!.call() } val acceptedAnnotations by lazy { - val resolveMethodName = ScriptDependenciesResolver::class.memberFunctions.first().name - resolverClass.memberFunctions.find { it.name == resolveMethodName }?.annotations - ?.mapNotNull { it as? AcceptedAnnotations } - ?.flatMap { + val resolveMethod = ScriptDependenciesResolver::resolve + val resolverMethodAnnotations = + resolverClass.memberFunctions.find { + it.name == resolveMethod.name && + sameSignature(it, resolveMethod) + } + ?.annotations + ?.filterIsInstance() + resolverMethodAnnotations?.flatMap { val v = it.supportedAnnotationClasses - v.toList() // TODO: find out why if inlined, it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass" + v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass") } ?: emptyList() } @@ -103,23 +106,24 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val .map { KtAnnotationWrapper(it) } .mapNotNull { wrappedAnn -> // TODO: consider advanced matching using semantic similar to actual resolving - supportedAnnotationClasses.find { wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName } - ?.let { Pair(it, wrappedAnn) } + supportedAnnotationClasses.find { + wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName + }?.let { it to wrappedAnn } } - val annotations = fileAnnotations.map { + val annotations = fileAnnotations.map { annClassToWrapper -> try { - val handler = AnnProxyInvocationHandler(it.first, it.second.valueArguments) - val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(it.first.java), handler) as Annotation - Pair(it.first, proxy) + val handler = AnnProxyInvocationHandler(annClassToWrapper.first, annClassToWrapper.second.valueArguments) + val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(annClassToWrapper.first.java), handler) as Annotation + annClassToWrapper.first to proxy } catch (ex: Exception) { - Pair(it.first, InvalidScriptResolverAnnotation(it.second.name, it.second.valueArguments, ex)) + annClassToWrapper.first to InvalidScriptResolverAnnotation(annClassToWrapper.second.name, annClassToWrapper.second.valueArguments, ex) } } val fileDeps = dependenciesResolvers.mapNotNull { resolverWrapper -> - val supportedAnnotations = annotations.mapNotNull { ann -> - val annFQN = ann.first.qualifiedName - if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) ann.second else null + val supportedAnnotations = annotations.mapNotNull { annClassToWrapper -> + val annFQN = annClassToWrapper.first.qualifiedName + if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) annClassToWrapper.second else null } resolverWrapper.resolver.resolve(getFile(file), supportedAnnotations, environment, previousDependencies) } @@ -148,7 +152,6 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val } } -@Suppress("unused") class InvalidScriptResolverAnnotation(val name: String, val params: Iterable, val error: Exception? = null) : Annotation class AnnProxyInvocationHandler>(val targetAnnClass: K, val annParams: List>) : InvocationHandler { @@ -161,4 +164,12 @@ class AnnProxyInvocationHandler>(val targetAnnClass: K, v } return null } -} \ No newline at end of file +} + +internal fun sameSignature(left: KFunction<*>, right: KFunction<*>): Boolean = + left.parameters.size == right.parameters.size && + left.parameters.zip(right.parameters).all { + it.first.kind == KParameter.Kind.INSTANCE || + it.first.type == it.second.type + } + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt index 8435273df13..f9455203495 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt @@ -42,24 +42,24 @@ interface ScriptTemplateProvider { } fun makeScriptDefsFromTemplateProviderExtensions(project: Project, - errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex)): List = + errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }): List = makeScriptDefsFromTemplateProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplateProvider.EP_NAME).extensions.asIterable(), errorsHandler) fun makeScriptDefsFromTemplateProviders(providers: Iterable, - errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex) + errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex } ): List { val idToVersion = hashMapOf() - return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull { + return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull { provider -> try { - idToVersion.get(it.id)?.let { ver -> errorsHandler(it, RuntimeException("Conflicting scriptTemplateProvider ${it.id}, using one with version $ver")) } - val loader = URLClassLoader(it.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader) - val cl = loader.loadClass(it.templateClassName) - idToVersion.put(it.id, it.version) - KotlinScriptDefinitionFromTemplate(cl.kotlin, it.environment) + idToVersion.get(provider.id)?.let { ver -> errorsHandler(provider, RuntimeException("Conflicting scriptTemplateProvider ${provider.id}, using one with version $ver")) } + val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader) + val cl = loader.loadClass(provider.templateClassName) + idToVersion.put(provider.id, provider.version) + KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.environment) } catch (ex: Exception) { - errorsHandler(it, ex) + errorsHandler(provider, ex) null } } diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt index 154a97a1162..d771ed40649 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt @@ -36,6 +36,9 @@ import java.io.File import java.net.URLClassLoader import kotlin.reflect.KClass +// TODO: the contetnts of this file should go into ScriptTest.kt and replace appropriate xml-based functionality, +// as soon as the the latter is removed from the codebase + class ScriptTest2 { @Test fun testScriptWithParam() { diff --git a/idea/idea-core/idea-core.iml b/idea/idea-core/idea-core.iml index e3568f246dc..3238fb2a99b 100644 --- a/idea/idea-core/idea-core.iml +++ b/idea/idea-core/idea-core.iml @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt index 819797dc7d8..406fdf5e23c 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt @@ -114,7 +114,7 @@ class KotlinScriptConfigurationManager( } private fun reloadScriptDefinitions() { - (makeScriptDefsFromTemplateProviderExtensions(project /* TODO: add logging here */) + + (makeScriptDefsFromTemplateProviderExtensions(project, { ep, ex -> /* TODO: add logging here */ }) + loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) }).let { if (it.isNotEmpty()) { scriptDefinitionProvider.setScriptDefinitions(it + StandardScriptDefinition) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/gradleScriptTemplateProvider.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/gradleScriptTemplateProvider.kt index b2cede3f5e6..6a994ac4880 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/gradleScriptTemplateProvider.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/gradleScriptTemplateProvider.kt @@ -35,7 +35,7 @@ class GradleScriptTemplateProvider(project: Project, gim: GradleInstallationMana gradleLibsPath?.listFiles { file -> file.extension == "jar" && depLibsPrefixes.any { file.name.startsWith(it) } } ?.map { it.canonicalPath } ?: emptyList() - override val environment: Map? = kotlin.collections.mapOf("gradleHome" to gradleHome) + override val environment: Map? = mapOf("gradleHome" to gradleHome) companion object { private val depLibsPrefixes = listOf("gradle-script-kotlin", "gradle-core")