diff --git a/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java b/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java new file mode 100644 index 00000000000..d18bab52218 --- /dev/null +++ b/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java @@ -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 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 parameters, + @NotNull List 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(); + } +} diff --git a/compiler/backend-common/src/org/jetbrains/jet/backend/common/DataClassMethodGenerator.java b/compiler/backend-common/src/org/jetbrains/jet/backend/common/DataClassMethodGenerator.java new file mode 100644 index 00000000000..adecabd0ac7 --- /dev/null +++ b/compiler/backend-common/src/org/jetbrains/jet/backend/common/DataClassMethodGenerator.java @@ -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 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 constructorParameters); + + protected abstract void generateToStringMethod(@NotNull List properties); + + protected abstract void generateHashCodeMethod(@NotNull List properties); + + protected abstract void generateEqualsMethod(@NotNull List 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 constructorParameters) { + FunctionDescriptor copyFunction = bindingContext.get(BindingContext.DATA_CLASS_COPY_FUNCTION, classDescriptor); + if (copyFunction != null) { + generateCopyFunction(copyFunction, constructorParameters); + } + } + + private void generateDataClassToStringIfNeeded(@NotNull List properties) { + ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString(); + if (!hasDeclaredNonTrivialMember(CodegenUtil.TO_STRING_METHOD_NAME, stringClass)) { + generateToStringMethod(properties); + } + } + + private void generateDataClassHashCodeIfNeeded(@NotNull List properties) { + ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt(); + if (!hasDeclaredNonTrivialMember(CodegenUtil.HASH_CODE_METHOD_NAME, intClass)) { + generateHashCodeMethod(properties); + } + } + + private void generateDataClassEqualsIfNeeded(@NotNull List properties) { + ClassDescriptor booleanClass = KotlinBuiltIns.getInstance().getBoolean(); + ClassDescriptor anyClass = KotlinBuiltIns.getInstance().getAny(); + if (!hasDeclaredNonTrivialMember(CodegenUtil.EQUALS_METHOD_NAME, booleanClass, anyClass)) { + generateEqualsMethod(properties); + } + } + + private List getDataProperties() { + List 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 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; + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index bb5006d5536..76461c67274 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -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 getDataProperties() { - ArrayList 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 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 properties) { - ClassDescriptor stringClass = KotlinBuiltIns.getInstance().getString(); - if (!hasDeclaredNonTrivialMember("toString", stringClass)) { - generateDataClassToStringMethod(properties); - } - } - - private void generateDataClassHashCodeIfNeeded(@NotNull List properties) { - ClassDescriptor intClass = KotlinBuiltIns.getInstance().getInt(); - if (!hasDeclaredNonTrivialMember("hashCode", intClass)) { - generateDataClassHashCodeMethod(properties); - } - } - - private void generateDataClassEqualsIfNeeded(@NotNull List 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 constructorParameters) { + ImplementationBodyCodegen.this.generateCopyFunction(function); } - return false; + @Override + public void generateToStringMethod(@NotNull List properties) { + ImplementationBodyCodegen.this.generateDataClassToStringMethod(properties); + } + + @Override + public void generateHashCodeMethod(@NotNull List properties) { + ImplementationBodyCodegen.this.generateDataClassHashCodeMethod(properties); + } + + @Override + public void generateEqualsMethod(@NotNull List properties) { + ImplementationBodyCodegen.this.generateDataClassEqualsMethod(properties); + } } private void generateDataClassEqualsMethod(@NotNull List 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