Merge scripting-impl to scripting-compiler-impl module...
to continue merging with the branch with further scripting refactorings
This commit is contained in:
@@ -9,6 +9,7 @@ plugins {
|
||||
dependencies {
|
||||
compileOnly(project(":compiler:frontend"))
|
||||
compileOnly(project(":compiler:frontend.java"))
|
||||
compileOnly(project(":compiler:frontend.script"))
|
||||
compileOnly(project(":compiler:psi"))
|
||||
compileOnly(project(":compiler:plugin-api"))
|
||||
compileOnly(project(":compiler:cli"))
|
||||
@@ -37,15 +38,15 @@ sourceSets {
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.3"
|
||||
apiVersion = "1.3"
|
||||
languageVersion = "1.2"
|
||||
apiVersion = "1.2"
|
||||
freeCompilerArgs += "-Xskip-metadata-version-check"
|
||||
}
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
val jar = runtimeJar {}
|
||||
runtimeJar()
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.NameUtils
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.starProjectedType
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.DependenciesResolver
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.host.getScriptingClass
|
||||
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
|
||||
import kotlin.script.experimental.location.ScriptExpectedLocation
|
||||
import kotlin.script.experimental.util.getOrError
|
||||
|
||||
// temporary trick with passing Any as a template and overwriting it below, TODO: fix after introducing new script definitions hierarchy
|
||||
abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinition(Any::class) {
|
||||
|
||||
abstract val scriptCompilationConfiguration: ScriptCompilationConfiguration
|
||||
|
||||
abstract val hostConfiguration: ScriptingHostConfiguration
|
||||
|
||||
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
|
||||
}
|
||||
|
||||
override val template: KClass<*> get() = baseClass
|
||||
|
||||
override val name: String
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: "Kotlin Script"
|
||||
|
||||
override val fileType: LanguageFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override fun isScript(fileName: String): Boolean =
|
||||
fileName.endsWith(".$fileExtension")
|
||||
|
||||
override fun getScriptName(script: KtScript): Name {
|
||||
val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name)
|
||||
return Name.identifier(fileBasedName.identifier.removeSuffix(".$fileExtension"))
|
||||
}
|
||||
|
||||
override val annotationsForSamWithReceivers: List<String>
|
||||
get() = emptyList()
|
||||
|
||||
override val dependencyResolver: DependenciesResolver by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
BridgeDependenciesResolver(scriptCompilationConfiguration)
|
||||
}
|
||||
|
||||
override val acceptedAnnotations: List<KClass<out Annotation>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
|
||||
.orEmpty()
|
||||
.map { getScriptingClass(it) as KClass<out Annotation> }
|
||||
}
|
||||
|
||||
override val implicitReceivers: List<KType> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.implicitReceivers]
|
||||
.orEmpty()
|
||||
.map { getScriptingClass(it).starProjectedType }
|
||||
}
|
||||
|
||||
override val providedProperties: List<Pair<String, KType>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.providedProperties]
|
||||
?.map { (k, v) -> k to getScriptingClass(v).starProjectedType }.orEmpty()
|
||||
}
|
||||
|
||||
override val additionalCompilerArguments: List<String>
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
|
||||
.orEmpty()
|
||||
|
||||
override val scriptExpectedLocations: List<ScriptExpectedLocation>
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.ide.acceptedLocations]?.map {
|
||||
when(it) {
|
||||
ScriptAcceptedLocation.Sources -> ScriptExpectedLocation.SourcesOnly
|
||||
ScriptAcceptedLocation.Tests -> ScriptExpectedLocation.TestsOnly
|
||||
ScriptAcceptedLocation.Libraries -> ScriptExpectedLocation.Libraries
|
||||
ScriptAcceptedLocation.Project -> ScriptExpectedLocation.Project
|
||||
ScriptAcceptedLocation.Everywhere -> ScriptExpectedLocation.Everywhere
|
||||
}
|
||||
} ?: listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly)
|
||||
|
||||
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
|
||||
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
|
||||
}
|
||||
|
||||
private fun getScriptingClass(type: KotlinType) =
|
||||
scriptingClassGetter(
|
||||
type,
|
||||
KotlinScriptDefinition::class, // Assuming that the KotlinScriptDefinition class is loaded in the proper classloader
|
||||
hostConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class KotlinScriptDefinitionAdapterFromNewAPI(
|
||||
override val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
override val hostConfiguration: ScriptingHostConfiguration
|
||||
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
|
||||
|
||||
override val name: String get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: super.name
|
||||
|
||||
override val fileExtension: String
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.KotlinType
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.fileExtension
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.host.configurationDependencies
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
|
||||
class LazyScriptDefinitionFromDiscoveredClass internal constructor(
|
||||
private val annotationsFromAsm: ArrayList<BinAnnData>,
|
||||
private val className: String,
|
||||
private val classpath: List<File>,
|
||||
private val messageCollector: MessageCollector
|
||||
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
|
||||
|
||||
constructor(
|
||||
classBytes: ByteArray,
|
||||
className: String,
|
||||
classpath: List<File>,
|
||||
messageCollector: MessageCollector
|
||||
) : this(loadAnnotationsFromClass(classBytes), className, classpath, messageCollector)
|
||||
|
||||
override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
|
||||
configurationDependencies.append(JvmDependency(classpath))
|
||||
}
|
||||
}
|
||||
|
||||
override val scriptCompilationConfiguration: ScriptCompilationConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
|
||||
)
|
||||
try {
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(className),
|
||||
hostConfiguration,
|
||||
LazyScriptDefinitionFromDiscoveredClass::class
|
||||
)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className")
|
||||
InvalidScriptDefinition
|
||||
} catch (ex: Exception) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR,
|
||||
"Error processing script definition class $className: ${ex.message}\nclasspath:\n${classpath.joinToString("\n", " ")}"
|
||||
)
|
||||
InvalidScriptDefinition
|
||||
}
|
||||
}
|
||||
|
||||
override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName }?.args
|
||||
?.find { it.name == "fileExtension" }?.value
|
||||
?: scriptCompilationConfiguration.let {
|
||||
it[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||
}
|
||||
}
|
||||
|
||||
override val name: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName!! }?.args?.find { it.name == "name" }?.value
|
||||
?: super.name
|
||||
}
|
||||
}
|
||||
|
||||
val InvalidScriptDefinition = ScriptCompilationConfiguration()
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
|
||||
|
||||
protected val lock = ReentrantReadWriteLock()
|
||||
|
||||
protected abstract val currentDefinitions: Sequence<KotlinScriptDefinition>
|
||||
|
||||
private var _cachedDefinitions: Sequence<KotlinScriptDefinition>? = null
|
||||
private val cachedDefinitions: Sequence<KotlinScriptDefinition>
|
||||
get() {
|
||||
assert(lock.readLockCount > 0) { "cachedDefinitions should only be used under the read lock" }
|
||||
if (_cachedDefinitions == null) lock.write {
|
||||
_cachedDefinitions = CachingSequence(currentDefinitions.constrainOnce())
|
||||
}
|
||||
return _cachedDefinitions!!
|
||||
}
|
||||
|
||||
protected fun clearCache() {
|
||||
lock.write {
|
||||
_cachedDefinitions = null
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun nonScriptFileName(fileName: String) = nonScriptFilenameSuffixes.any {
|
||||
fileName.endsWith(it, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? =
|
||||
if (nonScriptFileName(fileName)) null
|
||||
else lock.read {
|
||||
cachedDefinitions.firstOrNull { it.isScript(fileName) }
|
||||
}
|
||||
|
||||
override fun isScript(fileName: String) = findScriptDefinition(fileName) != null
|
||||
|
||||
override fun getKnownFilenameExtensions(): Sequence<String> = lock.read {
|
||||
cachedDefinitions.map { it.fileExtension }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// TODO: find a common place for storing kotlin-related extensions and reuse values from it everywhere
|
||||
protected val nonScriptFilenameSuffixes = arrayOf(".${KotlinFileType.EXTENSION}", ".${JavaFileType.DEFAULT_EXTENSION}")
|
||||
}
|
||||
}
|
||||
|
||||
private class CachingSequence<T>(from: Sequence<T>) : Sequence<T> {
|
||||
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
private val sequenceIterator = from.iterator()
|
||||
private val cache = arrayListOf<T>()
|
||||
|
||||
private inner class CachingIterator : Iterator<T> {
|
||||
|
||||
private var cacheCursor = 0
|
||||
|
||||
override fun hasNext(): Boolean =
|
||||
lock.read { cacheCursor < cache.size }
|
||||
// iterator's hasNext can mutate the iterator's state, therefore write lock is needed
|
||||
|| lock.write { cacheCursor < cache.size || sequenceIterator.hasNext() }
|
||||
|
||||
override fun next(): T {
|
||||
lock.read {
|
||||
if (cacheCursor < cache.size) return cache[cacheCursor++]
|
||||
}
|
||||
// lock.write is not an upgrade but retake, therefore - one more check needed
|
||||
lock.write {
|
||||
return if (cacheCursor < cache.size) cache[cacheCursor++]
|
||||
else sequenceIterator.next().also { cache.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> = CachingIterator()
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionsSource
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
import kotlin.coroutines.experimental.buildSequence
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.KotlinType
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.templates.ScriptTemplateDefinition
|
||||
|
||||
const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/"
|
||||
const val SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT = ".classname"
|
||||
|
||||
class ScriptDefinitionsFromClasspathDiscoverySource(
|
||||
private val classpath: List<File>,
|
||||
private val scriptResolverEnv: Map<String, Any?>,
|
||||
private val messageCollector: MessageCollector
|
||||
) : ScriptDefinitionsSource {
|
||||
|
||||
override val definitions: Sequence<KotlinScriptDefinition> = run {
|
||||
discoverScriptTemplatesInClasspath(
|
||||
classpath,
|
||||
this::class.java.classLoader,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun discoverScriptTemplatesInClasspath(
|
||||
classpath: List<File>,
|
||||
baseClassLoader: ClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): Sequence<KotlinScriptDefinition> = buildSequence {
|
||||
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
|
||||
val loader = LazyClasspathWithClassLoader(baseClassLoader) { classpath }
|
||||
|
||||
// for jar files the definition class is expected in the same jar as the discovery file
|
||||
// in case of directories, the class output may come separate from the resources, so some candidates should be deffered and processed later
|
||||
val defferedDirDependencies = ArrayList<File>()
|
||||
val defferedDefinitionCandidates = ArrayList<String>()
|
||||
for (dep in classpath) {
|
||||
try {
|
||||
when {
|
||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||
JarFile(dep).use { jar ->
|
||||
if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) {
|
||||
val definitionNames = jar.entries().asSequence().mapNotNull {
|
||||
if (it.isDirectory || !it.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) null
|
||||
else it.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH).removeSuffix(SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT)
|
||||
}.toList()
|
||||
val (loadedDefinitions, notFoundClasses) =
|
||||
definitionNames.partitionLoadJarDefinitions(jar, loader, scriptResolverEnv, messageCollector)
|
||||
if (notFoundClasses.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to find script definitions [${notFoundClasses.joinToString(", ")}]"
|
||||
)
|
||||
}
|
||||
loadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dep.isDirectory -> {
|
||||
defferedDirDependencies.add(dep) // there is no way to know that the dependency is fully "used" so we add it to the list anyway
|
||||
val discoveryDir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH)
|
||||
if (discoveryDir.isDirectory) {
|
||||
val (foundDefinitionClasses, notFoundDefinitions) = discoveryDir.listFiles().map { it.name }
|
||||
.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||
foundDefinitionClasses.forEach {
|
||||
yield(it)
|
||||
}
|
||||
defferedDefinitionCandidates.addAll(notFoundDefinitions)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
||||
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
var remainingDefinitionCandidates: List<String> = defferedDefinitionCandidates
|
||||
for (dep in defferedDirDependencies) {
|
||||
if (remainingDefinitionCandidates.isEmpty()) break
|
||||
try {
|
||||
val (foundDefinitionClasses, notFoundDefinitions) =
|
||||
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||
foundDefinitionClasses.forEach {
|
||||
yield(it)
|
||||
}
|
||||
remainingDefinitionCandidates = notFoundDefinitions
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
if (remainingDefinitionCandidates.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadScriptTemplatesFromClasspath(
|
||||
scriptTemplates: List<String>,
|
||||
classpath: List<File>,
|
||||
dependenciesClasspath: List<File>,
|
||||
baseClassLoader: ClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): Sequence<KotlinScriptDefinition> =
|
||||
if (scriptTemplates.isEmpty()) emptySequence()
|
||||
else buildSequence {
|
||||
// trying the direct classloading from baseClassloader first, since this is the most performant variant
|
||||
val (initialLoadedDefinitions, initialNotFoundTemplates) = scriptTemplates.partitionMapNotNull {
|
||||
loadScriptDefinition(
|
||||
baseClassLoader,
|
||||
it,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
initialLoadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
// then searching the remaining templates in the supplied classpath
|
||||
|
||||
var remainingTemplates = initialNotFoundTemplates
|
||||
val classpathAndLoader =
|
||||
LazyClasspathWithClassLoader(baseClassLoader) { classpath + dependenciesClasspath }
|
||||
for (dep in classpath) {
|
||||
if (remainingTemplates.isEmpty()) break
|
||||
|
||||
try {
|
||||
val (loadedDefinitions, notFoundTemplates) = when {
|
||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||
JarFile(dep).use { jar ->
|
||||
remainingTemplates.partitionLoadJarDefinitions(jar, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||
}
|
||||
}
|
||||
dep.isDirectory -> {
|
||||
remainingTemplates.partitionLoadDirDefinitions(dep, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||
}
|
||||
else -> {
|
||||
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
||||
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
|
||||
DefinitionsLoadPartitionResult(
|
||||
listOf(),
|
||||
remainingTemplates
|
||||
)
|
||||
}
|
||||
}
|
||||
if (loadedDefinitions.isNotEmpty()) {
|
||||
loadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
remainingTemplates = notFoundTemplates
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingTemplates.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to find script definition classes: ${remainingTemplates.joinToString(", ")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class DefinitionsLoadPartitionResult(
|
||||
val loaded: List<KotlinScriptDefinition>,
|
||||
val notFound: List<String>
|
||||
)
|
||||
|
||||
private inline fun List<String>.partitionLoadDefinitions(
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector,
|
||||
getBytes: (String) -> ByteArray?
|
||||
): DefinitionsLoadPartitionResult {
|
||||
val loaded = ArrayList<KotlinScriptDefinition>()
|
||||
val notFound = ArrayList<String>()
|
||||
for (definitionName in this) {
|
||||
val classBytes = getBytes(definitionName)
|
||||
val definition = classBytes?.let {
|
||||
loadScriptDefinition(
|
||||
it,
|
||||
definitionName,
|
||||
classpathAndLoader,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
when {
|
||||
definition != null -> loaded.add(definition)
|
||||
classBytes != null -> {}
|
||||
else -> notFound.add(definitionName)
|
||||
}
|
||||
}
|
||||
return DefinitionsLoadPartitionResult(loaded, notFound)
|
||||
}
|
||||
|
||||
private fun List<String>.partitionLoadJarDefinitions(
|
||||
jar: JarFile,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||
jar.getJarEntry("${definitionName.replace('.', '/')}.class")?.let { jar.getInputStream(it).readBytes() }
|
||||
}
|
||||
|
||||
private fun List<String>.partitionLoadDirDefinitions(
|
||||
dir: File,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||
File(dir, "${definitionName.replace('.', '/')}.class").takeIf { it.exists() && it.isFile }?.readBytes()
|
||||
}
|
||||
|
||||
private fun loadScriptDefinition(
|
||||
templateClassBytes: ByteArray,
|
||||
templateClassName: String,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): KotlinScriptDefinition? {
|
||||
val anns = loadAnnotationsFromClass(templateClassBytes)
|
||||
for (ann in anns) {
|
||||
var def: KotlinScriptDefinition? = null
|
||||
if (ann.name == KotlinScript::class.simpleName) {
|
||||
def = LazyScriptDefinitionFromDiscoveredClass(
|
||||
anns,
|
||||
templateClassName,
|
||||
classpathAndLoader.classpath,
|
||||
messageCollector
|
||||
)
|
||||
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
|
||||
val templateClass = classpathAndLoader.classLoader.loadClass(templateClassName).kotlin
|
||||
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, classpathAndLoader.classpath)
|
||||
}
|
||||
if (def != null) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: Added template $templateClassName from ${classpathAndLoader.classpath}"
|
||||
)
|
||||
return def
|
||||
}
|
||||
}
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadScriptDefinition(
|
||||
classLoader: ClassLoader,
|
||||
template: String,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): KotlinScriptDefinition? {
|
||||
try {
|
||||
val cls = classLoader.loadClass(template)
|
||||
val def =
|
||||
if (cls.annotations.firstIsInstanceOrNull<KotlinScript>() != null) {
|
||||
val environment = defaultJvmScriptingHostConfiguration
|
||||
KotlinScriptDefinitionAdapterFromNewAPI(
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(cls.kotlin),
|
||||
environment,
|
||||
KotlinScriptDefinition::class
|
||||
),
|
||||
environment
|
||||
)
|
||||
} else {
|
||||
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
|
||||
}
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.INFO,
|
||||
"Added script definition $template to configuration: name = ${def.name}, " +
|
||||
"resolver = ${def.dependencyResolver.javaClass.name}"
|
||||
)
|
||||
return def
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
// not found - not an error, return null
|
||||
} catch (ex: Exception) {
|
||||
// other exceptions - might be an error
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Error on loading script definition $template: ${ex.message}"
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private class LazyClasspathWithClassLoader(baseClassLoader: ClassLoader, getClasspath: () -> List<File>) {
|
||||
val classpath by lazy { getClasspath() }
|
||||
val classLoader by lazy {
|
||||
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T, R> Iterable<T>.partitionMapNotNull(fn: (T) -> R?): Pair<List<R>, List<T>> {
|
||||
val mapped = ArrayList<R>()
|
||||
val failed = ArrayList<T>()
|
||||
for (v in this) {
|
||||
val r = fn(v)
|
||||
if (r != null) {
|
||||
mapped.add(r)
|
||||
} else {
|
||||
failed.add(v)
|
||||
}
|
||||
}
|
||||
return mapped to failed
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
|
||||
internal class BinAnnArgData(
|
||||
val name: String?,
|
||||
val value: String
|
||||
)
|
||||
|
||||
internal class BinAnnData(
|
||||
val name: String,
|
||||
val args: ArrayList<BinAnnArgData> = arrayListOf()
|
||||
)
|
||||
|
||||
private class TemplateAnnotationVisitor(val anns: ArrayList<BinAnnData> = arrayListOf()) : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
anns.last().args.add(BinAnnArgData(name, value.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private class TemplateClassVisitor(val annVisitor: TemplateAnnotationVisitor) : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
val shortName = Type.getType(desc).internalName.substringAfterLast("/")
|
||||
if (shortName.startsWith("KotlinScript") || shortName.startsWith("ScriptTemplate")) {
|
||||
annVisitor.anns.add(BinAnnData(shortName))
|
||||
return annVisitor
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadAnnotationsFromClass(fileContents: ByteArray): ArrayList<BinAnnData> {
|
||||
|
||||
val visitor =
|
||||
TemplateClassVisitor(TemplateAnnotationVisitor())
|
||||
|
||||
ClassReader(fileContents).accept(visitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
|
||||
return visitor.annVisitor.anns
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.definitions
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
|
||||
fun PsiFile.scriptDefinition(): KotlinScriptDefinition? {
|
||||
// Do not use psiFile.script, see comments in findScriptDefinition
|
||||
if (this !is KtFile/* || this.script == null*/) return null
|
||||
val file = virtualFile ?: originalFile.virtualFile ?: return null
|
||||
if (file.isNonScript()) return null
|
||||
|
||||
return scriptDefinitionByFileName(project, file.name)
|
||||
}
|
||||
|
||||
fun VirtualFile.findScriptDefinition(project: Project): KotlinScriptDefinition? {
|
||||
if (isNonScript()) return null
|
||||
// Do not use psiFile.script here because this method can be called during indexes access
|
||||
// and accessing stubs may cause deadlock
|
||||
// TODO: measure performance effect and if necessary consider detecting indexing here or using separate logic for non-IDE operations to speed up filtering
|
||||
if ((PsiManager.getInstance(project).findFile(this) as? KtFile)/*?.script*/ == null) return null
|
||||
|
||||
return scriptDefinitionByFileName(project, name)
|
||||
}
|
||||
|
||||
fun scriptDefinitionByFileName(project: Project, fileName: String): KotlinScriptDefinition {
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) ?: return null
|
||||
?: throw IllegalStateException("Unable to get script definition: ScriptDefinitionProvider is not configured.")
|
||||
|
||||
return scriptDefinitionProvider.findScriptDefinition(fileName) ?: scriptDefinitionProvider.getDefaultScriptDefinition()
|
||||
}
|
||||
|
||||
private fun VirtualFile.isNonScript(): Boolean =
|
||||
isDirectory ||
|
||||
extension == KotlinFileType.EXTENSION ||
|
||||
extension == JavaClassFileType.INSTANCE.defaultExtension ||
|
||||
!this.isKotlinFileType()
|
||||
|
||||
private fun VirtualFile.isKotlinFileType(): Boolean {
|
||||
val typeRegistry = FileTypeRegistry.getInstance()
|
||||
return typeRegistry.getFileTypeByFile(this) == KotlinFileType.INSTANCE ||
|
||||
typeRegistry.getFileTypeByFileName(name) == KotlinFileType.INSTANCE
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.extensions
|
||||
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtImportInfo
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension
|
||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||
|
||||
class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension {
|
||||
|
||||
// initially copied from org.jetbrains.kotlin.resolve.lazy.FileScopeFactory.DefaultImportImpl, but kept separate to simplify dependencies
|
||||
// and allow easier extension
|
||||
private class ScriptExtraImportImpl(private val importPath: ImportPath) : KtImportInfo {
|
||||
override val isAllUnder: Boolean get() = importPath.isAllUnder
|
||||
|
||||
override val importContent = KtImportInfo.ImportContent.FqNameBased(importPath.fqName)
|
||||
|
||||
override val aliasName: String? get() = importPath.alias?.asString()
|
||||
|
||||
override val importedFqName: FqName? get() = importPath.fqName
|
||||
}
|
||||
|
||||
override fun getExtraImports(ktFile: KtFile): Collection<KtImportInfo> =
|
||||
ktFile.takeIf { it.isScript() }?.let { file ->
|
||||
val scriptDependencies = ScriptDependenciesProvider.getInstance(file.project)?.getScriptDependencies(file.originalFile)
|
||||
scriptDependencies?.imports?.map {
|
||||
ScriptExtraImportImpl(
|
||||
ImportPath.fromString(it)
|
||||
)
|
||||
}
|
||||
}.orEmpty()
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.extensions
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
||||
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.scripting.shared.resolve.LazyScriptDescriptor
|
||||
|
||||
class ScriptingResolveExtension : SyntheticResolveExtension {
|
||||
override fun generateSyntheticClasses(
|
||||
thisDescriptor: PackageFragmentDescriptor,
|
||||
name: Name,
|
||||
ctx: LazyClassContext,
|
||||
declarationProvider: PackageMemberDeclarationProvider,
|
||||
result: MutableSet<ClassDescriptor>
|
||||
) {
|
||||
declarationProvider.getScriptDeclarations(name).mapTo(result) {
|
||||
LazyScriptDescriptor(ctx as ResolveSession, thisDescriptor, name, it)
|
||||
}
|
||||
|
||||
super.generateSyntheticClasses(thisDescriptor, name, ctx, declarationProvider, result)
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared
|
||||
|
||||
const val KOTLIN_SCRIPTING_PLUGIN_ID = "kotlin.scripting"
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
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.resolve.lazy.descriptors.LazyClassMemberScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.KotlinTypeFactory
|
||||
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
|
||||
|
||||
class LazyScriptClassMemberScope(
|
||||
resolveSession: ResolveSession,
|
||||
declarationProvider: ClassMemberDeclarationProvider,
|
||||
private val scriptDescriptor: LazyScriptDescriptor,
|
||||
trace: BindingTrace
|
||||
) : LazyClassMemberScope(resolveSession, declarationProvider, scriptDescriptor, trace) {
|
||||
|
||||
private val scriptPrimaryConstructor: () -> ClassConstructorDescriptorImpl? = resolveSession.storageManager.createNullableLazyValue {
|
||||
val baseClass = scriptDescriptor.baseClassDescriptor()
|
||||
val baseConstructorDescriptor = baseClass?.unsubstitutedPrimaryConstructor
|
||||
if (baseConstructorDescriptor != null) {
|
||||
val implicitReceiversParamTypes =
|
||||
scriptDescriptor.implicitReceivers.mapIndexed { idx, receiver ->
|
||||
val name =
|
||||
if (receiver is ScriptDescriptor) "$IMPORTED_SCRIPT_PARAM_NAME_PREFIX${receiver.name}"
|
||||
else "$IMPLICIT_RECEIVER_PARAM_NAME_PREFIX$idx"
|
||||
name to receiver.defaultType
|
||||
}
|
||||
val providedPropertiesParamTypes =
|
||||
scriptDescriptor.scriptProvidedProperties.map {
|
||||
it.name.identifier to it.type
|
||||
}
|
||||
val annotations = baseConstructorDescriptor.annotations
|
||||
val constructorDescriptor = ClassConstructorDescriptorImpl.create(
|
||||
scriptDescriptor, annotations, baseConstructorDescriptor.isPrimary, scriptDescriptor.source
|
||||
)
|
||||
var paramsIndexBase = baseConstructorDescriptor.valueParameters.lastIndex + 1
|
||||
val syntheticParameters =
|
||||
(implicitReceiversParamTypes + providedPropertiesParamTypes).map { param: Pair<String, KotlinType> ->
|
||||
ValueParameterDescriptorImpl(
|
||||
constructorDescriptor,
|
||||
null,
|
||||
paramsIndexBase++,
|
||||
Annotations.EMPTY,
|
||||
Name.identifier(param.first),
|
||||
param.second,
|
||||
false, false, false, null, SourceElement.NO_SOURCE
|
||||
)
|
||||
}
|
||||
val parameters = baseConstructorDescriptor.valueParameters.map { it.copy(constructorDescriptor, it.name, it.index) } +
|
||||
syntheticParameters
|
||||
constructorDescriptor.initialize(parameters, baseConstructorDescriptor.visibility)
|
||||
constructorDescriptor.returnType = scriptDescriptor.defaultType
|
||||
constructorDescriptor
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolvePrimaryConstructor(): ClassConstructorDescriptor? {
|
||||
val constructor = scriptPrimaryConstructor()
|
||||
?: ClassConstructorDescriptorImpl.create(
|
||||
scriptDescriptor,
|
||||
Annotations.EMPTY,
|
||||
true,
|
||||
SourceElement.NO_SOURCE
|
||||
).initialize(
|
||||
emptyList(),
|
||||
Visibilities.PUBLIC
|
||||
)
|
||||
setDeferredReturnType(constructor)
|
||||
return constructor
|
||||
}
|
||||
|
||||
override fun getNonDeclaredProperties(name: Name, result: MutableSet<PropertyDescriptor>) {
|
||||
super.getNonDeclaredProperties(name, result)
|
||||
if (scriptDescriptor.resultFieldName() == name.asString()) {
|
||||
scriptDescriptor.resultValue?.let {
|
||||
result.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPropertiesFromPrimaryConstructorParameters(name: Name, result: MutableSet<PropertyDescriptor>) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val IMPLICIT_RECEIVER_PARAM_NAME_PREFIX = "\$\$implicitReceiver"
|
||||
const val IMPORTED_SCRIPT_PARAM_NAME_PREFIX = "\$\$importedScript"
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.substitute(vararg types: KotlinType): KotlinType? =
|
||||
KotlinTypeFactory.simpleType(this.defaultType, arguments = types.map { it.asTypeProjection() })
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_FILE
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_PSI
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
import org.jetbrains.kotlin.resolve.source.toSourceElement
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.script.ScriptPriorities
|
||||
import org.jetbrains.kotlin.scripting.shared.definitions.scriptDefinitionByFileName
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
|
||||
class LazyScriptDescriptor(
|
||||
val resolveSession: ResolveSession,
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
name: Name,
|
||||
internal val scriptInfo: KtScriptInfo
|
||||
) : ScriptDescriptor, LazyClassDescriptor(
|
||||
resolveSession,
|
||||
containingDeclaration,
|
||||
name,
|
||||
scriptInfo,
|
||||
/* isExternal = */ false
|
||||
) {
|
||||
init {
|
||||
resolveSession.trace.record(BindingContext.SCRIPT, scriptInfo.script, this)
|
||||
}
|
||||
|
||||
override fun getResultValue(): ReplResultPropertyDescriptor? {
|
||||
val expression = scriptInfo.script
|
||||
.getChildOfType<KtBlockExpression>()
|
||||
?.getChildrenOfType<KtScriptInitializer>()?.lastOrNull()
|
||||
?.getChildOfType<KtExpression>()
|
||||
|
||||
val type = expression?.let {
|
||||
resolveSession.trace.bindingContext.getType(it)
|
||||
}
|
||||
|
||||
return if (type != null && !type.isUnit() && !type.isNothing()) {
|
||||
resultFieldName()?.let {
|
||||
ReplResultPropertyDescriptor(
|
||||
Name.identifier(it),
|
||||
type,
|
||||
this.thisAsReceiverParameter,
|
||||
this,
|
||||
expression.toSourceElement()
|
||||
)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
fun resultFieldName(): String? {
|
||||
val scriptPriority = scriptInfo.script.getUserData(ScriptPriorities.PRIORITY_KEY)
|
||||
if (scriptPriority != null) {
|
||||
return "res$scriptPriority"
|
||||
}
|
||||
val scriptName = name.asString()
|
||||
return if (scriptName.startsWith("Line_")) {
|
||||
"res${scriptName.split("_")[1]}"
|
||||
} else "\$\$result"
|
||||
}
|
||||
|
||||
private val sourceElement = scriptInfo.script.toSourceElement()
|
||||
|
||||
override fun getSource() = sourceElement
|
||||
|
||||
private val priority: Int = ScriptPriorities.getScriptPriority(scriptInfo.script)
|
||||
|
||||
override fun getPriority() = priority
|
||||
|
||||
val scriptDefinition: () -> KotlinScriptDefinition = resolveSession.storageManager.createLazyValue {
|
||||
scriptDefinitionByFileName(resolveSession.project, scriptInfo.script.containingKtFile.name)
|
||||
}
|
||||
|
||||
override fun substitute(substitutor: TypeSubstitutor) = this
|
||||
|
||||
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R =
|
||||
visitor.visitScriptDescriptor(this, data)
|
||||
|
||||
override fun createMemberScope(c: LazyClassContext, declarationProvider: ClassMemberDeclarationProvider): LazyScriptClassMemberScope =
|
||||
LazyScriptClassMemberScope(
|
||||
// Must be a ResolveSession for scripts
|
||||
c as ResolveSession,
|
||||
declarationProvider,
|
||||
this,
|
||||
c.trace
|
||||
)
|
||||
|
||||
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
|
||||
|
||||
internal val baseClassDescriptor: () -> ClassDescriptor? = resolveSession.storageManager.createNullableLazyValue {
|
||||
val template = scriptDefinition().template
|
||||
findTypeDescriptor(
|
||||
template,
|
||||
if (template.qualifiedName?.startsWith("kotlin.script.templates.standard") == true) Errors.MISSING_SCRIPT_STANDARD_TEMPLATE
|
||||
else Errors.MISSING_SCRIPT_BASE_CLASS
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeSupertypes() = listOf(baseClassDescriptor()?.defaultType ?: builtIns.anyType)
|
||||
|
||||
private inner class ImportedScriptDescriptorsFinder {
|
||||
|
||||
val fileManager = VirtualFileManager.getInstance()
|
||||
val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL)
|
||||
val psiManager = PsiManager.getInstance(scriptInfo.script.project)
|
||||
|
||||
operator fun invoke(importedScriptFile: File): ScriptDescriptor? {
|
||||
|
||||
fun errorDescriptor(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ScriptDescriptor? {
|
||||
reportErrorString1(errorDiagnostic, importedScriptFile.path)
|
||||
return null
|
||||
}
|
||||
|
||||
val vfile = localFS.findFileByPath(importedScriptFile.path)
|
||||
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_FILE)
|
||||
val psiFile = psiManager.findFile(vfile)
|
||||
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_PSI)
|
||||
// Note: is not an error now - if import references other valid source file, it is simply compiled along with script
|
||||
// TODO: check if this is the behavior we want to have - see #KT-28916
|
||||
val ktScript = (psiFile as? KtFile)?.declarations?.firstIsInstanceOrNull<KtScript>()
|
||||
?: return null
|
||||
return resolveSession.getScriptDescriptor(ktScript) as ScriptDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
private val scriptImplicitReceivers: () -> List<ClassDescriptor> = resolveSession.storageManager.createLazyValue {
|
||||
val res = ArrayList<ClassDescriptor>()
|
||||
|
||||
val importedScriptsFiles = ScriptDependenciesProvider.getInstance(scriptInfo.script.project)
|
||||
?.getScriptDependencies(scriptInfo.script.containingKtFile)?.scripts
|
||||
if (importedScriptsFiles != null) {
|
||||
val findImportedScriptDescriptor = ImportedScriptDescriptorsFinder()
|
||||
importedScriptsFiles.mapNotNullTo(res) {
|
||||
findImportedScriptDescriptor(it)
|
||||
}
|
||||
}
|
||||
|
||||
scriptDefinition().implicitReceivers.mapNotNullTo(res) { receiver ->
|
||||
findTypeDescriptor(receiver, Errors.MISSING_SCRIPT_RECEIVER_CLASS)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||
findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic)
|
||||
|
||||
internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||
findTypeDescriptor(type.classId, type.toString(), errorDiagnostic)
|
||||
|
||||
internal fun findTypeDescriptor(
|
||||
classId: ClassId?, typeName: String,
|
||||
errorDiagnostic: DiagnosticFactory1<PsiElement, String>?
|
||||
): ClassDescriptor? {
|
||||
val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) }
|
||||
if (typeDescriptor == null) {
|
||||
reportErrorString1(errorDiagnostic, classId?.asSingleFqName()?.toString() ?: typeName)
|
||||
}
|
||||
return typeDescriptor
|
||||
}
|
||||
|
||||
private fun reportErrorString1(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?, arg: String) {
|
||||
if (errorDiagnostic != null) {
|
||||
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
|
||||
resolveSession.trace.report(
|
||||
errorDiagnostic.on(
|
||||
scriptInfo.script,
|
||||
arg
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImplicitReceivers(): List<ClassDescriptor> = scriptImplicitReceivers()
|
||||
|
||||
private val scriptProvidedProperties: () -> ScriptProvidedPropertiesDescriptor = resolveSession.storageManager.createLazyValue {
|
||||
ScriptProvidedPropertiesDescriptor(this)
|
||||
}
|
||||
|
||||
override fun getScriptProvidedProperties(): List<PropertyDescriptor> = scriptProvidedProperties().properties()
|
||||
|
||||
private val scriptOuterScope: () -> LexicalScope = resolveSession.storageManager.createLazyValue {
|
||||
var outerScope = super.getOuterScope()
|
||||
val outerScopeReceivers = implicitReceivers.let {
|
||||
if (scriptDefinition().providedProperties.isEmpty()) {
|
||||
it
|
||||
} else {
|
||||
it + ScriptProvidedPropertiesDescriptor(this)
|
||||
}
|
||||
}
|
||||
for (receiverClassDescriptor in outerScopeReceivers.asReversed()) {
|
||||
outerScope = LexicalScopeImpl(
|
||||
outerScope,
|
||||
receiverClassDescriptor,
|
||||
true,
|
||||
receiverClassDescriptor.thisAsReceiverParameter,
|
||||
LexicalScopeKind.CLASS_MEMBER_SCOPE
|
||||
)
|
||||
}
|
||||
outerScope
|
||||
}
|
||||
|
||||
override fun getOuterScope(): LexicalScope = scriptOuterScope()
|
||||
|
||||
private val scriptClassAnnotations: () -> Annotations = resolveSession.storageManager.createLazyValue {
|
||||
baseClassDescriptor()?.annotations?.let { ann ->
|
||||
FilteredAnnotations(ann) { fqname ->
|
||||
val shortName = fqname.shortName().identifier
|
||||
// TODO: consider more precise annotation filtering
|
||||
!shortName.startsWith("KotlinScript") && !shortName.startsWith("ScriptTemplate")
|
||||
}
|
||||
} ?: super.annotations
|
||||
}
|
||||
|
||||
override val annotations: Annotations
|
||||
get() = scriptClassAnnotations()
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class ReplResultPropertyDescriptor(
|
||||
name: Name,
|
||||
kotlinType: KotlinType,
|
||||
receiver: ReceiverParameterDescriptor?,
|
||||
script: ScriptDescriptor,
|
||||
source: SourceElement
|
||||
) : PropertyDescriptorImpl(
|
||||
script,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
false,
|
||||
name,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
source,
|
||||
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
|
||||
/* isDelegated = */ false
|
||||
) {
|
||||
init {
|
||||
setType(kotlinType, emptyList(), receiver, null)
|
||||
initialize(
|
||||
null, null
|
||||
)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.MutableClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
|
||||
class ScriptProvidedPropertiesDescriptor(script: LazyScriptDescriptor) :
|
||||
MutableClassDescriptor(
|
||||
script,
|
||||
ClassKind.CLASS, false, false,
|
||||
Name.special("<synthetic script provided properties for ${script.name}>"),
|
||||
SourceElement.NO_SOURCE,
|
||||
LockBasedStorageManager.NO_LOCKS
|
||||
) {
|
||||
|
||||
init {
|
||||
modality = Modality.FINAL
|
||||
visibility = Visibilities.PUBLIC
|
||||
setTypeParameterDescriptors(emptyList())
|
||||
createTypeConstructor()
|
||||
}
|
||||
|
||||
private val memberScope: () -> ScriptProvidedPropertiesMemberScope = script.resolveSession.storageManager.createLazyValue {
|
||||
ScriptProvidedPropertiesMemberScope(
|
||||
script.name.identifier,
|
||||
properties()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUnsubstitutedMemberScope(): MemberScope = memberScope()
|
||||
|
||||
val properties: () -> List<ScriptProvidedPropertyDescriptor> = script.resolveSession.storageManager.createLazyValue {
|
||||
script.scriptDefinition().providedProperties.mapNotNull { (name, type) ->
|
||||
script.findTypeDescriptor(type, Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)?.let {
|
||||
name to it
|
||||
}
|
||||
}.map { (name, classDescriptor) ->
|
||||
ScriptProvidedPropertyDescriptor(
|
||||
Name.identifier(name),
|
||||
classDescriptor,
|
||||
thisAsReceiverParameter,
|
||||
true,
|
||||
script
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptProvidedPropertiesMemberScope(
|
||||
private val scriptId: String,
|
||||
private val providedProperties: List<PropertyDescriptor>
|
||||
) : MemberScopeImpl() {
|
||||
override fun getContributedDescriptors(
|
||||
kindFilter: DescriptorKindFilter,
|
||||
nameFilter: (Name) -> Boolean
|
||||
): Collection<DeclarationDescriptor> =
|
||||
providedProperties
|
||||
|
||||
override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> =
|
||||
providedProperties.filter { it.name == name }
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println("Scope of script provided properties: $scriptId")
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class ScriptProvidedPropertyDescriptor(
|
||||
name: Name,
|
||||
typeDescriptor: ClassDescriptor,
|
||||
receiver: ReceiverParameterDescriptor?,
|
||||
isVar: Boolean,
|
||||
script: ScriptDescriptor
|
||||
) : PropertyDescriptorImpl(
|
||||
script,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PRIVATE,
|
||||
isVar,
|
||||
name,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
|
||||
/* isDelegated = */ false
|
||||
) {
|
||||
init {
|
||||
setType(typeDescriptor.defaultType, emptyList(), receiver, null)
|
||||
initialize(
|
||||
makePropertyGetterDescriptor(),
|
||||
if (!isVar) null else makePropertySetterDescriptor()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertyGetterDescriptor() =
|
||||
PropertyGetterDescriptorImpl(
|
||||
this,
|
||||
Annotations.EMPTY,
|
||||
this.modality,
|
||||
this.visibility,
|
||||
/* isDefault = */
|
||||
false, /* isExternal = */
|
||||
false, /* isInline = */
|
||||
false,
|
||||
this.kind,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(returnType)
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertySetterDescriptor() =
|
||||
PropertySetterDescriptorImpl(
|
||||
this,
|
||||
Annotations.EMPTY,
|
||||
this.modality,
|
||||
this.visibility,
|
||||
/* isDefault = */
|
||||
false, /* isExternal = */
|
||||
false, /* isInline = */
|
||||
false,
|
||||
this.kind,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(
|
||||
ValueParameterDescriptorImpl(
|
||||
this,
|
||||
null,
|
||||
0,
|
||||
Annotations.EMPTY,
|
||||
Name.special("<set-?>"),
|
||||
returnType,
|
||||
/* declaresDefaultValue = */
|
||||
false, /* isCrossinline = */
|
||||
false, /* isNoinline = */
|
||||
false,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
)
|
||||
)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.shared.resolve
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
val KClass<*>.classId: ClassId
|
||||
get() = this.java.enclosingClass?.kotlin?.classId?.createNestedClassId(Name.identifier(simpleName!!))
|
||||
?: ClassId.topLevel(FqName(qualifiedName!!))
|
||||
|
||||
val KType.classId: ClassId?
|
||||
get() = classifier?.let { it as? KClass<*> }?.classId
|
||||
Reference in New Issue
Block a user