Add script class annotations generation from the base class
This commit is contained in:
@@ -44,6 +44,7 @@ class ScriptCodegen private constructor(
|
|||||||
typeMapper.mapSupertype(scriptDescriptor.getSuperClassOrAny().defaultType, null).internalName,
|
typeMapper.mapSupertype(scriptDescriptor.getSuperClassOrAny().defaultType, null).internalName,
|
||||||
mapSupertypesNames(typeMapper, scriptDescriptor.getSuperInterfaces(), null)
|
mapSupertypesNames(typeMapper, scriptDescriptor.getSuperInterfaces(), null)
|
||||||
)
|
)
|
||||||
|
AnnotationCodegen.forClass(v.visitor, this, typeMapper).genAnnotations(scriptDescriptor, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generateBody() {
|
override fun generateBody() {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public interface Errors {
|
|||||||
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_BASE_CLASS = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_BASE_CLASS = DiagnosticFactory1.create(ERROR);
|
||||||
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR);
|
||||||
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS = DiagnosticFactory1.create(ERROR);
|
||||||
|
DiagnosticFactory2<PsiElement, String, String> INVALID_SCRIPT_TARGET_ANNOTATION_CLASS = DiagnosticFactory2.create(ERROR);
|
||||||
DiagnosticFactory1<PsiElement, String> PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<PsiElement, String> PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR);
|
||||||
DiagnosticFactory2<PsiElement, String, IncompatibleVersionErrorData<?>> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR);
|
DiagnosticFactory2<PsiElement, String, IncompatibleVersionErrorData<?>> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR);
|
||||||
|
|
||||||
|
|||||||
+1
@@ -360,6 +360,7 @@ public class DefaultErrorMessages {
|
|||||||
MAP.put(MISSING_SCRIPT_BASE_CLASS, "Cannot access script base class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
MAP.put(MISSING_SCRIPT_BASE_CLASS, "Cannot access script base class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
||||||
MAP.put(MISSING_SCRIPT_RECEIVER_CLASS, "Cannot access implicit script receiver class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
MAP.put(MISSING_SCRIPT_RECEIVER_CLASS, "Cannot access implicit script receiver class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
||||||
MAP.put(MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS, "Cannot access script environment property class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
MAP.put(MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS, "Cannot access script environment property class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
||||||
|
MAP.put(INVALID_SCRIPT_TARGET_ANNOTATION_CLASS, "Cannot access script generated target annotation class ''{0}'': {1}. Check your module classpath for missing or conflicting dependencies", TO_STRING, TO_STRING);
|
||||||
MAP.put(PRE_RELEASE_CLASS, "{0} is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler", TO_STRING);
|
MAP.put(PRE_RELEASE_CLASS, "{0} is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler", TO_STRING);
|
||||||
MAP.put(INCOMPATIBLE_CLASS,
|
MAP.put(INCOMPATIBLE_CLASS,
|
||||||
"{0} was compiled with an incompatible version of Kotlin. {1}",
|
"{0} was compiled with an incompatible version of Kotlin. {1}",
|
||||||
|
|||||||
+44
-4
@@ -18,11 +18,14 @@ package org.jetbrains.kotlin.resolve.lazy.descriptors
|
|||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.*
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.ConstantValue
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.ConstantValueFactory
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||||
@@ -99,18 +102,18 @@ class LazyScriptDescriptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1<PsiElement, String>): ClassDescriptor? =
|
internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||||
findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic)
|
findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic)
|
||||||
|
|
||||||
internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, String>): ClassDescriptor? =
|
internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||||
findTypeDescriptor(type.classId, type.toString(), errorDiagnostic)
|
findTypeDescriptor(type.classId, type.toString(), errorDiagnostic)
|
||||||
|
|
||||||
internal fun findTypeDescriptor(
|
internal fun findTypeDescriptor(
|
||||||
classId: ClassId?, typeName: String,
|
classId: ClassId?, typeName: String,
|
||||||
errorDiagnostic: DiagnosticFactory1<PsiElement, String>
|
errorDiagnostic: DiagnosticFactory1<PsiElement, String>?
|
||||||
): ClassDescriptor? {
|
): ClassDescriptor? {
|
||||||
val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) }
|
val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) }
|
||||||
if (typeDescriptor == null) {
|
if (typeDescriptor == null && errorDiagnostic != null) {
|
||||||
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
|
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
|
||||||
resolveSession.trace.report(
|
resolveSession.trace.report(
|
||||||
errorDiagnostic.on(
|
errorDiagnostic.on(
|
||||||
@@ -152,4 +155,41 @@ class LazyScriptDescriptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getOuterScope(): LexicalScope = scriptOuterScope()
|
override fun getOuterScope(): LexicalScope = scriptOuterScope()
|
||||||
|
|
||||||
|
private val scriptClassAnnotations: () -> Annotations = resolveSession.storageManager.createLazyValue {
|
||||||
|
val baseAnnotations = 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
|
||||||
|
val syntheticAnnotations =
|
||||||
|
scriptDefinition().targetClassAnnotations.mapNotNull { annInstance ->
|
||||||
|
try {
|
||||||
|
val annDescriptor = findTypeDescriptor(annInstance.annotationClass, null) ?: throw Exception("class not found")
|
||||||
|
val annArguments = HashMap<Name, ConstantValue<*>>()
|
||||||
|
for (arg in annInstance.annotationClass.java.declaredMethods) {
|
||||||
|
val argVal = ConstantValueFactory.createConstantValue(arg.invoke(annInstance))
|
||||||
|
?: throw Exception("Unsupported annotation argument: ${arg.name}")
|
||||||
|
annArguments[Name.identifier(arg.name)] = argVal
|
||||||
|
}
|
||||||
|
AnnotationDescriptorImpl(annDescriptor.defaultType, annArguments, source)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
resolveSession.trace.report(
|
||||||
|
Errors.INVALID_SCRIPT_TARGET_ANNOTATION_CLASS.on(
|
||||||
|
scriptInfo.script,
|
||||||
|
annInstance.toString(),
|
||||||
|
e.message ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (syntheticAnnotations.isEmpty()) baseAnnotations
|
||||||
|
else CompositeAnnotations(baseAnnotations, AnnotationsImpl(syntheticAnnotations))
|
||||||
|
}
|
||||||
|
|
||||||
|
override val annotations: Annotations
|
||||||
|
get() = scriptClassAnnotations()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ open class KotlinScriptDefinition(open val template: KClass<out Any>) : UserData
|
|||||||
open val implicitReceivers: List<KType> get() = emptyList()
|
open val implicitReceivers: List<KType> get() = emptyList()
|
||||||
|
|
||||||
open val environmentVariables: List<Pair<String, KType>> get() = emptyList()
|
open val environmentVariables: List<Pair<String, KType>> get() = emptyList()
|
||||||
|
|
||||||
|
open val targetClassAnnotations: List<Annotation> get() = emptyList()
|
||||||
|
|
||||||
|
open val targetMethodAnnotations: List<Annotation> get() = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class)
|
object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class)
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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.codegen
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
|
import org.jetbrains.kotlin.script.util.scriptCompilationClasspathFromContextOrStlib
|
||||||
|
import org.jetbrains.kotlin.scripting.compiler.plugin.configureScriptDefinitions
|
||||||
|
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||||
|
import org.jetbrains.kotlin.test.TestJdkKind
|
||||||
|
import org.jetbrains.kotlin.utils.PathUtil
|
||||||
|
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_COMMON_JAR
|
||||||
|
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_COMPILER_PLUGIN_JAR
|
||||||
|
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_JVM_JAR
|
||||||
|
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_MISC_JAR
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.script.experimental.annotations.KotlinScript
|
||||||
|
import kotlin.script.experimental.annotations.KotlinScriptDefaultCompilationConfiguration
|
||||||
|
import kotlin.script.experimental.api.ScriptCompileConfigurationProperties
|
||||||
|
import kotlin.script.experimental.util.TypedKey
|
||||||
|
|
||||||
|
|
||||||
|
class CustomScriptCodegenTest : CodegenTestCase() {
|
||||||
|
|
||||||
|
fun testAnnotatedDefinition() {
|
||||||
|
createScriptTestEnvironment("org.jetbrains.kotlin.codegen.TestScriptWithAnnotatedBaseClass")
|
||||||
|
loadScript("val x = 1")
|
||||||
|
val res = generateScriptClass()
|
||||||
|
assertNull(res.safeGetAnnotation(KotlinScript::class))
|
||||||
|
assertNotNull(res.safeGetAnnotation(MyScriptClassAnnotation::class))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateScriptClass(): Class<*> = generateClass("ScriptTest")
|
||||||
|
|
||||||
|
private fun loadScript(text: String) {
|
||||||
|
myFiles = CodegenTestFiles.create("scriptTest.kts", text, myEnvironment.project)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createScriptTestEnvironment(vararg scriptDefinitions: String) {
|
||||||
|
if (myEnvironment != null) {
|
||||||
|
throw IllegalStateException("must not set up myEnvironment twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalDependencies =
|
||||||
|
scriptCompilationClasspathFromContextOrStlib("tests-common", "kotlin-stdlib") +
|
||||||
|
File(TestScriptWithReceivers::class.java.protectionDomain.codeSource.location.toURI().path) +
|
||||||
|
with(PathUtil.kotlinPathsForDistDirectory) {
|
||||||
|
arrayOf(
|
||||||
|
KOTLIN_SCRIPTING_COMPILER_PLUGIN_JAR, KOTLIN_SCRIPTING_COMMON_JAR,
|
||||||
|
KOTLIN_SCRIPTING_JVM_JAR, KOTLIN_SCRIPTING_MISC_JAR
|
||||||
|
).mapNotNull { File(libPath, it).takeIf { it.exists() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
val configuration = createConfiguration(
|
||||||
|
ConfigurationKind.ALL,
|
||||||
|
TestJdkKind.MOCK_JDK,
|
||||||
|
additionalDependencies,
|
||||||
|
emptyList(),
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
if (scriptDefinitions.isNotEmpty()) {
|
||||||
|
configureScriptDefinitions(
|
||||||
|
scriptDefinitions.asList(), configuration, this::class.java.classLoader, MessageCollector.NONE, emptyMap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
myEnvironment = KotlinCoreEnvironment.createForTests(
|
||||||
|
testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Class<*>.safeGetAnnotation(ann: KClass<out Annotation>): Annotation? =
|
||||||
|
getAnnotation(classLoader.loadClass(ann.qualifiedName) as Class<Annotation>)
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class MyScriptClassAnnotation
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@KotlinScript
|
||||||
|
@MyScriptClassAnnotation()
|
||||||
|
abstract class TestScriptWithAnnotatedBaseClass
|
||||||
|
|
||||||
|
object TestScriptWithMethodAnnotationsConfiguration : ArrayList<Pair<TypedKey<*>, Any?>>(
|
||||||
|
listOf(
|
||||||
|
ScriptCompileConfigurationProperties.generatedClassAnnotations to listOf()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@KotlinScript
|
||||||
|
@KotlinScriptDefaultCompilationConfiguration(TestScriptWithMethodAnnotationsConfiguration::class)
|
||||||
|
abstract class TestScriptWithMethodAnnotations
|
||||||
|
|
||||||
+8
@@ -79,6 +79,14 @@ abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinit
|
|||||||
ScriptExpectedLocation.TestsOnly
|
ScriptExpectedLocation.TestsOnly
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val targetClassAnnotations: List<Annotation>
|
||||||
|
get() = scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.generatedClassAnnotations)
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
|
override val targetMethodAnnotations: List<Annotation>
|
||||||
|
get() = scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.generatedMethodAnnotations)
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
scriptDefinition.properties.getOrNull(ScriptingEnvironmentProperties.getScriptingClass)
|
scriptDefinition.properties.getOrNull(ScriptingEnvironmentProperties.getScriptingClass)
|
||||||
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
|
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
|
||||||
|
|||||||
Reference in New Issue
Block a user