diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java index 35500be2999..3022868a1d7 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java @@ -104,12 +104,16 @@ public final class AnalyzerWithCompilerReport { private void reportIncompleteHierarchies() { assert analysisResult != null; - Collection incompletes = analysisResult.getBindingContext().getKeys(BindingContext.INCOMPLETE_HIERARCHY); - if (!incompletes.isEmpty()) { + BindingContext bindingContext = analysisResult.getBindingContext(); + Collection classes = bindingContext.getKeys(TraceBasedErrorReporter.INCOMPLETE_HIERARCHY); + if (!classes.isEmpty()) { StringBuilder message = new StringBuilder("The following classes have incomplete hierarchies:\n"); - for (ClassDescriptor incomplete : incompletes) { - String fqName = DescriptorUtils.getFqName(incomplete).asString(); - message.append(" ").append(fqName).append("\n"); + for (ClassDescriptor descriptor : classes) { + String fqName = DescriptorUtils.getFqName(descriptor).asString(); + List unresolved = bindingContext.get(TraceBasedErrorReporter.INCOMPLETE_HIERARCHY, descriptor); + assert unresolved != null && !unresolved.isEmpty() : + "Incomplete hierarchy should be reported with names of unresolved superclasses: " + fqName; + message.append(" ").append(fqName).append(", unresolved: ").append(unresolved).append("\n"); } messageCollectorWrapper.report(CompilerMessageSeverity.ERROR, message.toString(), CompilerMessageLocation.NO_LOCATION); } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.java index f5bb53c0875..cb30ba60196 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/TraceBasedErrorReporter.java @@ -20,6 +20,7 @@ import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; +import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass; import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass; import org.jetbrains.kotlin.resolve.BindingTrace; @@ -28,11 +29,13 @@ import org.jetbrains.kotlin.util.slicedMap.Slices; import org.jetbrains.kotlin.util.slicedMap.WritableSlice; import javax.inject.Inject; +import java.util.List; public class TraceBasedErrorReporter implements ErrorReporter { private static final Logger LOG = Logger.getInstance(TraceBasedErrorReporter.class); public static final WritableSlice ABI_VERSION_ERRORS = Slices.createCollectiveSlice(); + public static final WritableSlice> INCOMPLETE_HIERARCHY = Slices.createCollectiveSlice(); private BindingTrace trace; @@ -46,6 +49,11 @@ public class TraceBasedErrorReporter implements ErrorReporter { trace.record(ABI_VERSION_ERRORS, (VirtualFileKotlinClass) kotlinClass, actualVersion); } + @Override + public void reportIncompleteHierarchy(@NotNull ClassDescriptor descriptor, @NotNull List unresolvedSuperClasses) { + trace.record(INCOMPLETE_HIERARCHY, descriptor, unresolvedSuperClasses); + } + @Override public void reportCannotInferVisibility(@NotNull CallableMemberDescriptor descriptor) { OverrideResolver.createCannotInferVisibilityReporter(trace).invoke(descriptor); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 9ff81c0c6b5..90baefe025f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -232,8 +232,6 @@ public interface BindingContext { WritableSlice FILE_TO_PACKAGE_FRAGMENT = Slices.createSimpleSlice(); WritableSlice> PACKAGE_TO_FILES = Slices.createSimpleSlice(); - WritableSlice INCOMPLETE_HIERARCHY = Slices.createCollectiveSetSlice(); - @SuppressWarnings("UnusedDeclaration") @Deprecated // This field is needed only for the side effects of its initializer Void _static_initializer = BasicWritableSlice.initSliceDebugNames(BindingContext.class); diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Sub.java b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Sub.java new file mode 100644 index 00000000000..d617c4d0abc --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Sub.java @@ -0,0 +1,4 @@ +package test; + +public class Sub extends Super { +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Super.java b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Super.java new file mode 100644 index 00000000000..f9f818bf480 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/library/test/Super.java @@ -0,0 +1,7 @@ +package test; + +public class Super { + public String foo() { + return "!"; + } +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/output.txt new file mode 100644 index 00000000000..510c27b1497 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/output.txt @@ -0,0 +1,3 @@ +ERROR: compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/source.kt: (5, 22) Unresolved reference: foo +ERROR: The following classes have incomplete hierarchies: + test.Sub, unresolved: [Super] diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/source.kt new file mode 100644 index 00000000000..a03c65d3865 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/incompleteHierarchyInJava/source.kt @@ -0,0 +1,5 @@ +import test.Sub + +class SubSub : Sub() + +fun bar() = SubSub().foo() diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java b/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java index f48fc17826f..56e343f15aa 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliBaseTest.java @@ -17,10 +17,11 @@ package org.jetbrains.kotlin.cli; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import kotlin.Charsets; +import kotlin.io.IoPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.cli.common.CLICompiler; import org.jetbrains.kotlin.cli.common.ExitCode; @@ -33,7 +34,10 @@ import org.jetbrains.kotlin.utils.UtilsPackage; import org.junit.Rule; import org.junit.rules.TestName; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; import java.util.List; public class CliBaseTest { @@ -46,7 +50,7 @@ public class CliBaseTest { public final TestName testName = new TestName(); @NotNull - private static Pair executeCompilerGrabOutput(@NotNull CLICompiler compiler, @NotNull List args) { + public static Pair executeCompilerGrabOutput(@NotNull CLICompiler compiler, @NotNull List args) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); PrintStream origErr = System.err; try { @@ -88,7 +92,7 @@ public class CliBaseTest { @NotNull final String testDataDir, @NotNull final String tempDir ) throws IOException { - List lines = FileUtil.loadLines(new FileInputStream(argsFilePath)); + List lines = IoPackage.readLines(new File(argsFilePath), Charsets.UTF_8); return ContainerUtil.mapNotNull(lines, new Function() { @Override diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index eac50d74a78..832b6adec0d 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -18,11 +18,15 @@ package org.jetbrains.kotlin.jvm.compiler; import com.google.common.collect.Iterables; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.analyzer.AnalysisResult; +import org.jetbrains.kotlin.cli.CliBaseTest; +import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; import org.jetbrains.kotlin.cli.common.messages.MessageCollectorPlainTextToStream; +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.JetCoreEnvironment; import org.jetbrains.kotlin.config.CompilerConfiguration; @@ -181,4 +185,30 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { assertEquals("There should be no diagnostics", 0, Iterables.size(bindingContext.getDiagnostics())); } + + public void testIncompleteHierarchyInJava() throws Exception { + // This test compiles a Java library of two classes (Super and Sub), then deletes Super.class and attempts to compile a Kotlin + // source against this broken library. The expected result is an "incomplete hierarchy" error message from the compiler + + JetTestUtils.compileJavaFiles( + Arrays.asList( + new File(getTestDataDirectory() + "/library/test/Super.java"), + new File(getTestDataDirectory() + "/library/test/Sub.java") + ), + Arrays.asList("-d", tmpdir.getPath()) + ); + + File superClassFile = new File(tmpdir + "/test/Super.class"); + assert superClassFile.delete() : "Can't delete " + superClassFile; + + File source = new File(getTestDataDirectory(), "source.kt"); + + Pair pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList( + source.getPath(), + "-classpath", tmpdir.getPath(), + "-d", tmpdir.getPath() + )); + + JetTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), pair.first); + } } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/ErrorReporter.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/ErrorReporter.java index faab00ecdd1..4ade4c19606 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/ErrorReporter.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/components/ErrorReporter.java @@ -19,11 +19,16 @@ package org.jetbrains.kotlin.load.java.components; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; +import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass; +import java.util.List; + public interface ErrorReporter { void reportIncompatibleAbiVersion(@NotNull KotlinJvmBinaryClass kotlinClass, int actualVersion); + void reportIncompleteHierarchy(@NotNull ClassDescriptor descriptor, @NotNull List unresolvedSuperClasses); + void reportCannotInferVisibility(@NotNull CallableMemberDescriptor descriptor); void reportLoadingError(@NotNull String message, @Nullable Exception exception); diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt index 4ad18561de4..7f69f55e95b 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt @@ -30,10 +30,13 @@ import org.jetbrains.kotlin.load.java.lazy.resolveAnnotations import org.jetbrains.kotlin.load.java.lazy.types.toAttributes import org.jetbrains.kotlin.resolve.scopes.InnerClassesScopeWrapper import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.utils.* import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.types.AbstractClassTypeConstructor +import java.util.ArrayList +import org.jetbrains.kotlin.utils.toReadOnlyList +import org.jetbrains.kotlin.load.java.structure.JavaType +import org.jetbrains.kotlin.load.java.structure.JavaClassifierType class LazyJavaClassDescriptor( private val outerC: LazyJavaResolverContext, @@ -106,23 +109,35 @@ class LazyJavaClassDescriptor( jClass.getTypeParameters().map { p -> c.typeParameterResolver.resolveTypeParameter(p) - ?: throw AssertionError("Parameter $p surely belongs to class ${jClass}, so it must be resolved") + ?: throw AssertionError("Parameter $p surely belongs to class $jClass, so it must be resolved") } } override fun getParameters(): List = parameters() private val supertypes = c.storageManager.createLazyValue> { - jClass.getSupertypes().stream() - .map { - supertype -> - c.typeResolver.transformJavaType(supertype, TypeUsage.SUPERTYPE.toAttributes()) - } - .filter { supertype -> !supertype.isError() && !KotlinBuiltIns.isAnyOrNullableAny(supertype) } - .toList() - .ifEmpty { - listOf(KotlinBuiltIns.getInstance().getAnyType()) - } + val javaTypes = jClass.getSupertypes() + val result = ArrayList(javaTypes.size()) + val incomplete = ArrayList(0) + + for (javaType in javaTypes) { + val jetType = c.typeResolver.transformJavaType(javaType, TypeUsage.SUPERTYPE.toAttributes()) + if (jetType.isError()) { + incomplete.add(javaType) + continue + } + if (!KotlinBuiltIns.isAnyOrNullableAny(jetType)) { + result.add(jetType) + } + } + + if (incomplete.isNotEmpty()) { + c.errorReporter.reportIncompleteHierarchy(getDeclarationDescriptor(), incomplete.map { javaType -> + (javaType as JavaClassifierType).getPresentableText() + }) + } + + if (result.isNotEmpty()) result.toReadOnlyList() else listOf(KotlinBuiltIns.getInstance().getAnyType()) } override fun getSupertypes(): Collection = supertypes() diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/LoggingErrorReporter.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/LoggingErrorReporter.kt index 98ff28e38a2..38b2fd268ca 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/LoggingErrorReporter.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/LoggingErrorReporter.kt @@ -20,12 +20,17 @@ import org.jetbrains.kotlin.load.java.components.ErrorReporter import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.kotlin.descriptors.ClassDescriptor class LoggingErrorReporter(private val log: Logger) : ErrorReporter { override fun reportLoadingError(message: String, exception: Exception?) { log.error(message, exception) } + override fun reportIncompleteHierarchy(descriptor: ClassDescriptor, unresolvedSuperClasses: List) { + log.error("Incomplete hierarchy for $descriptor. Super classes are not found: $unresolvedSuperClasses") + } + override fun reportCannotInferVisibility(descriptor: CallableMemberDescriptor) { log.error("Could not infer visibility for $descriptor") }