diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt index a0cf4fbcc6c..9d511dde775 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.backend.common import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtClass @@ -112,7 +114,8 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject ): FunctionDescriptor? = classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) .singleOrNull { function -> - function.kind == CallableMemberDescriptor.Kind.SYNTHESIZED && + function.kind.let { kind -> kind == SYNTHESIZED || kind == FAKE_OVERRIDE } && + function.modality != Modality.FINAL && areParametersOk(function.valueParameters) && function.returnType != null && isReturnTypeOk(function.returnType!!) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/LazyClassContext.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/LazyClassContext.kt index d70db038599..cffded68fda 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/LazyClassContext.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/LazyClassContext.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.resolve.lazy +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker import org.jetbrains.kotlin.incremental.components.LookupTracker @@ -36,4 +37,5 @@ interface LazyClassContext { val annotationResolver: AnnotationResolver val lookupTracker: LookupTracker val supertypeLoopChecker: SupertypeLoopChecker + val languageVersionSettings: LanguageVersionSettings } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java index fef3ad71530..bf32842c951 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java @@ -24,6 +24,7 @@ import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.ReadOnly; +import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.context.GlobalContext; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.annotations.Annotations; @@ -48,7 +49,6 @@ import org.jetbrains.kotlin.storage.*; import org.jetbrains.kotlin.utils.SmartList; import javax.inject.Inject; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -79,6 +79,7 @@ public class ResolveSession implements KotlinCodeAnalyzer, LazyClassContext { private LookupTracker lookupTracker; private LocalDescriptorResolver localDescriptorResolver; private SupertypeLoopChecker supertypeLoopsResolver; + private LanguageVersionSettings languageVersionSettings; @Inject public void setJetImportFactory(KtImportsFactory jetImportFactory) { @@ -125,6 +126,11 @@ public class ResolveSession implements KotlinCodeAnalyzer, LazyClassContext { this.lookupTracker = lookupTracker; } + @Inject + public void setLanguageVersionSettings(@NotNull LanguageVersionSettings languageVersionSettings) { + this.languageVersionSettings = languageVersionSettings; + } + // Only calls from injectors expected @Deprecated public ResolveSession( @@ -427,4 +433,10 @@ public class ResolveSession implements KotlinCodeAnalyzer, LazyClassContext { public void setLocalDescriptorResolver(@NotNull LocalDescriptorResolver localDescriptorResolver) { this.localDescriptorResolver = localDescriptorResolver; } + + @NotNull + @Override + public LanguageVersionSettings getLanguageVersionSettings() { + return languageVersionSettings; + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt index 00639f426d0..259e5060c83 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.resolve.lazy.descriptors import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE @@ -29,7 +30,10 @@ import org.jetbrains.kotlin.incremental.components.LookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.record import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.lazy.LazyClassContext import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider @@ -182,25 +186,27 @@ open class LazyClassMemberScope( result.add(DataClassDescriptorResolver.createCopyFunctionDescriptor(constructor.valueParameters, thisDescriptor, trace)) } - fun shouldAddFunctionFromAny(checkParameters: (FunctionDescriptor) -> Boolean): Boolean { - // Add 'equals', 'hashCode', 'toString' iff there is no such declared member AND there is no such final member in supertypes - return result.none(checkParameters) && - fromSupertypes.none { checkParameters(it) && it.modality == Modality.FINAL } - } + if (c.languageVersionSettings.supportsFeature(LanguageFeature.DataClassInheritance)) { + fun shouldAddFunctionFromAny(checkParameters: (FunctionDescriptor) -> Boolean): Boolean { + // Add 'equals', 'hashCode', 'toString' iff there is no such declared member AND there is no such final member in supertypes + return result.none(checkParameters) && + fromSupertypes.none { checkParameters(it) && it.modality == Modality.FINAL } + } - if (name == DataClassDescriptorResolver.EQUALS_METHOD_NAME && shouldAddFunctionFromAny { function -> - val parameters = function.valueParameters - parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type) - }) { - result.add(DataClassDescriptorResolver.createEqualsFunctionDescriptor(thisDescriptor)) - } + if (name == DataClassDescriptorResolver.EQUALS_METHOD_NAME && shouldAddFunctionFromAny { function -> + val parameters = function.valueParameters + parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type) + }) { + result.add(DataClassDescriptorResolver.createEqualsFunctionDescriptor(thisDescriptor)) + } - if (name == DataClassDescriptorResolver.HASH_CODE_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { - result.add(DataClassDescriptorResolver.createHashCodeFunctionDescriptor(thisDescriptor)) - } + if (name == DataClassDescriptorResolver.HASH_CODE_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { + result.add(DataClassDescriptorResolver.createHashCodeFunctionDescriptor(thisDescriptor)) + } - if (name == DataClassDescriptorResolver.TO_STRING_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { - result.add(DataClassDescriptorResolver.createToStringFunctionDescriptor(thisDescriptor)) + if (name == DataClassDescriptorResolver.TO_STRING_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { + result.add(DataClassDescriptorResolver.createToStringFunctionDescriptor(thisDescriptor)) + } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LocalClassifierAnalyzer.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LocalClassifierAnalyzer.kt index bc61818ed84..a6dacd7cdc9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LocalClassifierAnalyzer.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LocalClassifierAnalyzer.kt @@ -46,9 +46,7 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.PsiBasedClassMemberDeclara import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope -import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes import org.jetbrains.kotlin.storage.StorageManager -import org.jetbrains.kotlin.types.DynamicTypesSettings class LocalClassifierAnalyzer( private val globalContext: GlobalContext, @@ -87,7 +85,8 @@ class LocalClassifierAnalyzer( funcionDescriptorResolver, typeResolver, annotationResolver, - supertypeLoopChecker + supertypeLoopChecker, + languageVersionSettings ) ) @@ -110,7 +109,8 @@ class LocalClassDescriptorHolder( val functionDescriptorResolver: FunctionDescriptorResolver, val typeResolver: TypeResolver, val annotationResolver: AnnotationResolver, - val supertypeLoopChecker: SupertypeLoopChecker + val supertypeLoopChecker: SupertypeLoopChecker, + val languageVersionSettings: LanguageVersionSettings ) { // We do not need to synchronize here, because this code is used strictly from one thread private var classDescriptor: ClassDescriptor? = null @@ -146,6 +146,7 @@ class LocalClassDescriptorHolder( override val annotationResolver = this@LocalClassDescriptorHolder.annotationResolver override val lookupTracker: LookupTracker = LookupTracker.DO_NOTHING override val supertypeLoopChecker = this@LocalClassDescriptorHolder.supertypeLoopChecker + override val languageVersionSettings = this@LocalClassDescriptorHolder.languageVersionSettings } , containingDeclaration, diff --git a/compiler/testData/codegen/box/compatibility/dataClassEqualsHashCodeToString.kt b/compiler/testData/codegen/box/compatibility/dataClassEqualsHashCodeToString.kt new file mode 100644 index 00000000000..c0fd15fad4d --- /dev/null +++ b/compiler/testData/codegen/box/compatibility/dataClassEqualsHashCodeToString.kt @@ -0,0 +1,13 @@ +// LANGUAGE_VERSION: 1.0 + +data class Foo(val s: String) + +fun box(): String { + val f1 = Foo("OK") + val f2 = Foo("OK") + if (f1 != f2) return "Fail equals" + if (f1.hashCode() != f2.hashCode()) return "Fail hashCode" + if (f1.toString() != f2.toString() || f1.toString() != "Foo(s=OK)") return "Fail toString: $f1 $f2" + + return f1.s +} diff --git a/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.kt b/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.kt index 118be78bd3d..0030126b1a6 100644 --- a/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.kt +++ b/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.kt @@ -11,3 +11,13 @@ class Derived: Base(42) data class Nasty(val z: Int, val y: Int): Base(z) data class Complex(val y: Int): Allowed, NotAllowed() + + + +interface AbstractEqualsHashCodeToString { + override fun equals(other: Any?): Boolean + override fun hashCode(): Int + override fun toString(): String +} + +data class ImplInterface(val s: String) : AbstractEqualsHashCodeToString diff --git a/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.txt b/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.txt index 57c30c711d6..915c184b476 100644 --- a/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.txt +++ b/compiler/testData/diagnostics/tests/sourceCompatibility/noDataClassInheritance.txt @@ -1,5 +1,11 @@ package +public interface AbstractEqualsHashCodeToString { + public abstract override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ fun hashCode(): kotlin.Int + public abstract override /*1*/ fun toString(): kotlin.String +} + public interface Allowed { public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int @@ -11,9 +17,9 @@ public abstract data class Base { public final val x: kotlin.Int public final operator /*synthesized*/ fun component1(): kotlin.Int public final /*synthesized*/ fun copy(/*0*/ x: kotlin.Int = ...): Base - 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 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 data class Complex : Allowed, NotAllowed { @@ -21,9 +27,9 @@ public final data class Complex : Allowed, NotAllowed { public final val y: kotlin.Int public final operator /*synthesized*/ fun component1(): kotlin.Int public final /*synthesized*/ fun copy(/*0*/ y: kotlin.Int = ...): Complex - public open override /*2*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public open override /*2*/ /*synthesized*/ fun hashCode(): kotlin.Int - public open override /*2*/ /*synthesized*/ fun toString(): kotlin.String + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String } public final class Derived : Base { @@ -36,6 +42,16 @@ public final class Derived : Base { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public final data class ImplInterface : AbstractEqualsHashCodeToString { + public constructor ImplInterface(/*0*/ s: kotlin.String) + public final val s: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ s: kotlin.String = ...): ImplInterface + public abstract override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + public final data class Nasty : Base { public constructor Nasty(/*0*/ z: kotlin.Int, /*1*/ y: kotlin.Int) public final override /*1*/ /*fake_override*/ val x: kotlin.Int @@ -45,9 +61,9 @@ public final data class Nasty : Base { public final operator /*synthesized*/ fun component2(): kotlin.Int public final override /*1*/ /*fake_override*/ fun copy(/*0*/ x: kotlin.Int = ...): Base public final /*synthesized*/ fun copy(/*0*/ z: kotlin.Int = ..., /*1*/ y: kotlin.Int = ...): Nasty - 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 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 NotAllowed { diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java index 03c6885e831..fde853bb36f 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -36,10 +36,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; -import org.jetbrains.kotlin.config.CompilerConfiguration; -import org.jetbrains.kotlin.config.CompilerConfigurationKey; -import org.jetbrains.kotlin.config.JVMConfigurationKeys; -import org.jetbrains.kotlin.config.JvmTarget; +import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.psi.KtFile; @@ -146,6 +143,7 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { @NotNull CompilerConfiguration configuration ) { List kotlinConfigurationFlags = new ArrayList(0); + LanguageVersion explicitLanguageVersion = null; for (TestFile testFile : testFilesWithConfigurationDirectives) { kotlinConfigurationFlags.addAll(InTextDirectivesUtils.findListWithPrefixes(testFile.content, "// KOTLIN_CONFIGURATION_FLAGS:")); @@ -156,6 +154,16 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { assert jvmTarget != null : "Unknown target: " + targetString; configuration.put(JVMConfigurationKeys.JVM_TARGET, jvmTarget); } + + String version = InTextDirectivesUtils.findStringWithPrefixes(testFile.content, "// LANGUAGE_VERSION:"); + if (version != null) { + assert explicitLanguageVersion == null : "Should not specify LANGUAGE_VERSION twice"; + explicitLanguageVersion = LanguageVersion.fromVersionString(version); + } + } + + if (explicitLanguageVersion != null) { + // configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, new LanguageVersionSettingsImpl(explicitLanguageVersion)); } updateConfigurationWithFlags(configuration, kotlinConfigurationFlags); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index fe00143b978..cdd4d4f8ad7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -3617,6 +3617,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/compatibility") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Compatibility extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInCompatibility() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/compatibility"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("dataClassEqualsHashCodeToString.kt") + public void testDataClassEqualsHashCodeToString() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/compatibility/dataClassEqualsHashCodeToString.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/constants") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)