From 1b42095dc17e61a89c01a531de25eb3fdc67310b Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Thu, 13 Jul 2017 20:48:10 +0300 Subject: [PATCH] Support @AcceptedAnnotations for different kinds of resolvers --- .../script/AsyncDependencyResolverWrapper.kt | 4 +- ...inScriptDefinitionFromAnnotatedTemplate.kt | 35 +++++-- .../jetbrains/kotlin/script/legacyWrappers.kt | 13 ++- .../testData/script/acceptedAnnotations.kts | 1 + .../kotlin/scripts/ScriptTemplateTest.kt | 99 +++++++++++++++++-- .../acceptedAnnotations/script.kts | 3 + .../acceptedAnnotations/template/template.kt | 29 ++++++ ...onfigurationHighlightingTestGenerated.java | 6 ++ 8 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 compiler/testData/script/acceptedAnnotations.kts create mode 100644 idea/testData/script/definition/highlighting/acceptedAnnotations/script.kts create mode 100644 idea/testData/script/definition/highlighting/acceptedAnnotations/template/template.kt diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/AsyncDependencyResolverWrapper.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/AsyncDependencyResolverWrapper.kt index 9cec19a0999..10f3043908a 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/AsyncDependencyResolverWrapper.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/AsyncDependencyResolverWrapper.kt @@ -23,7 +23,9 @@ import kotlin.script.dependencies.ScriptContents import kotlin.script.dependencies.experimental.AsyncDependenciesResolver // wraps AsyncDependenciesResolver to provide implementation for synchronous DependenciesResolver::resolve -class AsyncDependencyResolverWrapper(private val delegate: AsyncDependenciesResolver): AsyncDependenciesResolver { +class AsyncDependencyResolverWrapper( + override val delegate: AsyncDependenciesResolver +): AsyncDependenciesResolver, DependencyResolverWrapper { override fun resolve( scriptContents: ScriptContents, environment: Environment diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionFromAnnotatedTemplate.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionFromAnnotatedTemplate.kt index 4fb94e17d0a..9c319ba089e 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionFromAnnotatedTemplate.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionFromAnnotatedTemplate.kt @@ -99,22 +99,31 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate( override val acceptedAnnotations: List> by lazy { fun sameSignature(left: KFunction<*>, right: KFunction<*>): Boolean = + left.name == right.name && left.parameters.size == right.parameters.size && left.parameters.zip(right.parameters).all { it.first.kind == KParameter.Kind.INSTANCE || it.first.type == it.second.type } - val resolveMethod = ScriptDependenciesResolver::resolve - val resolverMethodAnnotations = - dependencyResolver::class.memberFunctions.find { function -> - function.name == resolveMethod.name && - sameSignature(function, resolveMethod) - }?.annotations?.filterIsInstance() - resolverMethodAnnotations?.flatMap { - it.supportedAnnotationClasses.toList() + val resolveFunctions = getResolveFunctions() + + dependencyResolver.unwrap()::class.memberFunctions + .filter { function -> resolveFunctions.any { sameSignature(function, it) } } + .flatMap { it.annotations } + .filterIsInstance() + .flatMap { it.supportedAnnotationClasses.toList() } + .distinctBy { it.qualifiedName } + } + + private fun getResolveFunctions(): List> { + // DependenciesResolver::resolve, ScriptDependenciesResolver::resolve, AsyncDependenciesResolver::resolveAsync + return AsyncDependenciesResolver::class.memberFunctions.filter { it.name == "resolve" || it.name == "resolveAsync" }.also { + assert(it.size == 3) { + AsyncDependenciesResolver::class.memberFunctions + .joinToString(prefix = "${AsyncDependenciesResolver::class.qualifiedName} api changed, fix this code") { it.name } + } } - ?: emptyList() } override val name = template.simpleName!! @@ -147,4 +156,12 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate( companion object { internal val log = Logger.getInstance(KotlinScriptDefinitionFromAnnotatedTemplate::class.java) } +} + +interface DependencyResolverWrapper { + val delegate: T +} + +fun ScriptDependenciesResolver.unwrap(): ScriptDependenciesResolver { + return if (this is DependencyResolverWrapper<*>) delegate.unwrap() else this } \ No newline at end of file diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/legacyWrappers.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/legacyWrappers.kt index 998e1a2cb00..b480acbf6a9 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/legacyWrappers.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/legacyWrappers.kt @@ -24,7 +24,11 @@ import kotlin.script.dependencies.Environment import kotlin.script.dependencies.ScriptDependencies import kotlin.script.dependencies.ScriptReport -internal class LegacyPackageDependencyResolverWrapper(val legacyResolver: ScriptDependenciesResolver) : kotlin.script.dependencies.DependenciesResolver { +// NOTE: not implementing resolver wrapper since it was not implemented initially and this is deprecated API +@Deprecated("Compatibility with deprecated API") +internal class LegacyPackageDependencyResolverWrapper( + val legacyResolver: ScriptDependenciesResolver +) : kotlin.script.dependencies.DependenciesResolver { override fun resolve( scriptContents: kotlin.script.dependencies.ScriptContents, environment: Environment @@ -62,15 +66,16 @@ internal class LegacyPackageDependencyResolverWrapper(val legacyResolver: Script private fun ScriptContents.Position.convertPosition(): ScriptReport.Position = ScriptReport.Position(line, col) } -internal class ApiChangeDependencyResolverWrapper(val legacyResolver: kotlin.script.dependencies.ScriptDependenciesResolver) - : kotlin.script.dependencies.DependenciesResolver { +internal class ApiChangeDependencyResolverWrapper( + override val delegate: kotlin.script.dependencies.ScriptDependenciesResolver +) : kotlin.script.dependencies.DependenciesResolver, DependencyResolverWrapper { override fun resolve( scriptContents: kotlin.script.dependencies.ScriptContents, environment: Environment ): DependenciesResolver.ResolveResult { val reports = ArrayList() - val legacyDeps = legacyResolver.resolve( + val legacyDeps = delegate.resolve( scriptContents, environment, { sev, msg, pos -> diff --git a/compiler/testData/script/acceptedAnnotations.kts b/compiler/testData/script/acceptedAnnotations.kts new file mode 100644 index 00000000000..073a6c4e343 --- /dev/null +++ b/compiler/testData/script/acceptedAnnotations.kts @@ -0,0 +1 @@ +@file: [TestAnno1 TestAnno2] \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt index 27bf01506c6..2eb956bec53 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.jetbrains.kotlin.scripts import com.intellij.openapi.util.Disposer @@ -46,6 +45,7 @@ import java.lang.Exception import java.lang.reflect.InvocationTargetException import java.net.URL import java.net.URLClassLoader +import java.util.concurrent.Future import kotlin.reflect.KClass import kotlin.script.dependencies.* import kotlin.script.dependencies.DependenciesResolver.ResolveResult @@ -260,6 +260,24 @@ class ScriptTemplateTest { assertEqualsTrimmed(NUM_4_LINE + FIB_SCRIPT_OUTPUT_TAIL, out) } + @Test + fun testAcceptedAnnotationsSync() { + val aClass = compileScript("acceptedAnnotations.kts", ScriptWithAcceptedAnnotationsSyncResolver::class, null) + Assert.assertNotNull(aClass) + } + + @Test + fun testAcceptedAnnotationsAsync() { + val aClass = compileScript("acceptedAnnotations.kts", ScriptWithAcceptedAnnotationsAsyncResolver::class, null) + Assert.assertNotNull(aClass) + } + + @Test + fun testAcceptedAnnotationsLegacy() { + val aClass = compileScript("acceptedAnnotations.kts", ScriptWithAcceptedAnnotationsLegacyResolver::class, null) + Assert.assertNotNull(aClass) + } + @Test fun testSmokeScriptException() { val aClass = compileScript("smoke_exception.kts", ScriptWithArrayParam::class) @@ -349,14 +367,15 @@ open class TestKotlinScriptDummyDependenciesResolver : DependenciesResolver { imports = listOf("org.jetbrains.kotlin.scripts.DependsOn", "org.jetbrains.kotlin.scripts.DependsOnTwo") ).asSuccess() } - - protected fun classpathFromClassloader(): List = - (TestKotlinScriptDependenciesResolver::class.java.classLoader as? URLClassLoader)?.urLs - ?.mapNotNull(URL::toFile) - ?.filter { it.path.contains("out") && it.path.contains("test") } - ?: emptyList() } +private fun classpathFromClassloader(): List = + (TestKotlinScriptDependenciesResolver::class.java.classLoader as? URLClassLoader)?.urLs + ?.mapNotNull(URL::toFile) + ?.filter { it.path.contains("out") && it.path.contains("test") } + ?: emptyList() + + open class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesResolver() { private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } @@ -417,6 +436,63 @@ class TestAsyncResolver : TestKotlinScriptDependenciesResolver(), AsyncDependenc super.resolve(scriptContents, environment) } +@Target(AnnotationTarget.FILE) +annotation class TestAnno1 +@Target(AnnotationTarget.FILE) +annotation class TestAnno2 +@Target(AnnotationTarget.FILE) +annotation class TestAnno3 + +private val annotationFqNames = listOf(TestAnno1::class, TestAnno2::class, TestAnno3::class).map { it.qualifiedName!! } + +interface AcceptedAnnotationsCheck { + fun checkHasAnno1Annotation(scriptContents: ScriptContents): ResolveResult.Success { + val actualAnnotations = scriptContents.annotations + Assert.assertTrue( + "Loaded annotation: $actualAnnotations", + actualAnnotations.single().annotationClass.qualifiedName == TestAnno1::class.qualifiedName + ) + + return ScriptDependencies( + classpath = classpathFromClassloader(), + imports = annotationFqNames + ).asSuccess() + } +} + +class TestAcceptedAnnotationsSyncResolver: DependenciesResolver, AcceptedAnnotationsCheck { + @AcceptedAnnotations(TestAnno1::class, TestAnno3::class) + override fun resolve(scriptContents: ScriptContents, environment: Environment): ResolveResult { + return checkHasAnno1Annotation(scriptContents) + } +} + +class TestAcceptedAnnotationsAsyncResolver: AsyncDependenciesResolver, AcceptedAnnotationsCheck { + @AcceptedAnnotations(TestAnno1::class, TestAnno3::class) + override suspend fun resolveAsync(scriptContents: ScriptContents, environment: Environment): ResolveResult { + return checkHasAnno1Annotation(scriptContents) + } +} + +class TestAcceptedAnnotationsLegacyResolver: ScriptDependenciesResolver, AcceptedAnnotationsCheck { + @AcceptedAnnotations(TestAnno1::class, TestAnno3::class) + override fun resolve( + script: ScriptContents, + environment: Environment?, + report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit, + previousDependencies: KotlinScriptExternalDependencies? + ): Future { + checkHasAnno1Annotation(script) + return object : KotlinScriptExternalDependencies { + override val classpath: Iterable + get() = classpathFromClassloader() + + override val imports: Iterable + get() = annotationFqNames + }.asFuture() + } +} + @ScriptTemplateDefinition( scriptFilePattern =".*\\.kts", resolver = TestKotlinScriptDummyDependenciesResolver::class) @@ -474,6 +550,15 @@ abstract class ScriptReportingErrors(val num: Int) @ScriptTemplateDefinition(resolver = TestAsyncResolver::class) abstract class ScriptWithAsyncResolver(val num: Int) +@ScriptTemplateDefinition(resolver = TestAcceptedAnnotationsSyncResolver::class) +abstract class ScriptWithAcceptedAnnotationsSyncResolver + +@ScriptTemplateDefinition(resolver = TestAcceptedAnnotationsAsyncResolver::class) +abstract class ScriptWithAcceptedAnnotationsAsyncResolver + +@ScriptTemplateDefinition(resolver = TestAcceptedAnnotationsLegacyResolver::class) +abstract class ScriptWithAcceptedAnnotationsLegacyResolver + @Target(AnnotationTarget.FILE) @Retention(AnnotationRetention.RUNTIME) annotation class DependsOn(val path: String) diff --git a/idea/testData/script/definition/highlighting/acceptedAnnotations/script.kts b/idea/testData/script/definition/highlighting/acceptedAnnotations/script.kts new file mode 100644 index 00000000000..6994853ec39 --- /dev/null +++ b/idea/testData/script/definition/highlighting/acceptedAnnotations/script.kts @@ -0,0 +1,3 @@ +@file: [Anno custom.scriptDefinition.Anno2 NonExistentAnno] + +val c = 3 diff --git a/idea/testData/script/definition/highlighting/acceptedAnnotations/template/template.kt b/idea/testData/script/definition/highlighting/acceptedAnnotations/template/template.kt new file mode 100644 index 00000000000..b25c9efa591 --- /dev/null +++ b/idea/testData/script/definition/highlighting/acceptedAnnotations/template/template.kt @@ -0,0 +1,29 @@ +package custom.scriptDefinition + +import java.io.File +import kotlin.script.dependencies.* +import kotlin.script.templates.ScriptTemplateDefinition + +class TestDependenciesResolver : DependenciesResolver { + @kotlin.script.templates.AcceptedAnnotations(Anno::class) + override fun resolve( + scriptContents: ScriptContents, + environment: Environment + ): DependenciesResolver.ResolveResult { + val annoFQN = Anno::class.qualifiedName!! + assert(scriptContents.annotations.single().annotationClass.qualifiedName == annoFQN) + return ScriptDependencies( + classpath = listOf(environment["template-classes"] as File), + imports = listOf(annoFQN) + ).asSuccess() + } +} + +@Target(AnnotationTarget.FILE) +annotation class Anno + +@Target(AnnotationTarget.FILE) +annotation class Anno2 + +@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts") +class Template \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationHighlightingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationHighlightingTestGenerated.java index 6d1e38bd5c0..2d9470ffd29 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationHighlightingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/script/ScriptConfigurationHighlightingTestGenerated.java @@ -32,6 +32,12 @@ import java.util.regex.Pattern; @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class ScriptConfigurationHighlightingTestGenerated extends AbstractScriptConfigurationHighlightingTest { + @TestMetadata("acceptedAnnotations") + public void testAcceptedAnnotations() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/acceptedAnnotations/"); + doTest(fileName); + } + @TestMetadata("additionalImports") public void testAdditionalImports() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/additionalImports/");