From 5fbb9bfe3b33815bbe086dddbfffd3c6c8a577cb Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 20 Nov 2015 23:06:24 +0300 Subject: [PATCH] Report error on repeated upper bounds for type parameters --- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../kotlin/resolve/DescriptorResolver.java | 20 +++++++++---- .../tests/generics/TypeParameterBounds.kt | 14 ++++----- .../tests/typeParameters/repeatedBound.kt | 7 +++++ .../tests/typeParameters/repeatedBound.txt | 30 +++++++++++++++++++ .../typeParameters/upperBoundCannotBeArray.kt | 4 +-- .../checkers/DiagnosticsTestGenerated.java | 6 ++++ idea/testData/checker/TypeParameterBounds.kt | 4 +-- .../SecondaryConstructors.expected.kt | 2 +- .../SecondaryConstructors.kt | 4 ++- .../SecondaryConstructors.kt | 4 ++- .../SecondaryConstructors.txt | 6 ++-- .../stubBuilder/TypeParams/TypeParams.kt | 6 ++-- .../stubBuilder/TypeParams/TypeParams.txt | 22 ++++++-------- .../AbstractDecompiledTextBaseTest.kt | 10 +++---- 16 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt create mode 100644 compiler/testData/diagnostics/tests/typeParameters/repeatedBound.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 86645d177e5..746161e9243 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -265,6 +265,7 @@ public interface Errors { DiagnosticFactory0 DYNAMIC_UPPER_BOUND = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ONLY_ONE_CLASS_BOUND_ALLOWED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 REPEATED_BOUND = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 CONFLICTING_UPPER_BOUNDS = DiagnosticFactory1.create(ERROR, DECLARATION_NAME); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 39eab89b9fb..ed8e6183c59 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -395,6 +395,7 @@ public class DefaultErrorMessages { MAP.put(FINAL_UPPER_BOUND, "''{0}'' is a final type, and thus a value of the type parameter is predetermined", RENDER_TYPE); MAP.put(UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, "Extension function type can not be used as an upper bound"); MAP.put(ONLY_ONE_CLASS_BOUND_ALLOWED, "Only one of the upper bounds can be a class"); + MAP.put(REPEATED_BOUND, "Type parameter already has this bound"); MAP.put(DYNAMIC_UPPER_BOUND, "Dynamic type can not be used as an upper bound"); MAP.put(USELESS_ELVIS, "Elvis operator (?:) always returns the left operand of non-nullable type {0}", RENDER_TYPE); MAP.put(USELESS_ELVIS_ON_FUNCTION_LITERAL, "Left operand of elvis operator (?:) is function literal"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java index 0379b3a028a..43ab979b55b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java @@ -21,6 +21,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.intellij.psi.PsiElement; import kotlin.CollectionsKt; +import kotlin.Pair; import kotlin.SetsKt; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; @@ -534,18 +535,25 @@ public class DescriptorResolver { if (tasks.isEmpty()) return; Set classBoundEncountered = new HashSet(); + Set> allBounds = new HashSet>(); + for (UpperBoundCheckerTask checkerTask : tasks) { Name typeParameterName = checkerTask.typeParameterName; KotlinType upperBound = checkerTask.upperBoundType; KtTypeReference upperBoundElement = checkerTask.upperBound; if (!upperBound.isError()) { - ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(upperBound); - if (classDescriptor != null) { - ClassKind kind = classDescriptor.getKind(); - if (kind == ClassKind.CLASS || kind == ClassKind.ENUM_CLASS || kind == ClassKind.OBJECT) { - if (!classBoundEncountered.add(typeParameterName)) { - trace.report(ONLY_ONE_CLASS_BOUND_ALLOWED.on(upperBoundElement)); + if (!allBounds.add(new Pair(typeParameterName, upperBound.getConstructor()))) { + trace.report(REPEATED_BOUND.on(upperBoundElement)); + } + else { + ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(upperBound); + if (classDescriptor != null) { + ClassKind kind = classDescriptor.getKind(); + if (kind == ClassKind.CLASS || kind == ClassKind.ENUM_CLASS || kind == ClassKind.OBJECT) { + if (!classBoundEncountered.add(typeParameterName)) { + trace.report(ONLY_ONE_CLASS_BOUND_ALLOWED.on(upperBoundElement)); + } } } } diff --git a/compiler/testData/diagnostics/tests/generics/TypeParameterBounds.kt b/compiler/testData/diagnostics/tests/generics/TypeParameterBounds.kt index 324f74bfafd..5a657a14d8f 100644 --- a/compiler/testData/diagnostics/tests/generics/TypeParameterBounds.kt +++ b/compiler/testData/diagnostics/tests/generics/TypeParameterBounds.kt @@ -6,15 +6,15 @@ interface B interface D -interface IncorrectF<T : D> where T : D +interface IncorrectF<T : D> where T : D -interface CorrectF<T> where T : D, T : D +interface CorrectF<T> where T : D, T : D interface G -interface IncorrectH<T : G>> where T : G> +interface IncorrectH<T : G>> where T : G> -interface CorrectH<T> where T : G>, T : G> +interface CorrectH<T> where T : G>, T : G> interface I>> { fun <S : T?> incorrectFoo() where S : G> @@ -22,8 +22,8 @@ interface I>> { fun <S> correctFoo() where S : T?, S : G> } -interface incorrectJ<T: G>> where T : G> +interface incorrectJ<T: G>> where T : G> -interface correctJ<T> where T : G>, T : G> +interface correctJ<T> where T : G>, T : G> -fun <T> bar() where T : D, T : D {} \ No newline at end of file +fun <T> bar() where T : D, T : D {} diff --git a/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt b/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt new file mode 100644 index 00000000000..960c7c0a2ae --- /dev/null +++ b/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt @@ -0,0 +1,7 @@ +interface I1 + +class A1 where T : I1, T : I1 +class A2 where T : I1, T : I1? +class A3 where K : V, K : V + +fun f1() where T : I1, T : I1 {} diff --git a/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.txt b/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.txt new file mode 100644 index 00000000000..d295a63fa54 --- /dev/null +++ b/compiler/testData/diagnostics/tests/typeParameters/repeatedBound.txt @@ -0,0 +1,30 @@ +package + +public fun f1(): kotlin.Unit where T : I1 + +public final class A1 where T : I1 { + public constructor A1() where T : I1 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A2 where T : I1? { + public constructor A2() where T : I1? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class A3 where K : V { + public constructor A3() where K : V + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I1 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/typeParameters/upperBoundCannotBeArray.kt b/compiler/testData/diagnostics/tests/typeParameters/upperBoundCannotBeArray.kt index 12197d018ba..f7c3b9979ba 100644 --- a/compiler/testData/diagnostics/tests/typeParameters/upperBoundCannotBeArray.kt +++ b/compiler/testData/diagnostics/tests/typeParameters/upperBoundCannotBeArray.kt @@ -1,6 +1,6 @@ fun <A : Array> f1() {} fun A : Array> f2() {} -fun A> f3() where A : Array, A : Array {} +fun A> f3() where A : Array, A : Array {} fun <T : IntArray> f4() {} @@ -9,7 +9,7 @@ fun <T> f5() where T : Array {} val <T : Array> T.v: String get() = "" class C2A : Array> -interface C3A> where A : Array, A : Array +interface C3A> where A : Array, A : Array fun foo() { class C1<A : Array> { diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 86d25a050ae..c7c2eca9978 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -17022,6 +17022,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("repeatedBound.kt") + public void testRepeatedBound() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/typeParameters/repeatedBound.kt"); + doTest(fileName); + } + @TestMetadata("upperBoundCannotBeArray.kt") public void testUpperBoundCannotBeArray() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/typeParameters/upperBoundCannotBeArray.kt"); diff --git a/idea/testData/checker/TypeParameterBounds.kt b/idea/testData/checker/TypeParameterBounds.kt index 237678bb9a8..1509ca4691b 100644 --- a/idea/testData/checker/TypeParameterBounds.kt +++ b/idea/testData/checker/TypeParameterBounds.kt @@ -6,6 +6,6 @@ interface B interface D -interface CorrectF<T> where T : D, T : D +interface CorrectF<T> where T : D, T : D -fun <T> bar() where T : D, T : D {} \ No newline at end of file +fun <T> bar() where T : D, T : D {} diff --git a/idea/testData/decompiler/decompiledText/SecondaryConstructors.expected.kt b/idea/testData/decompiler/decompiledText/SecondaryConstructors.expected.kt index a47d372275a..e031908db5f 100644 --- a/idea/testData/decompiler/decompiledText/SecondaryConstructors.expected.kt +++ b/idea/testData/decompiler/decompiledText/SecondaryConstructors.expected.kt @@ -8,7 +8,7 @@ public final class SecondaryConstructors public constructor(x: kotlin.Boolean) { private constructor(x: kotlin.Int) { /* compiled code */ } - public final inner class Inner where G : kotlin.Number { + public final inner class Inner where G : java.io.Serializable { public constructor(x: T, g: G) { /* compiled code */ } } diff --git a/idea/testData/decompiler/decompiledText/SecondaryConstructors/SecondaryConstructors.kt b/idea/testData/decompiler/decompiledText/SecondaryConstructors/SecondaryConstructors.kt index e3d0f241bac..6530f879197 100644 --- a/idea/testData/decompiler/decompiledText/SecondaryConstructors/SecondaryConstructors.kt +++ b/idea/testData/decompiler/decompiledText/SecondaryConstructors/SecondaryConstructors.kt @@ -1,5 +1,7 @@ package test +import java.io.Serializable + class SecondaryConstructors(x: Boolean) { init { } @@ -13,7 +15,7 @@ class SecondaryConstructors(x: Boolean) { private constructor(x: Int) : this(x < 0) { } - inner class Inner where G : Number { + inner class Inner where G : Serializable { constructor(x: T, g: G) { } } diff --git a/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.kt b/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.kt index e3d0f241bac..6530f879197 100644 --- a/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.kt +++ b/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.kt @@ -1,5 +1,7 @@ package test +import java.io.Serializable + class SecondaryConstructors(x: Boolean) { init { } @@ -13,7 +15,7 @@ class SecondaryConstructors(x: Boolean) { private constructor(x: Int) : this(x < 0) { } - inner class Inner where G : Number { + inner class Inner where G : Serializable { constructor(x: T, g: G) { } } diff --git a/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.txt b/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.txt index 11299b66474..a70197acfde 100644 --- a/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.txt +++ b/idea/testData/decompiler/stubBuilder/SecondaryConstructors/SecondaryConstructors.txt @@ -60,8 +60,10 @@ PsiJetFileStubImpl[package=test] TYPE_REFERENCE: USER_TYPE:[isAbsoluteInRootPackage=false] USER_TYPE:[isAbsoluteInRootPackage=false] - REFERENCE_EXPRESSION:[referencedName=kotlin] - REFERENCE_EXPRESSION:[referencedName=Number] + USER_TYPE:[isAbsoluteInRootPackage=false] + REFERENCE_EXPRESSION:[referencedName=java] + REFERENCE_EXPRESSION:[referencedName=io] + REFERENCE_EXPRESSION:[referencedName=Serializable] CLASS_BODY: SECONDARY_CONSTRUCTOR: MODIFIER_LIST:[public] diff --git a/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.kt b/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.kt index bdc5b88848d..2c61e98653d 100644 --- a/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.kt +++ b/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.kt @@ -1,6 +1,8 @@ package test -class TypeParams Int, T4, T5 : Any?, T6 : T5, T7 : Any> where T1 : Any?, T1 : Int?, T1 : Int, T2 : String, T7 : T6 { +import java.io.Serializable + +class TypeParams Int, T4, T5 : Any?, T6 : T5, T7 : Any> where T1 : Cloneable?, T1 : Serializable, T2 : String, T7 : T6 { fun useParams(p1: T1, p2: (T2) -> Unit, p3: T3, p4: T4, P5: T5) { } @@ -15,7 +17,7 @@ class TypeParams Int, T4, T5 : Any?, T6 : T5, fun withOwnParams(p1: G1, p2: G2, p3: G3, p4: T1, p5: (T2) -> Unit) { } - fun withOwnParamsAndTypeConstraints(p1: G1, p2: G2, p3: G3, p4: T1, p5: (T2) -> Unit) where G3 : G1, G3 : String, G3 : String? { + fun withOwnParamsAndTypeConstraints(p1: G1, p2: G2, p3: G3, p4: T1, p5: (T2) -> Unit) where G3 : G1, G3 : String, G3 : Serializable? { } fun withOwnParamsClashing(p1: T1, p2: T2, p3: T3, p4: T4, p5: T5) { diff --git a/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.txt b/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.txt index db183086b13..2a12775533e 100644 --- a/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.txt +++ b/idea/testData/decompiler/stubBuilder/TypeParams/TypeParams.txt @@ -57,22 +57,16 @@ PsiJetFileStubImpl[package=test] USER_TYPE:[isAbsoluteInRootPackage=false] USER_TYPE:[isAbsoluteInRootPackage=false] REFERENCE_EXPRESSION:[referencedName=kotlin] - REFERENCE_EXPRESSION:[referencedName=Any] - TYPE_CONSTRAINT: - REFERENCE_EXPRESSION:[referencedName=T1] - TYPE_REFERENCE: - NULLABLE_TYPE: - USER_TYPE:[isAbsoluteInRootPackage=false] - USER_TYPE:[isAbsoluteInRootPackage=false] - REFERENCE_EXPRESSION:[referencedName=kotlin] - REFERENCE_EXPRESSION:[referencedName=Int] + REFERENCE_EXPRESSION:[referencedName=Cloneable] TYPE_CONSTRAINT: REFERENCE_EXPRESSION:[referencedName=T1] TYPE_REFERENCE: USER_TYPE:[isAbsoluteInRootPackage=false] USER_TYPE:[isAbsoluteInRootPackage=false] - REFERENCE_EXPRESSION:[referencedName=kotlin] - REFERENCE_EXPRESSION:[referencedName=Int] + USER_TYPE:[isAbsoluteInRootPackage=false] + REFERENCE_EXPRESSION:[referencedName=java] + REFERENCE_EXPRESSION:[referencedName=io] + REFERENCE_EXPRESSION:[referencedName=Serializable] TYPE_CONSTRAINT: REFERENCE_EXPRESSION:[referencedName=T7] TYPE_REFERENCE: @@ -371,8 +365,10 @@ PsiJetFileStubImpl[package=test] NULLABLE_TYPE: USER_TYPE:[isAbsoluteInRootPackage=false] USER_TYPE:[isAbsoluteInRootPackage=false] - REFERENCE_EXPRESSION:[referencedName=kotlin] - REFERENCE_EXPRESSION:[referencedName=String] + USER_TYPE:[isAbsoluteInRootPackage=false] + REFERENCE_EXPRESSION:[referencedName=java] + REFERENCE_EXPRESSION:[referencedName=io] + REFERENCE_EXPRESSION:[referencedName=Serializable] FUN:[fqName=test.TypeParams.withOwnParamsClashing, hasBlockBody=true, hasBody=true, hasTypeParameterListBeforeFunctionName=true, isExtension=false, isTopLevel=false, name=withOwnParamsClashing] MODIFIER_LIST:[public final] TYPE_PARAMETER_LIST: diff --git a/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/AbstractDecompiledTextBaseTest.kt b/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/AbstractDecompiledTextBaseTest.kt index 07ae49ad308..72bf24be7cd 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/AbstractDecompiledTextBaseTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/AbstractDecompiledTextBaseTest.kt @@ -22,13 +22,13 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.PsiRecursiveElementVisitor import com.intellij.testFramework.LightProjectDescriptor -import com.intellij.testFramework.UsefulTestCase import org.jetbrains.kotlin.idea.test.JdkAndMockLibraryProjectDescriptor import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext -import kotlin.test.fail +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File public abstract class AbstractDecompiledTextBaseTest( baseDirectory: String, @@ -44,9 +44,9 @@ public abstract class AbstractDecompiledTextBaseTest( public fun doTest(path: String) { val fileToDecompile = getFileToDecompile() - val psiFile = PsiManager.getInstance(getProject()).findFile(fileToDecompile)!! + val psiFile = PsiManager.getInstance(project).findFile(fileToDecompile)!! checkPsiFile(psiFile) - UsefulTestCase.assertSameLinesWithFile(path.substring(0, path.length() - 1) + ".expected.kt", psiFile.getText()) + KotlinTestUtils.assertEqualsToFile(File(path.substring(0, path.length - 1) + ".expected.kt"), psiFile.text) checkThatFileWasParsedCorrectly(psiFile) } @@ -64,4 +64,4 @@ public abstract class AbstractDecompiledTextBaseTest( } }) } -} \ No newline at end of file +}