KT-60193 K2 scripts: configuration discovery might fail silently
Due to possible data races configuration discovery might fail. So far, it happened silently and we used so-called default one. This configuration is unaware of specific implicit imports, receivers, base class, etc. Hence, broken highlighting and navigation. This commit introduces the following changes: 1. Having default configuration for building `FirScript` is no longer an option. Missing configuration means error reported via exception. 2. Every configuration usage is now logged in DEBUG mode. Troubleshooting becomes easier. ^KT-60193 fixed
This commit is contained in:
+88
-80
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.scripting.compiler.plugin.services
|
package org.jetbrains.kotlin.scripting.compiler.plugin.services
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.kotlin.*
|
import org.jetbrains.kotlin.*
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
@@ -49,94 +50,102 @@ class FirScriptConfiguratorExtensionImpl(
|
|||||||
override fun FirScriptBuilder.configure(fileBuilder: FirFileBuilder) {
|
override fun FirScriptBuilder.configure(fileBuilder: FirFileBuilder) {
|
||||||
val sourceFile = fileBuilder.sourceFile ?: return
|
val sourceFile = fileBuilder.sourceFile ?: return
|
||||||
|
|
||||||
withConfigurationIfAny(sourceFile) { configuration ->
|
val configuration = getOrLoadConfiguration(sourceFile)
|
||||||
// TODO: rewrite/extract decision logic for clarity
|
check(configuration != null) { "Configuration for ${sourceFile.asString()} wasn't found" }
|
||||||
configuration[ScriptCompilationConfiguration.baseClass]?.let { baseClass ->
|
|
||||||
val baseClassFqn = FqName.fromSegments(baseClass.typeName.split("."))
|
|
||||||
contextReceivers.add(buildContextReceiverWithFqName(baseClassFqn, Name.special(SCRIPT_SPECIAL_NAME_STRING)))
|
|
||||||
|
|
||||||
val baseClassSymbol =
|
// TODO: rewrite/extract decision logic for clarity
|
||||||
session.dependenciesSymbolProvider.getClassLikeSymbolByClassId(ClassId(baseClassFqn.parent(), baseClassFqn.shortName()))
|
configuration[ScriptCompilationConfiguration.baseClass]?.let { baseClass ->
|
||||||
as? FirRegularClassSymbol
|
val baseClassFqn = FqName.fromSegments(baseClass.typeName.split("."))
|
||||||
if (baseClassSymbol != null) {
|
contextReceivers.add(buildContextReceiverWithFqName(baseClassFqn, Name.special(SCRIPT_SPECIAL_NAME_STRING)))
|
||||||
// assuming that if base class will be unresolved, the error will be reported on the contextReceiver
|
|
||||||
baseClassSymbol.fir.primaryConstructorIfAny(session)?.fir?.valueParameters?.forEach { baseCtorParameter ->
|
val baseClassSymbol =
|
||||||
parameters.add(
|
session.dependenciesSymbolProvider.getClassLikeSymbolByClassId(ClassId(baseClassFqn.parent(), baseClassFqn.shortName()))
|
||||||
buildProperty {
|
as? FirRegularClassSymbol
|
||||||
moduleData = session.moduleData
|
if (baseClassSymbol != null) {
|
||||||
origin = FirDeclarationOrigin.ScriptCustomization
|
// assuming that if base class will be unresolved, the error will be reported on the contextReceiver
|
||||||
// TODO: copy type parameters?
|
baseClassSymbol.fir.primaryConstructorIfAny(session)?.fir?.valueParameters?.forEach { baseCtorParameter ->
|
||||||
returnTypeRef = baseCtorParameter.returnTypeRef
|
parameters.add(
|
||||||
name = baseCtorParameter.name
|
buildProperty {
|
||||||
symbol = FirPropertySymbol(name)
|
moduleData = session.moduleData
|
||||||
status = FirDeclarationStatusImpl(Visibilities.Local, Modality.FINAL)
|
origin = FirDeclarationOrigin.ScriptCustomization
|
||||||
isLocal = true
|
// TODO: copy type parameters?
|
||||||
isVar = false
|
returnTypeRef = baseCtorParameter.returnTypeRef
|
||||||
}
|
name = baseCtorParameter.name
|
||||||
)
|
symbol = FirPropertySymbol(name)
|
||||||
}
|
status = FirDeclarationStatusImpl(Visibilities.Local, Modality.FINAL)
|
||||||
|
isLocal = true
|
||||||
|
isVar = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configuration[ScriptCompilationConfiguration.implicitReceivers]?.forEach { implicitReceiver ->
|
}
|
||||||
contextReceivers.add(buildContextReceiverWithFqName(FqName.fromSegments(implicitReceiver.typeName.split("."))))
|
configuration[ScriptCompilationConfiguration.implicitReceivers]?.forEach { implicitReceiver ->
|
||||||
}
|
contextReceivers.add(buildContextReceiverWithFqName(FqName.fromSegments(implicitReceiver.typeName.split("."))))
|
||||||
configuration[ScriptCompilationConfiguration.providedProperties]?.forEach { propertyName, propertyType ->
|
}
|
||||||
val typeRef = buildUserTypeRef {
|
configuration[ScriptCompilationConfiguration.providedProperties]?.forEach { propertyName, propertyType ->
|
||||||
isMarkedNullable = propertyType.isNullable
|
val typeRef = buildUserTypeRef {
|
||||||
propertyType.typeName.split(".").forEach {
|
isMarkedNullable = propertyType.isNullable
|
||||||
qualifier.add(FirQualifierPartImpl(null, Name.identifier(it), FirTypeArgumentListImpl(null)))
|
propertyType.typeName.split(".").forEach {
|
||||||
}
|
qualifier.add(FirQualifierPartImpl(null, Name.identifier(it), FirTypeArgumentListImpl(null)))
|
||||||
}
|
|
||||||
parameters.add(
|
|
||||||
buildProperty {
|
|
||||||
moduleData = session.moduleData
|
|
||||||
origin = FirDeclarationOrigin.ScriptCustomization
|
|
||||||
returnTypeRef = typeRef
|
|
||||||
name = Name.identifier(propertyName)
|
|
||||||
symbol = FirPropertySymbol(name)
|
|
||||||
status = FirDeclarationStatusImpl(Visibilities.Local, Modality.FINAL)
|
|
||||||
isLocal = true
|
|
||||||
isVar = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
configuration[ScriptCompilationConfiguration.annotationsForSamWithReceivers]?.forEach {
|
|
||||||
_knownAnnotationsForSamWithReceiver.add(it.typeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration[ScriptCompilationConfiguration.defaultImports]?.forEach { defaultImport ->
|
|
||||||
val trimmed = defaultImport.trim()
|
|
||||||
val endsWithStar = trimmed.endsWith("*")
|
|
||||||
val stripped = if (endsWithStar) trimmed.substring(0, trimmed.length - 2) else trimmed
|
|
||||||
val fqName = FqName.fromSegments(stripped.split("."))
|
|
||||||
fileBuilder.imports += buildImport {
|
|
||||||
fileBuilder.sourceFile?.project()?.let {
|
|
||||||
val dummyElement = KtPsiFactory(it, markGenerated = true).createColon()
|
|
||||||
source = KtFakeSourceElement(dummyElement, KtFakeSourceElementKind.ImplicitImport)
|
|
||||||
}
|
|
||||||
importedFqName = fqName
|
|
||||||
isAllUnder = endsWithStar
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
parameters.add(
|
||||||
|
buildProperty {
|
||||||
|
moduleData = session.moduleData
|
||||||
|
origin = FirDeclarationOrigin.ScriptCustomization
|
||||||
|
returnTypeRef = typeRef
|
||||||
|
name = Name.identifier(propertyName)
|
||||||
|
symbol = FirPropertySymbol(name)
|
||||||
|
status = FirDeclarationStatusImpl(Visibilities.Local, Modality.FINAL)
|
||||||
|
isLocal = true
|
||||||
|
isVar = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
configuration[ScriptCompilationConfiguration.annotationsForSamWithReceivers]?.forEach {
|
||||||
|
_knownAnnotationsForSamWithReceiver.add(it.typeName)
|
||||||
|
}
|
||||||
|
|
||||||
configuration[ScriptCompilationConfiguration.annotationsForSamWithReceivers]?.forEach {
|
configuration[ScriptCompilationConfiguration.defaultImports]?.forEach { defaultImport ->
|
||||||
_knownAnnotationsForSamWithReceiver.add(it.typeName)
|
val trimmed = defaultImport.trim()
|
||||||
|
val endsWithStar = trimmed.endsWith("*")
|
||||||
|
val stripped = if (endsWithStar) trimmed.substring(0, trimmed.length - 2) else trimmed
|
||||||
|
val fqName = FqName.fromSegments(stripped.split("."))
|
||||||
|
fileBuilder.imports += buildImport {
|
||||||
|
fileBuilder.sourceFile?.project()?.let {
|
||||||
|
val dummyElement = KtPsiFactory(it, markGenerated = true).createColon()
|
||||||
|
source = KtFakeSourceElement(dummyElement, KtFakeSourceElementKind.ImplicitImport)
|
||||||
|
}
|
||||||
|
importedFqName = fqName
|
||||||
|
isAllUnder = endsWithStar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configuration[ScriptCompilationConfiguration.annotationsForSamWithReceivers]?.forEach {
|
||||||
|
_knownAnnotationsForSamWithReceiver.add(it.typeName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withConfigurationIfAny(file: KtSourceFile, body: (ScriptCompilationConfiguration) -> Unit) {
|
private fun KtSourceFile.asString() = path ?: name
|
||||||
val configuration = session.scriptDefinitionProviderService?.let { providerService ->
|
|
||||||
val sourceCode = file.toSourceCode()
|
|
||||||
val ktFile = sourceCode?.originalKtFile()
|
|
||||||
with(providerService) {
|
|
||||||
ktFile?.let { configurationFor(it) }
|
|
||||||
?: sourceCode?.let { configurationFor(it) }
|
|
||||||
?: defaultConfiguration()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration?.let { body.invoke(it) }
|
private fun logConfiguration(file: KtSourceFile, config: ScriptCompilationConfiguration) {
|
||||||
|
log.debug(
|
||||||
|
"Using configuration: ${file.asString()} => " +
|
||||||
|
"(${config[ScriptCompilationConfiguration.displayName]}, " +
|
||||||
|
"ext=.${config[ScriptCompilationConfiguration.fileExtension]}, " +
|
||||||
|
"pattern=${config[ScriptCompilationConfiguration.filePathPattern]})"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrLoadConfiguration(file: KtSourceFile): ScriptCompilationConfiguration? {
|
||||||
|
val service = checkNotNull(session.scriptDefinitionProviderService)
|
||||||
|
val sourceCode = file.toSourceCode()
|
||||||
|
val ktFile = sourceCode?.originalKtFile()
|
||||||
|
return with(service) {
|
||||||
|
ktFile?.let { asKtFile -> configurationFor(asKtFile)?.also { logConfiguration(file, it) } }
|
||||||
|
?: sourceCode?.let { asSourceCode -> configurationFor(asSourceCode)?.also { logConfiguration(file, it) } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildContextReceiverWithFqName(classFqn: FqName, customName: Name? = null) =
|
private fun buildContextReceiverWithFqName(classFqn: FqName, customName: Name? = null) =
|
||||||
@@ -160,6 +169,8 @@ class FirScriptConfiguratorExtensionImpl(
|
|||||||
get() = _knownAnnotationsForSamWithReceiver
|
get() = _knownAnnotationsForSamWithReceiver
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = Logger.getInstance(FirScriptConfiguratorExtensionImpl::class.java)
|
||||||
|
|
||||||
fun getFactory(hostConfiguration: ScriptingHostConfiguration): Factory {
|
fun getFactory(hostConfiguration: ScriptingHostConfiguration): Factory {
|
||||||
return Factory { session -> FirScriptConfiguratorExtensionImpl(session, hostConfiguration) }
|
return Factory { session -> FirScriptConfiguratorExtensionImpl(session, hostConfiguration) }
|
||||||
}
|
}
|
||||||
@@ -178,9 +189,6 @@ private fun FirScriptDefinitionProviderService.configurationFor(file: KtFile): S
|
|||||||
private fun FirScriptDefinitionProviderService.configurationFor(sourceCode: SourceCode): ScriptCompilationConfiguration? =
|
private fun FirScriptDefinitionProviderService.configurationFor(sourceCode: SourceCode): ScriptCompilationConfiguration? =
|
||||||
definitionProvider?.findDefinition(sourceCode)?.compilationConfiguration
|
definitionProvider?.findDefinition(sourceCode)?.compilationConfiguration
|
||||||
|
|
||||||
private fun FirScriptDefinitionProviderService.defaultConfiguration(): ScriptCompilationConfiguration? =
|
|
||||||
definitionProvider?.getDefaultDefinition()?.compilationConfiguration
|
|
||||||
|
|
||||||
fun KtSourceFile.toSourceCode(): SourceCode? = when (this) {
|
fun KtSourceFile.toSourceCode(): SourceCode? = when (this) {
|
||||||
is KtPsiSourceFile -> (psiFile as? KtFile)?.let(::KtFileScriptSource) ?: VirtualFileScriptSource(psiFile.virtualFile)
|
is KtPsiSourceFile -> (psiFile as? KtFile)?.let(::KtFileScriptSource) ?: VirtualFileScriptSource(psiFile.virtualFile)
|
||||||
is KtVirtualFileSourceFile -> VirtualFileScriptSource(virtualFile)
|
is KtVirtualFileSourceFile -> VirtualFileScriptSource(virtualFile)
|
||||||
|
|||||||
Reference in New Issue
Block a user