Add support for script environment variables to the frontend

This commit is contained in:
Ilya Chernikov
2018-04-16 12:42:36 +02:00
parent 15f9ad9531
commit 516924cda1
5 changed files with 149 additions and 16 deletions
@@ -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);
@@ -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}",
@@ -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)
@@ -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()
}
}