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,
|
||||
mapSupertypesNames(typeMapper, scriptDescriptor.getSuperInterfaces(), null)
|
||||
)
|
||||
AnnotationCodegen.forClass(v.visitor, this, typeMapper).genAnnotations(scriptDescriptor, null)
|
||||
}
|
||||
|
||||
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_RECEIVER_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);
|
||||
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_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(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(INCOMPATIBLE_CLASS,
|
||||
"{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 org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
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.module
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
internal fun findTypeDescriptor(
|
||||
classId: ClassId?, typeName: String,
|
||||
errorDiagnostic: DiagnosticFactory1<PsiElement, String>
|
||||
errorDiagnostic: DiagnosticFactory1<PsiElement, String>?
|
||||
): ClassDescriptor? {
|
||||
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
|
||||
resolveSession.trace.report(
|
||||
errorDiagnostic.on(
|
||||
@@ -152,4 +155,41 @@ class LazyScriptDescriptor(
|
||||
}
|
||||
|
||||
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 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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
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) {
|
||||
scriptDefinition.properties.getOrNull(ScriptingEnvironmentProperties.getScriptingClass)
|
||||
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
|
||||
|
||||
Reference in New Issue
Block a user