Kapt3: Generate import statements in stubs

This commit is contained in:
Yan Zhulanow
2017-02-07 00:06:42 +03:00
parent 69a063b0b2
commit 131570080d
10 changed files with 138 additions and 5 deletions
@@ -40,6 +40,7 @@ class KaptContext(
val compiler: KaptJavaCompiler val compiler: KaptJavaCompiler
val fileManager: JavacFileManager val fileManager: JavacFileManager
val options: Options val options: Options
val javaLog: KaptJavaLog
init { init {
KaptJavaLog.preRegister(context, logger.messageCollector) KaptJavaLog.preRegister(context, logger.messageCollector)
@@ -50,6 +51,8 @@ class KaptContext(
fileManager = context.get(JavaFileManager::class.java) as JavacFileManager fileManager = context.get(JavaFileManager::class.java) as JavacFileManager
compiler = JavaCompiler.instance(context) as KaptJavaCompiler compiler = JavaCompiler.instance(context) as KaptJavaCompiler
javaLog = compiler.log as KaptJavaLog
options = Options.instance(context) options = Options.instance(context)
for ((key, value) in processorOptions) { for ((key, value) in processorOptions) {
val option = if (value.isEmpty()) "-A$key" else "-A$key=$value" val option = if (value.isEmpty()) "-A$key" else "-A$key=$value"
@@ -65,6 +65,7 @@ fun KaptContext.doAnnotationProcessing(
val parsedJavaFiles = compiler.parseFiles(javaFileObjects) val parsedJavaFiles = compiler.parseFiles(javaFileObjects)
compilerAfterAP = try { compilerAfterAP = try {
javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap()
val analyzedFiles = compiler.stopIfErrorOccurred( val analyzedFiles = compiler.stopIfErrorOccurred(
CompileStates.CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)) CompileStates.CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources))
compiler.processAnnotations(analyzedFiles) compiler.processAnnotations(analyzedFiles)
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.kapt3.javac package org.jetbrains.kotlin.kapt3.javac
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.Context import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.JCDiagnostic import com.sun.tools.javac.util.JCDiagnostic
import com.sun.tools.javac.util.Log import com.sun.tools.javac.util.Log
@@ -24,12 +25,14 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter
import java.io.PrintWriter import java.io.PrintWriter
import javax.tools.JavaFileObject
class KaptJavaLog( class KaptJavaLog(
context: Context, context: Context,
errWriter: PrintWriter, errWriter: PrintWriter,
warnWriter: PrintWriter, warnWriter: PrintWriter,
noticeWriter: PrintWriter noticeWriter: PrintWriter,
val interceptorData: DiagnosticInterceptorData
) : Log(context, errWriter, warnWriter, noticeWriter) { ) : Log(context, errWriter, warnWriter, noticeWriter) {
init { init {
context.put(Log.outKey, noticeWriter) context.put(Log.outKey, noticeWriter)
@@ -40,9 +43,44 @@ class KaptJavaLog(
return return
} }
val targetElement = diagnostic.diagnosticPosition
if (diagnostic.code.contains("err.cant.resolve") && targetElement != null) {
val sourceFile = interceptorData.files[diagnostic.source]
if (sourceFile != null) {
val insideImports = targetElement.tree in sourceFile.imports
// Ignore resolve errors in import statements
if (insideImports) return
}
}
super.report(diagnostic) super.report(diagnostic)
} }
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 { companion object {
private val IGNORED_DIAGNOSTICS = setOf( private val IGNORED_DIAGNOSTICS = setOf(
"compiler.err.name.clash.same.erasure", "compiler.err.name.clash.same.erasure",
@@ -50,16 +88,22 @@ class KaptJavaLog(
"compiler.err.name.clash.same.erasure.no.override.1", "compiler.err.name.clash.same.erasure.no.override.1",
"compiler.err.name.clash.same.erasure.no.hide", "compiler.err.name.clash.same.erasure.no.hide",
"compiler.err.already.defined", "compiler.err.already.defined",
"compiler.err.annotation.type.not.applicable") "compiler.err.annotation.type.not.applicable",
"compiler.err.doesnt.exist")
internal fun preRegister(context: Context, messageCollector: MessageCollector) { internal fun preRegister(context: Context, messageCollector: MessageCollector) {
val interceptorData = DiagnosticInterceptorData()
context.put(Log.logKey, Context.Factory<Log> { context.put(Log.logKey, Context.Factory<Log> {
fun makeWriter(severity: CompilerMessageSeverity) = PrintWriter(MessageCollectorBackedWriter(messageCollector, severity)) fun makeWriter(severity: CompilerMessageSeverity) = PrintWriter(MessageCollectorBackedWriter(messageCollector, severity))
val errWriter = makeWriter(ERROR) val errWriter = makeWriter(ERROR)
val warnWriter = makeWriter(STRONG_WARNING) val warnWriter = makeWriter(STRONG_WARNING)
val noticeWriter = makeWriter(INFO) val noticeWriter = makeWriter(INFO)
KaptJavaLog(it, errWriter, warnWriter, noticeWriter) KaptJavaLog(it, errWriter, warnWriter, noticeWriter, interceptorData)
}) })
} }
} }
class DiagnosticInterceptorData {
var files: Map<JavaFileObject, JCTree.JCCompilationUnit> = emptyMap()
}
} }
@@ -26,7 +26,7 @@ import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Type.* import org.jetbrains.org.objectweb.asm.Type.*
class KaptTreeMaker(context: Context) : TreeMaker(context) { class KaptTreeMaker(context: Context) : TreeMaker(context) {
private val nameTable = Names.instance(context).table val nameTable = Names.instance(context).table
fun Type(type: Type): JCTree.JCExpression { fun Type(type: Type): JCTree.JCExpression {
convertBuiltinType(type)?.let { return it } convertBuiltinType(type)?.let { return it }
@@ -29,7 +29,9 @@ import org.jetbrains.kotlin.kapt3.*
import org.jetbrains.kotlin.kapt3.javac.KaptTreeMaker import org.jetbrains.kotlin.kapt3.javac.KaptTreeMaker
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
import org.jetbrains.kotlin.kapt3.util.* import org.jetbrains.kotlin.kapt3.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
@@ -131,7 +133,7 @@ class ClassFileToSourceStubConverter(
val classDeclaration = convertClass(clazz, packageName, true) ?: return null val classDeclaration = convertClass(clazz, packageName, true) ?: return null
val imports = JavacList.nil<JCTree>() val imports = if (correctErrorTypes) convertImports(ktFile, classDeclaration) else JavacList.nil()
val classes = JavacList.of<JCTree>(classDeclaration) val classes = JavacList.of<JCTree>(classDeclaration)
val topLevel = treeMaker.TopLevel(packageAnnotations, packageClause, imports + classes) val topLevel = treeMaker.TopLevel(packageAnnotations, packageClause, imports + classes)
@@ -144,6 +146,44 @@ class ClassFileToSourceStubConverter(
return topLevel return topLevel
} }
private fun convertImports(file: KtFile, classDeclaration: JCClassDecl): JavacList<JCTree> {
val imports = mutableListOf<JCTree>()
for (importDirective in file.importDirectives) {
// Qualified name should be valid Java fq-name
val importedFqName = importDirective.importedFqName ?: continue
if (!isValidQualifiedName(importedFqName)) continue
val shortName = importedFqName.shortName()
if (shortName.asString() == classDeclaration.simpleName.toString()) continue
// If alias is specified, it also should be valid Java name
val aliasName = importDirective.aliasName
if (aliasName != null /*TODO support aliases */ /*&& getValidIdentifierName(aliasName) == null*/) continue
val importedReference = getReferenceExpression(importDirective.importedReference)
?.let { kaptContext.bindingContext[BindingContext.REFERENCE_TARGET, it] }
if (importedReference is CallableDescriptor) continue
val importedExpr = treeMaker.FqName(importedFqName.asString())
imports += if (importDirective.isAllUnder) {
treeMaker.Import(treeMaker.Select(importedExpr, treeMaker.nameTable.names.asterisk), false)
} else {
treeMaker.Import(importedExpr, false)
}
}
return JavacList.from(imports)
}
private tailrec fun getReferenceExpression(expression: KtExpression?): KtReferenceExpression? = when (expression) {
is KtReferenceExpression -> expression
is KtQualifiedExpression -> getReferenceExpression(expression.selectorExpression)
else -> null
}
/** /**
* Returns false for the inner classes or if the origin for the class was not found. * Returns false for the inner classes or if the origin for the class was not found.
*/ */
@@ -456,6 +496,10 @@ class ClassFileToSourceStubConverter(
return ifNonError() return ifNonError()
} }
private fun isValidQualifiedName(name: FqName): Boolean {
return name.pathSegments().all { getValidIdentifierName(it.asString(), false) != null }
}
fun getValidIdentifierName(name: String, canBeConstructor: Boolean = false): String? { fun getValidIdentifierName(name: String, canBeConstructor: Boolean = false): String? {
if (canBeConstructor && name == "<init>") { if (canBeConstructor && name == "<init>") {
return name return name
@@ -96,6 +96,7 @@ abstract class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3T
val javaFiles = convert(kaptRunner, typeMapper, generateNonExistentClass, correctErrorTypes) val javaFiles = convert(kaptRunner, typeMapper, generateNonExistentClass, correctErrorTypes)
kaptRunner.javaLog.interceptorData.files = javaFiles.map { it.sourceFile to it }.toMap()
if (validate) kaptRunner.compiler.enterTrees(javaFiles) if (validate) kaptRunner.compiler.enterTrees(javaFiles)
val actualRaw = javaFiles.sortedBy { it.sourceFile.name }.joinToString (FILE_SEPARATOR) val actualRaw = javaFiles.sortedBy { it.sourceFile.name }.joinToString (FILE_SEPARATOR)
@@ -102,6 +102,12 @@ public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFi
doTest(fileName); doTest(fileName);
} }
@TestMetadata("importsForErrorTypes.kt")
public void testImportsForErrorTypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/importsForErrorTypes.kt");
doTest(fileName);
}
@TestMetadata("inheritanceSimple.kt") @TestMetadata("inheritanceSimple.kt")
public void testInheritanceSimple() throws Exception { public void testInheritanceSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/inheritanceSimple.kt"); String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/inheritanceSimple.kt");
@@ -0,0 +1,12 @@
// CORRECT_ERROR_TYPES
import java.util.zip.*
import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.MICROSECONDS
import java.util.concurrent.TimeUnit.*
import java.util.Calendar.*
fun test(): Any? {
return null
}
@@ -0,0 +1,18 @@
import java.util.zip.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit.MICROSECONDS;
import java.util.concurrent.TimeUnit.*;
import java.util.Calendar.*;
public final class ImportsForErrorTypesKt {
public ImportsForErrorTypesKt() {
super();
}
@org.jetbrains.annotations.Nullable()
public static final java.lang.Object test() {
return null;
}
}
@@ -1,3 +1,5 @@
import java.util.Calendar;
public final class MyType<T extends java.lang.Object> { public final class MyType<T extends java.lang.Object> {
public MyType() { public MyType() {
@@ -8,6 +10,8 @@ public final class MyType<T extends java.lang.Object> {
//////////////////// ////////////////////
import java.util.Calendar;
@kotlin.Suppress(names = {"UNRESOLVED_REFERENCE"}) @kotlin.Suppress(names = {"UNRESOLVED_REFERENCE"})
public final class Test { public final class Test {
@org.jetbrains.annotations.NotNull() @org.jetbrains.annotations.NotNull()