From 1760befa37f297189b7b8ae0359e5ef35091cbc0 Mon Sep 17 00:00:00 2001
From: Mikhael Bogdanov <mikhael.bogdanov@jetbrains.com>
Date: Fri, 6 Aug 2021 15:29:49 +0200
Subject: [PATCH] [KAPT] Add experimental JDK 17 support
Bug: https://youtrack.jetbrains.com/issue/KT-47583
Origin: upstream, commit:1760befa37f297189b7b8ae0359e5ef35091cbc0
Applied-Upstream: 1.6.0
Forwarded: not-needed
Last-Update: 2023-01-31

 #KT-47583 Fixed
---
 .../kotlin/kapt3/base/KaptContext.kt          |  31 ++-
 .../kotlin/kapt3/base/javac/KaptJavaLog.kt    |  27 ++-
 .../kotlin/kapt3/base/javac/KaptJavaLog17.kt  | 210 ++++++++++++++++++
 .../kapt3/base/util/WriterBackedKaptLogger.kt |   2 +-
 .../kotlin/kapt3/base/util/java9Utils.kt      |   1 +
 .../kapt3/test/AbstractKotlinKapt3Test.kt     |   4 +-
 6 files changed, 253 insertions(+), 22 deletions(-)
 create mode 100644 plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt

--- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt
+++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt
@@ -16,10 +16,9 @@
 import org.jetbrains.kotlin.base.kapt3.KaptOptions
 import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
 import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess
-import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler
-import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager
-import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog
+import org.jetbrains.kotlin.kapt3.base.javac.*
 import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
+import org.jetbrains.kotlin.kapt3.base.util.isJava17OrLater
 import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
 import org.jetbrains.kotlin.kapt3.base.util.putJavacOption
 import java.io.Closeable
@@ -31,7 +30,7 @@
     val compiler: KaptJavaCompiler
     val fileManager: KaptJavaFileManager
     private val javacOptions: Options
-    val javaLog: KaptJavaLog
+    val javaLog: KaptJavaLogBase
     val cacheManager: JavaClassCacheManager?
 
     val sourcesToReprocess: SourcesToReprocess
@@ -39,12 +38,24 @@
     protected open fun preregisterTreeMaker(context: Context) {}
 
     private fun preregisterLog(context: Context) {
-        val interceptorData = KaptJavaLog.DiagnosticInterceptorData()
+        val interceptorData = KaptJavaLogBase.DiagnosticInterceptorData()
         context.put(Log.logKey, Context.Factory<Log> { newContext ->
-            KaptJavaLog(
-                options.projectBaseDir, newContext, logger.errorWriter, logger.warnWriter, logger.infoWriter,
-                interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS]
-            )
+            if (isJava17OrLater()) {
+                newContext.put(Log.outKey, logger.infoWriter)
+                val errKey = (Log::class.java.fields.firstOrNull() { it.name == "errKey" }
+                    ?: error("Can't find errKey field in Log.class")).get(null)
+                @Suppress("UNCHECKED_CAST")
+                newContext.put(errKey as Context.Key<java.io.PrintWriter>, logger.errorWriter)
+                KaptJavaLog17(
+                    options.projectBaseDir, newContext,
+                    interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS]
+                )
+            } else {
+                KaptJavaLog(
+                    options.projectBaseDir, newContext, logger.errorWriter, logger.warnWriter, logger.infoWriter,
+                    interceptorData, options[KaptFlag.MAP_DIAGNOSTIC_LOCATIONS]
+                )
+            }
         })
     }
 
@@ -134,7 +145,7 @@
 
         ClassReader.instance(context).saveParameterNames = true
 
