From 6c0602e24f372963350b594fed9c7828a5540641 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Hannu=20Niemist=C3=B6?= Date: Wed, 15 Mar 2017 16:13:46 +0200 Subject: [PATCH] (refs #7090) Generated Function objects implement equals and hashCode Change-Id: I93118f7bd0f975bb68e14b7288dad6546a62faa1 --- .../codegen/utils/CodeBuilderUtils.java | 121 +++--- .../internal/codegen/utils/ModuleBuilder.java | 8 +- .../codegen/utils/ValueFromMethod.java | 246 ++++++++++--- .../internal/interpreted/ILambda.java | 345 ++++++++++++------ .../runtime/generation/GenerateFunctions.java | 66 +++- .../src/org/simantics/scl/runtime/Lists.java | 17 +- .../function/UnsaturatedFunction1.java | 28 ++ .../function/UnsaturatedFunction2.java | 34 ++ .../function/UnsaturatedFunction3.java | 40 ++ .../function/UnsaturatedFunction4.java | 46 +++ .../function/UnsaturatedFunction5.java | 52 +++ .../function/UnsaturatedFunction6.java | 58 +++ .../function/UnsaturatedFunction7.java | 64 ++++ .../function/UnsaturatedFunction8.java | 70 ++++ .../function/UnsaturatedFunctionN.java | 21 ++ .../compiler/tests/ModuleRegressionTests.java | 1 + .../compiler/tests/scl/FunctionIdentity.scl | 9 + 17 files changed, 994 insertions(+), 232 deletions(-) create mode 100644 tests/org.simantics.scl.compiler.tests/src/org/simantics/scl/compiler/tests/scl/FunctionIdentity.scl diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/utils/CodeBuilderUtils.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/utils/CodeBuilderUtils.java index cc8f3e0c5..81f7b117a 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/utils/CodeBuilderUtils.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/codegen/utils/CodeBuilderUtils.java @@ -93,72 +93,75 @@ public class CodeBuilderUtils { tsmb.finish(); } - if(generateEqualsAndHashCode) { - // Create equals - { - TypeDesc CLASS = TypeDesc.forClass(Class.class); + if(generateEqualsAndHashCode) + implementHashCodeAndEquals(classBuilder, recordName, fieldNamePrefix, types); + } - MethodBuilderBase tsmb = classBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "equals", TypeDesc.BOOLEAN, Constants.OBJECTS[1]); - LocalVariable parameter = tsmb.getParameter(0); - Label success = tsmb.createLabel(); - Label failure = tsmb.createLabel(); + public static void implementHashCodeAndEquals(ClassBuilder classBuilder, String recordName, String fieldNamePrefix, TypeDesc[] types) { + // Create equals + { + TypeDesc CLASS = TypeDesc.forClass(Class.class); - // Check type - tsmb.loadThis(); - tsmb.loadLocal(parameter); - tsmb.ifComparisonBranch(success, "==", TypeDesc.OBJECT); - tsmb.loadLocal(parameter); - tsmb.ifNullBranch(failure, true); - tsmb.loadLocal(parameter); - tsmb.invokeVirtual("java/lang/Object", "getClass", CLASS, Constants.EMPTY_TYPEDESC_ARRAY); - tsmb.loadThis(); - tsmb.invokeVirtual("java/lang/Object", "getClass", CLASS, Constants.EMPTY_TYPEDESC_ARRAY); - tsmb.ifComparisonBranch(failure, "!=", CLASS); - tsmb.loadLocal(parameter); - tsmb.checkCast(classBuilder.getType()); - LocalVariable other = tsmb.createLocalVariable("other", classBuilder.getType()); - tsmb.storeLocal(other); + MethodBuilderBase tsmb = classBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "equals", TypeDesc.BOOLEAN, Constants.OBJECTS[1]); + LocalVariable parameter = tsmb.getParameter(0); + Label success = tsmb.createLabel(); + Label failure = tsmb.createLabel(); - // Compare fields - for(int i=0;i { + private final Method method; + private final boolean returnsVoid; + + private Arity1Func(Method method, boolean returnsVoid) { + this.method = method; + this.returnsVoid = returnsVoid; + } + + @Override + public Object apply(Object p0) { + try { + Object ret = method.invoke(null, p0); + return returnsVoid ? Tuple0.INSTANCE : ret; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return method == null ? 0 : method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Arity1Func other = (Arity1Func) obj; + return method.equals(other.method); + } + } + + private static final class Arity2Func extends FunctionImpl2 { + private final Method method; + private final boolean returnsVoid; + + private Arity2Func(Method method, boolean returnsVoid) { + this.method = method; + this.returnsVoid = returnsVoid; + } + + @Override + public Object apply(Object p0, Object p1) { + try { + Object ret = method.invoke(null, p0, p1); + return returnsVoid ? Tuple0.INSTANCE : ret; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return method == null ? 0 : method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Arity2Func other = (Arity2Func) obj; + return method.equals(other.method); + } + } + + private static final class Arity3Func extends FunctionImpl3 { + private final Method method; + private final boolean returnsVoid; + + private Arity3Func(Method method, boolean returnsVoid) { + this.method = method; + this.returnsVoid = returnsVoid; + } + + @Override + public Object apply(Object p0, Object p1, Object p2) { + try { + Object ret = method.invoke(null, p0, p1, p2); + return returnsVoid ? Tuple0.INSTANCE : ret; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return method == null ? 0 : method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Arity3Func other = (Arity3Func) obj; + return method.equals(other.method); + } + } + + private static final class Arity4Func extends FunctionImpl4 { + private final Method method; + private final boolean returnsVoid; + + private Arity4Func(Method method, boolean returnsVoid) { + this.method = method; + this.returnsVoid = returnsVoid; + } + + @Override + public Object apply(Object p0, Object p1, Object p2, Object p3) { + try { + Object ret = method.invoke(null, p0, p1, p2, p3); + return returnsVoid ? Tuple0.INSTANCE : ret; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return method == null ? 0 : method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Arity4Func other = (Arity4Func) obj; + return method.equals(other.method); + } + } + + private static final class ArityNFunc extends FunctionImplN { + private final Method method; + private final boolean returnsVoid; + + private ArityNFunc(int arity, Method method, boolean returnsVoid) { + super(arity); + this.method = method; + this.returnsVoid = returnsVoid; + } + + @Override + public Object doApply(Object... ps) { + try { + Object ret = method.invoke(null, ps); + return returnsVoid ? Tuple0.INSTANCE : ret; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return method == null ? 0 : method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ArityNFunc other = (ArityNFunc) obj; + return method.equals(other.method); + } + } + public static Object getValueFromStaticMethod(final Method method) throws ReflectiveOperationException { int arity = method.getParameterTypes().length; final boolean returnsVoid = method.getReturnType().equals(void.class); @@ -20,65 +206,15 @@ public class ValueFromMethod { return returnsVoid ? Tuple0.INSTANCE : ret; } case 1: - return new FunctionImpl1() { - @Override - public Object apply(Object p0) { - try { - Object ret = method.invoke(null, p0); - return returnsVoid ? Tuple0.INSTANCE : ret; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - }; + return new Arity1Func(method, returnsVoid); case 2: - return new FunctionImpl2() { - @Override - public Object apply(Object p0, Object p1) { - try { - Object ret = method.invoke(null, p0, p1); - return returnsVoid ? Tuple0.INSTANCE : ret; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - }; + return new Arity2Func(method, returnsVoid); case 3: - return new FunctionImpl3() { - @Override - public Object apply(Object p0, Object p1, Object p2) { - try { - Object ret = method.invoke(null, p0, p1, p2); - return returnsVoid ? Tuple0.INSTANCE : ret; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - }; + return new Arity3Func(method, returnsVoid); case 4: - return new FunctionImpl4() { - @Override - public Object apply(Object p0, Object p1, Object p2, Object p3) { - try { - Object ret = method.invoke(null, p0, p1, p2, p3); - return returnsVoid ? Tuple0.INSTANCE : ret; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - }; + return new Arity4Func(method, returnsVoid); default: - return new FunctionImplN(arity) { - @Override - public Object doApply(Object... ps) { - try { - Object ret = method.invoke(null, ps); - return returnsVoid ? Tuple0.INSTANCE : ret; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - }; + return new ArityNFunc(arity, method, returnsVoid); } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/interpreted/ILambda.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/interpreted/ILambda.java index aac13af73..0695ebb4d 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/interpreted/ILambda.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/internal/interpreted/ILambda.java @@ -1,5 +1,7 @@ package org.simantics.scl.compiler.internal.interpreted; +import java.util.Arrays; + import org.simantics.scl.runtime.function.FunctionImpl1; import org.simantics.scl.runtime.function.FunctionImpl2; import org.simantics.scl.runtime.function.FunctionImpl3; @@ -7,11 +9,229 @@ import org.simantics.scl.runtime.function.FunctionImpl4; import org.simantics.scl.runtime.function.FunctionImplN; public class ILambda implements IExpression { + private final class Arity1Func extends FunctionImpl1 { + private final Object[] inheritedVariableBindings; + + private Arity1Func(Object[] inheritedVariableBindings) { + this.inheritedVariableBindings = inheritedVariableBindings; + } + + @Override + public Object apply(Object param0) { + Object[] newVariableBindings = new Object[variableBindingsLength]; + int i = 0;; + for(;i < inheritedVariableBindings.length;++i) + newVariableBindings[i] = inheritedVariableBindings[i]; + newVariableBindings[i] = param0; + return body.execute(newVariableBindings); + } + + @Override + public String toString() { + return ILambda.this.toString(inheritedVariableBindings); + } + + @Override + public int hashCode() { + return ILambda.this.hashCode() + 31*Arrays.hashCode(inheritedVariableBindings); + } + + private ILambda getParent() { + return ILambda.this; + } + + @Override + public boolean equals(Object obj) { + if(obj == this) + return true; + if(obj == null || obj.getClass() != getClass()) + return false; + Arity1Func other = (Arity1Func)obj; + return ILambda.this == other.getParent() && Arrays.equals(inheritedVariableBindings, other.inheritedVariableBindings); + } + } + + private final class Arity2Func extends FunctionImpl2 { + private final Object[] inheritedVariableBindings; + + private Arity2Func(Object[] inheritedVariableBindings) { + this.inheritedVariableBindings = inheritedVariableBindings; + } + + @Override + public Object apply(Object param0, Object param1) { + Object[] newVariableBindings = new Object[variableBindingsLength]; + int i = 0;; + for(;i < inheritedVariableBindings.length;++i) + newVariableBindings[i] = inheritedVariableBindings[i]; + newVariableBindings[i++] = param0; + newVariableBindings[i] = param1; + return body.execute(newVariableBindings); + } + + @Override + public String toString() { + return ILambda.this.toString(inheritedVariableBindings); + } + + @Override + public int hashCode() { + return ILambda.this.hashCode() + 31*Arrays.hashCode(inheritedVariableBindings); + } + + private ILambda getParent() { + return ILambda.this; + } + + @Override + public boolean equals(Object obj) { + if(obj == this) + return true; + if(obj == null || obj.getClass() != getClass()) + return false; + Arity2Func other = (Arity2Func)obj; + return ILambda.this == other.getParent() && Arrays.equals(inheritedVariableBindings, other.inheritedVariableBindings); + } + } + + private final class Arity3Func extends FunctionImpl3 { + private final Object[] inheritedVariableBindings; + + private Arity3Func(Object[] inheritedVariableBindings) { + this.inheritedVariableBindings = inheritedVariableBindings; + } + + @Override + public Object apply(Object param0, Object param1, Object param2) { + Object[] newVariableBindings = new Object[variableBindingsLength]; + int i = 0;; + for(;i < inheritedVariableBindings.length;++i) + newVariableBindings[i] = inheritedVariableBindings[i]; + newVariableBindings[i++] = param0; + newVariableBindings[i++] = param1; + newVariableBindings[i] = param2; + return body.execute(newVariableBindings); + } + + @Override + public String toString() { + return ILambda.this.toString(inheritedVariableBindings); + } + + @Override + public int hashCode() { + return ILambda.this.hashCode() + 31*Arrays.hashCode(inheritedVariableBindings); + } + + private ILambda getParent() { + return ILambda.this; + } + + @Override + public boolean equals(Object obj) { + if(obj == this) + return true; + if(obj == null || obj.getClass() != getClass()) + return false; + Arity3Func other = (Arity3Func)obj; + return ILambda.this == other.getParent() && Arrays.equals(inheritedVariableBindings, other.inheritedVariableBindings); + } + } + + private final class Arity4Func extends FunctionImpl4 { + private final Object[] inheritedVariableBindings; + + private Arity4Func(Object[] inheritedVariableBindings) { + this.inheritedVariableBindings = inheritedVariableBindings; + } + + @Override + public Object apply(Object param0, Object param1, Object param2, Object param3) { + Object[] newVariableBindings = new Object[variableBindingsLength]; + int i = 0;; + for(;i < inheritedVariableBindings.length;++i) + newVariableBindings[i] = inheritedVariableBindings[i]; + newVariableBindings[i++] = param0; + newVariableBindings[i++] = param1; + newVariableBindings[i++] = param2; + newVariableBindings[i] = param3; + return body.execute(newVariableBindings); + } + + @Override + public String toString() { + return ILambda.this.toString(inheritedVariableBindings); + } + + @Override + public int hashCode() { + return ILambda.this.hashCode() + 31*Arrays.hashCode(inheritedVariableBindings); + } + + private ILambda getParent() { + return ILambda.this; + } + + @Override + public boolean equals(Object obj) { + if(obj == this) + return true; + if(obj == null || obj.getClass() != getClass()) + return false; + Arity4Func other = (Arity4Func)obj; + return ILambda.this == other.getParent() && Arrays.equals(inheritedVariableBindings, other.inheritedVariableBindings); + } + } + + private final class ArityNFunc extends FunctionImplN { + private final Object[] inheritedVariableBindings; + + private ArityNFunc(Object[] inheritedVariableBindings) { + super(arity); + this.inheritedVariableBindings = inheritedVariableBindings; + } + + @Override + public Object doApply(Object... ps) { + Object[] newVariableBindings = new Object[variableBindingsLength]; + int i = 0;; + for(;i < inheritedVariableBindings.length;++i) + newVariableBindings[i] = inheritedVariableBindings[i]; + for(Object p : ps) + newVariableBindings[i++] = p; + return body.execute(newVariableBindings); + } + + @Override + public String toString() { + return ILambda.this.toString(inheritedVariableBindings); + } + + @Override + public int hashCode() { + return ILambda.this.hashCode() + 31*Arrays.hashCode(inheritedVariableBindings); + } + + private ILambda getParent() { + return ILambda.this; + } + + @Override + public boolean equals(Object obj) { + if(obj == this) + return true; + if(obj == null || obj.getClass() != getClass()) + return false; + ArityNFunc other = (ArityNFunc)obj; + return ILambda.this == other.getParent() && Arrays.equals(inheritedVariableBindings, other.inheritedVariableBindings); + } + } + private final int[] inheritedVariableIds; private final int arity; private final int variableBindingsLength; private final IExpression body; - + public ILambda(int[] inheritedVariableIds, int arity, int variableBindingsLength, IExpression body) { this.inheritedVariableIds = inheritedVariableIds; @@ -20,7 +240,6 @@ public class ILambda implements IExpression { this.body = body; } - @SuppressWarnings("rawtypes") @Override public Object execute(Object[] variableBindings) { final Object[] inheritedVariableBindings = new Object[inheritedVariableIds.length]; @@ -28,100 +247,18 @@ public class ILambda implements IExpression { inheritedVariableBindings[i] = variableBindings[inheritedVariableIds[i]]; switch(arity) { case 1: - return new FunctionImpl1() { - @Override - public Object apply(Object param0) { - Object[] newVariableBindings = new Object[variableBindingsLength]; - int i = 0;; - for(;i < inheritedVariableBindings.length;++i) - newVariableBindings[i] = inheritedVariableBindings[i]; - newVariableBindings[i] = param0; - return body.execute(newVariableBindings); - } - - @Override - public String toString() { - return ILambda.this.toString(inheritedVariableBindings); - } - }; + return new Arity1Func(inheritedVariableBindings); case 2: - return new FunctionImpl2() { - @Override - public Object apply(Object param0, Object param1) { - Object[] newVariableBindings = new Object[variableBindingsLength]; - int i = 0;; - for(;i < inheritedVariableBindings.length;++i) - newVariableBindings[i] = inheritedVariableBindings[i]; - newVariableBindings[i++] = param0; - newVariableBindings[i] = param1; - return body.execute(newVariableBindings); - } - - @Override - public String toString() { - return ILambda.this.toString(inheritedVariableBindings); - } - }; + return new Arity2Func(inheritedVariableBindings); case 3: - return new FunctionImpl3() { - @Override - public Object apply(Object param0, Object param1, Object param2) { - Object[] newVariableBindings = new Object[variableBindingsLength]; - int i = 0;; - for(;i < inheritedVariableBindings.length;++i) - newVariableBindings[i] = inheritedVariableBindings[i]; - newVariableBindings[i++] = param0; - newVariableBindings[i++] = param1; - newVariableBindings[i] = param2; - return body.execute(newVariableBindings); - } - - @Override - public String toString() { - return ILambda.this.toString(inheritedVariableBindings); - } - }; + return new Arity3Func(inheritedVariableBindings); case 4: - return new FunctionImpl4() { - @Override - public Object apply(Object param0, Object param1, Object param2, Object param3) { - Object[] newVariableBindings = new Object[variableBindingsLength]; - int i = 0;; - for(;i < inheritedVariableBindings.length;++i) - newVariableBindings[i] = inheritedVariableBindings[i]; - newVariableBindings[i++] = param0; - newVariableBindings[i++] = param1; - newVariableBindings[i++] = param2; - newVariableBindings[i] = param3; - return body.execute(newVariableBindings); - } - - @Override - public String toString() { - return ILambda.this.toString(inheritedVariableBindings); - } - }; + return new Arity4Func(inheritedVariableBindings); default: - return new FunctionImplN(arity) { - @Override - public Object doApply(Object... ps) { - Object[] newVariableBindings = new Object[variableBindingsLength]; - int i = 0;; - for(;i < inheritedVariableBindings.length;++i) - newVariableBindings[i] = inheritedVariableBindings[i]; - for(Object p : ps) - newVariableBindings[i++] = p; - return body.execute(newVariableBindings); - } - - @Override - public String toString() { - return ILambda.this.toString(inheritedVariableBindings); - } - }; + return new ArityNFunc(inheritedVariableBindings); } } - + @Override public String toString() { StringBuilder b = new StringBuilder(); @@ -136,22 +273,22 @@ public class ILambda implements IExpression { b.append(')'); return b.toString(); } - + public String toString(Object[] variableBindings) { - StringBuilder sb = new StringBuilder(); - appendVariableBindings(sb, variableBindings); - sb.append(this.toString()); - return sb.toString(); + StringBuilder sb = new StringBuilder(); + appendVariableBindings(sb, variableBindings); + sb.append(this.toString()); + return sb.toString(); } - + private static void appendVariableBindings(StringBuilder sb, Object[] variableBindings) { - if (variableBindings.length > 0) { - sb.append("(let {"); - for(int i = 0; i < variableBindings.length; i++) { - if (i > 0) sb.append("; "); - sb.append("v").append(i).append("=").append(variableBindings[i].toString()); - } - sb.append("} in "); - } + if (variableBindings.length > 0) { + sb.append("(let {"); + for(int i = 0; i < variableBindings.length; i++) { + if (i > 0) sb.append("; "); + sb.append("v").append(i).append("=").append(variableBindings[i].toString()); + } + sb.append("} in "); + } } } diff --git a/bundles/org.simantics.scl.runtime/generation/org/simantics/scl/runtime/generation/GenerateFunctions.java b/bundles/org.simantics.scl.runtime/generation/org/simantics/scl/runtime/generation/GenerateFunctions.java index adf2dee30..2e61986e8 100644 --- a/bundles/org.simantics.scl.runtime/generation/org/simantics/scl/runtime/generation/GenerateFunctions.java +++ b/bundles/org.simantics.scl.runtime/generation/org/simantics/scl/runtime/generation/GenerateFunctions.java @@ -11,9 +11,9 @@ public class GenerateFunctions { public static final int MAX_ARITY = 8; public static final String HEADER = - "/**\n" - + " * This code is generated in " + GenerateFunctions.class.getName() + ".\n" - + " * Do not edit manually!\n" + "/**\r\n" + + " * This code is generated in " + GenerateFunctions.class.getName() + ".\r\n" + + " * Do not edit manually!\r\n" + " */" ; @@ -342,6 +342,7 @@ public class GenerateFunctions { p.println(" nps[i + " + n + "] = ps[i];"); p.println(" return f.applyArray(nps);"); p.println(" }"); + p.println(); } { p.println(" @Override"); @@ -353,6 +354,40 @@ public class GenerateFunctions { p.println(" sb.append(\")\");"); p.println(" return sb.toString();"); p.println(" }"); + p.println(); + } + { + p.println(" @Override"); + p.println(" public int hashCode() {"); + p.println(" int result = f.hashCode();"); + for(int i=0;i a + x + +main = [atan2 1==atan2 1, atan2 1==atan2 2, f 1==f 1, f 1==f 2] +-- +[true, false, true, false] \ No newline at end of file -- 2.47.1