diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaFunctionResolver.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaFunctionResolver.java index aea2dbd7c3c..0c940aa6d66 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaFunctionResolver.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaFunctionResolver.java @@ -192,7 +192,7 @@ public final class JavaFunctionResolver { throw new IllegalStateException("non-static method in subclass"); } - if (!RawTypesCheck.hasRawTypesInHierarchicalSignature(psiMethod)) { + if (!RawTypesCheck.hasRawTypesInHierarchicalSignature(psiMethod) && JavaMethodSignatureUtil.isMethodReturnTypeCompatible(psiMethod)) { if (signatureErrors.isEmpty()) { checkFunctionsOverrideCorrectly(method, superFunctions, functionDescriptorImpl); } diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaMethodSignatureUtil.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaMethodSignatureUtil.java new file mode 100644 index 00000000000..8f4c9f1a292 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/resolver/JavaMethodSignatureUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.lang.resolve.java.resolver; + +import com.intellij.psi.*; +import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.MethodSignatureUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +// originally from com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil +class JavaMethodSignatureUtil { + // This and following methods are originally from com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil + static boolean isMethodReturnTypeCompatible(@NotNull PsiMethod method) { + HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); + List superSignatures = methodSignature.getSuperSignatures(); + + PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType()); + PsiClass aClass = method.getContainingClass(); + if (aClass == null) return false; + for (MethodSignatureBackedByPsiMethod superMethodSignature : superSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + PsiType declaredReturnType = superMethod.getReturnType(); + PsiType superReturnType = declaredReturnType; + if (superMethodSignature.isRaw()) superReturnType = TypeConversionUtil.erasure(declaredReturnType); + if (returnType == null || superReturnType == null || method == superMethod) continue; + PsiClass superClass = superMethod.getContainingClass(); + if (superClass == null) continue; + if (!areMethodsReturnTypesCompatible(superMethodSignature, superReturnType, method, methodSignature, returnType)) return false; + } + + return true; + } + + private static boolean areMethodsReturnTypesCompatible( + MethodSignatureBackedByPsiMethod superMethodSignature, + PsiType superReturnType, + PsiMethod method, + MethodSignatureBackedByPsiMethod methodSignature, + PsiType returnType + ) { + if (superReturnType == null) return false; + PsiType substitutedSuperReturnType; + final boolean isJdk15 = PsiUtil.isLanguageLevel5OrHigher(method); + if (isJdk15 && !superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5 + PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, + superMethodSignature); + substitutedSuperReturnType = unifyingSubstitutor == null + ? superReturnType + : unifyingSubstitutor.substitute(superReturnType); + } + else { + substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType)); + } + + if (returnType.equals(substitutedSuperReturnType)) return true; + if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) { + if (isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) { + return true; + } + } + + return false; + } + + private JavaMethodSignatureUtil() { + } +} diff --git a/compiler/testData/loadJavaCustom/returnNotSubtype/ReturnNotSubtype.txt b/compiler/testData/loadJavaCustom/returnNotSubtype/ReturnNotSubtype.txt new file mode 100644 index 00000000000..1883e6beb53 --- /dev/null +++ b/compiler/testData/loadJavaCustom/returnNotSubtype/ReturnNotSubtype.txt @@ -0,0 +1,22 @@ +package test + +public trait ReturnNotSubtype : java.lang.Object { + + public trait Sub : test.ReturnNotSubtype.Super { + public abstract override /*1*/ fun _void() : jet.Boolean + public abstract override /*1*/ fun array() : jet.Array? + public abstract override /*1*/ fun klass() : java.lang.Class? + public abstract override /*1*/ fun string1() : Unit + public abstract override /*1*/ fun string2() : jet.MutableList? + public abstract override /*1*/ fun t() : java.lang.Void? + } + + public trait Super : java.lang.Object { + public abstract fun _void() : Unit + public abstract fun array() : jet.Array? + public abstract fun klass() : java.lang.Class? + public abstract fun string1() : jet.String? + public abstract fun string2() : jet.String? + public abstract fun t() : T? + } +} diff --git a/compiler/testData/loadJavaCustom/returnNotSubtype/test/ReturnNotSubtype.java b/compiler/testData/loadJavaCustom/returnNotSubtype/test/ReturnNotSubtype.java new file mode 100644 index 00000000000..361471e845b --- /dev/null +++ b/compiler/testData/loadJavaCustom/returnNotSubtype/test/ReturnNotSubtype.java @@ -0,0 +1,32 @@ +package test; + +import java.util.List; +import org.jetbrains.jet.jvm.compiler.annotation.ExpectLoadError; + +public interface ReturnNotSubtype { + interface Super { + T t(); + + void _void(); + + String string1(); + String string2(); + + Class klass(); + + T[] array(); + } + + interface Sub extends Super { + Void t(); + + boolean _void(); + + void string1(); + List string2(); + + Class klass(); + + Void[] array(); + } +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractLoadCompiledKotlinTest.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractLoadCompiledKotlinTest.java index c5d065050c6..ecbbbdc34ff 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractLoadCompiledKotlinTest.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/AbstractLoadCompiledKotlinTest.java @@ -46,7 +46,7 @@ public abstract class AbstractLoadCompiledKotlinTest extends TestCaseWithTmpdir TEST_PACKAGE_FQNAME); assert namespaceFromSource != null; Assert.assertEquals("test", namespaceFromSource.getName().getName()); - NamespaceDescriptor namespaceFromClass = LoadDescriptorUtil.loadTestNamespaceAndBindingContextFromBinaries( + NamespaceDescriptor namespaceFromClass = LoadDescriptorUtil.loadTestNamespaceAndBindingContextFromJavaRoot( tmpdir, getTestRootDisposable(), ConfigurationKind.JDK_ONLY).first; compareNamespaces(namespaceFromSource, namespaceFromClass, NamespaceComparator.DONT_INCLUDE_METHODS_OF_OBJECT.checkPrimaryConstructors(true), txtFile); diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadDescriptorUtil.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadDescriptorUtil.java index b22fc2748fb..2f476d18e03 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadDescriptorUtil.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadDescriptorUtil.java @@ -67,7 +67,7 @@ public final class LoadDescriptorUtil { ) throws IOException { compileKotlinToDirAndGetAnalyzeExhaust(kotlinFile, outDir, disposable, configurationKind); - return loadTestNamespaceAndBindingContextFromBinaries(outDir, disposable, ConfigurationKind.JDK_ONLY).first; + return loadTestNamespaceAndBindingContextFromJavaRoot(outDir, disposable, ConfigurationKind.JDK_ONLY).first; } @NotNull @@ -86,8 +86,8 @@ public final class LoadDescriptorUtil { } @NotNull - public static Pair loadTestNamespaceAndBindingContextFromBinaries( - @NotNull File outDir, + public static Pair loadTestNamespaceAndBindingContextFromJavaRoot( + @NotNull File javaRoot, @NotNull Disposable disposable, @NotNull ConfigurationKind configurationKind ) { @@ -95,7 +95,7 @@ public final class LoadDescriptorUtil { CompilerConfiguration configuration = JetTestUtils.compilerConfigurationForTests( configurationKind, TestJdkKind.MOCK_JDK, JetTestUtils.getAnnotationsJar(), - outDir, + javaRoot, ForTestCompileRuntime.runtimeJarForTests(), new File("compiler/tests") // for @ExpectLoadError annotation ); @@ -117,7 +117,7 @@ public final class LoadDescriptorUtil { ) throws IOException { compileJavaWithAnnotationsJar(javaFiles, outDir); - return loadTestNamespaceAndBindingContextFromBinaries(outDir, disposable, configurationKind); + return loadTestNamespaceAndBindingContextFromJavaRoot(outDir, disposable, configurationKind); } private static void compileJavaWithAnnotationsJar(@NotNull Collection javaFiles, @NotNull File outDir) throws IOException { diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadJavaCustomTest.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadJavaCustomTest.java index 83a19d04450..825072d9466 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadJavaCustomTest.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/LoadJavaCustomTest.java @@ -50,6 +50,7 @@ import java.util.Collections; import java.util.List; import static org.jetbrains.jet.jvm.compiler.LoadDescriptorUtil.compileJavaAndLoadTestNamespaceAndBindingContextFromBinary; +import static org.jetbrains.jet.jvm.compiler.LoadDescriptorUtil.loadTestNamespaceAndBindingContextFromJavaRoot; import static org.jetbrains.jet.test.util.NamespaceComparator.compareNamespaceWithFile; /* @@ -81,6 +82,15 @@ public final class LoadJavaCustomTest extends KotlinTestWithEnvironment { AbstractLoadJavaTest.checkJavaNamespace(expectedFile, javaNamespaceAndBindingContext); } + private void doTestNoCompile(@NotNull String expectedFileName, @NotNull String javaRoot) throws Exception { + File expectedFile = new File(expectedFileName); + + Pair javaNamespaceAndBindingContext + = loadTestNamespaceAndBindingContextFromJavaRoot(new File(javaRoot), getTestRootDisposable(), ConfigurationKind.JDK_ONLY); + + AbstractLoadJavaTest.checkJavaNamespace(expectedFile, javaNamespaceAndBindingContext); + } + public void testPackageLocalVisibility() throws Exception { String dir = PATH + "/packageLocalVisibility/simple/"; String javaDir = dir + "/java"; @@ -172,6 +182,11 @@ public final class LoadJavaCustomTest extends KotlinTestWithEnvironment { dir + "MethodTypeParameterErased.java"); } + public void testReturnNotSubtype() throws Exception { + String dir = PATH + "/returnNotSubtype/"; + doTestNoCompile(dir + "ReturnNotSubtype.txt", dir); + } + public static class SubclassingKotlinInJavaTest extends KotlinTestWithEnvironmentManagement { public void testSubclassingKotlinInJava() throws Exception { doTest(PATH + "/" + getTestName(true));