diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 543094cba25..3cb26257efd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -426,6 +426,7 @@ public interface Errors { DiagnosticFactory0 SEALED_CLASS_CONSTRUCTOR_CALL = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SEALED_SUPERTYPE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SEALED_SUPERTYPE_IN_LOCAL_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory2 SEALED_INHERITOR_IN_DIFFERENT_PACKAGE = DiagnosticFactory2.create(ERROR); // Companion objects 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 119c9b782c9..c6d18ba488d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -633,6 +633,7 @@ public class DefaultErrorMessages { MAP.put(DATA_CLASS_CANNOT_HAVE_CLASS_SUPERTYPES, "Data class inheritance from other classes is forbidden"); MAP.put(SEALED_SUPERTYPE, "This type is sealed, so it can be inherited by only its own nested classes or objects"); MAP.put(SEALED_SUPERTYPE_IN_LOCAL_CLASS, "Local class cannot extend a sealed class"); + MAP.put(SEALED_INHERITOR_IN_DIFFERENT_PACKAGE, "Inheritor of sealed class or interface declared in package {1} but it must be in package {2} where base class is declared", TO_STRING, TO_STRING); MAP.put(SINGLETON_IN_SUPERTYPE, "Cannot inherit from a singleton"); MAP.put(CLASS_CANNOT_BE_EXTENDED_DIRECTLY, "Class {0} cannot be extended directly", NAME); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 25ff6d7309b..34391d681cc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -40,7 +40,8 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( FunInterfaceDeclarationChecker(), DeprecatedSinceKotlinAnnotationChecker, ContractDescriptionBlockChecker, - PrivateInlineFunctionsReturningAnonymousObjectsChecker + PrivateInlineFunctionsReturningAnonymousObjectsChecker, + SealedInheritorInSamePackageChecker, ) private val DEFAULT_CALL_CHECKERS = listOf( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SealedInheritorInSamePackageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SealedInheritorInSamePackageChecker.kt new file mode 100644 index 00000000000..31be306ed15 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/SealedInheritorInSamePackageChecker.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.checkers + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.containingPackage +import org.jetbrains.kotlin.descriptors.isSealed +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType + +object SealedInheritorInSamePackageChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (!context.languageVersionSettings.supportsFeature(LanguageFeature.FreedomForSealedClasses)) return + if (descriptor !is ClassDescriptor || declaration !is KtClassOrObject) return + val classPackage = descriptor.containingPackage() ?: return // local class, SEALED_SUPERTYPE already reported + for (superTypeListEntry in declaration.superTypeListEntries) { + val typeReference = superTypeListEntry.typeReference ?: continue + val superType = typeReference.getAbbreviatedTypeOrType(context.trace.bindingContext)?.unwrap() ?: continue + val superClass = superType.constructor.declarationDescriptor ?: continue + if (!superClass.isSealed()) continue + val superClassPackage = superClass.containingPackage() ?: continue + if (classPackage != superClassPackage) { + context.trace.report(Errors.SEALED_INHERITOR_IN_DIFFERENT_PACKAGE.on(typeReference, classPackage, superClassPackage)) + } + } + } +} diff --git a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.fir.kt b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.fir.kt index fb520120ea8..aa349ea0459 100644 --- a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.fir.kt +++ b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.fir.kt @@ -3,16 +3,22 @@ // FILE: a.kt +package foo + sealed class Base { class A : Base() } // FILE: b.kt +package foo + class B : Base() // FILE: c.kt +package foo + class Container { class C : Base() @@ -24,3 +30,11 @@ class Container { class LocalClass : Base() {} // Should be an error } } + +// FILE: E.kt + +package bar + +import foo.Base + +class E : Base() diff --git a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.kt b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.kt index b54b8f82f2c..4e7c7fbf94a 100644 --- a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.kt +++ b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.kt @@ -3,16 +3,22 @@ // FILE: a.kt +package foo + sealed class Base { class A : Base() } // FILE: b.kt +package foo + class B : Base() // FILE: c.kt +package foo + class Container { class C : Base() @@ -24,3 +30,11 @@ class Container { class LocalClass : Base() {} // Should be an error } } + +// FILE: E.kt + +package bar + +import foo.Base + +class E : Base() diff --git a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.txt b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.txt index 51fcecaca3b..dcfabb459ad 100644 --- a/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.txt +++ b/compiler/testData/diagnostics/tests/sealed/MultipleFiles_enabled.txt @@ -1,45 +1,58 @@ package -public final class B : Base { - public constructor B() - 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 -} +package bar { -public sealed class Base { - internal constructor Base() - 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 A : Base { - public constructor A() + public final class E : foo.Base { + public constructor E() 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 Container { - public constructor Container() - public final val anon: Base - 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 final fun someFun(): kotlin.Unit - public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +package foo { - public final class C : Base { - public constructor C() + public final class B : foo.Base { + public constructor B() 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 inner class D : Base { - public constructor D() + public sealed class Base { + internal constructor Base() 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 A : foo.Base { + public constructor A() + 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 Container { + public constructor Container() + public final val anon: foo.Base + 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 final fun someFun(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class C : foo.Base { + public constructor C() + 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 inner class D : foo.Base { + public constructor D() + 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/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt index f70c0276280..81062155718 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt @@ -10,11 +10,14 @@ import org.jetbrains.kotlin.builtins.StandardNames.CONTINUATION_INTERFACE_FQ_NAM import org.jetbrains.kotlin.incremental.components.LookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinTypeFactory import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.utils.sure +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun ModuleDescriptor.resolveClassByFqName(fqName: FqName, lookupLocation: LookupLocation): ClassDescriptor? { if (fqName.isRoot) return null @@ -56,3 +59,21 @@ fun DeclarationDescriptor.isTopLevelInPackage(name: String, packageName: String) } fun CallableDescriptor.isSupportedForCallableReference() = this is PropertyDescriptor || this is FunctionDescriptor + +@OptIn(ExperimentalContracts::class) +fun DeclarationDescriptor.isSealed(): Boolean { + contract { + returns(true) implies (this@isSealed is ClassDescriptor) + } + return DescriptorUtils.isSealedClass(this) +} + +fun DeclarationDescriptor.containingPackage(): FqName? { + var container = containingDeclaration + while (true) { + if (container == null || container is PackageFragmentDescriptor) break + container = container.containingDeclaration + } + require(container is PackageFragmentDescriptor?) + return container?.fqName +}