From 5045fa446a92539c72e82b07396c483685962708 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 16 Aug 2018 11:30:57 +0300 Subject: [PATCH] Prohibit recursive inline classes --- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../checkers/InlineClassDeclarationChecker.kt | 18 +++-- .../inlineClasses/recursiveInlineClasses.kt | 16 ++++ .../inlineClasses/recursiveInlineClasses.txt | 73 +++++++++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 5 ++ .../DiagnosticsUsingJavacTestGenerated.java | 5 ++ .../load/kotlin/typeSignatureMapping.kt | 8 +- .../kotlin/resolve/inlineClassesUtils.kt | 11 +++ 9 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt create mode 100644 compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 71488bcc889..91e2e995a5b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -329,6 +329,7 @@ public interface Errors { DiagnosticFactory1 INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 INLINE_CLASS_CANNOT_IMPLEMENT_INTERFACE_BY_DELEGATION = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 INLINE_CLASS_CANNOT_EXTEND_CLASSES = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 INLINE_CLASS_CANNOT_BE_RECURSIVE = DiagnosticFactory0.create(ERROR); // Secondary constructors 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 7f53db8218b..f76d09e2558 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -658,6 +658,7 @@ public class DefaultErrorMessages { MAP.put(INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE, "Inline class cannot have value parameter of type ''{0}''", RENDER_TYPE); MAP.put(INLINE_CLASS_CANNOT_IMPLEMENT_INTERFACE_BY_DELEGATION, "Inline class cannot implement an interface by delegation"); MAP.put(INLINE_CLASS_CANNOT_EXTEND_CLASSES, "Inline class cannot extend classes"); + MAP.put(INLINE_CLASS_CANNOT_BE_RECURSIVE, "Inline class cannot be recursive"); MAP.put(VARIANCE_ON_TYPE_PARAMETER_NOT_ALLOWED, "Variance annotations are only allowed for type parameters of classes and interfaces"); MAP.put(BOUND_ON_TYPE_ALIAS_PARAMETER_NOT_ALLOWED, "Bounds are not allowed on type alias parameters"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt index e86e8759a77..a345a7af584 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt @@ -12,10 +12,7 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.modalityModifier import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.isInlineClass -import org.jetbrains.kotlin.resolve.substitutedUnderlyingType +import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isTypeParameter @@ -80,10 +77,15 @@ object InlineClassDeclarationChecker : DeclarationChecker { } val baseParameterType = descriptor.safeAs()?.defaultType?.substitutedUnderlyingType() - if (baseParameterType != null && baseParameterType.isInapplicableParameterType()) { - val typeReference = baseParameter.typeReference - if (typeReference != null) { - trace.report(Errors.INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE.on(typeReference, baseParameterType)) + val baseParameterTypeReference = baseParameter.typeReference + if (baseParameterType != null && baseParameterTypeReference != null) { + if (baseParameterType.isInapplicableParameterType()) { + trace.report(Errors.INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE.on(baseParameterTypeReference, baseParameterType)) + return + } + + if (baseParameterType.isRecursiveInlineClassType()) { + trace.report(Errors.INLINE_CLASS_CANNOT_BE_RECURSIVE.on(baseParameterTypeReference)) return } } diff --git a/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt b/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt new file mode 100644 index 00000000000..db3dca8a5b6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt @@ -0,0 +1,16 @@ +// !LANGUAGE: +InlineClasses + +inline class Test1(val x: Test1) + +inline class Test2A(val x: Test2B) +inline class Test2B(val x: Test2A) + +inline class Test3A(val x: Test3B) +inline class Test3B(val x: Test3C) +inline class Test3C(val x: Test3A) + +inline class TestNullable(val x: TestNullable?) + +inline class TestRecursionInTypeArguments(val x: List) + +inline class TestRecursionInArray(val x: Array) \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.txt b/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.txt new file mode 100644 index 00000000000..1640a62bb82 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.txt @@ -0,0 +1,73 @@ +package + +public final inline class Test1 { + public constructor Test1(/*0*/ x: Test1) + public final val x: Test1 + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class Test2A { + public constructor Test2A(/*0*/ x: Test2B) + public final val x: Test2B + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class Test2B { + public constructor Test2B(/*0*/ x: Test2A) + public final val x: Test2A + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class Test3A { + public constructor Test3A(/*0*/ x: Test3B) + public final val x: Test3B + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class Test3B { + public constructor Test3B(/*0*/ x: Test3C) + public final val x: Test3C + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class Test3C { + public constructor Test3C(/*0*/ x: Test3A) + public final val x: Test3A + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class TestNullable { + public constructor TestNullable(/*0*/ x: TestNullable?) + public final val x: TestNullable? + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class TestRecursionInArray { + public constructor TestRecursionInArray(/*0*/ x: kotlin.Array) + public final val x: kotlin.Array + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final inline class TestRecursionInTypeArguments { + public constructor TestRecursionInTypeArguments(/*0*/ x: kotlin.collections.List) + public final val x: kotlin.collections.List + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 7340fe58713..2c2b6a34c41 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -10917,6 +10917,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { runTest("compiler/testData/diagnostics/tests/inlineClasses/propertiesWithBackingFieldsInsideInlineClass.kt"); } + @TestMetadata("recursiveInlineClasses.kt") + public void testRecursiveInlineClasses() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt"); + } + @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception { runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 4bff27ed11a..3d5cf43a16c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -10917,6 +10917,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing runTest("compiler/testData/diagnostics/tests/inlineClasses/propertiesWithBackingFieldsInsideInlineClass.kt"); } + @TestMetadata("recursiveInlineClasses.kt") + public void testRecursiveInlineClasses() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/recursiveInlineClasses.kt"); + } + @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception { runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt"); diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt index 800530acfde..665ed4d60e3 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt @@ -12,13 +12,10 @@ import org.jetbrains.kotlin.load.java.typeEnhancement.hasEnhancedNullability import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames -import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe -import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType -import org.jetbrains.kotlin.resolve.substitutedUnderlyingType -import org.jetbrains.kotlin.resolve.unsubstitutedUnderlyingType import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections import org.jetbrains.kotlin.utils.DO_NOTHING_3 @@ -129,7 +126,8 @@ fun mapType( } descriptor is ClassDescriptor -> { - if (descriptor.isInline && !mode.needInlineClassWrapping) { + // NB if inline class is recursive, it's ok to map it as wrapped + if (descriptor.isInline && !mode.needInlineClassWrapping && !kotlinType.isRecursiveInlineClassType()) { val typeForMapping = computeUnderlyingType(kotlinType) if (typeForMapping != null) { val newMode = if (typeForMapping.isInlineClassType()) mode else mode.wrapInlineClassesMode() diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt index ff930abb2db..384a30e2dbb 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt @@ -34,6 +34,17 @@ fun KotlinType.substitutedUnderlyingType(): KotlinType? { return memberScope.getContributedVariables(parameter.name, NoLookupLocation.FOR_ALREADY_TRACKED).singleOrNull()?.type } +fun KotlinType.isRecursiveInlineClassType() = + isRecursiveInlineClassTypeInner(hashSetOf()) + +private fun KotlinType.isRecursiveInlineClassTypeInner(visited: HashSet): Boolean { + val descriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return false + if (visited.contains(descriptor)) return true + if (!descriptor.isInlineClass()) return false + visited.add(descriptor) + return unsubstitutedUnderlyingType()?.isRecursiveInlineClassTypeInner(visited) ?: false +} + fun KotlinType.isNullableUnderlyingType(): Boolean { if (!isInlineClassType()) return false val underlyingType = unsubstitutedUnderlyingType() ?: return false