001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.backend.common;
018
019 import com.google.common.collect.Lists;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
023 import org.jetbrains.kotlin.descriptors.*;
024 import org.jetbrains.kotlin.name.Name;
025 import org.jetbrains.kotlin.psi.KtClass;
026 import org.jetbrains.kotlin.psi.KtClassOrObject;
027 import org.jetbrains.kotlin.psi.KtParameter;
028 import org.jetbrains.kotlin.resolve.BindingContext;
029 import org.jetbrains.kotlin.resolve.BindingContextUtils;
030 import org.jetbrains.kotlin.resolve.OverrideResolver;
031 import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
032
033 import java.util.Collections;
034 import java.util.List;
035
036 /**
037 * A platform-independent logic for generating data class synthetic methods.
038 * TODO: data class with zero components gets no toString/equals/hashCode methods. This is inconsistent and should be
039 * changed here with the platform backends adopted.
040 */
041 public abstract class DataClassMethodGenerator {
042 private final KtClassOrObject declaration;
043 private final BindingContext bindingContext;
044 private final ClassDescriptor classDescriptor;
045 private final KotlinBuiltIns builtIns;
046
047 public DataClassMethodGenerator(KtClassOrObject declaration, BindingContext bindingContext) {
048 this.declaration = declaration;
049 this.bindingContext = bindingContext;
050 this.classDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration);
051 this.builtIns = DescriptorUtilsKt.getBuiltIns(classDescriptor);
052 }
053
054 public void generate() {
055 generateComponentFunctionsForDataClasses();
056
057 generateCopyFunctionForDataClasses(getPrimaryConstructorParameters());
058
059 List<PropertyDescriptor> properties = getDataProperties();
060 if (!properties.isEmpty()) {
061 generateDataClassToStringIfNeeded(properties);
062 generateDataClassHashCodeIfNeeded(properties);
063 generateDataClassEqualsIfNeeded(properties);
064 }
065 }
066
067 protected abstract void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter);
068
069 protected abstract void generateCopyFunction(@NotNull FunctionDescriptor function, @NotNull List<KtParameter> constructorParameters);
070
071 protected abstract void generateToStringMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
072
073 protected abstract void generateHashCodeMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
074
075 protected abstract void generateEqualsMethod(@NotNull FunctionDescriptor function, @NotNull List<PropertyDescriptor> properties);
076
077 @NotNull
078 protected ClassDescriptor getClassDescriptor() {
079 return classDescriptor;
080 }
081
082 private void generateComponentFunctionsForDataClasses() {
083 ConstructorDescriptor constructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
084 // primary constructor should exist for data classes
085 // but when generating light-classes still need to check we have one
086 if (constructor == null) return;
087
088 for (ValueParameterDescriptor parameter : constructor.getValueParameters()) {
089 FunctionDescriptor function = bindingContext.get(BindingContext.DATA_CLASS_COMPONENT_FUNCTION, parameter);
090 if (function != null) {
091 generateComponentFunction(function, parameter);
092 }
093 }
094 }
095
096 private void generateCopyFunctionForDataClasses(List<KtParameter> constructorParameters) {
097 FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor);
098 if (copyFunction != null) {
099 generateCopyFunction(copyFunction, constructorParameters);
100 }
101 }
102
103 private void generateDataClassToStringIfNeeded(@NotNull List<PropertyDescriptor> properties) {
104 FunctionDescriptor function = getDeclaredMember("toString", builtIns.getString());
105 if (function != null && isTrivial(function)) {
106 generateToStringMethod(function, properties);
107 }
108 }
109
110 private void generateDataClassHashCodeIfNeeded(@NotNull List<PropertyDescriptor> properties) {
111 FunctionDescriptor function = getDeclaredMember("hashCode", builtIns.getInt());
112 if (function != null && isTrivial(function)) {
113 generateHashCodeMethod(function, properties);
114 }
115 }
116
117 private void generateDataClassEqualsIfNeeded(@NotNull List<PropertyDescriptor> properties) {
118 FunctionDescriptor function = getDeclaredMember("equals", builtIns.getBoolean(), builtIns.getAny());
119 if (function != null && isTrivial(function)) {
120 generateEqualsMethod(function, properties);
121 }
122 }
123
124 private List<PropertyDescriptor> getDataProperties() {
125 List<PropertyDescriptor> result = Lists.newArrayList();
126 for (KtParameter parameter : getPrimaryConstructorParameters()) {
127 if (parameter.hasValOrVar()) {
128 result.add(bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter));
129 }
130 }
131 return result;
132 }
133
134 @NotNull
135 private List<KtParameter> getPrimaryConstructorParameters() {
136 if (declaration instanceof KtClass) {
137 return declaration.getPrimaryConstructorParameters();
138 }
139 return Collections.emptyList();
140 }
141
142 @Nullable
143 private FunctionDescriptor getDeclaredMember(
144 @NotNull String name,
145 @NotNull ClassDescriptor returnedClassifier,
146 @NotNull ClassDescriptor... valueParameterClassifiers
147 ) {
148 return CodegenUtil.getDeclaredFunctionByRawSignature(
149 classDescriptor, Name.identifier(name), returnedClassifier, valueParameterClassifiers
150 );
151 }
152
153 /**
154 * @return true if the member is an inherited implementation of a method from Any
155 */
156 private boolean isTrivial(@NotNull FunctionDescriptor function) {
157 if (function.getKind() == CallableMemberDescriptor.Kind.DECLARATION) {
158 return false;
159 }
160
161 for (CallableDescriptor overridden : OverrideResolver.getOverriddenDeclarations(function)) {
162 if (overridden instanceof CallableMemberDescriptor
163 && ((CallableMemberDescriptor) overridden).getKind() == CallableMemberDescriptor.Kind.DECLARATION
164 && !overridden.getContainingDeclaration().equals(builtIns.getAny())) {
165 return false;
166 }
167 }
168
169 return true;
170 }
171 }