Refactor script definition and related parts:

- simplify script definition interface, convert it to class
 - create simple definitions right from base
 - refactor (rename and simplify) script definition with annotated template
 - simplify usages of script definition in many places
This commit is contained in:
Ilya Chernikov
2016-09-20 23:34:14 +02:00
parent 6bc488d95d
commit fe69185cd4
24 changed files with 290 additions and 358 deletions
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.codegen;
import kotlin.Pair;
import kotlin.jvm.functions.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.context.CodegenContext;
@@ -24,18 +23,17 @@ import org.jetbrains.kotlin.codegen.context.MethodContext;
import org.jetbrains.kotlin.codegen.context.ScriptContext;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -154,17 +152,19 @@ public class ScriptCodegen extends MemberCodegen<KtScript> {
iv.invokespecial("java/lang/Object", "<init>", "()V", false);
}
else {
List<Pair<Name, KotlinType>> superclassParamsMap = scriptDescriptor.getScriptParametersToPassToSuperclass();
ConstructorDescriptor ctorDesc = DescriptorUtilsKt.getConstructorByParamsMap(superclass, superclassParamsMap);
// TODO: check if it is correct way to find a promary constructor
Collection<ClassConstructorDescriptor> ctorDescriptors = superclass.getConstructors();
assert ctorDescriptors.size() == 1;
ConstructorDescriptor ctorDesc = ctorDescriptors.iterator().next();
assert ctorDesc != null;
iv.load(0, classType);
List<ValueParameterDescriptor> valueParameters = scriptDescriptor.getUnsubstitutedPrimaryConstructor().getValueParameters();
for (Pair<Name, KotlinType> superclassParam: superclassParamsMap) {
for (ValueParameterDescriptor superclassParam: ctorDesc.getValueParameters()) {
ValueParameterDescriptor valueParam = null;
for (ValueParameterDescriptor vpd: valueParameters) {
if (vpd.getName().equals(superclassParam.getFirst())) {
if (vpd.getName().equals(superclassParam.getName())) {
valueParam = vpd;
break;
}
@@ -41,7 +41,7 @@ import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHand
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.script.StandardScriptDefinition
import org.jetbrains.kotlin.util.PerformanceCounter
import org.jetbrains.kotlin.utils.KotlinPaths
@@ -400,7 +400,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
for (template in scriptTemplates) {
try {
val cls = classloader.loadClass(template)
val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, scriptResolverEnv)
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, scriptResolverEnv)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def)
messageCollector.report(
CompilerMessageSeverity.INFO,
@@ -43,8 +43,6 @@ import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
import org.jetbrains.kotlin.script.ScriptParameter
import java.io.File
private val logger = Logger.getInstance(GenericRepl::class.java)
@@ -56,7 +54,7 @@ open class GenericReplChecker(
) : ReplChecker {
protected val environment = run {
compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, DelegatingScriptDefWithNoParams(scriptDefinition))
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
}
@@ -71,10 +69,6 @@ open class GenericReplChecker(
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder)
protected class DelegatingScriptDefWithNoParams(parent: KotlinScriptDefinition) : KotlinScriptDefinition by parent {
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> = emptyList()
}
protected var lineState: LineState? = null
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
@@ -41,7 +41,7 @@ import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import java.io.PrintWriter
import java.net.URLClassLoader
@@ -179,7 +179,7 @@ class ReplInterpreter(
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinitionFromTemplate(Any::class) {
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
}
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.daemon.common.RemoteInputStream
import org.jetbrains.kotlin.daemon.common.RemoteOperationsTracer
import org.jetbrains.kotlin.daemon.common.RemoteOutputStream
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.utils.PathUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
@@ -85,7 +85,7 @@ open class KotlinJvmReplService(
try {
val cls = classloader.loadClass(templateClassName)
val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, emptyMap())
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, emptyMap())
messageCollector.report(
CompilerMessageSeverity.INFO,
"New script definition $templateClassName: files pattern = \"${def.scriptFilePattern}\", resolver = ${def.resolver?.javaClass?.name}",
@@ -22,9 +22,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.stubs.KotlinScriptStub;
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;
import org.jetbrains.kotlin.script.GetScriptDefinitionKt;
import org.jetbrains.kotlin.script.KotlinScriptDefinition;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProviderKt;
import java.util.List;
@@ -37,7 +36,7 @@ public class KtScript extends KtNamedDeclarationStub<KotlinScriptStub> implement
private KotlinScriptDefinition getKotlinScriptDefinition() {
if (!kotlinScriptDefinitionInitialized) {
KtFile ktFile = getContainingKtFile();
kotlinScriptDefinitionField = GetScriptDefinitionKt.getScriptDefinition(ktFile);
kotlinScriptDefinitionField = KotlinScriptDefinitionProviderKt.getScriptDefinition(ktFile);
kotlinScriptDefinitionInitialized = true;
assert kotlinScriptDefinitionField != null : "Should not parse a script without definition: " + ktFile.toString();
}
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
import org.jetbrains.kotlin.script.getScriptParameters
class LazyScriptClassMemberScope(
resolveSession: ResolveSession,
@@ -29,8 +29,8 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProv
import org.jetbrains.kotlin.resolve.source.toSourceElement
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptPriorities
import org.jetbrains.kotlin.script.getKotlinTypeByFqName
import org.jetbrains.kotlin.script.getScriptDefinition
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.utils.ifEmpty
@@ -79,14 +79,5 @@ class LazyScriptDescriptor(
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
override fun computeSupertypes() = scriptDefinition.getScriptSupertypes(this).ifEmpty { listOf(builtIns.anyType) }
override fun getScriptParametersToPassToSuperclass(): List<Pair<Name, KotlinType>> {
val scriptParams = scriptDefinition.getScriptParameters(this)
return scriptDefinition.getScriptParametersToPassToSuperclass(this).map { name ->
Pair(name,
scriptParams.find { it.name == name }?.type
?: throw RuntimeException("Unknown script parameter '$name' is specified as a script base class parameter"))
}
}
override fun computeSupertypes() = listOf(getKotlinTypeByFqName(this, scriptDefinition.template.qualifiedName!!)).ifEmpty { listOf(builtIns.anyType) }
}
@@ -18,48 +18,28 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.*
import java.io.File
import java.lang.RuntimeException
import java.lang.UnsupportedOperationException
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.script.StandardScriptTemplate
interface KotlinScriptDefinition {
val name: String get() = "Kotlin Script"
open class KotlinScriptDefinition(val template: KClass<out Any>) {
val template: KClass<out Any>
open val name: String = "Kotlin Script"
// TODO: consider creating separate type (subtype? for kotlin scripts)
val fileType: LanguageFileType get() = KotlinFileType.INSTANCE
open val fileType: LanguageFileType = KotlinFileType.INSTANCE
fun <TF> isScript(file: TF): Boolean =
open fun <TF> isScript(file: TF): Boolean =
getFileName(file).endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
// TODO: replace these 3 functions with template property
fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter>
fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> = emptyList()
fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> = emptyList()
fun getScriptName(script: KtScript): Name =
open fun getScriptName(script: KtScript): Name =
ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
open fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
}
interface KotlinScriptExternalDependencies {
@@ -70,54 +50,5 @@ interface KotlinScriptExternalDependencies {
val scripts: Iterable<File> get() = emptyList()
}
class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScriptExternalDependencies>) : KotlinScriptExternalDependencies {
override val javaHome: String? get() = dependencies.firstOrNull { it.javaHome != null }?.javaHome
override val classpath: Iterable<File> get() = dependencies.flatMap { it.classpath }
override val imports: Iterable<String> get() = dependencies.flatMap { it.imports }
override val sources: Iterable<File> get() = dependencies.flatMap { it.sources }
override val scripts: Iterable<File> get() = dependencies.flatMap { it.scripts }
}
object StandardScriptDefinition : KotlinScriptDefinition(StandardScriptTemplate::class)
data class ScriptParameter(val name: Name, val type: KotlinType)
object StandardScriptDefinition : KotlinScriptDefinitionFromTemplate(StandardScriptTemplate::class)
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
getKotlinTypeByFqName(scriptDescriptor,
kClass.qualifiedName ?: throw RuntimeException("Cannot get FQN from $kClass"))
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
ClassId.topLevel(FqName(fqName)),
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
).defaultType
// TODO: support star projections
// TODO: support annotations on types and type parameters
// TODO: support type parameters on types and type projections
fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
val classifier = kType.classifier
if (classifier !is KClass<*>)
throw UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
val type = getKotlinType(scriptDescriptor, classifier)
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
val isNullable = kType.isMarkedNullable
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
}
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
val kType = kTypeProjection.type ?: throw UnsupportedOperationException("Star projections are not supported")
val type = getKotlinTypeByKType(scriptDescriptor, kType)
val variance = when (kTypeProjection.variance) {
KVariance.IN -> Variance.IN_VARIANCE
KVariance.OUT -> Variance.OUT_VARIANCE
KVariance.INVARIANT -> Variance.INVARIANT
null -> throw UnsupportedOperationException("Star projections are not supported")
}
return TypeProjectionImpl(variance, type)
}
@@ -0,0 +1,154 @@
/*
* 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.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.memberFunctions
import kotlin.reflect.primaryConstructor
open class KotlinScriptDefinitionFromAnnotatedTemplate(
template: KClass<out Any>,
providedResolver: ScriptDependenciesResolver? = null,
providedScriptFilePattern: String? = null,
val environment: Map<String, Any?>? = null
) : KotlinScriptDefinition(template) {
val scriptFilePattern by lazy {
providedScriptFilePattern ?: template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>()?.scriptFilePattern ?: DEFAULT_SCRIPT_FILE_PATTERN
}
val resolver: ScriptDependenciesResolver? by lazy {
val defAnn by lazy { template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>() }
when {
providedResolver != null -> providedResolver
// TODO: logScriptDefMessage missing or invalid constructor
defAnn != null ->
try {
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
log.error("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
}
}
catch (ex: ClassCastException) {
log.error("[kts] Script def error ${ex.message}")
null
}
else -> BasicScriptDependenciesResolver()
}
}
private val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolver::class.memberFunctions.find {
it.name == resolveMethod.name &&
sameSignature(it, resolveMethod)
}
?.annotations
?.filterIsInstance<AcceptedAnnotations>()
resolverMethodAnnotations?.flatMap {
val v = it.supportedAnnotationClasses
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
}
?: emptyList()
}
override val name = template.simpleName!!
override fun <TF> isScript(file: TF): Boolean =
scriptFilePattern.let { Regex(it).matches(getFileName(file)) }
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
override fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
val script = BasicScriptContents(file, getAnnotations = {
val classLoader = (template as Any).javaClass.classLoader
getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { KtAnnotationWrapper(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
.map { it.getProxy(classLoader) }
})
try {
val fileDeps = resolver?.resolve(script, environment, ::logScriptDefMessage, previousDependencies)
// TODO: use it as a Future
return fileDeps?.get()
}
catch (ex: ClassCastException) {
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.ERROR, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
return null
}
}
private fun <TF> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
is File -> {
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
getAnnotationEntriesFromVirtualFile(virtualFile, project)
}
else -> throw IllegalArgumentException("Unsupported file type $file")
}
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
if (file is KtFile) file.annotationEntries
else throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psiFile)
}
class BasicScriptContents<out TF>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
}
companion object {
internal val log = Logger.getInstance(KotlinScriptDefinitionFromAnnotatedTemplate::class.java)
}
}
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
when (reportSeverity) {
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromAnnotatedTemplate.log.error(msg)
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromAnnotatedTemplate.log.warn(msg)
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromAnnotatedTemplate.log.info(msg)
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromAnnotatedTemplate.log.debug(msg)
}
}
@@ -72,3 +72,9 @@ class KotlinScriptDefinitionProvider {
ServiceManager.getService(project, KotlinScriptDefinitionProvider::class.java)
}
}
fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
@@ -40,19 +41,18 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
val path = getFilePath(file)
return cache[path]
?: if (cacheOfNulls.contains(path)) null
else scriptDefinitionProvider.findScriptDefinition(file)
?.let { it.getDependenciesFor(file, project, null) }
.apply {
log.info("[kts] new cached deps for $path: ${this?.classpath?.joinToString(File.pathSeparator)}")
cacheLock.write {
if (this == null) {
cacheOfNulls.add(path)
}
else {
cache.put(path, this)
else scriptDefinitionProvider.findScriptDefinition(file)?.getDependenciesFor(file, project, null)
.apply {
log.info("[kts] new cached deps for $path: ${this?.classpath?.joinToString(File.pathSeparator)}")
cacheLock.write {
if (this == null) {
cacheOfNulls.add(path)
}
else {
cache.put(path, this)
}
}
}
}
}
// optimized for initial caching, additional handling of possible duplicates to save a call to distinct
@@ -162,3 +162,5 @@ internal fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean =
!(first.hasNext() || second.hasNext())
}
fun getScriptExternalDependencies(file: VirtualFile, project: Project): KotlinScriptExternalDependencies? =
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file)
@@ -1,30 +0,0 @@
/*
* 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.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
fun getScriptExternalDependencies(file: VirtualFile, project: Project): KotlinScriptExternalDependencies? =
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file)
@@ -1,172 +0,0 @@
/*
* 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.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
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 kotlin.reflect.KClass
import kotlin.reflect.memberFunctions
import kotlin.reflect.primaryConstructor
open class KotlinScriptDefinitionFromTemplate(final override val template: KClass<out Any>,
val resolver: ScriptDependenciesResolver? = null,
val scriptFilePattern: String? = null,
val environment: Map<String, Any?>? = null
) : KotlinScriptDefinition {
// TODO: remove this and simplify definitionAnnotation as soon as deprecated annotations will be removed
internal class ScriptTemplateDefinitionData(val resolverClass: KClass<out ScriptDependenciesResolver>,
makeResolver: () -> ScriptDependenciesResolver?,
val scriptFilePattern: String?) {
val resolver: ScriptDependenciesResolver? by lazy(makeResolver)
val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
val resolveMethod = ScriptDependenciesResolver::resolve
val resolverMethodAnnotations =
resolverClass.memberFunctions.find {
it.name == resolveMethod.name &&
sameSignature(it, resolveMethod)
}
?.annotations
?.filterIsInstance<AcceptedAnnotations>()
resolverMethodAnnotations?.flatMap {
val v = it.supportedAnnotationClasses
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
}
?: emptyList()
}
}
private val definitionData by lazy {
val defAnn = template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>()
val filePattern = scriptFilePattern ?: defAnn?.scriptFilePattern ?: DEFAULT_SCRIPT_FILE_PATTERN
when {
resolver != null -> ScriptTemplateDefinitionData(resolver.javaClass.kotlin, { resolver }, filePattern)
// TODO: logScriptDefMessage missing or invalid constructor
defAnn != null -> ScriptTemplateDefinitionData(defAnn.resolver,
{
try {
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
log.error("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
}
}
catch (ex: ClassCastException) {
log.error("[kts] Script def error ${ex.message}")
null
}
},
filePattern)
else -> ScriptTemplateDefinitionData(BasicScriptDependenciesResolver::class, ::BasicScriptDependenciesResolver, filePattern)
}
}
override val name = template.simpleName!!
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
template.primaryConstructor!!.parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> =
listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!))
override fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> =
getScriptParameters(scriptDescriptor).map { it.name }
override fun <TF> isScript(file: TF): Boolean =
definitionData.scriptFilePattern?.let { Regex(it).matches(getFileName(file)) } ?: false
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
override fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
val script = BasicScriptContents(file, getAnnotations = {
val classLoader = (template as Any).javaClass.classLoader
getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
definitionData.acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { KtAnnotationWrapper(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
.map { it.getProxy(classLoader) }
})
try {
val fileDeps = definitionData.resolver?.resolve(script, environment, ::logScriptDefMessage, previousDependencies)
// TODO: use it as a Future
return fileDeps?.get()
}
catch (ex: ClassCastException) {
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.ERROR, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
return null
}
}
private fun <TF> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
is File -> {
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
getAnnotationEntriesFromVirtualFile(virtualFile, project)
}
else -> throw IllegalArgumentException("Unsupported file type $file")
}
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
if (file is KtFile) file.annotationEntries
else throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psiFile)
}
class BasicScriptContents<out TF>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File? by lazy { getFile(myFile) }
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
override val text: CharSequence? by lazy { getFileContents(myFile) }
}
companion object {
internal val log = Logger.getInstance(KotlinScriptDefinitionFromTemplate::class.java)
}
}
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
when (reportSeverity) {
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromTemplate.log.error(msg)
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromTemplate.log.warn(msg)
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromTemplate.log.info(msg)
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromTemplate.log.debug(msg)
}
}
@@ -48,13 +48,13 @@ interface ScriptTemplatesProvider {
fun makeScriptDefsFromTemplatesProviderExtensions(project: Project,
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { ep, ex -> throw ex }
): List<KotlinScriptDefinitionFromTemplate> =
): List<KotlinScriptDefinitionFromAnnotatedTemplate> =
makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(),
errorsHandler)
fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProvider>,
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { ep, ex -> throw ex }
): List<KotlinScriptDefinitionFromTemplate> {
): List<KotlinScriptDefinitionFromAnnotatedTemplate> {
val idToVersion = hashMapOf<String, Int>()
return providers.filter { it.isValid }.sortedByDescending { it.version }.flatMap { provider ->
try {
@@ -65,12 +65,12 @@ fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProv
provider.templateClassNames.map {
val cl = loader.loadClass(it)
idToVersion.put(provider.id, provider.version)
KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
}
}
catch (ex: Exception) {
errorsHandler(provider, ex)
emptyList<KotlinScriptDefinitionFromTemplate>()
emptyList<KotlinScriptDefinitionFromAnnotatedTemplate>()
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.*
import kotlin.reflect.*
data class ScriptParameter(val name: Name, val type: KotlinType)
fun KotlinScriptDefinition.getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
template.primaryConstructor?.parameters
?.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
?: emptyList()
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
getKotlinTypeByFqName(scriptDescriptor,
kClass.qualifiedName ?: throw java.lang.RuntimeException("Cannot get FQN from $kClass"))
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
ClassId.topLevel(FqName(fqName)),
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
).defaultType
// TODO: support star projections
// TODO: support annotations on types and type parameters
// TODO: support type parameters on types and type projections
fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
val classifier = kType.classifier
if (classifier !is KClass<*>)
throw java.lang.UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
val type = getKotlinType(scriptDescriptor, classifier)
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
val isNullable = kType.isMarkedNullable
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
}
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
val kType = kTypeProjection.type ?: throw java.lang.UnsupportedOperationException("Star projections are not supported")
val type = getKotlinTypeByKType(scriptDescriptor, kType)
val variance = when (kTypeProjection.variance) {
KVariance.IN -> Variance.IN_VARIANCE
KVariance.OUT -> Variance.OUT_VARIANCE
KVariance.INVARIANT -> Variance.INVARIANT
null -> throw java.lang.UnsupportedOperationException("Star projections are not supported")
}
return TypeProjectionImpl(variance, type)
}
@@ -23,7 +23,8 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.addKotlinSourceRoots
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.script.ScriptTemplateDefinition
import org.jetbrains.kotlin.scripts.ScriptWithIntParam
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
@@ -32,8 +33,8 @@ import org.jetbrains.org.objectweb.asm.Opcodes
class ScriptGenTest : CodegenTestCase() {
companion object {
private val FIB_SCRIPT_DEFINITION = KotlinScriptDefinitionFromTemplate(ScriptWithIntParam::class, scriptFilePattern = ".*\\.lang\\.kt")
private val NO_PARAM_SCRIPT_DEFINITION = KotlinScriptDefinitionFromTemplate(Any::class, scriptFilePattern = ".*\\.kts")
private val FIB_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(ScriptWithIntParam::class, providedScriptFilePattern = ".*\\.lang\\.kt")
private val NO_PARAM_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(Any::class, providedScriptFilePattern = ".*\\.kts")
}
override fun setUp() {
@@ -228,7 +228,7 @@ class ScriptTemplateTest {
runIsolated: Boolean = true,
suppressOutput: Boolean = false,
includeKotlinRuntime: Boolean = true): Class<*>? =
compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput, includeKotlinRuntime)
compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput, includeKotlinRuntime)
private fun compileScriptImpl(
scriptPath: String,
@@ -16,12 +16,7 @@
package org.jetbrains.kotlin.descriptors;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.List;
public interface ScriptDescriptor extends ClassDescriptor {
int getPriority();
@@ -29,6 +24,4 @@ public interface ScriptDescriptor extends ClassDescriptor {
@NotNull
@Override
ClassConstructorDescriptor getUnsubstitutedPrimaryConstructor();
List<Pair<Name, KotlinType>> getScriptParametersToPassToSuperclass();
}
@@ -255,20 +255,6 @@ fun <D : CallableDescriptor> D.overriddenTreeUniqueAsSequence(useOriginal: Boole
return doBuildOverriddenTreeAsSequence()
}
fun ClassDescriptor.getConstructorByParams(params: List<KotlinType>): ConstructorDescriptor? =
getConstructors().firstOrNull {
when {
it.valueParameters.isEmpty() && params.isEmpty() -> true
it.valueParameters.size != params.size -> false
else -> it.valueParameters.zip(params) { ctorP, p -> ctorP.type == p }.all { it }
}
}
// TODO: inline and remove as soon as all usage sies are converted to kotlin
fun getConstructorByParamsMap(classDescriptor: ClassDescriptor, params: List<Pair<Name, KotlinType>>): ConstructorDescriptor? =
classDescriptor.getConstructorByParams(params.map { it.second })
fun CallableDescriptor.varargParameterPosition() =
valueParameters.indexOfFirst { it.varargElementType != null }
@@ -21,7 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ResolveScopeProvider
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.script.StandardScriptDefinition
import org.jetbrains.kotlin.script.getScriptDefinition
import kotlin.script.StandardScriptTemplate
@@ -34,7 +34,7 @@ class KotlinScriptResolveScopeProvider : ResolveScopeProvider() {
scriptDefinition == null -> null
// This is a workaround for completion in scripts and REPL to provide module dependencies
scriptDefinition == StandardScriptDefinition || scriptDefinition.template == Any::class -> null
scriptDefinition is KotlinScriptDefinitionFromTemplate -> // TODO: should include the file itself
scriptDefinition is KotlinScriptDefinitionFromAnnotatedTemplate -> // TODO: should include the file itself
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspathScope()
else -> null
}
@@ -67,7 +67,7 @@ import org.jetbrains.kotlin.psi.moduleInfo
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyScriptDescriptor
import org.jetbrains.kotlin.resolve.repl.ReplState
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
import java.awt.Color
import java.awt.Font
@@ -124,7 +124,7 @@ class KotlinConsoleRunner(
val executor = CommandExecutor(this)
var compilerHelper: ConsoleCompilerHelper by Delegates.notNull()
private val consoleScriptDefinition = object : KotlinScriptDefinitionFromTemplate(Any::class) {
private val consoleScriptDefinition = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
override fun <TF> isScript(file: TF): Boolean {
val vf = when (file) {
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.addKotlinSourceRoot
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import org.junit.Test
@@ -99,7 +99,7 @@ done
environment: Map<String, Any?>? = null,
runIsolated: Boolean = true,
suppressOutput: Boolean = false): Class<*>? =
compileScriptImpl("src/test/resources/scripts/" + scriptFileName, KotlinScriptDefinitionFromTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput)
compileScriptImpl("src/test/resources/scripts/" + scriptFileName, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput)
private fun compileScriptImpl(
scriptPath: String,