Extract data class generation logic into backend-independent module
This commit is contained in:
committed by
Zalim Bashorov
parent
00e65c8771
commit
59a09c04af
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.backend.common;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
|
||||
import org.jetbrains.jet.lang.resolve.calls.CallResolverUtil;
|
||||
import org.jetbrains.jet.lang.resolve.name.Name;
|
||||
import org.jetbrains.jet.lang.types.JetType;
|
||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Backend-independent utility class.
|
||||
*/
|
||||
public class CodegenUtil {
|
||||
|
||||
private CodegenUtil() {
|
||||
}
|
||||
|
||||
// TODO: consider putting predefined method signatures here too.
|
||||
public static final String EQUALS_METHOD_NAME = "equals";
|
||||
public static final String TO_STRING_METHOD_NAME = "toString";
|
||||
public static final String HASH_CODE_METHOD_NAME = "hashCode";
|
||||
|
||||
@Nullable
|
||||
public static FunctionDescriptor getDeclaredFunctionByRawSignature(
|
||||
@NotNull ClassDescriptor owner,
|
||||
@NotNull Name name,
|
||||
@NotNull ClassifierDescriptor returnedClassifier,
|
||||
@NotNull ClassifierDescriptor... valueParameterClassifiers
|
||||
) {
|
||||
Collection<FunctionDescriptor> functions = owner.getDefaultType().getMemberScope().getFunctions(name);
|
||||
for (FunctionDescriptor function : functions) {
|
||||
if (!CallResolverUtil.isOrOverridesSynthesized(function)
|
||||
&& function.getTypeParameters().isEmpty()
|
||||
&& valueParameterClassesMatch(function.getValueParameters(), Arrays.asList(valueParameterClassifiers))
|
||||
&& rawTypeMatches(function.getReturnType(), returnedClassifier)) {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FunctionDescriptor getAnyEqualsMethod() {
|
||||
ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
|
||||
FunctionDescriptor function =
|
||||
getDeclaredFunctionByRawSignature(anyClass, Name.identifier(EQUALS_METHOD_NAME),
|
||||
KotlinBuiltIns.getInstance().getBoolean(),
|
||||
anyClass);
|
||||
assert function != null;
|
||||
return function;
|
||||
}
|
||||
|
||||
public static FunctionDescriptor getAnyToStringMethod() {
|
||||
ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
|
||||
FunctionDescriptor function =
|
||||
getDeclaredFunctionByRawSignature(anyClass, Name.identifier(TO_STRING_METHOD_NAME),
|
||||
KotlinBuiltIns.getInstance().getString());
|
||||
assert function != null;
|
||||
return function;
|
||||
}
|
||||
|
||||
public static FunctionDescriptor getAnyHashCodeMethod() {
|
||||
ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
|
||||
FunctionDescriptor function =
|
||||
getDeclaredFunctionByRawSignature(anyClass, Name.identifier(HASH_CODE_METHOD_NAME),
|
||||
KotlinBuiltIns.getInstance().getInt());
|
||||
assert function != null;
|
||||
return function;
|
||||
}
|
||||
|
||||
private static boolean valueParameterClassesMatch(
|
||||
@NotNull List<ValueParameterDescriptor> parameters,
|
||||
@NotNull List<ClassifierDescriptor> classifiers
|
||||
) {
|
||||
if (parameters.size() != classifiers.size()) return false;
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
ValueParameterDescriptor parameterDescriptor = parameters.get(i);
|
||||
ClassifierDescriptor classDescriptor = classifiers.get(i);
|
||||
if (!rawTypeMatches(parameterDescriptor.getType(), classDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean rawTypeMatches(JetType type, ClassifierDescriptor classifier) {
|
||||
return type.getConstructor().getDeclarationDescriptor().getOriginal() == classifier.getOriginal();
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.backend.common;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.lang.descriptors.*;
|
||||
import org.jetbrains.jet.lang.psi.JetClass;
|
||||
import org.jetbrains.jet.lang.psi.JetClassOrObject;
|
||||
import org.jetbrains.jet.lang.psi.JetParameter;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
|
||||
import org.jetbrains.jet.lang.resolve.OverrideResolver;
|
||||
import org.jetbrains.jet.lang.resolve.name.Name;
|
||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A platform-independent logic for generating data class synthetic methods.
|
||||
* TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
|
||||
* changed here with the platform backends adopted.
|
||||
*/
|
||||
public abstract class DataClassMethodGenerator {
|
||||
private final JetClassOrObject declaration;
|
||||
private final BindingContext bindingContext;
|
||||
private final ClassDescriptor classDescriptor;
|
||||
|
||||
public DataClassMethodGenerator(JetClassOrObject declaration, BindingContext bindingContext) {
|
||||
this.declaration = declaration;
|
||||
this.bindingContext = bindingContext;
|
||||
this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
|
||||
}
|
||||
|
||||
public void generate() {
|
||||
generateComponentFunctionsForDataClasses();
|
||||
|
||||
generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
|
||||
|
||||
List<PropertyDescriptor> properties = getDataProperties();
|
||||
if (!properties.isEmpty()) {
|
||||
generateDataClassToStringIfNeeded(properties);
|
||||
generateDataClassHashCodeIfNeeded(properties);
|
||||
generateDataClassEqualsIfNeeded(properties);
|
||||
}
|
||||
}
|
||||
|
||||
// Backend-specific implementations.
|
||||
protected abstract void generateComponentFunction(
|
||||
@NotNull FunctionDescriptor function,
|
||||
@NotNull ValueParameterDescriptor parameter
|
||||
);
|
||||
|
||||
protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<JetParameter> constructorParameters);
|
||||
|
||||
protected abstract void generateToStringMethod(@NotNull List<PropertyDescriptor> properties);
|
||||
|
||||
protected abstract void generateHashCodeMethod(@NotNull List<PropertyDescriptor> properties);
|
||||
|
||||
protected abstract void generateEqualsMethod(@NotNull List<PropertyDescriptor> properties);
|
||||
|
||||
protected ClassDescriptor getClassDescriptor() {
|
||||
return classDescriptor;
|
||||
}
|
||||
|
||||
private void generateComponentFunctionsForDataClasses() {
|
||||
if (!declaration.hasPrimaryConstructor()) return;
|
||||
|
||||
ConstructorDescriptor constructor = classDescriptor.getConstructors().iterator().next();
|
||||
|
||||
for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
|
||||
FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
|
||||
if (function != null) {
|
||||
generateComponentFunction(function, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCopyFunctionForDataClasses(List<JetParameter> constructorParameters) {
|
||||
FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
|
||||
if (copyFunction != null) {
|
||||
generateCopyFunction(copyFunction, constructorParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString();
|
||||
if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) {
|
||||
generateToStringMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt();
|
||||
if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) {
|
||||
generateHashCodeMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean();
|
||||
ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
|
||||
if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) {
|
||||
generateEqualsMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PropertyDescriptor> getDataProperties() {
|
||||
List<PropertyDescriptor> result = Lists.newArrayList();
|
||||
for (JetParameter parameter : getPrimaryConstructorParameters()) {
|
||||
if (parameter.getValOrVarNode() != null) {
|
||||
result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private
|
||||
@NotNull
|
||||
List<JetParameter> getPrimaryConstructorParameters() {
|
||||
if (declaration instanceof JetClass) {
|
||||
return ((JetClass) declaration).getPrimaryConstructorParameters();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
|
||||
*/
|
||||
private boolean hasDeclaredNonTrivialMember(
|
||||
@NotNull String name,
|
||||
@NotNull ClassDescriptor returnedClassifier,
|
||||
@NotNull ClassDescriptor... valueParameterClassifiers
|
||||
) {
|
||||
FunctionDescriptor function =
|
||||
CodegenUtil.getDeclaredFunctionByRawSignature(classDescriptor, Name.identifier(name), returnedClassifier,
|
||||
valueParameterClassifiers);
|
||||
if (function == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
|
||||
if (overridden instanceof CallableMemberDescriptor
|
||||
&& ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
|
||||
&& !overridden.getContainingDeclaration().equals(KotlinBuiltIns.getInstance().getAny())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.intellij.util.ArrayUtil;
|
||||
import kotlin.Function0;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.backend.common.DataClassMethodGenerator;
|
||||
import org.jetbrains.jet.codegen.binding.CalculatedClosure;
|
||||
import org.jetbrains.jet.codegen.binding.CodegenBinding;
|
||||
import org.jetbrains.jet.codegen.binding.MutableClosure;
|
||||
@@ -41,7 +42,6 @@ import org.jetbrains.jet.lang.psi.*;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
|
||||
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
|
||||
import org.jetbrains.jet.lang.resolve.OverrideResolver;
|
||||
import org.jetbrains.jet.lang.resolve.calls.CallResolverUtil;
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
|
||||
@@ -619,86 +619,45 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
return classifier;
|
||||
}
|
||||
|
||||
private List<PropertyDescriptor> getDataProperties() {
|
||||
ArrayList<PropertyDescriptor> result = Lists.newArrayList();
|
||||
for (JetParameter parameter : getPrimaryConstructorParameters()) {
|
||||
if (parameter.getValOrVarNode() != null) {
|
||||
result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void generateFunctionsForDataClasses() {
|
||||
if (!KotlinBuiltIns.getInstance().isData(descriptor)) return;
|
||||
|
||||
generateComponentFunctionsForDataClasses();
|
||||
generateCopyFunctionForDataClasses();
|
||||
|
||||
List<PropertyDescriptor> properties = getDataProperties();
|
||||
if (!properties.isEmpty()) {
|
||||
generateDataClassToStringIfNeeded(properties);
|
||||
generateDataClassHashCodeIfNeeded(properties);
|
||||
generateDataClassEqualsIfNeeded(properties);
|
||||
}
|
||||
new DataClassMethodGeneratorImpl(myClass, bindingContext).generate();
|
||||
}
|
||||
|
||||
private void generateCopyFunctionForDataClasses() {
|
||||
FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, descriptor);
|
||||
if (copyFunction != null) {
|
||||
generateCopyFunction(copyFunction);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString();
|
||||
if (!hasDeclaredNonTrivialMember("toString", stringClass)) {
|
||||
generateDataClassToStringMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt();
|
||||
if (!hasDeclaredNonTrivialMember("hashCode", intClass)) {
|
||||
generateDataClassHashCodeMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
|
||||
ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean();
|
||||
ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny();
|
||||
if (!hasDeclaredNonTrivialMember("equals", booleanClass, anyClass)) {
|
||||
generateDataClassEqualsMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the class has a declared member with the given name anywhere in its hierarchy besides Any
|
||||
*/
|
||||
private boolean hasDeclaredNonTrivialMember(
|
||||
@NotNull String name,
|
||||
@NotNull ClassDescriptor returnedClassifier,
|
||||
@NotNull ClassDescriptor... valueParameterClassifiers
|
||||
) {
|
||||
FunctionDescriptor function =
|
||||
getDeclaredFunctionByRawSignature(descriptor, Name.identifier(name), returnedClassifier, valueParameterClassifiers);
|
||||
if (function == null) {
|
||||
return false;
|
||||
// A delegating class. Actual method implementations could easily move in.
|
||||
private class DataClassMethodGeneratorImpl extends DataClassMethodGenerator {
|
||||
DataClassMethodGeneratorImpl(
|
||||
JetClassOrObject klass,
|
||||
BindingContext bindingContext
|
||||
) {
|
||||
super(klass, bindingContext);
|
||||
}
|
||||
|
||||
if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
|
||||
return true;
|
||||
@Override
|
||||
public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) {
|
||||
ImplementationBodyCodegen.this.generateComponentFunction(function, parameter);
|
||||
}
|
||||
|
||||
for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
|
||||
if (overridden instanceof CallableMemberDescriptor
|
||||
&& ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
|
||||
&& !overridden.getContainingDeclaration().equals(KotlinBuiltIns.getInstance().getAny())) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<JetParameter> constructorParameters) {
|
||||
ImplementationBodyCodegen.this.generateCopyFunction(function);
|
||||
}
|
||||
|
||||
return false;
|
||||
@Override
|
||||
public void generateToStringMethod(@NotNull List<PropertyDescriptor> properties) {
|
||||
ImplementationBodyCodegen.this.generateDataClassToStringMethod(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateHashCodeMethod(@NotNull List<PropertyDescriptor> properties) {
|
||||
ImplementationBodyCodegen.this.generateDataClassHashCodeMethod(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateEqualsMethod(@NotNull List<PropertyDescriptor> properties) {
|
||||
ImplementationBodyCodegen.this.generateDataClassEqualsMethod(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDataClassEqualsMethod(@NotNull List<PropertyDescriptor> properties) {
|
||||
@@ -855,19 +814,6 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
private void generateComponentFunctionsForDataClasses() {
|
||||
if (!myClass.hasPrimaryConstructor() || !KotlinBuiltIns.getInstance().isData(descriptor)) return;
|
||||
|
||||
ConstructorDescriptor constructor = descriptor.getConstructors().iterator().next();
|
||||
|
||||
for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
|
||||
FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
|
||||
if (function != null) {
|
||||
generateComponentFunction(function, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull final ValueParameterDescriptor parameter) {
|
||||
functionCodegen.generateMethod(myClass, typeMapper.mapSignature(function), function, new FunctionGenerationStrategy() {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user