From 67f97fea42a46a27194c1d47a403b82d9975592e Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Fri, 27 Feb 2015 13:10:17 +0300 Subject: [PATCH] Prohibit data classes with no primary constructor --- .../common/DataClassMethodGenerator.java | 4 ++- .../load/kotlin/KotlinJvmCheckerProvider.kt | 2 +- .../jetbrains/kotlin/diagnostics/Errors.java | 1 + .../rendering/DefaultErrorMessages.java | 1 + .../resolve/AdditionalCheckerProvider.kt | 4 ++- .../resolve/DataClassAnnotationChecker.kt | 36 +++++++++++++++++++ .../secondaryConstructors/dataClasses.kt | 12 +++++++ .../secondaryConstructors/dataClasses.txt | 28 +++++++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 6 ++++ .../js/resolve/KotlinJsCheckerProvider.kt | 2 +- 10 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassAnnotationChecker.kt create mode 100644 compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.kt create mode 100644 compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.txt diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.java b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.java index 32673c28cd6..0763be63f7d 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.java +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.java @@ -80,7 +80,9 @@ public abstract class DataClassMethodGenerator { private void generateComponentFunctionsForDataClasses() { ConstructorDescriptor constructor = classDescriptor.getUnsubstitutedPrimaryConstructor(); - assert constructor != null : "Data class should have primary constructor"; + // primary constructor should exist for data classes + // but when generating light-classes still need to check we have one + if (constructor == null) return; for (ValueParameterDescriptor parameter : constructor.getValueParameters()) { FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt index c0e65bf406a..997b45b24f6 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt @@ -65,7 +65,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker public object KotlinJvmCheckerProvider : AdditionalCheckerProvider( - annotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()), + additionalAnnotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()), additionalCallCheckers = listOf(NeedSyntheticChecker()), additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker()) ) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index d70e155a0bd..a3b836767be 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -161,6 +161,7 @@ public interface Errors { DiagnosticFactory0 PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 INIT_KEYWORD_BEFORE_CLASS_INITIALIZER_EXPECTED = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 DELEGATION_SUPER_CALL_IN_ENUM_CONSTRUCTOR = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PRIMARY_CONSTRUCTOR_REQUIRED_FOR_DATA_CLASS = DiagnosticFactory0.create(ERROR); // Trait-specific 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 0a16f813a79..8ccb0fa8b9e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -418,6 +418,7 @@ public class DefaultErrorMessages { MAP.put(SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR, "Supertype initialization is impossible without primary constructor"); MAP.put(PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED, "Primary constructor call expected"); MAP.put(DELEGATION_SUPER_CALL_IN_ENUM_CONSTRUCTOR, "Call to super is not allowed in enum constructor"); + MAP.put(PRIMARY_CONSTRUCTOR_REQUIRED_FOR_DATA_CLASS, "Primary constructor required for data class"); MAP.put(INIT_KEYWORD_BEFORE_CLASS_INITIALIZER_EXPECTED, "Expecting 'init' keyword before class initializer"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/AdditionalCheckerProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AdditionalCheckerProvider.kt index b6a725d42a5..8a1dcea5137 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/AdditionalCheckerProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/AdditionalCheckerProvider.kt @@ -18,15 +18,17 @@ package org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.resolve.calls.checkers.* +private val DEFAULT_ANNOTATION_CHECKERS = listOf(DataClassAnnotationChecker()) private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker()) private val DEFAULT_TYPE_CHECKERS = listOf(TypeApproximator()) public abstract class AdditionalCheckerProvider( - public val annotationCheckers: List, + additionalAnnotationCheckers: List, additionalCallCheckers: List, additionalTypeCheckers: List ) { + public val annotationCheckers: List = DEFAULT_ANNOTATION_CHECKERS + additionalAnnotationCheckers public val callCheckers: List = DEFAULT_CALL_CHECKERS + additionalCallCheckers public val additionalTypeCheckers: List = DEFAULT_TYPE_CHECKERS + additionalTypeCheckers diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassAnnotationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassAnnotationChecker.kt new file mode 100644 index 00000000000..411a934c239 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassAnnotationChecker.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve + +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.diagnostics.* +import org.jetbrains.kotlin.builtins.* + + +public class DataClassAnnotationChecker : AnnotationChecker { + override fun check(declaration: JetDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink) { + if (descriptor !is ClassDescriptor) return + if (declaration !is JetClassOrObject) return + + if (KotlinBuiltIns.isData(descriptor)) { + if (descriptor.getUnsubstitutedPrimaryConstructor() == null && descriptor.getConstructors().isNotEmpty()) { + diagnosticHolder.report(Errors.PRIMARY_CONSTRUCTOR_REQUIRED_FOR_DATA_CLASS.on(declaration.getNameIdentifier())); + } + } + } +} diff --git a/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.kt b/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.kt new file mode 100644 index 00000000000..09c2bc61049 --- /dev/null +++ b/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.kt @@ -0,0 +1,12 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +data class A1(val x: String) { + constructor(): this("") {} +} + +data class A2() { + constructor(x: String): this() {} +} + +data class A3 { + constructor() {} +} diff --git a/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.txt b/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.txt new file mode 100644 index 00000000000..5b09200449b --- /dev/null +++ b/compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.txt @@ -0,0 +1,28 @@ +package + +kotlin.data() internal final class A1 { + public constructor A1() + public constructor A1(/*0*/ x: kotlin.String) + internal final val x: kotlin.String + internal final /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A1 + 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 +} + +kotlin.data() internal final class A2 { + public constructor A2() + public constructor A2(/*0*/ x: kotlin.String) + public final /*synthesized*/ fun copy(): A2 + 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 +} + +kotlin.data() internal final class A3 { + public constructor A3() + 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/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index b1f11f37773..c53e2361df4 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -10469,6 +10469,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("dataClasses.kt") + public void testDataClasses() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/dataClasses.kt"); + doTest(fileName); + } + @TestMetadata("enums.kt") public void testEnums() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/enums.kt"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt index 06d3a293459..eb729e6027e 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker public object KotlinJsCheckerProvider : AdditionalCheckerProvider( - annotationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker()), + additionalAnnotationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker()), additionalCallCheckers = listOf(JsCallChecker()), additionalTypeCheckers = listOf() )