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:
Alexander Udalov
2016-10-03 11:46:48 +03:00
parent 597912824b
commit 73652f309f
10 changed files with 122 additions and 36 deletions
@@ -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;
}
}
@@ -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))
}
}
}
@@ -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,
@@ -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
}
@@ -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
@@ -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)