From 19a969a5216bd7d821f8f51ca4a383e01784e66e Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Thu, 6 Dec 2018 19:58:41 +0100 Subject: [PATCH] Implement script import support in the compiler --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 2 + .../descriptors/LazyScriptClassMemberScope.kt | 6 +- .../lazy/descriptors/LazyScriptDescriptor.kt | 64 +++++++++++++++++-- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 5de19ae0626..e42e8421555 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -101,6 +101,8 @@ public interface Errors { DiagnosticFactory1 MISSING_SCRIPT_BASE_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_STANDARD_TEMPLATE = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 MISSING_IMPORTED_SCRIPT_FILE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 MISSING_IMPORTED_SCRIPT_PSI = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS = DiagnosticFactory1.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 e4653f6e464..cdb408ffdb4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -365,6 +365,8 @@ 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_STANDARD_TEMPLATE, "No script runtime was found in the classpath: class ''{0}'' not found. Please add kotlin-script-runtime.jar to the module 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_IMPORTED_SCRIPT_FILE, "Cannot find imported script file ''{0}''. Check your script imports", TO_STRING); + MAP.put(MISSING_IMPORTED_SCRIPT_PSI, "Imported script file ''{0}'' is not loaded. Check your script imports", TO_STRING); MAP.put(MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS, "Cannot access script provided property class ''{0}''. Check your module classpath for missing or conflicting dependencies", 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, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt index eaabab3dc38..29ac5e52739 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt @@ -41,7 +41,10 @@ class LazyScriptClassMemberScope( if (baseConstructorDescriptor != null) { val implicitReceiversParamTypes = scriptDescriptor.implicitReceivers.mapIndexed { idx, receiver -> - "$IMPLICIT_RECEIVER_PARAM_NAME_PREFIX$idx" to receiver.defaultType + 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 { @@ -103,6 +106,7 @@ class LazyScriptClassMemberScope( companion object { const val IMPLICIT_RECEIVER_PARAM_NAME_PREFIX = "\$\$implicitReceiver" + const val IMPORTED_SCRIPT_PARAM_NAME_PREFIX = "\$\$importedScript" } } 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 8a63574a197..a93c8ddcc58 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 @@ -16,17 +16,20 @@ package org.jetbrains.kotlin.resolve.lazy.descriptors +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.local.CoreLocalFileSystem 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.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtScriptInitializer +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 @@ -44,10 +47,13 @@ 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.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 @@ -143,10 +149,48 @@ class LazyScriptDescriptor( 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?): 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() + ?: return null + return resolveSession.getScriptDescriptor(ktScript) + } + } + private val scriptImplicitReceivers: () -> List = resolveSession.storageManager.createLazyValue { - scriptDefinition().implicitReceivers.mapNotNull { receiver -> + val res = ArrayList() + + 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?): ClassDescriptor? = @@ -160,16 +204,22 @@ class LazyScriptDescriptor( errorDiagnostic: DiagnosticFactory1? ): ClassDescriptor? { val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) } - if (typeDescriptor == null && errorDiagnostic != null) { + if (typeDescriptor == null) { + reportErrorString1(errorDiagnostic, classId?.asSingleFqName()?.toString() ?: typeName) + } + return typeDescriptor + } + + private fun reportErrorString1(errorDiagnostic: DiagnosticFactory1?, 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, - classId?.asSingleFqName()?.toString() ?: typeName + arg ) ) } - return typeDescriptor } override fun getImplicitReceivers(): List = scriptImplicitReceivers()