diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt index 76b660fcdc7..32d5edd2b4d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ScriptCodegen.kt @@ -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() { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 908084707e2..bec7aeefd4d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -101,6 +101,7 @@ public interface Errors { DiagnosticFactory1 MISSING_SCRIPT_BASE_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory2 INVALID_SCRIPT_TARGET_ANNOTATION_CLASS = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory2> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 58e02b173a6..ce541425af5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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}", diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt index 0a7185ed76b..58e2adf47e6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt @@ -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): ClassDescriptor? = + internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1?): ClassDescriptor? = findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic) - internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1): ClassDescriptor? = + internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1?): ClassDescriptor? = findTypeDescriptor(type.classId, type.toString(), errorDiagnostic) internal fun findTypeDescriptor( classId: ClassId?, typeName: String, - errorDiagnostic: DiagnosticFactory1 + errorDiagnostic: DiagnosticFactory1? ): 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>() + 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() } diff --git a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index 5dfcff41247..b88e561ecb7 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -58,6 +58,10 @@ open class KotlinScriptDefinition(open val template: KClass) : UserData open val implicitReceivers: List get() = emptyList() open val environmentVariables: List> get() = emptyList() + + open val targetClassAnnotations: List get() = emptyList() + + open val targetMethodAnnotations: List get() = emptyList() } object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CustomScriptCodegenTest.kt b/compiler/tests/org/jetbrains/kotlin/codegen/CustomScriptCodegenTest.kt new file mode 100644 index 00000000000..c1fe183147d --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CustomScriptCodegenTest.kt @@ -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): Annotation? = + getAnnotation(classLoader.loadClass(ann.qualifiedName) as Class) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class MyScriptClassAnnotation + +@Suppress("unused") +@KotlinScript +@MyScriptClassAnnotation() +abstract class TestScriptWithAnnotatedBaseClass + +object TestScriptWithMethodAnnotationsConfiguration : ArrayList, Any?>>( + listOf( + ScriptCompileConfigurationProperties.generatedClassAnnotations to listOf() + ) +) + +@Suppress("unused") +@KotlinScript +@KotlinScriptDefaultCompilationConfiguration(TestScriptWithMethodAnnotationsConfiguration::class) +abstract class TestScriptWithMethodAnnotations + diff --git a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt index 85473bb0b20..8f6326e00e9 100644 --- a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt +++ b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt @@ -79,6 +79,14 @@ abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinit ScriptExpectedLocation.TestsOnly ) + override val targetClassAnnotations: List + get() = scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.generatedClassAnnotations) + .orEmpty() + + override val targetMethodAnnotations: List + 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")