-        javaLog = compiler.log as KaptJavaLog
+        javaLog = compiler.log as KaptJavaLogBase
     }
 
     override fun close() {
--- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt
+++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog.kt
@@ -18,7 +18,20 @@
 import javax.tools.JavaFileObject
 import javax.tools.JavaFileObject.Kind
 import javax.tools.SimpleJavaFileObject
-import com.sun.tools.javac.util.List as JavacList
+
+interface KaptJavaLogBase {
+    val interceptorData: DiagnosticInterceptorData
+
+    val reportedDiagnostics: List<JCDiagnostic>
+
+    fun flush(kind: Log.WriterKind?)
+
+    fun flush()
+
+    class DiagnosticInterceptorData {
+        var files: Map<JavaFileObject, JCTree.JCCompilationUnit> = emptyMap()
+    }
+}
 
 class KaptJavaLog(
     private val projectBaseDir: File?,
@@ -26,9 +39,9 @@
     errWriter: PrintWriter,
     warnWriter: PrintWriter,
     noticeWriter: PrintWriter,
-    val interceptorData: DiagnosticInterceptorData,
+    override val interceptorData: KaptJavaLogBase.DiagnosticInterceptorData,
     private val mapDiagnosticLocations: Boolean
-) : Log(context, errWriter, warnWriter, noticeWriter) {
+) : Log(context, errWriter, warnWriter, noticeWriter), KaptJavaLogBase {
     private val stubLineInfo = KaptStubLineInformation()
     private val javacMessages = JavacMessages.instance(context)
 
@@ -36,7 +49,7 @@
         context.put(Log.outKey, noticeWriter)
     }
 
-    val reportedDiagnostics: List<JCDiagnostic>
+    override val reportedDiagnostics: List<JCDiagnostic>
         get() = _reportedDiagnostics
 
     private val _reportedDiagnostics = mutableListOf<JCDiagnostic>()
@@ -217,10 +230,6 @@
             "compiler.err.not.def.public.cant.access"
         )
     }
-
-    class DiagnosticInterceptorData {
-        var files: Map<JavaFileObject, JCTree.JCCompilationUnit> = emptyMap()
-    }
 }
 
 private val LINE_SEPARATOR: String = System.getProperty("line.separator")
@@ -256,7 +265,7 @@
     }
 }
 
