Report incomplete hierarchy error

This is the case when you reference a Java class in Kotlin whose superclass is
not resolved. Previously this fact was swallowed by LazyJavaClassDescriptor
leading to mysterious compilation errors

 #KT-5129 Fixed
This commit is contained in:
Alexander Udalov
2015-02-09 17:28:02 +03:00
parent 28d8c8906a
commit 4128655893
12 changed files with 111 additions and 23 deletions
@@ -104,12 +104,16 @@ public final class AnalyzerWithCompilerReport {
private void reportIncompleteHierarchies() {
assert analysisResult != null;
Collection<ClassDescriptor> incompletes = analysisResult.getBindingContext().getKeys(BindingContext.INCOMPLETE_HIERARCHY);
if (!incompletes.isEmpty()) {
BindingContext bindingContext = analysisResult.getBindingContext();
Collection<ClassDescriptor> 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<String> 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);
}
@@ -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<VirtualFileKotlinClass, Integer> ABI_VERSION_ERRORS = Slices.createCollectiveSlice();
public static final WritableSlice<ClassDescriptor, List<String>> 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<String> unresolvedSuperClasses) {
trace.record(INCOMPLETE_HIERARCHY, descriptor, unresolvedSuperClasses);
}
@Override
public void reportCannotInferVisibility(@NotNull CallableMemberDescriptor descriptor) {
OverrideResolver.createCannotInferVisibilityReporter(trace).invoke(descriptor);
@@ -232,8 +232,6 @@ public interface BindingContext {
WritableSlice<JetFile, PackageFragmentDescriptor> FILE_TO_PACKAGE_FRAGMENT = Slices.createSimpleSlice();
WritableSlice<FqName, Collection<JetFile>> PACKAGE_TO_FILES = Slices.createSimpleSlice();
WritableSlice<ClassDescriptor, Boolean> 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);
@@ -0,0 +1,4 @@
package test;
public class Sub extends Super {
}
@@ -0,0 +1,7 @@
package test;
public class Super {
public String foo() {
return "!";
}
}
@@ -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]
@@ -0,0 +1,5 @@
import test.Sub
class SubSub : Sub()
fun bar() = SubSub().foo()
@@ -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<String, ExitCode> executeCompilerGrabOutput(@NotNull CLICompiler<?> compiler, @NotNull List<String> args) {
public static Pair<String, ExitCode> executeCompilerGrabOutput(@NotNull CLICompiler<?> compiler, @NotNull List<String> 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<String> lines = FileUtil.loadLines(new FileInputStream(argsFilePath));
List<String> lines = IoPackage.readLines(new File(argsFilePath), Charsets.UTF_8);
return ContainerUtil.mapNotNull(lines, new Function<String, String>() {
@Override
@@ -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<String, ExitCode> pair = CliBaseTest.executeCompilerGrabOutput(new K2JVMCompiler(), Arrays.asList(
source.getPath(),
"-classpath", tmpdir.getPath(),
"-d", tmpdir.getPath()
));
JetTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), pair.first);
}
}
@@ -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<String> unresolvedSuperClasses);
void reportCannotInferVisibility(@NotNull CallableMemberDescriptor descriptor);
void reportLoadingError(@NotNull String message, @Nullable Exception exception);
@@ -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<TypeParameterDescriptor> = parameters()
private val supertypes = c.storageManager.createLazyValue<Collection<JetType>> {
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<JetType>(javaTypes.size())
val incomplete = ArrayList<JavaType>(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<JetType> = supertypes()
@@ -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<String>) {
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")
}