Add support for script environment variables to the frontend
This commit is contained in:
@@ -99,6 +99,7 @@ public interface Errors {
|
||||
|
||||
DiagnosticFactory1<PsiElement, FqName> MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, FqName> MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, FqName> MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory2<PsiElement, String, IncompatibleVersionErrorData<?>> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
|
||||
+1
@@ -364,6 +364,7 @@ public class DefaultErrorMessages {
|
||||
|
||||
MAP.put(MISSING_DEPENDENCY_CLASS, "Cannot access 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(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}",
|
||||
|
||||
+140
-16
@@ -16,8 +16,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.resolve.lazy.descriptors
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -28,17 +33,19 @@ import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
import org.jetbrains.kotlin.resolve.scopes.*
|
||||
import org.jetbrains.kotlin.resolve.source.toSourceElement
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptHelper
|
||||
import org.jetbrains.kotlin.script.ScriptPriorities
|
||||
import org.jetbrains.kotlin.script.getScriptDefinition
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
class LazyScriptDescriptor(
|
||||
val resolveSession: ResolveSession,
|
||||
@@ -91,24 +98,47 @@ class LazyScriptDescriptor(
|
||||
|
||||
private val scriptImplicitReceivers: () -> List<ClassDescriptor> = resolveSession.storageManager.createLazyValue {
|
||||
scriptDefinition.implicitReceivers.mapNotNull { receiver ->
|
||||
val receiverClassId = receiver.classifier?.let { it as? KClass<*> }?.java?.classId
|
||||
receiverClassId?.let {
|
||||
module.findClassAcrossModuleDependencies(it)
|
||||
?: also {
|
||||
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
|
||||
resolveSession.trace.report(
|
||||
Errors.MISSING_SCRIPT_RECEIVER_CLASS.on(scriptInfo.script, receiverClassId.asSingleFqName())
|
||||
)
|
||||
}
|
||||
}
|
||||
findTypeDescriptor(receiver, Errors.MISSING_SCRIPT_RECEIVER_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, FqName>): ClassDescriptor? {
|
||||
val receiverClassId = type.classifier?.let { it as? KClass<*> }?.classId
|
||||
return receiverClassId?.let {
|
||||
module.findClassAcrossModuleDependencies(it)
|
||||
} ?: also {
|
||||
// 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,
|
||||
receiverClassId?.asSingleFqName() ?: FqName(type.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImplicitReceivers(): List<ClassDescriptor> = scriptImplicitReceivers()
|
||||
|
||||
private val scriptEnvironmentProperties: () -> List<Pair<String, ClassDescriptor>> = resolveSession.storageManager.createLazyValue {
|
||||
scriptDefinition.environmentVariables.mapNotNull { (name, type) ->
|
||||
findTypeDescriptor(type, Errors.MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS)?.let {
|
||||
name to it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getScriptEnvironmentProperties(): List<Pair<String, ClassDescriptor>> = scriptEnvironmentProperties()
|
||||
|
||||
private val scriptOuterScope: () -> LexicalScope = resolveSession.storageManager.createLazyValue {
|
||||
var outerScope = super.getOuterScope()
|
||||
for (receiverClassDescriptor in implicitReceivers.asReversed()) {
|
||||
val outerScopeReceivers = implicitReceivers.let {
|
||||
if (scriptDefinition.environmentVariables.isEmpty()) {
|
||||
it
|
||||
} else {
|
||||
it + ScriptEnvironmentDescriptor(this)
|
||||
}
|
||||
}
|
||||
for (receiverClassDescriptor in outerScopeReceivers.asReversed()) {
|
||||
outerScope = LexicalScopeImpl(
|
||||
outerScope,
|
||||
receiverClassDescriptor,
|
||||
@@ -123,5 +153,99 @@ class LazyScriptDescriptor(
|
||||
override fun getOuterScope(): LexicalScope = scriptOuterScope()
|
||||
}
|
||||
|
||||
private val Class<*>.classId: ClassId
|
||||
get() = enclosingClass?.classId?.createNestedClassId(Name.identifier(simpleName)) ?: ClassId.topLevel(FqName(name))
|
||||
private val KClass<*>.classId: ClassId
|
||||
get() = this.java.enclosingClass?.kotlin?.classId?.createNestedClassId(Name.identifier(simpleName!!))
|
||||
?: ClassId.topLevel(FqName(qualifiedName!!))
|
||||
|
||||
private val KType.classId: ClassId?
|
||||
get() = classifier?.let { it as? KClass<*> }?.classId
|
||||
|
||||
private class ScriptEnvironmentDescriptor(script: LazyScriptDescriptor) :
|
||||
MutableClassDescriptor(
|
||||
script, ClassKind.CLASS, false, false,
|
||||
Name.special("<synthetic script environment for ${script.name}>"), SourceElement.NO_SOURCE, LockBasedStorageManager.NO_LOCKS
|
||||
) {
|
||||
|
||||
init {
|
||||
modality = Modality.FINAL
|
||||
visibility = Visibilities.PUBLIC
|
||||
setTypeParameterDescriptors(emptyList())
|
||||
createTypeConstructor()
|
||||
}
|
||||
|
||||
private val memberScope by lazy {
|
||||
ScriptEnvironmentMemberScope(
|
||||
script.name.identifier,
|
||||
script.getScriptEnvironmentProperties().map {
|
||||
makeEnvironmentPropertyDescriptor(
|
||||
Name.identifier(it.first),
|
||||
it.second,
|
||||
true,
|
||||
script
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUnsubstitutedMemberScope(): MemberScope = memberScope
|
||||
}
|
||||
|
||||
private class ScriptEnvironmentMemberScope(
|
||||
private val scriptId: String,
|
||||
private val environmentProperties: List<PropertyDescriptorImpl>
|
||||
) : MemberScopeImpl() {
|
||||
override fun getContributedDescriptors(
|
||||
kindFilter: DescriptorKindFilter,
|
||||
nameFilter: (Name) -> Boolean
|
||||
): Collection<DeclarationDescriptor> =
|
||||
environmentProperties.filter { kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK) && nameFilter(it.name) }
|
||||
|
||||
override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> =
|
||||
environmentProperties.filter { it.name == name }
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println("Scope of script environment: $scriptId")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScriptEnvironmentDescriptor.makeEnvironmentPropertyDescriptor(name: Name, typeDescriptor: ClassDescriptor, isVar: Boolean, script: LazyScriptDescriptor) =
|
||||
PropertyDescriptorImpl.create(
|
||||
script.containingDeclaration,
|
||||
Annotations.EMPTY, Modality.FINAL, Visibilities.PUBLIC,
|
||||
isVar,
|
||||
name,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
|
||||
/* isDelegated = */ true
|
||||
).also {
|
||||
it.setType(typeDescriptor.defaultType, emptyList<TypeParameterDescriptorImpl>(), thisAsReceiverParameter, null as KotlinType?)
|
||||
it.initialize(
|
||||
it.makePropertyGetterDescriptor(),
|
||||
if (!isVar) null else it.makePropertySetterDescriptor()
|
||||
)
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertyGetterDescriptor() =
|
||||
PropertyGetterDescriptorImpl(
|
||||
this, Annotations.EMPTY, Modality.FINAL, Visibilities.PUBLIC,
|
||||
/* isDefault = */ false, /* isExternal = */ false, /* isInline = */ false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED, null, SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(returnType)
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertySetterDescriptor() =
|
||||
PropertySetterDescriptorImpl(
|
||||
this, Annotations.EMPTY, Modality.FINAL, Visibilities.PUBLIC,
|
||||
/* isDefault = */ false, /* isExternal = */ false, /* isInline = */ false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED, null, SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(
|
||||
ValueParameterDescriptorImpl(
|
||||
this, null, 0, Annotations.EMPTY, Name.special("<set-?>"), returnType,
|
||||
/* declaresDefaultValue = */ false, /* isCrossinline = */ false, /* isNoinline = */ false,
|
||||
null, SourceElement.NO_SOURCE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ open class KotlinScriptDefinition(val template: KClass<out Any>) : UserDataHolde
|
||||
listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly)
|
||||
|
||||
open val implicitReceivers: List<KType> get() = emptyList()
|
||||
|
||||
open val environmentVariables: List<Pair<String, KType>> get() = emptyList()
|
||||
}
|
||||
|
||||
object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class)
|
||||
|
||||
+5
@@ -55,6 +55,11 @@ class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefini
|
||||
scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.scriptImplicitReceivers)
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override val environmentVariables: List<Pair<String, KType>> by lazy {
|
||||
scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.contextVariables)?.map { (k, v) -> k to v }
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user