From 8148bdc4d023d880c8e196edddafa6f65a060596 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Fri, 8 Jun 2018 15:30:27 +0300 Subject: [PATCH] Support inline classes in MPP resolver/checker model --- .../kotlin/resolve/DeclarationsChecker.kt | 5 +- .../ExpectedActualDeclarationChecker.kt | 26 ++++-- .../multiplatform/ExpectedActualResolver.kt | 4 +- .../inlineClasses/expectActualInlineClass.kt | 28 +++++++ .../inlineClasses/expectActualInlineClass.txt | 83 +++++++++++++++++++ .../incompatibleNestedClasses/output.txt | 10 +-- .../multiplatform/inlineClasses/common.kt | 4 + .../multiplatform/inlineClasses/js.kt | 5 ++ .../multiplatform/inlineClasses/jvm.kt | 5 ++ .../multiplatform/inlineClasses/output.txt | 23 +++++ .../checkers/DiagnosticsTestGenerated.java | 18 ++++ .../DiagnosticsUsingJavacTestGenerated.java | 18 ++++ ...MultiPlatformIntegrationTestGenerated.java | 18 ++++ .../kotlin/resolve/DescriptorUtils.kt | 3 + 14 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.txt create mode 100644 compiler/testData/multiplatform/inlineClasses/common.kt create mode 100644 compiler/testData/multiplatform/inlineClasses/js.kt create mode 100644 compiler/testData/multiplatform/inlineClasses/jvm.kt create mode 100644 compiler/testData/multiplatform/inlineClasses/output.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt index 77ec827fcc0..b953e472189 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt @@ -265,7 +265,10 @@ class DeclarationsChecker( trace.report(EXPECTED_ENUM_CONSTRUCTOR.on(declaration)) } - if (declaration is KtPrimaryConstructor && !DescriptorUtils.isAnnotationClass(constructorDescriptor.constructedClass)) { + if (declaration is KtPrimaryConstructor && + !DescriptorUtils.isAnnotationClass(constructorDescriptor.constructedClass) && + !constructorDescriptor.constructedClass.isInline + ) { for (parameter in declaration.valueParameters) { if (parameter.hasValOrVar()) { trace.report(EXPECTED_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER.on(parameter)) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index d87a6484616..82004b657c7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -26,11 +26,9 @@ import org.jetbrains.kotlin.incremental.components.ExpectActualTracker import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.BindingTrace -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor +import org.jetbrains.kotlin.resolve.descriptorUtil.isPrimaryConstructorOfInlineClass import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility @@ -137,9 +135,7 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { if (compatibility.allStrongIncompatibilities()) return if (Compatible in compatibility) { - // we suppress error, because annotation classes can only have one constructor and it's a 100% boilerplate - // to require every annotation constructor with additional parameters with default values be marked with the `actual` modifier - if (checkActual && !descriptor.isAnnotationConstructor()) { + if (checkActual && requireActualModifier(descriptor, trace)) { trace.report(Errors.ACTUAL_MISSING.on(reportOn)) } @@ -204,6 +200,22 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { } } + // we don't require `actual` modifier on + // - annotation constructors, because annotation classes can only have one constructor + // - inline class primary constructors, because inline class must have primary constructor + // - value parameter inside primary constructor of inline class, because inline class must have one value parameter + private fun requireActualModifier(descriptor: MemberDescriptor, trace: BindingTrace): Boolean { + return !descriptor.isAnnotationConstructor() && + !descriptor.isPrimaryConstructorOfInlineClass() && + !isMainPropertyOfInlineClass(descriptor, trace) + } + + private fun isMainPropertyOfInlineClass(descriptor: MemberDescriptor, trace: BindingTrace): Boolean { + if (descriptor !is PropertyDescriptor) return false + if (!descriptor.containingDeclaration.isInlineClass()) return false + return trace.bindingContext[BindingContext.BACKING_FIELD_REQUIRED, descriptor] == true + } + // This should ideally be handled by CallableMemberDescriptor.Kind, but default constructors have kind DECLARATION and non-empty source. // Their source is the containing KtClass instance though, as opposed to explicit constructors, whose source is KtConstructor private fun MemberDescriptor.isExplicitActualDeclaration(): Boolean = diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt index b64dd9ed690..15805fba5ef 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt @@ -197,7 +197,7 @@ object ExpectedActualResolver { object ClassKind : Incompatible("class kinds are different (class, interface, object, enum, annotation)") - object ClassModifiers : Incompatible("modifiers are different (companion, inner)") + object ClassModifiers : Incompatible("modifiers are different (companion, inner, inline)") object Supertypes : Incompatible("some supertypes are missing in the actual declaration") @@ -395,7 +395,7 @@ object ExpectedActualResolver { if (a.kind != b.kind) return Incompatible.ClassKind - if (!equalBy(a, b) { listOf(it.isCompanionObject, it.isInner) }) return Incompatible.ClassModifiers + if (!equalBy(a, b) { listOf(it.isCompanionObject, it.isInner, it.isInline) }) return Incompatible.ClassModifiers val aTypeParams = a.declaredTypeParameters val bTypeParams = b.declaredTypeParameters diff --git a/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt b/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt new file mode 100644 index 00000000000..db4c08e5a2b --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt @@ -0,0 +1,28 @@ +// !LANGUAGE: +MultiPlatformProjects, +InlineClasses +// MODULE: m1-common +// FILE: common.kt + +expect inline class Foo1(val x: Int) { + fun bar(): String +} + +expect inline class Foo2(val x: Int) + +expect inline class Foo3 + +expect class NonInlineExpect + +expect inline class NonInlineActual(val x: Int) + +// MODULE: m2-jvm(m1-common) +// FILE: jvm.kt + +actual inline class Foo1(val x: Int) { + actual fun bar(): String = "Hello" +} +actual inline class Foo2(val x: String) +actual inline class Foo3 + +actual inline class NonInlineExpect(val x: Int) + +actual class NonInlineActual actual constructor(actual val x: Int) diff --git a/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.txt b/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.txt new file mode 100644 index 00000000000..40c50ecded0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.txt @@ -0,0 +1,83 @@ +// -- Module: -- +package + +public final expect inline class Foo1 { + public constructor Foo1(/*0*/ x: kotlin.Int) + public expect final val x: kotlin.Int + public final expect fun bar(): kotlin.String + 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 expect inline class Foo2 { + public constructor Foo2(/*0*/ x: kotlin.Int) + public expect final val x: kotlin.Int + 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 expect inline class Foo3 { + 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 expect inline class NonInlineActual { + public constructor NonInlineActual(/*0*/ x: kotlin.Int) + public expect final val x: kotlin.Int + 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 expect class NonInlineExpect { + 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 +} + + +// -- Module: -- +package + +public final actual inline class Foo1 { + public constructor Foo1(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public final actual fun bar(): kotlin.String + 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 actual inline class Foo2 { + public constructor Foo2(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 actual inline class Foo3 { + public constructor Foo3() + 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 actual class NonInlineActual { + public constructor NonInlineActual(/*0*/ x: kotlin.Int) + public actual final val x: kotlin.Int + 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 actual inline class NonInlineExpect { + public constructor NonInlineExpect(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + 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/multiplatform/incompatibleNestedClasses/output.txt b/compiler/testData/multiplatform/incompatibleNestedClasses/output.txt index d9d3ba737b9..a78e92005b3 100644 --- a/compiler/testData/multiplatform/incompatibleNestedClasses/output.txt +++ b/compiler/testData/multiplatform/incompatibleNestedClasses/output.txt @@ -24,31 +24,31 @@ The following declaration is incompatible because class kinds are different (cla actual class N3 ^ compiler/testData/multiplatform/incompatibleNestedClasses/jvm.kt:8:5: error: actual class 'N2' has no corresponding expected declaration -The following declaration is incompatible because modifiers are different (companion, inner): +The following declaration is incompatible because modifiers are different (companion, inner, inline): public final expect class N2 actual inner class N2 ^ compiler/testData/multiplatform/incompatibleNestedClasses/jvm.kt:9:5: error: actual class 'I2' has no corresponding expected declaration -The following declaration is incompatible because modifiers are different (companion, inner): +The following declaration is incompatible because modifiers are different (companion, inner, inline): public final expect inner class I2 actual class I2 ^ compiler/testData/multiplatform/incompatibleNestedClasses/jvm.kt:13:5: error: actual companion object 'Companion' has no corresponding expected declaration -The following declaration is incompatible because modifiers are different (companion, inner): +The following declaration is incompatible because modifiers are different (companion, inner, inline): public expect object Companion actual companion object {} ^ compiler/testData/multiplatform/incompatibleNestedClasses/jvm.kt:14:5: error: actual object 'Factory' has no corresponding expected declaration -The following declaration is incompatible because modifiers are different (companion, inner): +The following declaration is incompatible because modifiers are different (companion, inner, inline): public expect companion object Factory actual object Factory ^ compiler/testData/multiplatform/incompatibleNestedClasses/jvm.kt:18:5: error: actual object 'Companion' has no corresponding expected declaration -The following declaration is incompatible because modifiers are different (companion, inner): +The following declaration is incompatible because modifiers are different (companion, inner, inline): public expect companion object actual object Companion diff --git a/compiler/testData/multiplatform/inlineClasses/common.kt b/compiler/testData/multiplatform/inlineClasses/common.kt new file mode 100644 index 00000000000..3c6824b5b2b --- /dev/null +++ b/compiler/testData/multiplatform/inlineClasses/common.kt @@ -0,0 +1,4 @@ +@file:Suppress("UNSUPPORTED_FEATURE") + +expect inline class Foo1(val x: Int) +expect inline class Foo2(val y: String) \ No newline at end of file diff --git a/compiler/testData/multiplatform/inlineClasses/js.kt b/compiler/testData/multiplatform/inlineClasses/js.kt new file mode 100644 index 00000000000..c4ae613a318 --- /dev/null +++ b/compiler/testData/multiplatform/inlineClasses/js.kt @@ -0,0 +1,5 @@ +@file:Suppress("UNSUPPORTED_FEATURE") + +actual inline class Foo1(val x: Int) + +actual class Foo2 actual constructor(actual val y: String) \ No newline at end of file diff --git a/compiler/testData/multiplatform/inlineClasses/jvm.kt b/compiler/testData/multiplatform/inlineClasses/jvm.kt new file mode 100644 index 00000000000..c4ae613a318 --- /dev/null +++ b/compiler/testData/multiplatform/inlineClasses/jvm.kt @@ -0,0 +1,5 @@ +@file:Suppress("UNSUPPORTED_FEATURE") + +actual inline class Foo1(val x: Int) + +actual class Foo2 actual constructor(actual val y: String) \ No newline at end of file diff --git a/compiler/testData/multiplatform/inlineClasses/output.txt b/compiler/testData/multiplatform/inlineClasses/output.txt new file mode 100644 index 00000000000..ea77928d764 --- /dev/null +++ b/compiler/testData/multiplatform/inlineClasses/output.txt @@ -0,0 +1,23 @@ +-- Common -- +Exit code: OK +Output: + +-- JVM -- +Exit code: COMPILATION_ERROR +Output: +compiler/testData/multiplatform/inlineClasses/jvm.kt:5:1: error: actual class 'Foo2' has no corresponding expected declaration +The following declaration is incompatible because modifiers are different (companion, inner, inline): + public final expect inline class Foo2 + +actual class Foo2 actual constructor(actual val y: String) +^ + +-- JS -- +Exit code: COMPILATION_ERROR +Output: +compiler/testData/multiplatform/inlineClasses/js.kt:5:1: error: actual class 'Foo2' has no corresponding expected declaration +The following declaration is incompatible because modifiers are different (companion, inner, inline): + public final expect inline class Foo2 + +actual class Foo2 actual constructor(actual val y: String) +^ diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index a405d87f224..c449f5bbb17 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -13020,6 +13020,24 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/multiplatform/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractDiagnosticsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("expectActualInlineClass.kt") + public void testExpectActualInlineClass() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt"); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/multiplatform/java") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 6d85c17e745..f9c7bc4fcc2 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -13020,6 +13020,24 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing } } + @TestMetadata("compiler/testData/diagnostics/tests/multiplatform/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractDiagnosticsUsingJavacTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("expectActualInlineClass.kt") + public void testExpectActualInlineClass() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/inlineClasses/expectActualInlineClass.kt"); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/multiplatform/java") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/multiplatform/MultiPlatformIntegrationTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/multiplatform/MultiPlatformIntegrationTestGenerated.java index 46e11c0f13a..f362a8a052d 100644 --- a/compiler/tests/org/jetbrains/kotlin/multiplatform/MultiPlatformIntegrationTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/multiplatform/MultiPlatformIntegrationTestGenerated.java @@ -84,6 +84,11 @@ public class MultiPlatformIntegrationTestGenerated extends AbstractMultiPlatform runTest("compiler/testData/multiplatform/incorrectImplInClass/"); } + @TestMetadata("inlineClasses") + public void testInlineClasses() throws Exception { + runTest("compiler/testData/multiplatform/inlineClasses/"); + } + @TestMetadata("jsNameClash") public void testJsNameClash() throws Exception { runTest("compiler/testData/multiplatform/jsNameClash/"); @@ -540,6 +545,19 @@ public class MultiPlatformIntegrationTestGenerated extends AbstractMultiPlatform } } + @TestMetadata("compiler/testData/multiplatform/inlineClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InlineClasses extends AbstractMultiPlatformIntegrationTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInInlineClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/multiplatform/inlineClasses"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); + } + } + @TestMetadata("compiler/testData/multiplatform/jsNameClash") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index 6d3a1dd62d8..482c1fcd3c2 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -434,3 +434,6 @@ fun isParameterOfAnnotation(parameterDescriptor: ParameterDescriptor): Boolean = fun DeclarationDescriptor.isAnnotationConstructor(): Boolean = this is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(this.constructedClass) + +fun DeclarationDescriptor.isPrimaryConstructorOfInlineClass(): Boolean = + this is ConstructorDescriptor && this.isPrimary && this.constructedClass.isInline