-private data class KotlinFileObject(val file: File) : SimpleJavaFileObject(file.toURI(), Kind.SOURCE) {
+/*private*/internal data class KotlinFileObject(val file: File) : SimpleJavaFileObject(file.toURI(), Kind.SOURCE) {
     override fun openOutputStream() = file.outputStream()
     override fun openWriter() = file.writer()
     override fun openInputStream() = file.inputStream()
--- /dev/null
+++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaLog17.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
+package org.jetbrains.kotlin.kapt3.base.javac
+
+import com.sun.tools.javac.tree.JCTree
+import com.sun.tools.javac.util.*
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType
+import org.jetbrains.kotlin.kapt3.base.stubs.KaptStubLineInformation
+import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition
+import java.io.*
+import javax.tools.Diagnostic
+
+// It's copy/paste as is of KaptJavaLog
+// TODO: most likely KaptJavaLog could be substituted with this version.
+// The only difference that is writers are passed through context
+class KaptJavaLog17(
+    private val projectBaseDir: File?,
+    context: Context,
+    override val interceptorData: KaptJavaLogBase.DiagnosticInterceptorData,
+    private val mapDiagnosticLocations: Boolean
+) : Log(context), KaptJavaLogBase {
+    private val stubLineInfo = KaptStubLineInformation()
+    private val javacMessages = JavacMessages.instance(context)
+
+    override val reportedDiagnostics: List<JCDiagnostic>
+        get() = _reportedDiagnostics
+
+    private val _reportedDiagnostics = mutableListOf<JCDiagnostic>()
+
+    override fun flush(kind: WriterKind?) {
+        super.flush(kind)
+
+        val diagnosticKind = when (kind) {
+            WriterKind.ERROR -> JCDiagnostic.DiagnosticType.ERROR
+            WriterKind.WARNING -> JCDiagnostic.DiagnosticType.WARNING
+            WriterKind.NOTICE -> JCDiagnostic.DiagnosticType.NOTE
+            else -> return
+        }
+
+        _reportedDiagnostics.removeAll { it.type == diagnosticKind }
+    }
+
+    override fun flush() {
+        super.flush()
+        _reportedDiagnostics.clear()
+    }
+
+    override fun report(diagnostic: JCDiagnostic) {
+        if (diagnostic.type == JCDiagnostic.DiagnosticType.ERROR && diagnostic.code in IGNORED_DIAGNOSTICS) {
+            return
+        }
+
+        if (diagnostic.type == JCDiagnostic.DiagnosticType.WARNING
+            && diagnostic.code == "compiler.warn.proc.unmatched.processor.options"
+            && diagnostic.args.singleOrNull() == "[kapt.kotlin.generated]"
+        ) {
+            // Do not report the warning about "kapt.kotlin.generated" option being ignored if it's the only ignored option
+            return
+        }
+
+        val targetElement = diagnostic.diagnosticPosition
+        val sourceFile = interceptorData.files[diagnostic.source]
+
+        if (diagnostic.code.contains("err.cant.resolve") && targetElement != null) {
+            if (sourceFile != null) {
+                val insideImports = targetElement.tree in sourceFile.imports
+                // Ignore resolve errors in import statements
+                if (insideImports) return
+            }
+        }
+
+        if (mapDiagnosticLocations && sourceFile != null && targetElement?.tree != null) {
+            val kotlinPosition = stubLineInfo.getPositionInKotlinFile(sourceFile, targetElement.tree)
+            val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) }
+            if (kotlinPosition != null && kotlinFile != null) {
+                val flags = JCDiagnostic.DiagnosticFlag.values().filterTo(mutableSetOf(), diagnostic::isFlagSet)
+
+                val kotlinDiagnostic = diags.create(
+                    diagnostic.type,
+                    diagnostic.lintCategory,
+                    flags,
+                    DiagnosticSource(KotlinFileObject(kotlinFile), this),
+                    JCDiagnostic.SimpleDiagnosticPosition(kotlinPosition.pos),
+                    diagnostic.code.stripCompilerKeyPrefix(),
+                    *diagnostic.args
+                )
+
+                reportDiagnostic(kotlinDiagnostic)
+
+                // Avoid reporting the diagnostic twice
+                return
+            }
+        }
+
+        reportDiagnostic(diagnostic)
+    }
+
+    private fun String.stripCompilerKeyPrefix(): String {
+        for (kind in listOf("err", "warn", "misc", "note")) {
+            val prefix = "compiler.$kind."
+            if (startsWith(prefix)) {
+                return drop(prefix.length)
+            }
+        }
+
+        return this
+    }
+
+    private fun reportDiagnostic(diagnostic: JCDiagnostic) {
+        if (diagnostic.kind == Diagnostic.Kind.ERROR) {
+            val oldErrors = nerrors
+            super.report(diagnostic)
+            if (nerrors > oldErrors) {
+                _reportedDiagnostics += diagnostic
+            }
+        } else if (diagnostic.kind == Diagnostic.Kind.WARNING) {
+            val oldWarnings = nwarnings
+            super.report(diagnostic)
+            if (nwarnings > oldWarnings) {
+                _reportedDiagnostics += diagnostic
+            }
+        } else {
+            super.report(diagnostic)
+        }
+    }
+
+    override fun writeDiagnostic(diagnostic: JCDiagnostic) {
+        if (hasDiagnosticListener()) {
+            diagListener.report(diagnostic)
+            return
+        }
+
+        val writer = when (diagnostic.type) {
+            DiagnosticType.FRAGMENT, null -> kotlin.error("Invalid root diagnostic type: ${diagnostic.type}")
+            DiagnosticType.NOTE -> super.getWriter(WriterKind.NOTICE)
+            DiagnosticType.WARNING -> super.getWriter(WriterKind.WARNING)
+            DiagnosticType.ERROR -> super.getWriter(WriterKind.ERROR)
+        }
+
+        val formattedMessage = diagnosticFormatter.format(diagnostic, javacMessages.currentLocale)
+            .lines()
+            .joinToString(LINE_SEPARATOR, postfix = LINE_SEPARATOR) { original ->
+                // Kotlin location is put as a sub-diagnostic, so the formatter indents it with four additional spaces (6 in total).
+                // It looks weird, especially in the build log inside IntelliJ, so let's make things a bit better.
+                val trimmed = original.trimStart()
+                // Typically, javac places additional details about the diagnostics indented by two spaces
+                if (trimmed.startsWith(KOTLIN_LOCATION_PREFIX)) "  " + trimmed else original
+            }
+
+        writer.print(formattedMessage)
+        writer.flush()
+    }
+
+    private fun getKotlinSourceFile(pos: KotlinPosition): File? {
+        return if (pos.isRelativePath) {
+            val basePath = this.projectBaseDir
+            if (basePath != null) File(basePath, pos.path) else null
+        } else {
+            File(pos.path)
+        }
+    }
+
+    private operator fun <T : JCTree> Iterable<T>.contains(element: JCTree?): Boolean {
+        if (element == null) {
+            return false
+        }
+
+        var found = false
+        val visitor = object : JCTree.Visitor() {
+            override fun visitImport(that: JCTree.JCImport) {
+                super.visitImport(that)
+                if (!found) that.qualid.accept(this)
+            }
+
+            override fun visitSelect(that: JCTree.JCFieldAccess) {
+                super.visitSelect(that)
+                if (!found) that.selected.accept(this)
+            }
+
+            override fun visitTree(that: JCTree) {
+                if (!found && element == that) found = true
+            }
+        }
+        this.forEach { if (!found) it.accept(visitor) }
+        return found
+    }
+
+    companion object {
+        private val LINE_SEPARATOR: String = System.getProperty("line.separator")
+        private val KOTLIN_LOCATION_PREFIX = "Kotlin location: "
+
+        private val IGNORED_DIAGNOSTICS = setOf(
+            "compiler.err.name.clash.same.erasure",
+            "compiler.err.name.clash.same.erasure.no.override",
+            "compiler.err.name.clash.same.erasure.no.override.1",
+            "compiler.err.name.clash.same.erasure.no.hide",
+            "compiler.err.already.defined",
+            "compiler.err.annotation.type.not.applicable",
+            "compiler.err.doesnt.exist",
+            "compiler.err.cant.resolve.location",
+            "compiler.err.duplicate.annotation.missing.container",
+            "compiler.err.not.def.access.package.cant.access",
+            "compiler.err.package.not.visible",
+            "compiler.err.not.def.public.cant.access"
+        )
+    }
+}
--- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt
+++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/WriterBackedKaptLogger.kt
@@ -10,7 +10,7 @@
 class WriterBackedKaptLogger(
     override val isVerbose: Boolean,
     override val infoWriter: PrintWriter = PrintWriter(System.out),
-    override val warnWriter: PrintWriter = PrintWriter(System.out),
+    override val warnWriter: PrintWriter = if (isJava17OrLater()) infoWriter else PrintWriter(System.out),
     override val errorWriter: PrintWriter = PrintWriter(System.err)
 ) : KaptLogger {
     override fun info(message: String) {
--- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt
+++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt
@@ -39,7 +39,7 @@
 import org.jetbrains.kotlin.kapt3.KaptContextForStubGeneration
 import org.jetbrains.kotlin.kapt3.base.KaptContext
 import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
-import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog
+import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLogBase
 import org.jetbrains.kotlin.kapt3.base.parseJavaFiles
 import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
 import org.jetbrains.kotlin.kapt3.prettyPrint
@@ -294,7 +294,7 @@
             .let { removeMetadataAnnotationContents(it) }
 
         if (kaptContext.compiler.shouldStop(CompileStates.CompileState.ENTER)) {
-            val log = Log.instance(kaptContext.context) as KaptJavaLog
+            val log = Log.instance(kaptContext.context) as KaptJavaLogBase
 
             val actualErrors = log.reportedDiagnostics
                 .filter { it.type == JCDiagnostic.DiagnosticType.ERROR }
--- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt
+++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/util/java9Utils.kt
@@ -24,7 +24,12 @@
 import com.sun.tools.javac.util.List as JavacList
 import org.jetbrains.kotlin.kapt3.base.plus
 
-fun isJava9OrLater(): Boolean = !System.getProperty("java.version").startsWith("1.")
+private fun getJavaVersion(): Int =
+    System.getProperty("java.specification.version")?.substringAfter('.')?.toIntOrNull() ?: 6
+
+fun isJava9OrLater() = getJavaVersion() >= 9
+fun isJava11OrLater() = getJavaVersion() >= 11
+fun isJava17OrLater() = getJavaVersion() >= 17
 
 fun Options.putJavacOption(jdk8Name: String, jdk9Name: String, value: String) {
     val option = if (isJava9OrLater()) {
