diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index edfc2561b61..86645d177e5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -264,6 +264,7 @@ public interface Errors { DiagnosticFactory1 FINAL_UPPER_BOUND = DiagnosticFactory1.create(WARNING); 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); 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 4b92e663076..39eab89b9fb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -394,6 +394,7 @@ public class DefaultErrorMessages { MAP.put(UPPER_BOUND_VIOLATED, "Type argument is not within its bounds: should be subtype of ''{0}''", RENDER_TYPE, RENDER_TYPE); 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(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/DeclarationsChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt index 69ba4567234..82064c2a1b5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt @@ -23,19 +23,19 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.* -import org.jetbrains.kotlin.types.checker.KotlinTypeChecker -import org.jetbrains.kotlin.types.typeUtil.* - -import java.util.* - -import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.resolve.BindingContext.TYPE import org.jetbrains.kotlin.resolve.BindingContext.TYPE_PARAMETER import org.jetbrains.kotlin.resolve.DescriptorUtils.classCanHaveAbstractMembers import org.jetbrains.kotlin.resolve.DescriptorUtils.classCanHaveOpenMembers +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.SubstitutionUtils +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.checker.KotlinTypeChecker +import org.jetbrains.kotlin.types.typeUtil.isNothing +import java.util.* fun KtDeclaration.checkTypeReferences(trace: BindingTrace) { if (this is KtCallableDeclaration) { @@ -147,38 +147,35 @@ class DeclarationsChecker( } private fun checkTypesInClassHeader(classOrObject: KtClassOrObject) { + fun KtTypeReference.type(): KotlinType? = trace.bindingContext.get(TYPE, this) + for (delegationSpecifier in classOrObject.getDelegationSpecifiers()) { - delegationSpecifier.typeReference?.check(checkBoundsForTypeInClassHeader = true) + val typeReference = delegationSpecifier.typeReference ?: continue + typeReference.type()?.let { DescriptorResolver.checkBounds(typeReference, it, trace) } + typeReference.checkNotEnumEntry(trace) } if (classOrObject !is KtClass) return - for (jetTypeParameter in classOrObject.typeParameters) { - jetTypeParameter.extendsBound?.check(checkBoundsForTypeInClassHeader = true, checkFinalUpperBounds = true) + val tasks = ArrayList() + + for (typeParameter in classOrObject.typeParameters) { + val typeReference = typeParameter.extendsBound ?: continue + val type = typeReference.type() ?: continue + tasks.add(DescriptorResolver.UpperBoundCheckerTask(typeParameter.nameAsName, typeReference, type)) } for (constraint in classOrObject.typeConstraints) { - constraint.boundTypeReference?.check(checkBoundsForTypeInClassHeader = true, checkFinalUpperBounds = true) + val typeReference = constraint.boundTypeReference ?: continue + val type = typeReference.type() ?: continue + val name = constraint.subjectTypeParameterName?.getReferencedNameAsName() ?: continue + tasks.add(DescriptorResolver.UpperBoundCheckerTask(name, typeReference, type)) } - } - private fun KtTypeReference.checkBoundsForTypeInClassHeader() { - trace.bindingContext.get(TYPE, this)?.let { DescriptorResolver.checkBounds(this, it, trace) } - } + DescriptorResolver.checkUpperBoundTypes(trace, tasks) - private fun KtTypeReference.checkFinalUpperBounds() { - trace.bindingContext.get(TYPE, this)?.let { DescriptorResolver.checkUpperBoundType(this, it, trace) } - } - - private fun KtTypeReference.check(checkBoundsForTypeInClassHeader: Boolean = false, checkFinalUpperBounds: Boolean = false) { - if (checkFinalUpperBounds) { - checkFinalUpperBounds() - } - else { - checkNotEnumEntry(trace) - } - if (checkBoundsForTypeInClassHeader) { - checkBoundsForTypeInClassHeader() + for (task in tasks) { + DescriptorResolver.checkBounds(task.upperBound, task.upperBoundType, trace) } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java index c10504723f9..0379b3a028a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java @@ -458,10 +458,12 @@ public class DescriptorResolver { } static final class UpperBoundCheckerTask { - KtTypeReference upperBound; - KotlinType upperBoundType; + public final Name typeParameterName; + public final KtTypeReference upperBound; + public final KotlinType upperBoundType; - private UpperBoundCheckerTask(KtTypeReference upperBound, KotlinType upperBoundType) { + UpperBoundCheckerTask(Name typeParameterName, KtTypeReference upperBound, KotlinType upperBoundType) { + this.typeParameterName = typeParameterName; this.upperBound = upperBound; this.upperBoundType = upperBoundType; } @@ -479,16 +481,16 @@ public class DescriptorResolver { List typeParameters = declaration.getTypeParameters(); Map parameterByName = Maps.newHashMap(); for (int i = 0; i < typeParameters.size(); i++) { - KtTypeParameter jetTypeParameter = typeParameters.get(i); + KtTypeParameter ktTypeParameter = typeParameters.get(i); TypeParameterDescriptorImpl typeParameterDescriptor = parameters.get(i); parameterByName.put(typeParameterDescriptor.getName(), typeParameterDescriptor); - KtTypeReference extendsBound = jetTypeParameter.getExtendsBound(); + KtTypeReference extendsBound = ktTypeParameter.getExtendsBound(); if (extendsBound != null) { KotlinType type = typeResolver.resolveType(scope, extendsBound, trace, false); typeParameterDescriptor.addUpperBound(type); - deferredUpperBoundCheckerTasks.add(new UpperBoundCheckerTask(extendsBound, type)); + deferredUpperBoundCheckerTasks.add(new UpperBoundCheckerTask(ktTypeParameter.getNameAsName(), extendsBound, type)); } } for (KtTypeConstraint constraint : declaration.getTypeConstraints()) { @@ -502,7 +504,7 @@ public class DescriptorResolver { KotlinType bound = null; if (boundTypeReference != null) { bound = typeResolver.resolveType(scope, boundTypeReference, trace, false); - deferredUpperBoundCheckerTasks.add(new UpperBoundCheckerTask(boundTypeReference, bound)); + deferredUpperBoundCheckerTasks.add(new UpperBoundCheckerTask(referencedName, boundTypeReference, bound)); } if (typeParameterDescriptor != null) { @@ -523,11 +525,33 @@ public class DescriptorResolver { } if (!(declaration instanceof KtClass)) { - for (UpperBoundCheckerTask checkerTask : deferredUpperBoundCheckerTasks) { - checkUpperBoundType(checkerTask.upperBound, checkerTask.upperBoundType, trace); + checkUpperBoundTypes(trace, deferredUpperBoundCheckerTasks); + checkNamesInConstraints(declaration, descriptor, scope, trace); + } + } + + public static void checkUpperBoundTypes(@NotNull BindingTrace trace, @NotNull List tasks) { + if (tasks.isEmpty()) return; + + Set classBoundEncountered = 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)); + } + } + } } - checkNamesInConstraints(declaration, descriptor, scope, trace); + checkUpperBoundType(upperBoundElement, upperBound, trace); } } diff --git a/compiler/testData/codegen/box/extensionProperties/genericValMultipleUpperBounds.kt b/compiler/testData/codegen/box/extensionProperties/genericValMultipleUpperBounds.kt index 7d1f2347a67..e2bd9657adc 100644 --- a/compiler/testData/codegen/box/extensionProperties/genericValMultipleUpperBounds.kt +++ b/compiler/testData/codegen/box/extensionProperties/genericValMultipleUpperBounds.kt @@ -1,8 +1,10 @@ -val T.valProp: T where T : Number, T : Int +import java.io.Serializable + +val T.valProp: T where T : Number, T : Serializable get() = this fun box(): String { 0.valProp return "OK" -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/MultipleBounds.kt b/compiler/testData/diagnostics/tests/MultipleBounds.kt index a4ef617d631..a6f98bb96d5 100644 --- a/compiler/testData/diagnostics/tests/MultipleBounds.kt +++ b/compiler/testData/diagnostics/tests/MultipleBounds.kt @@ -48,7 +48,7 @@ class BarFoo> class Buzz where T : Bar<Int>, T : nioho class XFoo> -class Y<T> where T : Foo, T : Bar +class Y<T> where T : Foo, T : Bar fun test2(t : T) where diff --git a/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.kt b/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.kt index c44a90d9212..050d4824c42 100644 --- a/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.kt +++ b/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.kt @@ -15,10 +15,4 @@ fun misleadingNullableSimple( ind: INDIRECT? ) {} -fun misleadingNullableMultiBound( - fb: FIRST_BOUND?, - sb: SECOND_BOUND? -) where FIRST_BOUND: Any?, FIRST_BOUND: Any, SECOND_BOUND: Any, SECOND_BOUND: Any? { -} - fun interactionWithRedundant(t: T??) {} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.txt b/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.txt index 47a55ee6c72..a167c9ff69b 100644 --- a/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.txt +++ b/compiler/testData/diagnostics/tests/nullableTypes/baseWithNullableUpperBound.txt @@ -1,7 +1,6 @@ package public fun interactionWithRedundant(/*0*/ t: T?): kotlin.Unit -public fun misleadingNullableMultiBound(/*0*/ fb: FIRST_BOUND?, /*1*/ sb: SECOND_BOUND?): kotlin.Unit where FIRST_BOUND : kotlin.Any, SECOND_BOUND : kotlin.Any? public fun misleadingNullableSimple(/*0*/ t: T?, /*1*/ t2: T?, /*2*/ n: N?, /*3*/ ind: INDIRECT?): kotlin.Unit public fun nonMisleadingNullable(/*0*/ nn: NN?, /*1*/ nnn: NNN?): kotlin.Unit public fun twoBounds(/*0*/ tb: TWO_BOUNDS?): kotlin.Unit where TWO_BOUNDS : NN diff --git a/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.kt b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.kt new file mode 100644 index 00000000000..880904e5ca9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.kt @@ -0,0 +1,18 @@ +open class C1 +open class C2 +open class C3 : C2() + +class A1 where T : C1, T : C2 +class A2 where T : C1, T : C2, T : C3 +class A3 where T : C2, T : C3 +class A4 where T : C3, T : C2 + +fun f1() where T : C1, T : C2, T : C3 {} +fun f2() where T : C2, T : C3 {} +fun f3() where T : C3, T : C2 {} + +enum class E1 +class A5<T> where T : C1, T : E1 + +object O1 +class A6<T> where T : O1, T : C2 diff --git a/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.txt b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.txt new file mode 100644 index 00000000000..f1d5c1d1ab4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.txt @@ -0,0 +1,91 @@ +package + +public fun f1(): kotlin.Unit where T : C2, T : C3 +public fun f2(): kotlin.Unit where T : C3 +public fun f3(): kotlin.Unit where T : C2 + +public final class A1 where T : C2 { + public constructor A1() where T : C2 + 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 : C2, T : C3 { + public constructor A2() where T : C2, T : C3 + 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 T : C3 { + public constructor A3() where T : C3 + 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 A4 where T : C2 { + public constructor A4() where T : C2 + 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 A5 where T : E1 { + public constructor A5() where T : E1 + 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 A6 where T : C2 { + public constructor A6() where T : C2 + 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 open class C1 { + public constructor C1() + 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 open class C2 { + public constructor C2() + 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 open class C3 : C2 { + public constructor C3() + 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 enum class E1 : kotlin.Enum { + private constructor E1() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E1): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + @kotlin.Deprecated(message = "Use 'values()' function instead", replaceWith = kotlin.ReplaceWith(expression = "this.values()", imports = {})) public final /*synthesized*/ val values: kotlin.Array + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E1 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public object O1 { + private constructor O1() + 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 21e69a75e99..12197d018ba 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 2ceaf45751c..86d25a050ae 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -16986,6 +16986,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/typeParameters"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("cannotHaveManyClassUpperBounds.kt") + public void testCannotHaveManyClassUpperBounds() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.kt"); + doTest(fileName); + } + @TestMetadata("deprecatedSyntax.kt") public void testDeprecatedSyntax() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/typeParameters/deprecatedSyntax.kt"); diff --git a/idea/testData/checker/MultipleBounds.kt b/idea/testData/checker/MultipleBounds.kt index c8ae3898b33..13f6b91f413 100644 --- a/idea/testData/checker/MultipleBounds.kt +++ b/idea/testData/checker/MultipleBounds.kt @@ -42,7 +42,7 @@ class BarFoo> class Buzz where T : Bar<Int>, T : nioho class XFoo> -class Y<T> where T : Foo, T : Bar +class Y<T> where T : Foo, T : Bar fun test2(t : T) where