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:
+9
-5
@@ -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);
|
||||
}
|
||||
|
||||
+8
@@ -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);
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package test;
|
||||
|
||||
public class Sub extends Super {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test;
|
||||
|
||||
public class Super {
|
||||
public String foo() {
|
||||
return "!";
|
||||
}
|
||||
}
|
||||
+3
@@ -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]
|
||||
+5
@@ -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
|
||||
|
||||
+30
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
+27
-12
@@ -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()
|
||||
|
||||
+5
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user