diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 7bf0d50a944..0bcd0900f9c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -99,6 +99,7 @@ public interface Errors { DiagnosticFactory1 MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 MISSING_SCRIPT_RECEIVER_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 MISSING_SCRIPT_ENVIRONMENT_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 c2ad2a2375e..f70d8917da6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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}", 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 d4ff0ed5fd8..b4baf75f4fd 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,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 = 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): 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 = scriptImplicitReceivers() + private val scriptEnvironmentProperties: () -> List> = resolveSession.storageManager.createLazyValue { + scriptDefinition.environmentVariables.mapNotNull { (name, type) -> + findTypeDescriptor(type, Errors.MISSING_SCRIPT_ENVIRONMENT_PROPERTY_CLASS)?.let { + name to it + } + } + } + + fun getScriptEnvironmentProperties(): List> = 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(""), 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 +) : MemberScopeImpl() { + override fun getContributedDescriptors( + kindFilter: DescriptorKindFilter, + nameFilter: (Name) -> Boolean + ): Collection = + environmentProperties.filter { kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK) && nameFilter(it.name) } + + override fun getContributedVariables(name: Name, location: LookupLocation): Collection = + 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(), 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(""), returnType, + /* declaresDefaultValue = */ false, /* isCrossinline = */ false, /* isNoinline = */ false, + null, SourceElement.NO_SOURCE + ) + ) + } diff --git a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index f5f84ab3ced..c5498f43628 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -56,6 +56,8 @@ open class KotlinScriptDefinition(val template: KClass) : UserDataHolde listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly) open val implicitReceivers: List get() = emptyList() + + open val environmentVariables: List> get() = emptyList() } object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class) 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 b085c0c82b2..58f921eb113 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 @@ -55,6 +55,11 @@ class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefini scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.scriptImplicitReceivers) ?: emptyList() } + + override val environmentVariables: List> by lazy { + scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.contextVariables)?.map { (k, v) -> k to v } + ?: emptyList() + } }