Extract data class generation logic into backend-independent module

This commit is contained in:
Peter Rybin
2014-05-06 02:51:20 +04:00
committed by Zalim Bashorov
parent 00e65c8771
commit 59a09c04af
3 changed files with 312 additions and 83 deletions
@@ -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();
}
}
@@ -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