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 }