Is it possible to modify the frontend ast without ...
# compiler
c
Is it possible to modify the frontend ast without writing it back to the source file? I'm writing a new runtime for jetpack compose (a la mosaic and Compose for Desktop) and want to automatically (and invisibly to the developer) add @Composable annotations by convention. This has to happen before
ComposableCallChecker
runs, which I think means it needs to happen in the frontend. I tried this is in a
AnalysisHandlerExtension
Copy code
myKtNamedFunction.addAnnotationEntry(KtPsiFactory(project).createAnnotationEntry("@androidx.compose.runtime.Composable"))
but it throws "Cannot modify a read-only file 'main.kt'"
k
Yes, but it is very hacky:
Copy code
class MyAnalysisExtension() : AnalysisHandlerExtension {

  override fun doAnalysis(
    project: Project,
    module: ModuleDescriptor,
    projectContext: ProjectContext,
    files: Collection<KtFile>,
    bindingTrace: BindingTrace,
    componentProvider: ComponentProvider
  ): AnalysisResult? {
    val mutableFiles = files as ArrayList<KtFile> // WARNING: hacky, can break any time
    for (i in mutableFiles.indices) {
       val newSourceString = mutableFiles[i].text // Change this source code string however you like
       mutableFiles[i] = replaceFile(file, newSourceString)
    }
    return super.doAnalysis(project, module, projectContext, files, bindingTrace, componentProvider)
  }

  private fun replaceFile(file: KtFile, newSource: String): KtFile {
    val result = KtFile(
      viewProvider =
      ReplacedFileViewProvider(file.manager, file.virtualFile, newSource),
      isCompiled = false
    )

    result.isCommonSource = file.isCommonSource
    return result
  }
}

class ReplacedFileViewProvider(
  psiManager: PsiManager,
  virtualFile: VirtualFile,
  private val newSource: String
) : SingleRootFileViewProvider(psiManager, virtualFile) {
  override fun getDocument(): Document? {
    return super.getDocument()?.also { it.setText(newSource) }
  }
}
I hope you can figure the way from here. This approach was inspired by the Arrow Meta codebase, to give credit where it’s due 🙂
c
You're right...that is hacky, lol. I managed to get an arrow-meta example (based on this https://github.com/arrow-kt/arrow-meta-examples/tree/main/generate-sources) working but only with kotlin 1.5.0. If I change kotlin to 1.6.0 it silently ignores the text change. Thanks for the tip though.
m
I thought all of the Compose runs in the backend. In that case you can probably make a backend plugin that adds the @Compose annotation and ensure (somehow) that this plugin runs before Compose's one.
c
That's what I was trying to do, but compose has a compiler extension class
ComposableCallChecker
that runs on the frontend and ensures that composables are called by composables; the signature is:
Copy code
override fun check(
        resolvedCall: ResolvedCall<*>,
        reportOn: PsiElement,
        context: CallCheckerContext
    ) {
c
So it means it’s impossible after 1.6? I also have similar needs...
c
I just found this on the #arrow channel: https://kotlinlang.slack.com/archives/C5UPMM0A0/p1640195566362900 . It looks like we need to wait until the FIR preview comes out, hopefully in 1.6.20.
k
@dmitriy.novozhilov That class seems to be interpreting backend IR. The only public method I see there returns an IrExpression. What we need is to add annotations and checks in the frontend, mainly because the backend IR plugins are not always called (JS IR backend being the worst offender here) or called too late (when annotations are already processed by the compiler). Can you expand a bit more on how this could be achieved using the IrInterpreter?
d
Oh, I replied to a wrong thread 🤦
As for original question: it's impossible to modify PSI in compiler plugin (actually, it is even impossible in compiler itself) But I suppose that original case (adding annotations on frontned) will be possible in compiler API of K2 compiler
k
Haha, got it, thank you 🙂