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;
}
}