Do not create synthesized equals/hashCode/toString in data classes in compatibility mode
To simplify migration from 1.0 to 1.1, do not allow data classes to automatically implement abstract equals/hashCode/toString declared in super-interfaces (KT-11306) if "-language-version 1.0" is specified
This commit is contained in:
+4
-1
@@ -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!!)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+23
-17
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -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,
|
||||
|
||||
+13
@@ -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
|
||||
}
|
||||
+10
@@ -11,3 +11,13 @@ class Derived: Base(42)
|
||||
<!DATA_CLASS_OVERRIDE_CONFLICT!>data<!> class Nasty(val z: Int, val y: Int): <!DATA_CLASS_CANNOT_HAVE_CLASS_SUPERTYPES!>Base<!>(z)
|
||||
|
||||
data class Complex(val y: Int): Allowed, <!DATA_CLASS_CANNOT_HAVE_CLASS_SUPERTYPES!>NotAllowed<!>()
|
||||
|
||||
|
||||
|
||||
interface AbstractEqualsHashCodeToString {
|
||||
override fun equals(other: Any?): Boolean
|
||||
override fun hashCode(): Int
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
data <!ABSTRACT_MEMBER_NOT_IMPLEMENTED!>class ImplInterface<!>(val s: String) : AbstractEqualsHashCodeToString
|
||||
|
||||
+25
-9
@@ -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 {
|
||||
|
||||
@@ -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<String> kotlinConfigurationFlags = new ArrayList<String>(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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user