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:
Andrei Klunnyi
2023-07-07 14:46:31 +02:00
parent 6867dffff1
commit c9eebffbfa
@@ -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)