From a209ee5082677a9189c17f21f0282cea548f8b16 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 16 Oct 2020 13:55:40 +0200 Subject: [PATCH 01/18] WIP: Reimplementing destructuring Trying to reimplement the destructuring feature better (see #524 for a full introduction to issues of current implementation). --- TODO.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..e763053c7 --- /dev/null +++ b/TODO.md @@ -0,0 +1,13 @@ +# Reimplementing destructuring + +branch: feat/destruct-reboot + +Trying to reimplement the destructuring feature better (see #524 for a full introduction to issues of current implementation). + +## TODO + +- [ ] implement the new desugarization +- [ ] update the known `destruct()` method in the stdlib + - [ ] provides a new destructuring method + - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) +- [ ] provides augmentations to adapt both ways From 0554a1ecaf24cc35641c6e07d936fc88b16679b4 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 16 Oct 2020 14:16:03 +0200 Subject: [PATCH 02/18] Implements the new desugarization --- .../golo/compiler/SugarExpansionVisitor.java | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java index b48ab256c..315b95b40 100644 --- a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java +++ b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java @@ -30,6 +30,7 @@ class SugarExpansionVisitor extends AbstractGoloIrVisitor { private final SymbolGenerator symbols = new SymbolGenerator("golo.compiler.sugar"); private final List functionsToAdd = new LinkedList<>(); private GoloModule module; + private final boolean useNewStyleDestruct = gololang.Runtime.loadBoolean("new", "golo.destruct.version", "GOLO_DESTRUCT_VERSION", false); @Override public void visitModule(GoloModule module) { @@ -236,6 +237,9 @@ public void visitCollectionLiteral(CollectionLiteral collection) { } } + /** + * Converts a literal function reference into a call to {@Predefined.fun}. + */ @Override public void visitConstantStatement(ConstantStatement constantStatement) { constantStatement.walk(this); @@ -373,8 +377,19 @@ public void visitForEachLoopStatement(ForEachLoopStatement foreachStatement) { /** * Destructuring assignment expansion. + */ + @Override + public void visitDestructuringAssignment(DestructuringAssignment assignment) { + Block replacement = useNewStyleDestruct ? newDestructuring(assignment) : oldDestructuring(assignment); + assignment.replaceInParentBy(replacement); + replacement.accept(this); + } + + /** + * Destructuring assignment expansion. + *

Old version (before 3.4.0) *

- * convert code like + * Converts code like *


    * let a, b, c... = expr
    * 
@@ -386,8 +401,7 @@ public void visitForEachLoopStatement(ForEachLoopStatement foreachStatement) { * let c = tmp: subTuple(2) * */ - @Override - public void visitDestructuringAssignment(DestructuringAssignment assignment) { + private Block oldDestructuring(DestructuringAssignment assignment) { LocalReference tmpRef = LocalReference.of(symbols.next("destruct")).synthetic(); Block block = Block.of(AssignmentStatement.create(tmpRef, invoke("destruct").on(assignment.expression()), true)); int last = assignment.getReferencesCount() - 1; @@ -402,8 +416,44 @@ public void visitDestructuringAssignment(DestructuringAssignment assignment) { assignment.isDeclaring())); idx++; } - assignment.replaceInParentBy(block); - block.accept(this); + return block; + } + + /** + * Destructuring assignment expansion. + *

Old version (after 3.4.0) + *

+ * Converts code like + *


+   * let a, b, c... = expr
+   * 
+ * into something equivalent to + *

+   * let tmp = expr: __$$_destruct(3, true)
+   * let a = tmp: get(0)
+   * let b = tmp: get(1)
+   * let c = tmp: get(2)
+   * 
+ */ + private Block newDestructuring(DestructuringAssignment assignment) { + LocalReference tmpRef = LocalReference.of(symbols.next("destruct")).synthetic(); + Block block = Block.of(AssignmentStatement.create(tmpRef, + invoke("__$$_destruct") + .withArgs( + ConstantStatement.of(assignment.getReferencesCount()), + ConstantStatement.of(assignment.isVarargs())) + .on(assignment.expression()), + true)); + int idx = 0; + for (LocalReference ref : assignment.getReferences()) { + block.add( + AssignmentStatement.create( + ref, + invoke("get").withArgs(ConstantStatement.of(idx)).on(tmpRef.lookup()), + assignment.isDeclaring())); + idx++; + } + return block; } /** From 668b9f82754b92236046a6d0e891645112ded803 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 16 Oct 2020 16:21:42 +0200 Subject: [PATCH 03/18] Adds new style stub and activate feature flag --- TODO.md | 5 +- src/main/golo/standard-augmentations.golo | 45 +++++++- src/main/java/gololang/AbstractRange.java | 15 +++ src/main/java/gololang/GoloStruct.java | 24 +++++ src/main/java/gololang/LazyList.java | 15 +++ src/main/java/gololang/Tuple.java | 27 +++++ src/main/java/gololang/Union.java | 15 +++ src/main/java/gololang/error/Result.java | 15 +++ src/main/java/gololang/ir/WhenClause.java | 5 + .../golo/compiler/SugarExpansionVisitor.java | 2 +- .../org/eclipse/golo/runtime/ArrayHelper.java | 6 ++ .../golo/runtime/ArrayMethodFinder.java | 4 + .../resources/for-execution/destruct.golo | 101 ++++++++++++------ 13 files changed, 242 insertions(+), 37 deletions(-) diff --git a/TODO.md b/TODO.md index e763053c7..154b5059a 100644 --- a/TODO.md +++ b/TODO.md @@ -6,8 +6,11 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr ## TODO -- [ ] implement the new desugarization +- [x] implement the new desugarization + - [ ] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member - [ ] update the known `destruct()` method in the stdlib - [ ] provides a new destructuring method - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) + - [ ] if applicable, use the new one in the old one - [ ] provides augmentations to adapt both ways +- [ ] update the doc diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 4364dd8a5..79326e15a 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -310,6 +310,34 @@ augment java.util.Collection { ---- function destruct = |this| -> Tuple.fromArray(this: toArray()) + ---- + New style destructuring helper. + + New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + members of the structure. + + - *param* `number`: number of variable that will be affected. + - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. + - *return* a tuple containing the values to assign. + ---- + function __$$_destruct = |this, number, substruct| { + # TODO: new style destruct + if substruct { + let a = newTypedArray(Object.class, number) + let r = _newWithSameType(this) + a: set(number - 1, r) + let it = this: iterator() + for (var i = 0, i < number - 1, i = i + 1) { + a: set(i, it: next()) + } + while (it: hasNext()) { + r: add(it: next()) + } + return Tuple.fromArray(a) + } + return this: destruct() + } + ---- Maps a function returning a collection and flatten the result (a.k.a bind) @@ -320,7 +348,7 @@ augment java.util.Collection { - *param* `func`: a mapping function returning a collection ---- function flatMap = |this, func| { - let result = this: newWithSameType() + let result = _newWithSameType(this) foreach elt in this { result: addAll(func(elt)) } @@ -862,6 +890,21 @@ augment java.util.Map$Entry { ---- function destruct = |this| -> [ this: getKey(), this: getValue() ] + ---- + New style destructuring helper. + + New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + members of the structure. + + - *param* `number`: number of variable that will be affected. + - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. + - *return* a tuple containing the values to assign. + ---- + function __$$_destruct = |this, number, substruct| { + # TODO: new style destruct + return this: destruct() + } + ---- Convert then entry into an array containing the key and the value. ---- diff --git a/src/main/java/gololang/AbstractRange.java b/src/main/java/gololang/AbstractRange.java index 7ca1bc790..3bce9425e 100644 --- a/src/main/java/gololang/AbstractRange.java +++ b/src/main/java/gololang/AbstractRange.java @@ -146,4 +146,19 @@ public Tuple destruct() { } return Tuple.fromArray(data); } + + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: new style destruct + return this.destruct(); + } } diff --git a/src/main/java/gololang/GoloStruct.java b/src/main/java/gololang/GoloStruct.java index 9ef2d78f5..f3a79d650 100644 --- a/src/main/java/gololang/GoloStruct.java +++ b/src/main/java/gololang/GoloStruct.java @@ -60,11 +60,35 @@ public Tuple values() { * Destructuration helper. * * @return a tuple with the current values. + * @deprecated This method should not be called directly and is no more used by new style destructuring. */ public Tuple destruct() { return Tuple.fromArray(toArray()); } + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + if (number == this.members.length && !substruct) { + return Tuple.fromArray(toArray()); + } + // TODO: defines a specific exception? + // TODO: localize the error message? + throw new Error(String.format("Non exact destructuring: %s with %d fields destructured into %d variables%s.", + this.getClass().getName(), + this.members.length, + number, + substruct ? " with sub structure" : "")); + } + /** * Array conversion. * diff --git a/src/main/java/gololang/LazyList.java b/src/main/java/gololang/LazyList.java index 231415634..9ff6ffe65 100644 --- a/src/main/java/gololang/LazyList.java +++ b/src/main/java/gololang/LazyList.java @@ -242,6 +242,21 @@ public Tuple destruct() { return new Tuple(head(), tail()); } + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: new style destruct + return this.destruct(); + } + /** * Returns the element at the specified position in this list. *

diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index e6d67baa1..a44406222 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -194,9 +194,36 @@ public Tuple tail() { * Helper for destructuring. * * @return the tuple itself + * @deprecated This method should not be called directly and is no more used by new style destructuring. */ public Tuple destruct() { return this; } + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: defines a specific exception? + // TODO: localize the error message? + if (number == this.data.length) { + return this; + } + if (number < this.data.length && substruct) { + Object[] destruct = new Object[number]; + System.arraycopy(this.data, 0, destruct, 0, number - 1); + destruct[number - 1] = this.subTuple(number - 1); + return fromArray(destruct); + } + throw new Error(String.format("Non exact destructuring: this tuple has %d values.", this.data.length)); + + } + /** * Extract a sub-tuple. * diff --git a/src/main/java/gololang/Union.java b/src/main/java/gololang/Union.java index 472ba6222..21ad7c91e 100644 --- a/src/main/java/gololang/Union.java +++ b/src/main/java/gololang/Union.java @@ -33,4 +33,19 @@ public Object[] toArray() { public Tuple destruct() { return Tuple.fromArray(toArray()); } + + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: new style destruct + return this.destruct(); + } } diff --git a/src/main/java/gololang/error/Result.java b/src/main/java/gololang/error/Result.java index bf9bc7358..ac4961bf5 100644 --- a/src/main/java/gololang/error/Result.java +++ b/src/main/java/gololang/error/Result.java @@ -661,5 +661,20 @@ public Tuple destruct() { return new Tuple(error, value); } + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return a tuple containing the values to assign. + */ + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: new style destruct + return this.destruct(); + } + } diff --git a/src/main/java/gololang/ir/WhenClause.java b/src/main/java/gololang/ir/WhenClause.java index 9ef9114f2..fc16f03ec 100644 --- a/src/main/java/gololang/ir/WhenClause.java +++ b/src/main/java/gololang/ir/WhenClause.java @@ -81,6 +81,11 @@ public List> children() { public Tuple destruct() { return new Tuple(condition, action); } + + public Tuple __$$_destruct(int number, boolean substruct) { + // TODO: new style destruct + return this.destruct(); + } } diff --git a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java index 315b95b40..e9fed32b4 100644 --- a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java +++ b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java @@ -30,7 +30,7 @@ class SugarExpansionVisitor extends AbstractGoloIrVisitor { private final SymbolGenerator symbols = new SymbolGenerator("golo.compiler.sugar"); private final List functionsToAdd = new LinkedList<>(); private GoloModule module; - private final boolean useNewStyleDestruct = gololang.Runtime.loadBoolean("new", "golo.destruct.version", "GOLO_DESTRUCT_VERSION", false); + private final boolean useNewStyleDestruct = gololang.Runtime.loadBoolean("new", "golo.destruct.version", "GOLO_DESTRUCT_VERSION", true); @Override public void visitModule(GoloModule module) { diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java index 268281e81..004b03d0e 100644 --- a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java +++ b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java @@ -12,6 +12,7 @@ import java.util.NoSuchElementException; import java.util.Objects; +import gololang.Tuple; import static java.util.Arrays.copyOfRange; public final class ArrayHelper { @@ -69,4 +70,9 @@ public static int indexOf(Object[] array, Object elt) { } return -1; } + + public static Tuple newStyleDestruct(Object[] array, int number, boolean sub) { + // TODO: new style destructuring on arrays + return Tuple.fromArray(array); + } } diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java index 7476fb329..5405a2122 100644 --- a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java +++ b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java @@ -68,6 +68,10 @@ private MethodHandle resolve() throws NoSuchMethodException, IllegalAccessExcept case "destruct": checkArity(0); return lookup.findStatic(gololang.Tuple.class, "fromArray", methodType(gololang.Tuple.class, Object[].class)); + case "__$$_destruct": + checkArity(2); + return lookup.findStatic( + ArrayHelper.class, "newStyleDestruct", methodType(gololang.Tuple.class, Object[].class, int.class, boolean.class)); case "equals": checkArity(1); return lookup.findStatic(Arrays.class, "equals", diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index 83fa1a725..a6cc97f22 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -3,6 +3,12 @@ module golotest.execution.Destructuring struct Point = { x, y } +struct Triplet = {a, b, c} + +local function fail = { + throw AssertionError("Test should fail") +} + function test_tuple_samesize = { let a, b, c = [1, 2, 3] require(a == 1, "err") @@ -30,53 +36,93 @@ function test_tuple_rest = { require(rest == [3, 4, 5], "err") } -function test_tuple_less = { +# ignored, old version. Should fail now. +function _tuple_less_old = { let fst, scd = [1, 2, 3, 4] require(fst == 1, "err") require(scd == 2, "err") } +function test_tuple_less_new = { + try { + let fst, scd = [1, 2, 3, 4] + fail() + } catch(e) { + require(e: message(): contains("Non exact destructuring"), "err") + } +} + + function test_struct = { let p = Point(3, 4) let x, y = p require(x == 3, "err") require(y == 4, "err") + + let a, b, c = Triplet(5, 6, 7) + require(a == 5, "err") + require(b == 6, "err") + require(c == 7, "err") } -function test_list = { - let l = list[1, 2, 3, 4, 5] +function test_struct_not_exact = { + try { + let x, y = Triplet(1, 2, 3) + fail() + } catch(e) { + require(e: message(): contains("Non exact destructuring"), "err") + } - let fst, scd, rest... = l - require(fst == 1, "err") - require(scd == 2, "err") - require(rest == [3, 4, 5], "err") + try { + let a, b, c, d = Triplet(4, 5, 6) + fail() + } catch(e) { + require(e: message(): contains("Non exact destructuring"), "err") + } + try { + let a, b, c... = Triplet(4, 5, 6) + fail() + } catch(e) { + require(e: message(): contains("Non exact destructuring"), "err") + } } -function test_array = { - let fst, scd, rest... = array[1, 2, 3, 4, 5] - require(fst == 1, "err") - require(scd == 2, "err") - require(rest == [3, 4, 5], "err") -} +function test_list = { + let l = list[1, 2, 3, 4, 5] -function test_range = { - let fst, scd, rest... = [1..6] + let fst, scd, rest... = l require(fst == 1, "err") require(scd == 2, "err") - require(rest == [3, 4, 5], "err") + require(rest == list[3, 4, 5], "err") - let a, b = [1..4] - require(a == 1, "err") - require(b == 2, "err") } +# function test_array = { +# let fst, scd, rest... = array[1, 2, 3, 4, 5] +# require(fst == 1, "err") +# require(scd == 2, "err") +# require(rest == [3, 4, 5], "err") +# } + +# function test_range = { +# let fst, scd, rest... = [1..6] +# require(fst == 1, "err") +# require(scd == 2, "err") +# require(rest == [3, 4, 5], "err") +# +# let a, b = [1..4] +# require(a == 1, "err") +# require(b == 2, "err") +# } + function test_foreach = { - let l = [ [1, 2, 3], [3, 4, 5] ] + let l = list[ [1, 2, 3], [3, 4, 5] ] var i = 0 - foreach a, b in l { + foreach a, b, c in l { require(a == l: get(i): get(0), "err") require(b == l: get(i): get(1), "err") + require(c == l: get(i): get(2), "err") i = i + 1 } @@ -139,16 +185,3 @@ function test_destruct_affectation_inside_closure = { require(closure() == 1, "err") } -function main = |args| { - test_tuple_samesize() - test_tuple_rest() - test_tuple_less() - test_tuple_var() - test_struct() - test_list() - test_range() - test_foreach() - test_map() - test_swap() - println("ok") -} From 8224f68ba01c95dfe97efb62fc9fccea79865c86 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 16 Oct 2020 23:46:25 +0200 Subject: [PATCH 04/18] Adds a custom exception --- TODO.md | 17 +++ .../InvalidDestructuringException.java | 42 ++++++ .../resources/for-execution/destruct.golo | 127 ++++++++++++------ 3 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java diff --git a/TODO.md b/TODO.md index 154b5059a..61a61f2df 100644 --- a/TODO.md +++ b/TODO.md @@ -8,9 +8,26 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] implement the new desugarization - [ ] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member +- [x] Adds a custom exception to signal invalid destructuring - [ ] update the known `destruct()` method in the stdlib - [ ] provides a new destructuring method + - [ ] Tuple + - [ ] array + - [ ] struct + - [ ] union + - [ ] iter/collections + - [ ] Result + - [ ] map item + - [ ] lazy lists + - [ ] ranges + - [ ] When IR - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) - [ ] if applicable, use the new one in the old one - [ ] provides augmentations to adapt both ways - [ ] update the doc + +## DOING + +throwing the sepecific exception in tuple and struct + -> stash implementing for array + diff --git a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java new file mode 100644 index 000000000..b90b0757b --- /dev/null +++ b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012-2020 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.golo.runtime; + +public class InvalidDestructuringException extends IllegalArgumentException { + // TODO: localize the error message? + + public InvalidDestructuringException(String message) { + super(message); + } + + public InvalidDestructuringException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidDestructuringException(Throwable cause) { + super(cause); + } + + public static InvalidDestructuringException tooManyValues(int expected) { + return new InvalidDestructuringException(String.format( + "Invalid destructuring: too many values (expecting %d).", + expected)); + } + + public static InvalidDestructuringException notEnoughValues(int expected, int available, boolean sub) { + return new InvalidDestructuringException(String.format( + "Invalid destructuring: not enough values (expecting%s %d and got %d).", + sub ? " at least" : "", + sub ? expected - 1 : expected, + available)); + } + +} diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index a6cc97f22..9d0cffafc 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -1,68 +1,115 @@ module golotest.execution.Destructuring +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +import org.eclipse.golo.runtime + struct Point = { x, y } struct Triplet = {a, b, c} local function fail = { - throw AssertionError("Test should fail") +throw AssertionError("Test should fail") } function test_tuple_samesize = { - let a, b, c = [1, 2, 3] - require(a == 1, "err") - require(b == 2, "err") - require(c == 3, "err") +let a, b, c = [1, 2, 3] +assertThat(a, `is(1)) +assertThat(b, `is(2)) +assertThat(c, `is(3)) +} + +function test_tuple_samesize_with_sub = { +let a, b, c... = [1, 2, 3] +assertThat(a, `is(1)) +assertThat(b, `is(2)) +assertThat(c, `is([3])) } function test_tuple_var = { - var a, b, c = [1, 2, 3] - require(a == 1, "err") - require(b == 2, "err") - require(c == 3, "err") - a = 4 - b = 5 - c = 6 - require(a == 4, "err") - require(b == 5, "err") - require(c == 6, "err") +var a, b, c = [1, 2, 3] +require(a == 1, "err") +require(b == 2, "err") +require(c == 3, "err") +a = 4 +b = 5 +c = 6 +require(a == 4, "err") +require(b == 5, "err") +require(c == 6, "err") } function test_tuple_rest = { - let fst, scd, rest... = [1, 2, 3, 4, 5] - require(fst == 1, "err") - require(scd == 2, "err") - require(rest == [3, 4, 5], "err") +let a, b, c... = [1, 2, 3, 4, 5] +assertThat(a, `is(1)) +assertThat(b, `is(2)) +assertThat(c, `is([3, 4, 5])) } # ignored, old version. Should fail now. function _tuple_less_old = { - let fst, scd = [1, 2, 3, 4] - require(fst == 1, "err") - require(scd == 2, "err") +let fst, scd = [1, 2, 3, 4] +require(fst == 1, "err") +require(scd == 2, "err") } function test_tuple_less_new = { - try { - let fst, scd = [1, 2, 3, 4] - fail() - } catch(e) { - require(e: message(): contains("Non exact destructuring"), "err") - } +try { + let fst, scd = [1, 2, 3, 4] + fail() +} catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) +} +} + +function test_tuple_more_with_sub = { +let a, b, c... = [1, 2] +assertThat(a, `is(1)) +assertThat(b, `is(2)) +assertThat(c, `is([])) } +function test_tuple_more = { +try { + let a, b, c = [1, 2] + fail() +} catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) +} +} + +function test_tuple_many_more_with_sub = { +try { + let a, b, c, d... = [1, 2] + fail() +} catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) +} +} + +function test_tuple_many_more = { +try { + let a, b, c, d = [1, 2] + fail() +} catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) +} +} -function test_struct = { - let p = Point(3, 4) - let x, y = p - require(x == 3, "err") - require(y == 4, "err") - let a, b, c = Triplet(5, 6, 7) - require(a == 5, "err") - require(b == 6, "err") - require(c == 7, "err") + +function test_struct = { +let p = Point(3, 4) +let x, y = p +require(x == 3, "err") +require(y == 4, "err") + +let a, b, c = Triplet(5, 6, 7) +require(a == 5, "err") +require(b == 6, "err") +require(c == 7, "err") } function test_struct_not_exact = { @@ -70,21 +117,21 @@ function test_struct_not_exact = { let x, y = Triplet(1, 2, 3) fail() } catch(e) { - require(e: message(): contains("Non exact destructuring"), "err") + assertThat(e, isA(InvalidDestructuringException.class)) } try { let a, b, c, d = Triplet(4, 5, 6) fail() } catch(e) { - require(e: message(): contains("Non exact destructuring"), "err") + assertThat(e, isA(InvalidDestructuringException.class)) } try { let a, b, c... = Triplet(4, 5, 6) fail() } catch(e) { - require(e: message(): contains("Non exact destructuring"), "err") + assertThat(e, isA(InvalidDestructuringException.class)) } } From 4fe110bab7f3ed15d0b1aad329299582bce0fe0a Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 16 Oct 2020 23:52:40 +0200 Subject: [PATCH 05/18] Full implementation for tuple and struct --- TODO.md | 4 ++-- src/main/java/gololang/GoloStruct.java | 16 +++++++-------- src/main/java/gololang/Tuple.java | 28 +++++++++++++++++--------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/TODO.md b/TODO.md index 61a61f2df..2a42485d2 100644 --- a/TODO.md +++ b/TODO.md @@ -11,9 +11,9 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] Adds a custom exception to signal invalid destructuring - [ ] update the known `destruct()` method in the stdlib - [ ] provides a new destructuring method - - [ ] Tuple + - [x] Tuple + - [x] struct - [ ] array - - [ ] struct - [ ] union - [ ] iter/collections - [ ] Result diff --git a/src/main/java/gololang/GoloStruct.java b/src/main/java/gololang/GoloStruct.java index f3a79d650..1d4267737 100644 --- a/src/main/java/gololang/GoloStruct.java +++ b/src/main/java/gololang/GoloStruct.java @@ -11,6 +11,7 @@ package gololang; import java.util.Iterator; +import org.eclipse.golo.runtime.InvalidDestructuringException; /** * Base class for Golo structure objects. @@ -76,17 +77,14 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct) { if (number == this.members.length && !substruct) { - return Tuple.fromArray(toArray()); + return toArray(); } - // TODO: defines a specific exception? - // TODO: localize the error message? - throw new Error(String.format("Non exact destructuring: %s with %d fields destructured into %d variables%s.", - this.getClass().getName(), - this.members.length, - number, - substruct ? " with sub structure" : "")); + if (number <= this.members.length) { + throw InvalidDestructuringException.notEnoughValues(number, this.members.length, substruct); + } + throw InvalidDestructuringException.tooManyValues(number); } /** diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index a44406222..1d1e3297b 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; +import org.eclipse.golo.runtime.InvalidDestructuringException; /** * Represents an tuple object. @@ -28,6 +29,8 @@ */ public final class Tuple implements HeadTail, Comparable { + private static final Tuple EMPTY = new Tuple(); + private final Object[] data; /** @@ -181,7 +184,7 @@ public Object head() { } /** - * Returns a new tuple containg the remaining elements. + * Returns a new tuple containing the remaining elements. * * @return a tuple. */ @@ -208,20 +211,25 @@ public Tuple tail() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: defines a specific exception? - // TODO: localize the error message? - if (number == this.data.length) { - return this; + public Object[] __$$_destruct(int number, boolean substruct) { + if (number < this.data.length && !substruct) { + throw InvalidDestructuringException.notEnoughValues(number, this.data.length, substruct); + } + if (number == this.data.length && !substruct) { + return Arrays.copyOf(this.data, number); } - if (number < this.data.length && substruct) { + if (number <= this.data.length && substruct) { Object[] destruct = new Object[number]; System.arraycopy(this.data, 0, destruct, 0, number - 1); destruct[number - 1] = this.subTuple(number - 1); - return fromArray(destruct); + return destruct; } - throw new Error(String.format("Non exact destructuring: this tuple has %d values.", this.data.length)); - + if (number == this.data.length + 1 && substruct) { + Object[] destruct = Arrays.copyOf(this.data, number); + destruct[number - 1] = EMPTY; + return destruct; + } + throw InvalidDestructuringException.tooManyValues(number); } /** From da08129b79b8e4fccb8110bb9ab22b4f6094ce9f Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 00:08:08 +0200 Subject: [PATCH 06/18] Implementation for arrays --- TODO.md | 7 +- src/main/golo/gololang/macros.golo | 2 +- src/main/golo/gololang/meta/Annotations.golo | 2 +- .../org/eclipse/golo/runtime/ArrayHelper.java | 23 ++- .../golo/runtime/ArrayMethodFinder.java | 2 +- .../resources/for-execution/destruct.golo | 185 +++++++++++------- src/test/resources/for-test/macro-utils.golo | 6 +- 7 files changed, 142 insertions(+), 85 deletions(-) diff --git a/TODO.md b/TODO.md index 2a42485d2..de856f385 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,7 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [ ] provides a new destructuring method - [x] Tuple - [x] struct - - [ ] array + - [x] array - [ ] union - [ ] iter/collections - [ ] Result @@ -26,8 +26,3 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [ ] provides augmentations to adapt both ways - [ ] update the doc -## DOING - -throwing the sepecific exception in tuple and struct - -> stash implementing for array - diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo index e2956ec00..40ed151f4 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -133,7 +133,7 @@ See also [`gololang.meta.Annotations::makeDeprecated`](meta/Annotations.html#mak macro deprecated = |args...| { # TODO: generate a list of deprecated functions and types (a la javadoc) ? # TODO: when swithching to java >= 9, adds the `since` and `forRemoval` arguments to the annotation - let positional, named = parseArguments(args) + let positional, named, _ = parseArguments(args) require(positional: size() > 0, "`deprecated` macro must be applied on an element") return gololang.meta.Annotations.makeDeprecated( named: get("since")?: value(), diff --git a/src/main/golo/gololang/meta/Annotations.golo b/src/main/golo/gololang/meta/Annotations.golo index 71e39be62..5f74da7c4 100644 --- a/src/main/golo/gololang/meta/Annotations.golo +++ b/src/main/golo/gololang/meta/Annotations.golo @@ -359,7 +359,7 @@ See [`parseArguments`](../macros/Utils.html#parseArguments_1), [`getLiteralValue`](../macros/Utils.html#getLiteralValue_1) ---- function extractAnnotationArguments = |annotation, args| { - let p, n = parseArguments(args) + let p, n, _ = parseArguments(args) let namedMap, unknown = extractAnnotationNamedArguments(annotation, n) let positionnals, elements = filterPositionnalArguments(p) let fields = extractAnnotationFields(annotation) diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java index 004b03d0e..a430bcf65 100644 --- a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java +++ b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java @@ -14,6 +14,7 @@ import java.util.Objects; import gololang.Tuple; import static java.util.Arrays.copyOfRange; +import static java.util.Arrays.copyOf; public final class ArrayHelper { @@ -71,8 +72,24 @@ public static int indexOf(Object[] array, Object elt) { return -1; } - public static Tuple newStyleDestruct(Object[] array, int number, boolean sub) { - // TODO: new style destructuring on arrays - return Tuple.fromArray(array); + public static Object[] newStyleDestruct(Object[] array, int number, boolean substruct) { + if (number < array.length && !substruct) { + throw InvalidDestructuringException.notEnoughValues(number, array.length, substruct); + } + if (number == array.length && !substruct) { + return copyOf(array, number); + } + if (number <= array.length && substruct) { + Object[] destruct = new Object[number]; + System.arraycopy(array, 0, destruct, 0, number - 1); + destruct[number - 1] = copyOfRange(array, number - 1, array.length); + return destruct; + } + if (number == array.length + 1 && substruct) { + Object[] destruct = copyOf(array, number); + destruct[number - 1] = new Object[0]; + return destruct; + } + throw InvalidDestructuringException.tooManyValues(number); } } diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java index 5405a2122..eb0960a9f 100644 --- a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java +++ b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java @@ -71,7 +71,7 @@ private MethodHandle resolve() throws NoSuchMethodException, IllegalAccessExcept case "__$$_destruct": checkArity(2); return lookup.findStatic( - ArrayHelper.class, "newStyleDestruct", methodType(gololang.Tuple.class, Object[].class, int.class, boolean.class)); + ArrayHelper.class, "newStyleDestruct", methodType(Object[].class, Object[].class, int.class, boolean.class)); case "equals": checkArity(1); return lookup.findStatic(Arrays.class, "equals", diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index 9d0cffafc..e03509762 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -11,105 +11,104 @@ struct Point = { x, y } struct Triplet = {a, b, c} local function fail = { -throw AssertionError("Test should fail") + throw AssertionError("Test should fail") } function test_tuple_samesize = { -let a, b, c = [1, 2, 3] -assertThat(a, `is(1)) -assertThat(b, `is(2)) -assertThat(c, `is(3)) + let a, b, c = [1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) } function test_tuple_samesize_with_sub = { -let a, b, c... = [1, 2, 3] -assertThat(a, `is(1)) -assertThat(b, `is(2)) -assertThat(c, `is([3])) + let a, b, c... = [1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is([3])) } function test_tuple_var = { -var a, b, c = [1, 2, 3] -require(a == 1, "err") -require(b == 2, "err") -require(c == 3, "err") -a = 4 -b = 5 -c = 6 -require(a == 4, "err") -require(b == 5, "err") -require(c == 6, "err") + var a, b, c = [1, 2, 3] + require(a == 1, "err") + require(b == 2, "err") + require(c == 3, "err") + a = 4 + b = 5 + c = 6 + require(a == 4, "err") + require(b == 5, "err") + require(c == 6, "err") } function test_tuple_rest = { -let a, b, c... = [1, 2, 3, 4, 5] -assertThat(a, `is(1)) -assertThat(b, `is(2)) -assertThat(c, `is([3, 4, 5])) + let a, b, c... = [1, 2, 3, 4, 5] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is([3, 4, 5])) } # ignored, old version. Should fail now. function _tuple_less_old = { -let fst, scd = [1, 2, 3, 4] -require(fst == 1, "err") -require(scd == 2, "err") + let fst, scd = [1, 2, 3, 4] + require(fst == 1, "err") + require(scd == 2, "err") } function test_tuple_less_new = { -try { - let fst, scd = [1, 2, 3, 4] - fail() -} catch(e) { - assertThat(e, isA(InvalidDestructuringException.class)) -} + try { + let fst, scd = [1, 2, 3, 4] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } } function test_tuple_more_with_sub = { -let a, b, c... = [1, 2] -assertThat(a, `is(1)) -assertThat(b, `is(2)) -assertThat(c, `is([])) + let a, b, c... = [1, 2] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is([])) } function test_tuple_more = { -try { - let a, b, c = [1, 2] - fail() -} catch(e) { - assertThat(e, isA(InvalidDestructuringException.class)) -} + try { + let a, b, c = [1, 2] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } } function test_tuple_many_more_with_sub = { -try { - let a, b, c, d... = [1, 2] - fail() -} catch(e) { - assertThat(e, isA(InvalidDestructuringException.class)) -} + try { + let a, b, c, d... = [1, 2] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } } function test_tuple_many_more = { -try { - let a, b, c, d = [1, 2] - fail() -} catch(e) { - assertThat(e, isA(InvalidDestructuringException.class)) -} + try { + let a, b, c, d = [1, 2] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } } - function test_struct = { -let p = Point(3, 4) -let x, y = p -require(x == 3, "err") -require(y == 4, "err") - -let a, b, c = Triplet(5, 6, 7) -require(a == 5, "err") -require(b == 6, "err") -require(c == 7, "err") + let p = Point(3, 4) + let x, y = p + require(x == 3, "err") + require(y == 4, "err") + + let a, b, c = Triplet(5, 6, 7) + require(a == 5, "err") + require(b == 6, "err") + require(c == 7, "err") } function test_struct_not_exact = { @@ -145,12 +144,58 @@ function test_list = { } -# function test_array = { -# let fst, scd, rest... = array[1, 2, 3, 4, 5] -# require(fst == 1, "err") -# require(scd == 2, "err") -# require(rest == [3, 4, 5], "err") -# } +function test_array_less = { + try { + let a, b = array[1, 2, 3, 4] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_array_less_with_sub = { + let fst, scd, rest... = array[1, 2, 3, 4, 5] + assertThat(fst, `is(1)) + assertThat(scd, `is(2)) + assertThat(rest, `is(arrayContaining(3, 4, 5))) +} + +function test_array_exact = { + let a, b, c = array[1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) +} + +function test_array_exact_with_sub = { + let a, b, c... = array[1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(arrayContaining(3))) +} + +function test_array_more = { + try { + let a, b, c = array[1, 2] + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let a, b, c, d... = array[1, 2] + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_array_more_with_sub = { + let a, b, c... = array[1, 2] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(emptyArray())) +} # function test_range = { # let fst, scd, rest... = [1..6] diff --git a/src/test/resources/for-test/macro-utils.golo b/src/test/resources/for-test/macro-utils.golo index 5f351471b..317b7ad00 100644 --- a/src/test/resources/for-test/macro-utils.golo +++ b/src/test/resources/for-test/macro-utils.golo @@ -122,7 +122,7 @@ function test_parseArguments = { var l, m, r = [null, null, null] - l, m = parseArguments(args, false) + l, m, r = parseArguments(args, false) assertThat( array[c: value() foreach c in l], `is( @@ -221,9 +221,9 @@ function test_thisModule = { } function main = |args| { - # test_parseArguments() + test_parseArguments() # test_extractLastArgument() # test_namedArgsToMap() # test_toplevel() - test_thisModule() + # test_thisModule() } From ee8ce9737cefb8ffe3a4a2715558942869d60672 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 00:54:44 +0200 Subject: [PATCH 07/18] Refactor tuple to use array implementation --- src/main/java/gololang/Tuple.java | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index 1d1e3297b..953bbee5d 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -14,6 +14,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import org.eclipse.golo.runtime.InvalidDestructuringException; +import org.eclipse.golo.runtime.ArrayHelper; /** * Represents an tuple object. @@ -49,6 +50,7 @@ public Tuple(Object... values) { * @return a tuple from the array values. */ public static Tuple fromArray(Object[] values) { + if (values.length == 0) { return EMPTY; } return new Tuple(values); } @@ -212,24 +214,11 @@ public Tuple tail() { * @return a tuple containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { - if (number < this.data.length && !substruct) { - throw InvalidDestructuringException.notEnoughValues(number, this.data.length, substruct); + Object[] destruct = ArrayHelper.newStyleDestruct(this.data, number, substruct); + if (number <= this.data.length + 1 && substruct) { + destruct[number - 1] = fromArray((Object[]) destruct[number - 1]); } - if (number == this.data.length && !substruct) { - return Arrays.copyOf(this.data, number); - } - if (number <= this.data.length && substruct) { - Object[] destruct = new Object[number]; - System.arraycopy(this.data, 0, destruct, 0, number - 1); - destruct[number - 1] = this.subTuple(number - 1); - return destruct; - } - if (number == this.data.length + 1 && substruct) { - Object[] destruct = Arrays.copyOf(this.data, number); - destruct[number - 1] = EMPTY; - return destruct; - } - throw InvalidDestructuringException.tooManyValues(number); + return destruct; } /** From 0f1dee4e475899e6bdbead60e6c8bd5be860103b Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 01:23:51 +0200 Subject: [PATCH 08/18] Implementation for union --- src/main/java/gololang/Union.java | 21 ++++++++-- .../InvalidDestructuringException.java | 4 +- .../resources/for-execution/destruct.golo | 38 +++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/main/java/gololang/Union.java b/src/main/java/gololang/Union.java index 21ad7c91e..9e3846243 100644 --- a/src/main/java/gololang/Union.java +++ b/src/main/java/gololang/Union.java @@ -10,19 +10,23 @@ package gololang; +import org.eclipse.golo.runtime.InvalidDestructuringException; + /** * Base class for Golo union objects. *

* This class defines common behavior. */ public abstract class Union { + + private static final Object[] EMPTY = new Object[0]; /** * Array conversion. * * @return an array containing the values (in member orders) */ public Object[] toArray() { - return new Object[]{}; + return EMPTY; } /** @@ -44,8 +48,17 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: new style destruct - return this.destruct(); + public Object[] __$$_destruct(int number, boolean substruct) { + Object[] fields = toArray(); + if (fields.length == 0) { + throw new InvalidDestructuringException("This union has no field"); + } + if (number == fields.length && !substruct) { + return fields; + } + if (number <= fields.length) { + throw InvalidDestructuringException.notEnoughValues(number, fields.length, substruct); + } + throw InvalidDestructuringException.tooManyValues(number); } } diff --git a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java index b90b0757b..ff94c7a6c 100644 --- a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java +++ b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java @@ -27,13 +27,13 @@ public InvalidDestructuringException(Throwable cause) { public static InvalidDestructuringException tooManyValues(int expected) { return new InvalidDestructuringException(String.format( - "Invalid destructuring: too many values (expecting %d).", + "too many values (expecting %d).", expected)); } public static InvalidDestructuringException notEnoughValues(int expected, int available, boolean sub) { return new InvalidDestructuringException(String.format( - "Invalid destructuring: not enough values (expecting%s %d and got %d).", + "not enough values (expecting%s %d and got %d).", sub ? " at least" : "", sub ? expected - 1 : expected, available)); diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index e03509762..c88e8b523 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -237,6 +237,7 @@ function test_map = { union MyUnion = { Foo = { x, y } Bar = { a, b, c } + Void } function test_union = { @@ -252,6 +253,43 @@ function test_union = { require(c == "c", "err") } +function test_union_bad = { + try { + let a, b, c = MyUnion.Foo(1, 2) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let a, b, c... = MyUnion.Foo(1, 2) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let a, b, c... = MyUnion.Bar(1, 2, 3) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let a, b = MyUnion.Bar(1, 2, 3) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let a, b = MyUnion.Void() + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + function test_swap = { var a, b = [1, 2] require(a == 1, "err") From 1c8d2025c85929c1b070a87b3d7fdd5c59e5eb49 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 02:03:12 +0200 Subject: [PATCH 09/18] Implementation for several couple like objects --- TODO.md | 14 +++++++------- src/main/golo/standard-augmentations.golo | 6 ++++-- src/main/java/gololang/error/Result.java | 9 ++++++--- src/main/java/gololang/ir/WhenClause.java | 9 ++++++--- src/test/java/gololang/error/ResultTest.java | 9 +++++++++ 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index de856f385..05f4319b2 100644 --- a/TODO.md +++ b/TODO.md @@ -9,18 +9,18 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] implement the new desugarization - [ ] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member - [x] Adds a custom exception to signal invalid destructuring -- [ ] update the known `destruct()` method in the stdlib - - [ ] provides a new destructuring method +- update the known `destruct()` method in the stdlib + - provides a new destructuring method - [x] Tuple - [x] struct - [x] array - - [ ] union + - [x] union - [ ] iter/collections - - [ ] Result - - [ ] map item - - [ ] lazy lists + - [x] Result + - [x] map item + - [x] lazy lists - [ ] ranges - - [ ] When IR + - [x] When IR - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) - [ ] if applicable, use the new one in the old one - [ ] provides augmentations to adapt both ways diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 79326e15a..3acdde68d 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -901,8 +901,10 @@ augment java.util.Map$Entry { - *return* a tuple containing the values to assign. ---- function __$$_destruct = |this, number, substruct| { - # TODO: new style destruct - return this: destruct() + if (number == 2 and not substruct) { + return array[this: getKey(), this: getValue()] + } + throw org.eclipse.golo.runtime.InvalidDestructuringException("A Map.Entry must destructure to exactly two values") } ---- diff --git a/src/main/java/gololang/error/Result.java b/src/main/java/gololang/error/Result.java index ac4961bf5..82bc22d87 100644 --- a/src/main/java/gololang/error/Result.java +++ b/src/main/java/gololang/error/Result.java @@ -20,6 +20,7 @@ import java.util.function.Predicate; import gololang.Tuple; import gololang.FunctionReference; +import org.eclipse.golo.runtime.InvalidDestructuringException; /** * A container object which represent the result of a maybe failing operation. @@ -671,9 +672,11 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: new style destruct - return this.destruct(); + public Object[] __$$_destruct(int number, boolean substruct) { + if (number == 2 && !substruct) { + return new Object[]{error, value}; + } + throw new InvalidDestructuringException("A Result must destructure to exactly two values"); } } diff --git a/src/main/java/gololang/ir/WhenClause.java b/src/main/java/gololang/ir/WhenClause.java index fc16f03ec..4840187bc 100644 --- a/src/main/java/gololang/ir/WhenClause.java +++ b/src/main/java/gololang/ir/WhenClause.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.List; import gololang.Tuple; +import org.eclipse.golo.runtime.InvalidDestructuringException; public final class WhenClause> extends GoloElement> { private ExpressionStatement condition; @@ -82,9 +83,11 @@ public Tuple destruct() { return new Tuple(condition, action); } - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: new style destruct - return this.destruct(); + public Object[] __$$_destruct(int number, boolean substruct) { + if (number == 2 && !substruct) { + return new Object[]{condition, action}; + } + throw new InvalidDestructuringException("A WhenClause must destructure to exactly two values"); } } diff --git a/src/test/java/gololang/error/ResultTest.java b/src/test/java/gololang/error/ResultTest.java index b36f5ca80..449994286 100644 --- a/src/test/java/gololang/error/ResultTest.java +++ b/src/test/java/gololang/error/ResultTest.java @@ -171,6 +171,15 @@ public void destruct() { assertThat(Result.error(e).destruct(), is(new Tuple(e, null))); } + @Test + public void destruct_newstyle() { + assertThat(Result.ok(42).__$$_destruct(2, false), is(gololang.Predefined.array(null, 42))); + assertThat(Result.empty().__$$_destruct(2, false), is(gololang.Predefined.array(null, null))); + + RuntimeException e = new RuntimeException("err"); + assertThat(Result.error(e).__$$_destruct(2, false), is(gololang.Predefined.array(e, null))); + } + @Test(expectedExceptions = java.lang.NullPointerException.class) public void mapNull() { Function f = null; From 6ef08ee85deb9734775e0f9472b68bcd9e94e39c Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 02:26:27 +0200 Subject: [PATCH 10/18] Implementation for lazy list --- src/main/java/gololang/LazyList.java | 26 +++++++- .../InvalidDestructuringException.java | 6 ++ src/test/java/gololang/LazyListTest.java | 5 ++ src/test/resources/for-test/lazylist.golo | 59 +++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/main/java/gololang/LazyList.java b/src/main/java/gololang/LazyList.java index 9ff6ffe65..f7840076f 100644 --- a/src/main/java/gololang/LazyList.java +++ b/src/main/java/gololang/LazyList.java @@ -16,6 +16,8 @@ import java.util.LinkedList; import java.util.Objects; +import org.eclipse.golo.runtime.InvalidDestructuringException; + /** * Represents a lazy list object. *

@@ -252,9 +254,27 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: new style destruct - return this.destruct(); + public Object[] __$$_destruct(int number, boolean substruct) { + Object[] destruct = new Object[number]; + LazyList current = this; + for (int i = 0; i < number - 1; i++) { + if (current.isEmpty()) { + throw InvalidDestructuringException.tooManyValues(number); + } + destruct[i] = current.head(); + current = current.tail(); + } + if (substruct) { + destruct[number - 1] = current; + } else if (current.isEmpty()) { + throw InvalidDestructuringException.tooManyValues(number); + } else { + destruct[number - 1] = current.head(); + if (!current.tail().isEmpty()) { + throw InvalidDestructuringException.notEnoughValues(number, substruct); + } + } + return destruct; } /** diff --git a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java index ff94c7a6c..158f73599 100644 --- a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java +++ b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java @@ -31,6 +31,12 @@ public static InvalidDestructuringException tooManyValues(int expected) { expected)); } + public static InvalidDestructuringException notEnoughValues(int expected, boolean sub) { + return new InvalidDestructuringException(String.format( + "not enough values (expecting %d).", + sub ? expected - 1 : expected)); + } + public static InvalidDestructuringException notEnoughValues(int expected, int available, boolean sub) { return new InvalidDestructuringException(String.format( "not enough values (expecting%s %d and got %d).", diff --git a/src/test/java/gololang/LazyListTest.java b/src/test/java/gololang/LazyListTest.java index 653aca7ab..4fcd8fa69 100644 --- a/src/test/java/gololang/LazyListTest.java +++ b/src/test/java/gololang/LazyListTest.java @@ -203,4 +203,9 @@ public void drop() throws Throwable { public void dropWhile() throws Throwable { evalTest("test_dropWhile"); } + + @Test + public void destruct() throws Throwable { + resultFor("test_destruct"); + } } diff --git a/src/test/resources/for-test/lazylist.golo b/src/test/resources/for-test/lazylist.golo index 36d3661c2..4905e9e4a 100644 --- a/src/test/resources/for-test/lazylist.golo +++ b/src/test/resources/for-test/lazylist.golo @@ -12,6 +12,11 @@ module golo.test.LazyList import gololang.LazyLists +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +import org.eclipse.golo.runtime + local function longLL = -> LazyList.cons(1, -> LazyList.cons(2, -> LazyList.cons(3, -> LazyList.cons(4, -> LazyList.cons(5, -> emptyList()))))) @@ -171,6 +176,59 @@ function test_dropWhile = -> [ [list[4, 5], longL(), list[]] ] +local function fail = { + throw AssertionError("Test should fail") +} + +function test_destruct = { + let a, b... = longLL() + assertThat(a, `is(longLL(): head())) + assertThat(b, `is(longLL(): tail())) + + let x, y, z... = longLL() + assertThat(x, `is(longLL(): head())) + assertThat(y, `is(longLL(): tail(): head())) + assertThat(z, `is(longLL(): tail(): tail())) + + let c, d, e, f, g = longLL() + assertThat(c, `is(1)) + assertThat(d, `is(2)) + assertThat(e, `is(3)) + assertThat(f, `is(4)) + assertThat(g, `is(5)) + + let h, i, j, k, l... = longLL() + assertThat(h, `is(1)) + assertThat(i, `is(2)) + assertThat(j, `is(3)) + assertThat(k, `is(4)) + assertThat(l: head(), `is(5)) + + let c1, d1, e1, f1, g1, h1... = longLL() + assertThat(c1, `is(1)) + assertThat(d1, `is(2)) + assertThat(e1, `is(3)) + assertThat(f1, `is(4)) + assertThat(g1, `is(5)) + assertThat(h1, `is(empty())) + + try { + let v, w = longLL() + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + + try { + let m, n, o, p, q, r = longLL() + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } + +} + + function main = |args| { require(test_empty(), "err: test_empty") require(test_head(): get(0) == test_head(): get(1), "err: test_head") @@ -205,4 +263,5 @@ function main = |args| { require(test_takeWhile(): get(0) == test_takeWhile(): get(1), "err: test_takeWhile") require(test_drop(): get(0) == test_drop(): get(1), "err: test_drop") require(test_dropWhile(): get(0) == test_dropWhile(): get(1), "err: test_dropWhile") + test_destruct() } From 43a42a3daa5446d2f4b1a677317177059c8f8744 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Sat, 17 Oct 2020 23:32:07 +0200 Subject: [PATCH 11/18] Implementation for ranges --- TODO.md | 2 +- src/main/java/gololang/AbstractRange.java | 47 ++++++++++++---- src/main/java/gololang/BigIntegerRange.java | 7 +++ src/main/java/gololang/CharRange.java | 8 +++ src/main/java/gololang/IntRange.java | 8 +++ src/main/java/gololang/LongRange.java | 8 +++ .../resources/for-execution/destruct.golo | 56 +++++++++++++++---- 7 files changed, 114 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 05f4319b2..64b82c774 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] Result - [x] map item - [x] lazy lists - - [ ] ranges + - [x] ranges - [x] When IR - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) - [ ] if applicable, use the new one in the old one diff --git a/src/main/java/gololang/AbstractRange.java b/src/main/java/gololang/AbstractRange.java index 3bce9425e..b9ae241f4 100644 --- a/src/main/java/gololang/AbstractRange.java +++ b/src/main/java/gololang/AbstractRange.java @@ -13,6 +13,8 @@ import java.util.AbstractCollection; import java.util.Iterator; import java.util.Arrays; +import java.util.Objects; +import org.eclipse.golo.runtime.InvalidDestructuringException; import static java.util.Objects.requireNonNull; @@ -110,18 +112,14 @@ public boolean equals(Object other) { } @SuppressWarnings("rawtypes") Range otherRange = (Range) other; - return this.from().equals(otherRange.from()) - && this.to().equals(otherRange.to()) - && this.increment() == otherRange.increment(); + return this.from.equals(otherRange.from()) + && this.to.equals(otherRange.to()) + && this.increment == otherRange.increment(); } @Override public int hashCode() { - return Arrays.hashCode(new int[]{ - this.from().hashCode(), - this.to().hashCode(), - this.increment()} - ); + return Objects.hash(this.from, this.to, this.increment); } @Override @@ -157,8 +155,35 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return a tuple containing the values to assign. */ - public Tuple __$$_destruct(int number, boolean substruct) { - // TODO: new style destruct - return this.destruct(); + public Object[] __$$_destruct(int number, boolean substruct) { + if (number < size() && !substruct) { + throw InvalidDestructuringException.notEnoughValues(number, size(), substruct); + } + if (number == size() && !substruct) { + return toArray(); + } + if (number <= size() && substruct) { + Object[] d = new Object[number]; + Iterator it = this.iterator(); + for (int i = 0; i < number - 1; i++) { + d[i] = it.next(); + } + d[number - 1] = newStartingFrom(it.next()); + return d; + } + if (number == size() + 1 && substruct) { + Object[] d = Arrays.copyOf(toArray(), number); + d[number - 1] = newStartingFrom(to()); + return d; + } + throw InvalidDestructuringException.tooManyValues(number); } + + /** + * Returns a copy of this range with a new starting value. + * + *

There is no check that the {@code newStart} value is compatible with the current start and increment. It is + * therefore possible that the new range yields different values than the original. + */ + public abstract Range newStartingFrom(T newStart); } diff --git a/src/main/java/gololang/BigIntegerRange.java b/src/main/java/gololang/BigIntegerRange.java index b959b320b..d1cffb0ea 100644 --- a/src/main/java/gololang/BigIntegerRange.java +++ b/src/main/java/gololang/BigIntegerRange.java @@ -104,5 +104,12 @@ public BigInteger next() { }; } + /** + * {@inheritDoc} + */ + @Override + public Range newStartingFrom(BigInteger newStart) { + return new BigIntegerRange(newStart, this.to()).incrementBy(this.increment()); + } } diff --git a/src/main/java/gololang/CharRange.java b/src/main/java/gololang/CharRange.java index d1620ee44..375f8b456 100644 --- a/src/main/java/gololang/CharRange.java +++ b/src/main/java/gololang/CharRange.java @@ -95,4 +95,12 @@ public Character next() { } }; } + + /** + * {@inheritDoc} + */ + @Override + public Range newStartingFrom(Character newStart) { + return new CharRange(newStart, this.to()).incrementBy(this.increment()); + } } diff --git a/src/main/java/gololang/IntRange.java b/src/main/java/gololang/IntRange.java index 93f65dbc7..cd689afb7 100644 --- a/src/main/java/gololang/IntRange.java +++ b/src/main/java/gololang/IntRange.java @@ -97,4 +97,12 @@ public Integer next() { } }; } + + /** + * {@inheritDoc} + */ + @Override + public Range newStartingFrom(Integer newStart) { + return new IntRange(newStart, this.to()).incrementBy(this.increment()); + } } diff --git a/src/main/java/gololang/LongRange.java b/src/main/java/gololang/LongRange.java index b1fa3db9a..3a9d7a6eb 100644 --- a/src/main/java/gololang/LongRange.java +++ b/src/main/java/gololang/LongRange.java @@ -97,4 +97,12 @@ public Long next() { } }; } + + /** + * {@inheritDoc} + */ + @Override + public Range newStartingFrom(Long newStart) { + return new LongRange(newStart, this.to()).incrementBy(this.increment()); + } } diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index c88e8b523..9ecd7fd41 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -197,16 +197,52 @@ function test_array_more_with_sub = { assertThat(c, `is(emptyArray())) } -# function test_range = { -# let fst, scd, rest... = [1..6] -# require(fst == 1, "err") -# require(scd == 2, "err") -# require(rest == [3, 4, 5], "err") -# -# let a, b = [1..4] -# require(a == 1, "err") -# require(b == 2, "err") -# } +function test_range_1 = { + let f, s, r... = [1..6] + assertThat(f, `is(1)) + assertThat(s, `is(2)) + assertThat(r, `is([3..6])) +} + +function test_range_2 = { + let x, y, z = [0..3] + assertThat(x, `is(0)) + assertThat(y, `is(1)) + assertThat(z, `is(2)) +} + +function test_range_3 = { + let c, d, e... = [0..3] + assertThat(c, `is(0)) + assertThat(d, `is(1)) + assertThat(e, `is([2..3])) +} + +function test_range_4 = { + let g, h, i, j... = [0..3] + assertThat(g, `is(0)) + assertThat(h, `is(1)) + assertThat(i, `is(2)) + assertThat(j, `is(emptyIterable())) +} + +function test_range_5 = { + try { + let a, b = [1..4] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_range_6 = { + try { + let k, l, m, n = [0..3] + fail() + } catch(e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} function test_foreach = { let l = list[ [1, 2, 3], [3, 4, 5] ] From 02127a3f06e1a1e7358d2a56703fccd8c778a0ae Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Mon, 19 Oct 2020 01:03:33 +0200 Subject: [PATCH 12/18] Implementation for collections and iterable/iterator augmentations --- TODO.md | 2 +- src/main/golo/standard-augmentations.golo | 75 ++++++- .../resources/for-execution/destruct.golo | 184 +++++++++++++++++- 3 files changed, 244 insertions(+), 17 deletions(-) diff --git a/TODO.md b/TODO.md index 64b82c774..1d6aac173 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,7 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] struct - [x] array - [x] union - - [ ] iter/collections + - [x] iter/collections - [x] Result - [x] map item - [x] lazy lists diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 3acdde68d..90220ed40 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -289,6 +289,18 @@ augment java.lang.Iterable { } return false } + + ---- + New style destructuring helper + ---- + function __$$_destruct = |this, number, substruct| { + let it = this: iterator() + let d = destructIterator(it, number, substruct) + if substruct { + d: set(number - 1, asInterfaceInstance(java.lang.Iterable.class, -> it)) + } + return d + } } # ............................................................................................... # @@ -321,21 +333,34 @@ augment java.util.Collection { - *return* a tuple containing the values to assign. ---- function __$$_destruct = |this, number, substruct| { - # TODO: new style destruct - if substruct { - let a = newTypedArray(Object.class, number) - let r = _newWithSameType(this) - a: set(number - 1, r) + if number < this: size() and not substruct { + throw org.eclipse.golo.runtime.InvalidDestructuringException.notEnoughValues(number, this: size(), substruct) + } + if number == this: size() and not substruct { + return this: toArray() + } + let d = newTypedArray(Object.class, number) + let col = _newWithSameType(this) + d: set(number - 1, col) + if number <= this: size() and substruct { let it = this: iterator() for (var i = 0, i < number - 1, i = i + 1) { - a: set(i, it: next()) + d: set(i, it: next()) } - while (it: hasNext()) { - r: add(it: next()) + while it: hasNext() { + col: add(it: next()) } - return Tuple.fromArray(a) + return d } - return this: destruct() + if number == this: size() + 1 and substruct { + var i = 0 + foreach v in this { + d: set(i, v) + i = i + 1 + } + return d + } + throw org.eclipse.golo.runtime.InvalidDestructuringException.tooManyValues(number) } ---- @@ -997,3 +1022,33 @@ augment java.io.BufferedReader { ---- function iterator = |this| -> gololang.IO$LinesIterator.of(this) } + +local function destructIterator = |it, number, substruct| { + let d = newTypedArray(Object.class, number) + if substruct { + d: set(number - 1, it) + } + let nbValues = match { + when substruct then number - 1 + otherwise number + } + try { + for (var i = 0, i < nbValues, i = i + 1) { + d: set(i, it: next()) + } + } catch (e) { + if e oftype java.util.NoSuchElementException.class { + throw org.eclipse.golo.runtime.InvalidDestructuringException.notEnoughValues(number, substruct) + } + throw e + } + if (it: hasNext() and not substruct) { + throw org.eclipse.golo.runtime.InvalidDestructuringException.tooManyValues(number) + } + return d +} + + +augment java.util.Iterator { + function __$$_destruct = |this, number, substruct| -> destructIterator(this, number, substruct) +} diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index 9ecd7fd41..045499fbb 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -134,16 +134,185 @@ function test_struct_not_exact = { } } -function test_list = { - let l = list[1, 2, 3, 4, 5] +function test_list_1 = { + let a, b, c... = list[1, 2, 3, 4, 5] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(list[3, 4, 5])) +} - let fst, scd, rest... = l - require(fst == 1, "err") - require(scd == 2, "err") - require(rest == list[3, 4, 5], "err") +function test_list_2 = { + let a, b, c... = list[1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(list[3])) +} + +function test_list_3 = { + let a, b, c = list[1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) +} + +function test_list_4 = { + let a, b, c, d... = list[1, 2, 3] + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) + assertThat(d, `is(list[])) +} +function test_list_5 = { + try { + let a, b = list[1, 2, 3] + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_list_6 = { + try { + let a, b, c = list[1, 2] + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } } +function test_list_7 = { + try { + let a, b, c, d... = list[1, 2] + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iter_1 = { + let a, b, c... = list[1, 2, 3, 4, 5]: iterator() + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, isA(java.util.Iterator.class)) + assertThat(c: next(), `is(3)) + assertThat(c: next(), `is(4)) + assertThat(c: next(), `is(5)) + assertThat(c: hasNext(), `is(false)) +} + +function test_iter_2 = { + let a, b, c... = list[1, 2, 3]: iterator() + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, isA(java.util.Iterator.class)) + assertThat(c: next(), `is(3)) + assertThat(c: hasNext(), `is(false)) +} + +function test_iter_3 = { + let a, b, c = list[1, 2, 3]: iterator() + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) +} + +function test_iter_4 = { + let a, b, c, d... = list[1, 2, 3]: iterator() + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) + assertThat(d, isA(java.util.Iterator.class)) + assertThat(d: hasNext(), `is(false)) +} + +function test_iter_5 = { + try { + let a, b = list[1, 2, 3]: iterator() + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iter_6 = { + try { + let a, b, c = list[1, 2]: iterator() + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iter_7 = { + try { + let a, b, c, d... = list[1, 2]:iterator() + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iterable_1 = { + let a, b, c... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3, 4, 5]: iterator()) + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, isA(java.lang.Iterable.class)) + assertThat(c, contains(3, 4, 5)) +} + +function test_iterable_2 = { + let a, b, c... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3]: iterator()) + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, isA(java.lang.Iterable.class)) + assertThat(c, contains(3)) +} + +function test_iterable_3 = { + let a, b, c = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3]: iterator()) + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) +} + +function test_iterable_4 = { + let a, b, c, d... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3]: iterator()) + assertThat(a, `is(1)) + assertThat(b, `is(2)) + assertThat(c, `is(3)) + assertThat(d, isA(java.lang.Iterable.class)) + assertThat(d, `is(emptyIterable())) +} + +function test_iterable_5 = { + try { + let a, b = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3]: iterator()) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iterable_6 = { + try { + let a, b, c = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2]: iterator()) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + +function test_iterable_7 = { + try { + let a, b, c, d... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2]:iterator()) + fail() + } catch (e) { + assertThat(e, isA(InvalidDestructuringException.class)) + } +} + + function test_array_less = { try { let a, b = array[1, 2, 3, 4] @@ -351,3 +520,6 @@ function test_destruct_affectation_inside_closure = { require(closure() == 1, "err") } +function main = |args| { + test_iterable_1() +} From 2965890696dd8bd763ec04be0f6bae9312891b4b Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Mon, 19 Oct 2020 01:30:16 +0200 Subject: [PATCH 13/18] Mark old style methode deprecated (and fix doc) --- TODO.md | 9 ++++++--- src/main/golo/standard-augmentations.golo | 18 ++++++++++++++---- src/main/java/gololang/AbstractRange.java | 7 ++++++- src/main/java/gololang/GoloStruct.java | 3 ++- src/main/java/gololang/LazyList.java | 4 +++- src/main/java/gololang/Tuple.java | 3 ++- src/main/java/gololang/Union.java | 4 +++- src/main/java/gololang/error/Result.java | 15 +++++++++++++-- src/main/java/gololang/ir/WhenClause.java | 15 +++++++++++++++ 9 files changed, 64 insertions(+), 14 deletions(-) diff --git a/TODO.md b/TODO.md index 1d6aac173..c2dc3f9ad 100644 --- a/TODO.md +++ b/TODO.md @@ -21,8 +21,11 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] lazy lists - [x] ranges - [x] When IR - - [ ] mark the old one deprecated (may depend on PR #551 to annotated golo code) - - [ ] if applicable, use the new one in the old one -- [ ] provides augmentations to adapt both ways + - [x] mark the old one deprecated (may depend on PR #551 to annotate golo code) + - [ ] use the real deprecated macro when #551 is merged +- [ ] provides augmentations to adapt both ways ? - [ ] update the doc + - [ ] describe new style destruct + - [ ] rationale + - [ ] document feature flag diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 90220ed40..2005ad555 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -318,9 +318,14 @@ augment java.util.Collection { ---- Destructuration helper. - * return a tuple of the values + - returns a tuple of the values + + *Deprecated since 3.4. This method should not be called directly and is no more used by new style destructuring. ---- - function destruct = |this| -> Tuple.fromArray(this: toArray()) + function destruct = |this| { + org.eclipse.golo.runtime.Warnings.deprecatedElement("destruct", "gololang.StandardAugmentations.Collection") + return Tuple.fromArray(this: toArray()) + } ---- New style destructuring helper. @@ -330,7 +335,7 @@ augment java.util.Collection { - *param* `number`: number of variable that will be affected. - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. - - *return* a tuple containing the values to assign. + - *return* an array containing the values to assign. ---- function __$$_destruct = |this, number, substruct| { if number < this: size() and not substruct { @@ -912,8 +917,13 @@ augment java.util.Map { augment java.util.Map$Entry { ---- Destructurate a map entry in key and value + + *Deprecated since 3.4. This method should not be called directly and is no more used by new style destructuring. ---- - function destruct = |this| -> [ this: getKey(), this: getValue() ] + function destruct = |this| { + org.eclipse.golo.runtime.Warnings.deprecatedElement("destruct", "gololang.StandardAugmentations.Map.Entry") + return [ this: getKey(), this: getValue() ] + } ---- New style destructuring helper. diff --git a/src/main/java/gololang/AbstractRange.java b/src/main/java/gololang/AbstractRange.java index b9ae241f4..cde3b7fdd 100644 --- a/src/main/java/gololang/AbstractRange.java +++ b/src/main/java/gololang/AbstractRange.java @@ -135,6 +135,11 @@ public boolean isEmpty() { return from.compareTo(to) >= 0; } + /** + * Destructuring helper. + * @deprecated This method should not be called directly and is no more used by new style destructuring. + */ + @Deprecated public Tuple destruct() { Object[] data = new Object[this.size()]; int i = 0; @@ -153,7 +158,7 @@ public Tuple destruct() { * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { if (number < size() && !substruct) { diff --git a/src/main/java/gololang/GoloStruct.java b/src/main/java/gololang/GoloStruct.java index 1d4267737..5e6a23246 100644 --- a/src/main/java/gololang/GoloStruct.java +++ b/src/main/java/gololang/GoloStruct.java @@ -63,6 +63,7 @@ public Tuple values() { * @return a tuple with the current values. * @deprecated This method should not be called directly and is no more used by new style destructuring. */ + @Deprecated public Tuple destruct() { return Tuple.fromArray(toArray()); } @@ -75,7 +76,7 @@ public Tuple destruct() { * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { if (number == this.members.length && !substruct) { diff --git a/src/main/java/gololang/LazyList.java b/src/main/java/gololang/LazyList.java index f7840076f..a31082bfd 100644 --- a/src/main/java/gololang/LazyList.java +++ b/src/main/java/gololang/LazyList.java @@ -239,7 +239,9 @@ public T[] toArray(T[] a) { * Destructuration helper. * * @return a tuple of head and tail + * @deprecated This method should not be called directly and is no more used by new style destructuring. */ + @Deprecated public Tuple destruct() { return new Tuple(head(), tail()); } @@ -252,7 +254,7 @@ public Tuple destruct() { * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { Object[] destruct = new Object[number]; diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index 953bbee5d..4689060ca 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -201,6 +201,7 @@ public Tuple tail() { * @return the tuple itself * @deprecated This method should not be called directly and is no more used by new style destructuring. */ + @Deprecated public Tuple destruct() { return this; } /** @@ -211,7 +212,7 @@ public Tuple tail() { * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { Object[] destruct = ArrayHelper.newStyleDestruct(this.data, number, substruct); diff --git a/src/main/java/gololang/Union.java b/src/main/java/gololang/Union.java index 9e3846243..9f837af3c 100644 --- a/src/main/java/gololang/Union.java +++ b/src/main/java/gololang/Union.java @@ -33,7 +33,9 @@ public Object[] toArray() { * Destructuration helper. * * @return a tuple with the current values. + * @deprecated This method should not be called directly and is no more used by new style destructuring. */ + @Deprecated public Tuple destruct() { return Tuple.fromArray(toArray()); } @@ -46,7 +48,7 @@ public Tuple destruct() { * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { Object[] fields = toArray(); diff --git a/src/main/java/gololang/error/Result.java b/src/main/java/gololang/error/Result.java index 82bc22d87..6f47f2fa9 100644 --- a/src/main/java/gololang/error/Result.java +++ b/src/main/java/gololang/error/Result.java @@ -657,7 +657,9 @@ public String toString() { * This allows to deal with error in the same way as Go does for instance. * * @return a 2-tuple containing the error and the value contained by this {@code Result} + * @deprecated This method should not be called directly and is no more used by new style destructuring. */ + @Deprecated public Tuple destruct() { return new Tuple(error, value); } @@ -665,12 +667,21 @@ public Tuple destruct() { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + *

Returns a 2-tuple containing the error and the value contained by this {@code Result}, so + * that it can be used in a destructuring golo assignment. The first value is the error, and the + * second is the correct value (mnemonic: “right” also means “correct”). For instance: + *


+   * let e, v = Result.ok(42)        # e is null and v is 42
+   * let e, v = Result.empty()       # e is null and v is null
+   * let e, v = Result.fail("error") # e is RuntimeException("error") and v is null
+   * 
+ *

This allows to deal with error in the same way as Go does for instance. + *

New style destructuring must be exact. The number of variables to be affected is thus checked against the number of * members of the structure. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return a tuple containing the values to assign. + * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct) { if (number == 2 && !substruct) { diff --git a/src/main/java/gololang/ir/WhenClause.java b/src/main/java/gololang/ir/WhenClause.java index 4840187bc..2c975c335 100644 --- a/src/main/java/gololang/ir/WhenClause.java +++ b/src/main/java/gololang/ir/WhenClause.java @@ -79,10 +79,25 @@ public List> children() { return Arrays.asList(condition, action); } + /** + * Destructuring helper. + * @deprecated This method should not be called directly and is no more used by new style destructuring. + */ + @Deprecated public Tuple destruct() { return new Tuple(condition, action); } + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return an array containing the values to assign. + */ public Object[] __$$_destruct(int number, boolean substruct) { if (number == 2 && !substruct) { return new Object[]{condition, action}; From 3c1be40f9466d2531d580f108c79e443db28f4d6 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Mon, 19 Oct 2020 01:49:38 +0200 Subject: [PATCH 14/18] Add skip special variable --- src/main/golo/standard-augmentations.golo | 45 ++++--- src/main/java/gololang/AbstractRange.java | 20 ++- src/main/java/gololang/GoloStruct.java | 2 +- src/main/java/gololang/LazyList.java | 15 ++- src/main/java/gololang/Tuple.java | 6 +- src/main/java/gololang/Union.java | 2 +- src/main/java/gololang/error/Result.java | 2 +- src/main/java/gololang/ir/WhenClause.java | 2 +- .../golo/compiler/SugarExpansionVisitor.java | 32 +++-- .../org/eclipse/golo/runtime/ArrayHelper.java | 28 +++- .../golo/runtime/ArrayMethodFinder.java | 4 +- .../InvalidDestructuringException.java | 3 +- src/test/java/gololang/LazyListTest.java | 1 + src/test/java/gololang/error/ResultTest.java | 6 +- .../resources/for-execution/destruct.golo | 123 +++++++++++++++++- src/test/resources/for-test/lazylist.golo | 15 +++ 16 files changed, 242 insertions(+), 64 deletions(-) diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 2005ad555..2102fcf01 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -293,10 +293,10 @@ augment java.lang.Iterable { ---- New style destructuring helper ---- - function __$$_destruct = |this, number, substruct| { + function __$$_destruct = |this, number, substruct, toSkip| { let it = this: iterator() - let d = destructIterator(it, number, substruct) - if substruct { + let d = destructIterator(it, number, substruct, toSkip) + if substruct and not toSkip: get(number - 1) { d: set(number - 1, asInterfaceInstance(java.lang.Iterable.class, -> it)) } return d @@ -337,30 +337,41 @@ augment java.util.Collection { - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. - *return* an array containing the values to assign. ---- - function __$$_destruct = |this, number, substruct| { + function __$$_destruct = |this, number, substruct, toSkip| { if number < this: size() and not substruct { throw org.eclipse.golo.runtime.InvalidDestructuringException.notEnoughValues(number, this: size(), substruct) } if number == this: size() and not substruct { - return this: toArray() + return org.eclipse.golo.runtime.ArrayHelper.nullify(this: toArray(), toSkip) } let d = newTypedArray(Object.class, number) - let col = _newWithSameType(this) + let col = match { + when toSkip: get(number - 1) then null + otherwise _newWithSameType(this) + } d: set(number - 1, col) if number <= this: size() and substruct { let it = this: iterator() for (var i = 0, i < number - 1, i = i + 1) { - d: set(i, it: next()) + if not toSkip: get(i) { + d: set(i, it: next()) + } else { + it: next() + } } - while it: hasNext() { - col: add(it: next()) + if col isnt null { + while it: hasNext() { + col: add(it: next()) + } } return d } if number == this: size() + 1 and substruct { var i = 0 foreach v in this { - d: set(i, v) + if not toSkip: get(i) { + d: set(i, v) + } i = i + 1 } return d @@ -935,7 +946,7 @@ augment java.util.Map$Entry { - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. - *return* a tuple containing the values to assign. ---- - function __$$_destruct = |this, number, substruct| { + function __$$_destruct = |this, number, substruct, toSkip| { if (number == 2 and not substruct) { return array[this: getKey(), this: getValue()] } @@ -1033,9 +1044,9 @@ augment java.io.BufferedReader { function iterator = |this| -> gololang.IO$LinesIterator.of(this) } -local function destructIterator = |it, number, substruct| { +local function destructIterator = |it, number, substruct, toSkip| { let d = newTypedArray(Object.class, number) - if substruct { + if substruct and not toSkip: get(number - 1) { d: set(number - 1, it) } let nbValues = match { @@ -1044,7 +1055,11 @@ local function destructIterator = |it, number, substruct| { } try { for (var i = 0, i < nbValues, i = i + 1) { - d: set(i, it: next()) + if toSkip: get(i) { + it: next() + } else { + d: set(i, it: next()) + } } } catch (e) { if e oftype java.util.NoSuchElementException.class { @@ -1060,5 +1075,5 @@ local function destructIterator = |it, number, substruct| { augment java.util.Iterator { - function __$$_destruct = |this, number, substruct| -> destructIterator(this, number, substruct) + function __$$_destruct = |this, number, substruct, toSkip| -> destructIterator(this, number, substruct, toSkip) } diff --git a/src/main/java/gololang/AbstractRange.java b/src/main/java/gololang/AbstractRange.java index cde3b7fdd..b1323d53d 100644 --- a/src/main/java/gololang/AbstractRange.java +++ b/src/main/java/gololang/AbstractRange.java @@ -160,26 +160,34 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number < size() && !substruct) { throw InvalidDestructuringException.notEnoughValues(number, size(), substruct); } if (number == size() && !substruct) { - return toArray(); + return org.eclipse.golo.runtime.ArrayHelper.nullify(toArray(), toSkip); } if (number <= size() && substruct) { Object[] d = new Object[number]; Iterator it = this.iterator(); for (int i = 0; i < number - 1; i++) { - d[i] = it.next(); + if (Boolean.valueOf(true).equals(toSkip[i])) { + it.next(); + } else { + d[i] = it.next(); + } + } + if (Boolean.valueOf(false).equals(toSkip[number - 1])) { + d[number - 1] = newStartingFrom(it.next()); } - d[number - 1] = newStartingFrom(it.next()); return d; } if (number == size() + 1 && substruct) { Object[] d = Arrays.copyOf(toArray(), number); - d[number - 1] = newStartingFrom(to()); - return d; + if (Boolean.valueOf(false).equals(toSkip[number - 1])) { + d[number - 1] = newStartingFrom(to()); + } + return org.eclipse.golo.runtime.ArrayHelper.nullify(d, toSkip); } throw InvalidDestructuringException.tooManyValues(number); } diff --git a/src/main/java/gololang/GoloStruct.java b/src/main/java/gololang/GoloStruct.java index 5e6a23246..54a8f00d4 100644 --- a/src/main/java/gololang/GoloStruct.java +++ b/src/main/java/gololang/GoloStruct.java @@ -78,7 +78,7 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number == this.members.length && !substruct) { return toArray(); } diff --git a/src/main/java/gololang/LazyList.java b/src/main/java/gololang/LazyList.java index a31082bfd..5898822e8 100644 --- a/src/main/java/gololang/LazyList.java +++ b/src/main/java/gololang/LazyList.java @@ -256,25 +256,26 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { Object[] destruct = new Object[number]; LazyList current = this; for (int i = 0; i < number - 1; i++) { if (current.isEmpty()) { throw InvalidDestructuringException.tooManyValues(number); } - destruct[i] = current.head(); + if (Boolean.valueOf(false).equals(toSkip[i])) { + destruct[i] = current.head(); + } current = current.tail(); } - if (substruct) { + if (substruct && Boolean.valueOf(false).equals(toSkip[number - 1])) { destruct[number - 1] = current; } else if (current.isEmpty()) { throw InvalidDestructuringException.tooManyValues(number); - } else { + } else if (!current.tail().isEmpty() && Boolean.valueOf(false).equals(toSkip[number - 1])) { + throw InvalidDestructuringException.notEnoughValues(number, substruct); + } else if (Boolean.valueOf(false).equals(toSkip[number - 1])) { destruct[number - 1] = current.head(); - if (!current.tail().isEmpty()) { - throw InvalidDestructuringException.notEnoughValues(number, substruct); - } } return destruct; } diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index 4689060ca..9495d5dba 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -214,9 +214,9 @@ public Tuple tail() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { - Object[] destruct = ArrayHelper.newStyleDestruct(this.data, number, substruct); - if (number <= this.data.length + 1 && substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { + Object[] destruct = ArrayHelper.newStyleDestruct(this.data, number, substruct, toSkip); + if (number <= this.data.length + 1 && substruct && destruct[number - 1] != null) { destruct[number - 1] = fromArray((Object[]) destruct[number - 1]); } return destruct; diff --git a/src/main/java/gololang/Union.java b/src/main/java/gololang/Union.java index 9f837af3c..5dd18b6d6 100644 --- a/src/main/java/gololang/Union.java +++ b/src/main/java/gololang/Union.java @@ -50,7 +50,7 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { Object[] fields = toArray(); if (fields.length == 0) { throw new InvalidDestructuringException("This union has no field"); diff --git a/src/main/java/gololang/error/Result.java b/src/main/java/gololang/error/Result.java index 6f47f2fa9..f983a6701 100644 --- a/src/main/java/gololang/error/Result.java +++ b/src/main/java/gololang/error/Result.java @@ -683,7 +683,7 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number == 2 && !substruct) { return new Object[]{error, value}; } diff --git a/src/main/java/gololang/ir/WhenClause.java b/src/main/java/gololang/ir/WhenClause.java index 2c975c335..9042711ae 100644 --- a/src/main/java/gololang/ir/WhenClause.java +++ b/src/main/java/gololang/ir/WhenClause.java @@ -98,7 +98,7 @@ public Tuple destruct() { * @param substruct whether the destructuring is complete or should contains a sub structure. * @return an array containing the values to assign. */ - public Object[] __$$_destruct(int number, boolean substruct) { + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number == 2 && !substruct) { return new Object[]{condition, action}; } diff --git a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java index e9fed32b4..bd97a8459 100644 --- a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java +++ b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java @@ -429,7 +429,7 @@ private Block oldDestructuring(DestructuringAssignment assignment) { * * into something equivalent to *


-   * let tmp = expr: __$$_destruct(3, true)
+   * let tmp = expr: __$$_destruct(3, true, array[false, false, false])
    * let a = tmp: get(0)
    * let b = tmp: get(1)
    * let c = tmp: get(2)
@@ -437,22 +437,30 @@ private Block oldDestructuring(DestructuringAssignment assignment) {
    */
   private Block newDestructuring(DestructuringAssignment assignment) {
     LocalReference tmpRef = LocalReference.of(symbols.next("destruct")).synthetic();
-    Block block = Block.of(AssignmentStatement.create(tmpRef,
+    Block block = Block.empty();
+    Object[] toSkip = new Object[assignment.getReferencesCount()];
+    int idx = 0;
+    for (LocalReference ref : assignment.getReferences()) {
+      if ("_".equals(ref.getName())) {
+        toSkip[idx] = ConstantStatement.of(true);
+      } else {
+        toSkip[idx] = ConstantStatement.of(false);
+        block.add(
+            AssignmentStatement.create(
+              ref,
+              invoke("get").withArgs(ConstantStatement.of(idx)).on(tmpRef.lookup()),
+              assignment.isDeclaring()));
+      }
+      idx++;
+    }
+    block.prepend(AssignmentStatement.create(tmpRef,
           invoke("__$$_destruct")
           .withArgs(
             ConstantStatement.of(assignment.getReferencesCount()),
-            ConstantStatement.of(assignment.isVarargs()))
+            ConstantStatement.of(assignment.isVarargs()),
+            CollectionLiteral.create(CollectionLiteral.Type.array, toSkip))
           .on(assignment.expression()),
         true));
-    int idx = 0;
-    for (LocalReference ref : assignment.getReferences()) {
-      block.add(
-          AssignmentStatement.create(
-            ref,
-            invoke("get").withArgs(ConstantStatement.of(idx)).on(tmpRef.lookup()),
-            assignment.isDeclaring()));
-      idx++;
-    }
     return block;
   }
 
diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java
index a430bcf65..ffc29cc4d 100644
--- a/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java
+++ b/src/main/java/org/eclipse/golo/runtime/ArrayHelper.java
@@ -72,23 +72,39 @@ public static int indexOf(Object[] array, Object elt) {
     return -1;
   }
 
-  public static Object[] newStyleDestruct(Object[] array, int number, boolean substruct) {
+  public static Object[] nullify(Object[] array, Object[] toSkip) {
+    if (array.length != toSkip.length) {
+      throw new IllegalArgumentException("Both array must have the same length");
+    }
+    for (int i = 0; i < array.length; i++) {
+      if (Boolean.valueOf(true).equals(toSkip[i])) { // cast + check for null all in one
+        array[i] = null;
+      }
+    }
+    return array;
+  }
+
+  public static Object[] newStyleDestruct(Object[] array, int number, boolean substruct, Object[] toSkip) {
     if (number < array.length && !substruct) {
       throw InvalidDestructuringException.notEnoughValues(number, array.length, substruct);
     }
     if (number == array.length && !substruct) {
-      return copyOf(array, number);
+      return nullify(copyOf(array, number), toSkip);
     }
     if (number <= array.length && substruct) {
       Object[] destruct = new Object[number];
       System.arraycopy(array, 0, destruct, 0, number - 1);
-      destruct[number - 1] = copyOfRange(array, number - 1, array.length);
-      return destruct;
+      if (Boolean.valueOf(false).equals(toSkip[number - 1])) {
+        destruct[number - 1] = copyOfRange(array, number - 1, array.length);
+      }
+      return nullify(destruct, toSkip);
     }
     if (number == array.length + 1 && substruct) {
       Object[] destruct = copyOf(array, number);
-      destruct[number - 1] = new Object[0];
-      return destruct;
+      if (Boolean.valueOf(false).equals(toSkip[number - 1])) {
+        destruct[number - 1] = new Object[0];
+      }
+      return nullify(destruct, toSkip);
     }
     throw InvalidDestructuringException.tooManyValues(number);
   }
diff --git a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java
index eb0960a9f..44421d6d5 100644
--- a/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java
+++ b/src/main/java/org/eclipse/golo/runtime/ArrayMethodFinder.java
@@ -69,9 +69,9 @@ private MethodHandle resolve() throws NoSuchMethodException, IllegalAccessExcept
         checkArity(0);
         return lookup.findStatic(gololang.Tuple.class, "fromArray", methodType(gololang.Tuple.class, Object[].class));
       case "__$$_destruct":
-        checkArity(2);
+        checkArity(3);
         return lookup.findStatic(
-            ArrayHelper.class, "newStyleDestruct", methodType(Object[].class, Object[].class, int.class, boolean.class));
+            ArrayHelper.class, "newStyleDestruct", methodType(Object[].class, Object[].class, int.class, boolean.class, Object[].class));
       case "equals":
         checkArity(1);
         return lookup.findStatic(Arrays.class, "equals",
diff --git a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java
index 158f73599..297fce562 100644
--- a/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java
+++ b/src/main/java/org/eclipse/golo/runtime/InvalidDestructuringException.java
@@ -33,7 +33,8 @@ public static InvalidDestructuringException tooManyValues(int expected) {
 
   public static InvalidDestructuringException notEnoughValues(int expected, boolean sub) {
     return new InvalidDestructuringException(String.format(
-          "not enough values (expecting %d).",
+          "not enough values (expecting%s %d).",
+            sub ? " at least" : "",
             sub ? expected - 1 : expected));
   }
 
diff --git a/src/test/java/gololang/LazyListTest.java b/src/test/java/gololang/LazyListTest.java
index 4fcd8fa69..cd9ee3968 100644
--- a/src/test/java/gololang/LazyListTest.java
+++ b/src/test/java/gololang/LazyListTest.java
@@ -207,5 +207,6 @@ public void dropWhile() throws Throwable {
   @Test
   public void destruct() throws Throwable {
     resultFor("test_destruct");
+    resultFor("test_destruct_skip");
   }
 }
diff --git a/src/test/java/gololang/error/ResultTest.java b/src/test/java/gololang/error/ResultTest.java
index 449994286..d046c4b1c 100644
--- a/src/test/java/gololang/error/ResultTest.java
+++ b/src/test/java/gololang/error/ResultTest.java
@@ -173,11 +173,11 @@ public void destruct() {
 
   @Test
   public void destruct_newstyle() {
-    assertThat(Result.ok(42).__$$_destruct(2, false), is(gololang.Predefined.array(null, 42)));
-    assertThat(Result.empty().__$$_destruct(2, false), is(gololang.Predefined.array(null, null)));
+    assertThat(Result.ok(42).__$$_destruct(2, false, null), is(gololang.Predefined.array(null, 42)));
+    assertThat(Result.empty().__$$_destruct(2, false, null), is(gololang.Predefined.array(null, null)));
 
     RuntimeException e = new RuntimeException("err");
-    assertThat(Result.error(e).__$$_destruct(2, false), is(gololang.Predefined.array(e, null)));
+    assertThat(Result.error(e).__$$_destruct(2, false, null), is(gololang.Predefined.array(e, null)));
   }
 
   @Test(expectedExceptions = java.lang.NullPointerException.class)
diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo
index 045499fbb..3490dc357 100644
--- a/src/test/resources/for-execution/destruct.golo
+++ b/src/test/resources/for-execution/destruct.golo
@@ -98,6 +98,23 @@ function test_tuple_many_more = {
   }
 }
 
+function test_tuple_skip_1 = {
+  let a, _, _, b = tuple[1, 2, 3, 4]
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
+function test_tuple_skip_2 = {
+  let a, _, _, c... = tuple[1, 2, 3, 4, 5]
+  assertThat(a, `is(1))
+  assertThat(c, `is(tuple[4, 5]))
+}
+
+function test_tuple_skip_3 = {
+  let _, b, _... = tuple[1, 2, 3, 4, 5]
+  assertThat(b, `is(2))
+}
+
 
 function test_struct = {
   let p = Point(3, 4)
@@ -190,6 +207,24 @@ function test_list_7 = {
   }
 }
 
+function test_list_skip_1 = {
+  let a, _, _, b = list[1, 2, 3, 4]
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
+function test_list_skip_2 = {
+  let a, _, _, c... = list[1, 2, 3, 4, 5]
+  assertThat(a, `is(1))
+  assertThat(c, `is(list[4, 5]))
+}
+
+function test_list_skip_3 = {
+  let _, b, _... = list[1, 2, 3, 4, 5]
+  assertThat(b, `is(2))
+}
+
+
 function test_iter_1 = {
   let a, b, c... = list[1, 2, 3, 4, 5]: iterator()
   assertThat(a, `is(1))
@@ -253,6 +288,25 @@ function test_iter_7 = {
   }
 }
 
+function test_iter_skip_1 = {
+  let a, _, _, b = list[1, 2, 3, 4]:iterator()
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
+function test_iter_skip_2 = {
+  let a, _, _, c... = list[1, 2, 3, 4, 5]: iterator()
+  assertThat(a, `is(1))
+  assertThat(c: next(), `is(4))
+  assertThat(c: next(), `is(5))
+  assertThat(c: hasNext(), `is(false))
+}
+
+function test_iter_skip_3 = {
+  let _, b, _... = list[1, 2, 3, 4, 5]: iterator()
+  assertThat(b, `is(2))
+}
+
 function test_iterable_1 = {
   let a, b, c... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3, 4, 5]: iterator())
   assertThat(a, `is(1))
@@ -312,6 +366,23 @@ function test_iterable_7 = {
   }
 }
 
+function test_iterable_skip_1 = {
+  let a, _, _, b = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3, 4]:iterator())
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
+function test_iterable_skip_2 = {
+  let a, _, _, c... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3, 4, 5]: iterator())
+  assertThat(a, `is(1))
+  assertThat(c, contains(4, 5))
+}
+
+function test_iterable_skip_3 = {
+  let _, b, _... = asInterfaceInstance(java.lang.Iterable.class, -> list[1, 2, 3, 4, 5]: iterator())
+  assertThat(b, `is(2))
+}
+
 
 function test_array_less = {
   try {
@@ -336,6 +407,12 @@ function test_array_exact = {
   assertThat(c, `is(3))
 }
 
+function test_array_exact_skip = {
+  let a, _, _, b = array[1, 2, 3, 4]
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
 function test_array_exact_with_sub = {
   let a, b, c... = array[1, 2, 3]
   assertThat(a, `is(1))
@@ -343,6 +420,17 @@ function test_array_exact_with_sub = {
   assertThat(c, `is(arrayContaining(3)))
 }
 
+function test_array_with_sub_skip = {
+  let a, _, _, c... = array[1, 2, 3, 4, 5]
+  assertThat(a, `is(1))
+  assertThat(c, `is(arrayContaining(4, 5)))
+}
+
+function test_array_with_sub_skip2 = {
+  let _, b, _... = array[1, 2, 3, 4, 5]
+  assertThat(b, `is(2))
+}
+
 function test_array_more = {
   try {
     let a, b, c = array[1, 2]
@@ -413,20 +501,45 @@ function test_range_6 = {
   }
 }
 
+function test_range_skip_1 = {
+  let a, _, _, b = [1..5]
+  assertThat(a, `is(1))
+  assertThat(b, `is(4))
+}
+
+function test_range_skip_2 = {
+  let a, _, _, c... = [1..6]
+  assertThat(a, `is(1))
+  assertThat(c, `is([4..6]))
+}
+
+function test_range_skip_3 = {
+  let _, b, _... = [1..6]
+  assertThat(b, `is(2))
+}
+
+
+
 function test_foreach = {
   let l = list[ [1, 2, 3], [3, 4, 5] ]
   var i = 0
   foreach a, b, c in l {
-    require(a == l: get(i): get(0), "err")
-    require(b == l: get(i): get(1), "err")
-    require(c == l: get(i): get(2), "err")
+    assertThat(a, `is(l: get(i): get(0)))
+    assertThat(b, `is(l: get(i): get(1)))
+    assertThat(c, `is(l: get(i): get(2)))
     i = i + 1
   }
 
   i = 0
   foreach a, b... in l {
-    require(a == l: get(i): head(), "err")
-    require(b == l: get(i): tail(), "err")
+    assertThat(a, `is(l: get(i): head()))
+    assertThat(b, `is(l: get(i): tail()))
+    i = i + 1
+  }
+
+  i = 0
+  foreach _, b, _ in l {
+    assertThat(b, `is(l: get(i): get(1)))
     i = i + 1
   }
 }
diff --git a/src/test/resources/for-test/lazylist.golo b/src/test/resources/for-test/lazylist.golo
index 4905e9e4a..11b1435cc 100644
--- a/src/test/resources/for-test/lazylist.golo
+++ b/src/test/resources/for-test/lazylist.golo
@@ -225,6 +225,21 @@ function test_destruct = {
   } catch(e) {
     assertThat(e, isA(InvalidDestructuringException.class))
   }
+}
+
+function test_destruct_skip = {
+  let s11, _, _, _, s12 = longLL()
+  assertThat(s11, `is(1))
+  assertThat(s12, `is(5))
+
+  let s21, _, _, s22... = longLL()
+  assertThat(s21, `is(1))
+  assertThat(s22: head(), `is(4))
+  assertThat(s22: tail():head(), `is(5))
+  assertThat(s22: tail():tail(), `is(empty()))
+
+  let _, s3, _... = longLL()
+  assertThat(s3, `is(2))
 
 }
 

From 5b668959e6761f136d5b0df3f0b2759f8995040f Mon Sep 17 00:00:00 2001
From: Yannick Loiseau 
Date: Mon, 19 Oct 2020 13:21:06 +0200
Subject: [PATCH 15/18] Macro for feature flag

---
 TODO.md                                       |  6 ++---
 src/main/golo/gololang/macros.golo            | 13 ++++++++++
 .../golo/compiler/SugarExpansionVisitor.java  | 12 +++++++--
 src/main/resources/messages.properties        |  1 +
 src/main/resources/messages_fr_FR.properties  |  1 +
 .../golo/compiler/CompileAndRunTest.java      | 11 ++------
 .../resources/for-execution/destruct-old.golo | 25 +++++++++++++++++++
 .../resources/for-execution/destruct.golo     |  7 ------
 8 files changed, 55 insertions(+), 21 deletions(-)
 create mode 100644 src/test/resources/for-execution/destruct-old.golo

diff --git a/TODO.md b/TODO.md
index c2dc3f9ad..66ce2f099 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,7 +7,7 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr
 ## TODO
 
 - [x] implement the new desugarization
-  - [ ] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member
+  - [x] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member
 - [x] Adds a custom exception to signal invalid destructuring
 - update the known `destruct()` method in the stdlib
   - provides a new destructuring method
@@ -22,8 +22,8 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr
     - [x] ranges
     - [x] When IR
   - [x] mark the old one deprecated (may depend on PR #551 to annotate golo code)
-    - [ ] use the real deprecated macro when #551 is merged
-- [ ] provides augmentations to adapt both ways ?
+    - [x] use the real deprecated macro when #551 is merged
+- [ ] ~~provides augmentations to adapt both ways ?~~
 - [ ] update the doc
   - [ ] describe new style destruct
   - [ ] rationale
diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo
index 40ed151f4..564e3f4da 100644
--- a/src/main/golo/gololang/macros.golo
+++ b/src/main/golo/gololang/macros.golo
@@ -140,3 +140,16 @@ macro deprecated = |args...| {
       named: get("comment")?: value(),
       positional)
 }
+
+----
+Use old-style destructuring for the current module.
+
+This macro customize the behavior of the destructuring feature by forcing the use of the `destruct` method instead of
+`_$$_destruct`.
+
+This is a toplevel macro.
+----
+@contextual
+macro useOldstyleDestruct = |self| {
+  self: enclosingModule(): metadata("golo.destruct.newstyle", false)
+}
diff --git a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java
index bd97a8459..228e9a713 100644
--- a/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java
+++ b/src/main/java/org/eclipse/golo/compiler/SugarExpansionVisitor.java
@@ -11,6 +11,7 @@
 package org.eclipse.golo.compiler;
 
 import gololang.ir.*;
+import gololang.Messages;
 import java.util.List;
 import java.util.Deque;
 import java.util.LinkedList;
@@ -30,7 +31,13 @@ class SugarExpansionVisitor extends AbstractGoloIrVisitor {
   private final SymbolGenerator symbols = new SymbolGenerator("golo.compiler.sugar");
   private final List functionsToAdd = new LinkedList<>();
   private GoloModule module;
-  private final boolean useNewStyleDestruct = gololang.Runtime.loadBoolean("new", "golo.destruct.version", "GOLO_DESTRUCT_VERSION", true);
+
+  private boolean useNewStyleDestruct() {
+    if (this.module.metadata("golo.destruct.newstyle") != null) {
+      return (boolean) this.module.metadata("golo.destruct.newstyle");
+    }
+    return gololang.Runtime.loadBoolean("true", "golo.destruct.newstyle", "GOLO_DESTRUCT_NEWSTYLE", true);
+  }
 
   @Override
   public void visitModule(GoloModule module) {
@@ -380,7 +387,7 @@ public void visitForEachLoopStatement(ForEachLoopStatement foreachStatement) {
    */
   @Override
   public void visitDestructuringAssignment(DestructuringAssignment assignment) {
-    Block replacement = useNewStyleDestruct ? newDestructuring(assignment) : oldDestructuring(assignment);
+    Block replacement = useNewStyleDestruct() ? newDestructuring(assignment) : oldDestructuring(assignment);
     assignment.replaceInParentBy(replacement);
     replacement.accept(this);
   }
@@ -402,6 +409,7 @@ public void visitDestructuringAssignment(DestructuringAssignment assignment) {
    * 
*/ private Block oldDestructuring(DestructuringAssignment assignment) { + Messages.warning(Messages.message("oldstyle_destruct", this.module.getPackageAndClass()), assignment); LocalReference tmpRef = LocalReference.of(symbols.next("destruct")).synthetic(); Block block = Block.of(AssignmentStatement.create(tmpRef, invoke("destruct").on(assignment.expression()), true)); int last = assignment.getReferencesCount() - 1; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 6362afce6..918722a2e 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -81,6 +81,7 @@ no_parameter_names = The function `{0}` has no parameter names but is called wit unavailable_class = `{0}` used in `{1}` can\u2019t be loaded.\n\tSee <{2}#warning-unavailable-class> for more information. deprecated_element = `{0}` in `{1}` is deprecated.\n\tSee <{2}#warning-deprecated> for more information. +oldstyle_destruct = `{0}` uses old-style destructuring, which is deprecated. # Documentation warnings and errors =========================================== multiple_package_desc = Multiple description files found for package `{0}`; using the first one.\n\tSee <{1}#warning-multiple-package-desc> for more information. diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index c70e6bfee..f255ef2b6 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -81,6 +81,7 @@ struct_private_member = Membre priv\u00e9 de `{0}` no_parameter_names = La fonction `{0}` n\u2019a pas de param\u00e8tre nomm\u00e9 mais est appel\u00e9e avec {1} comme noms d\u2019arguments.\n\tVoir <{2}#warning-no-parameter-names> pour plus d\u2019informations. unavailable_class = `{0}` utilis\u00e9e dans `{1}` ne peut pas \u00eatre charg\u00e9e.\n\tVoir <{2}#warning-unavailable-class> pour plus d\u2019informations. deprecated_element = `{0}` dans `{1}` est obsol\u00e8te.\n\tVoir <{2}#warning-deprecated> pour plus d\u2019informations. +oldstyle_destruct = `{0}` utilise l\u2019ancienne version de la d\u00e9structuration qui est obsol\u00e8te. # Documentation warnings and errors =========================================== multiple_package_desc = Fichiers de description surnum\u00e9raires pour le paquet `{0}`; utilisation du premier.\n\tVoir <{1}#warning-multiple-package-desc> pour plus d\u2019informations. diff --git a/src/test/java/org/eclipse/golo/compiler/CompileAndRunTest.java b/src/test/java/org/eclipse/golo/compiler/CompileAndRunTest.java index c9e71d64f..2f041fde5 100644 --- a/src/test/java/org/eclipse/golo/compiler/CompileAndRunTest.java +++ b/src/test/java/org/eclipse/golo/compiler/CompileAndRunTest.java @@ -1771,15 +1771,8 @@ public void destructuring() throws Throwable { if (bootstraping()) { return; } - Class moduleClass = compileAndLoadGoloModule(SRC, "destruct.golo"); - for (Method testMethod : getTestMethods(moduleClass)) { - try { - testMethod.invoke(null); - } catch (InvocationTargetException e) { - fail("method " + testMethod.getName() + " in " + SRC + "destruct.golo failed: " + - e.getCause()); - } - } + runTests(SRC, "destruct.golo", classLoader(this)); + runTests(SRC, "destruct-old.golo", classLoader(this)); } @Test diff --git a/src/test/resources/for-execution/destruct-old.golo b/src/test/resources/for-execution/destruct-old.golo new file mode 100644 index 000000000..007190e71 --- /dev/null +++ b/src/test/resources/for-execution/destruct-old.golo @@ -0,0 +1,25 @@ +module golotest.execution.DestructuringOld + +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +import org.eclipse.golo.runtime + +&useOldstyleDestruct() + +function test_tuple_old = { + let fst, scd = [1, 2, 3, 4] + assertThat(fst, `is(1)) + assertThat(scd, `is(2)) +} + +function test_list_old = { + let a, b... = list[1, 2, 3, 4] + assertThat(a, `is(1)) + assertThat(b, `is(tuple[2, 3, 4])) +} + +function main = |args| { + test_tuple_old() + test_list_old() +} diff --git a/src/test/resources/for-execution/destruct.golo b/src/test/resources/for-execution/destruct.golo index 3490dc357..a7dcf35d6 100644 --- a/src/test/resources/for-execution/destruct.golo +++ b/src/test/resources/for-execution/destruct.golo @@ -48,13 +48,6 @@ function test_tuple_rest = { assertThat(c, `is([3, 4, 5])) } -# ignored, old version. Should fail now. -function _tuple_less_old = { - let fst, scd = [1, 2, 3, 4] - require(fst == 1, "err") - require(scd == 2, "err") -} - function test_tuple_less_new = { try { let fst, scd = [1, 2, 3, 4] From e35336c9afe5ef45b1b03b6105359567028284ff Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Mon, 19 Oct 2020 16:11:43 +0200 Subject: [PATCH 16/18] Document new style destructuring --- TODO.md | 12 ++++---- doc/basics.adoc | 76 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/TODO.md b/TODO.md index 66ce2f099..a1952fe19 100644 --- a/TODO.md +++ b/TODO.md @@ -22,10 +22,10 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] ranges - [x] When IR - [x] mark the old one deprecated (may depend on PR #551 to annotate golo code) - - [x] use the real deprecated macro when #551 is merged -- [ ] ~~provides augmentations to adapt both ways ?~~ -- [ ] update the doc - - [ ] describe new style destruct - - [ ] rationale - - [ ] document feature flag + - ~~use the real deprecated macro when #551 is merged~~ (not possible, compilation dependency) +- ~~provides augmentations to adapt both ways ?~~ (not needed) +- [x] Adds a feature flag using property / macro to switch old/new style +- update the doc + - [x] describe new style destruct + - [x] document feature flag diff --git a/doc/basics.adoc b/doc/basics.adoc index 4a234a65b..ee205eba5 100644 --- a/doc/basics.adoc +++ b/doc/basics.adoc @@ -720,18 +720,20 @@ let a, b = [1, 2] # a = 1, b = 2 ---- -If there are more variables than values, an exception is raised. If there are -fewer, the remaining values are ignored. A special syntax is available to -assign the rest of the values, similar to varargs notation. For instance: +If there are more or fewer variables than values to assign, an exception is raised. +A special syntax is available to assign the rest of the values, similar to varargs notation. For instance: [source,golo] ---- -let a, b, c = [1, 2] # raises an exception -let a, b = [1, 2, 3] # a = 1, b = 2, 3 ignored -let a, b, c... = [1, 2, 3, 4, 5] # a = 1, b = 2, c = [3, 4, 5] +let a, b, c = list[1, 2] # raises an exception +let a, b = list[1, 2, 3] # raises an exception +let a, b, c... = list[1, 2, 3, 4, 5] # a = 1, b = 2, c = list[3, 4, 5] ---- -Any object having a `destruct()` method returning a tuple can be used in +The reminder has the same type than the destructured object. Note that some mainly container-like object can be destructured using this syntax. +For instance, golo structures can't use the rest syntax and raise an exception. + +Any object having a `__$$_destruct(number, rest, toSkip)` method returning an array can be used in destructuring assignments. Golo specific data structures and some Java native ones (arrays, maps, collections) can be destructured. Augmentations can be used to make an existing class destructurable. @@ -749,7 +751,7 @@ as well as java lists: [source,golo] ---- let lst = list[1, 2, 3, 4, 5] -let head, tail... = lst # head = 1, tail = [2, 3, 4, 5] +let head, tail... = lst # head = 1, tail = list[2, 3, 4, 5] ---- Already defined variables can also be assigned with destructuring. For @@ -769,6 +771,64 @@ foreach key, value in myMap: entrySet() { } ---- +It is moreover possible to ignore some of the destructured values by using the special `_` variable name. +[source,golo] +---- +let a, _, _, _, b = list[1, 2, 3, 4, 5] +# a = 1 and b = 5 +---- + +This name can be used with the remainer syntax, to avoid the creation of a new structure to hold the other values. +For instance: +[source,golo] +---- +let _, a, _, b, _... = [1..500] +---- + +Here, `a` is `2` and `b` is `4`. No new range is created for the remaining values. + +==== Destructuring method + +To be destructurable, an object must have a `__$$_destruct(number, rest, skip)` method, whose parameters are: + +- the number of values to be assigned to, +- whether the assignement has a rest (i.e. if remainer syntax `...` is used) +- a list describing the positions to skip (`_` special name) + +and must return an array containg the values to assign (`null` if skipped). + +For instance, the code: +[source,golo] +---- +let a, _, b... = [8..20] + +let it = list[1, 2, 3, 4, 5]: iterator() +let _, x, y, _... = it +---- + +is equivalent to: +[source,golo] +---- +let tmp1 = [8..20]: __$$_destruct(3, true, array[false, true, false]) +let a = tmp: get(0) +let b = tmp: get(2) + +let it = list[1, 2, 3, 4, 5]: iterator() +let tmp2 = it: __$$_destruct(4, true, array[true, false, false, true]) +let x = it: get(1) +let y = it: get(2) +---- + +where `tmp1` will be `array[8, null, [10..20]]` and `tmp2` will be `array[null, 2, 3, null]`. + + +[WARNING] +==== +As of version 3.4, the destructuring behavior has changed, particularly when the remainer aspect and when not enough variables are provided. +To use the old-syle behavior, one can use the `&useOldstyleDestruct()` macro to switch locally, or set the `golo.destruct.newstyle` property or +`GOLO_DESTRUCT_NEWSTYLE` environment variable to `true` to switch globally. +==== + === Operators Golo supports the following <>. From bc841df905a40232c6b328c967bdb50b47fb3fec Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Mon, 19 Oct 2020 16:46:04 +0200 Subject: [PATCH 17/18] Small refactorings and improvements - move implementations into interface as default method - make more IR elements destructurable --- TODO.md | 2 + src/main/java/gololang/AbstractRange.java | 51 ------------------ src/main/java/gololang/HeadTail.java | 36 +++++++++++++ src/main/java/gololang/LazyList.java | 34 ------------ src/main/java/gololang/Range.java | 54 +++++++++++++++++++ .../java/gololang/ir/BinaryOperation.java | 8 +++ .../java/gololang/ir/FunctionContainer.java | 8 ++- src/main/java/gololang/ir/NamedArgument.java | 8 +++ 8 files changed, 115 insertions(+), 86 deletions(-) diff --git a/TODO.md b/TODO.md index a1952fe19..4e30811e7 100644 --- a/TODO.md +++ b/TODO.md @@ -21,6 +21,8 @@ Trying to reimplement the destructuring feature better (see #524 for a full intr - [x] lazy lists - [x] ranges - [x] When IR + - [x] Binary operation IR + - [x] NamedArgument - [x] mark the old one deprecated (may depend on PR #551 to annotate golo code) - ~~use the real deprecated macro when #551 is merged~~ (not possible, compilation dependency) - ~~provides augmentations to adapt both ways ?~~ (not needed) diff --git a/src/main/java/gololang/AbstractRange.java b/src/main/java/gololang/AbstractRange.java index b1323d53d..bacb1cdb8 100644 --- a/src/main/java/gololang/AbstractRange.java +++ b/src/main/java/gololang/AbstractRange.java @@ -14,7 +14,6 @@ import java.util.Iterator; import java.util.Arrays; import java.util.Objects; -import org.eclipse.golo.runtime.InvalidDestructuringException; import static java.util.Objects.requireNonNull; @@ -149,54 +148,4 @@ public Tuple destruct() { } return Tuple.fromArray(data); } - - /** - * New style destructuring helper. - * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. - * - * @param number number of variable that will be affected. - * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return an array containing the values to assign. - */ - public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { - if (number < size() && !substruct) { - throw InvalidDestructuringException.notEnoughValues(number, size(), substruct); - } - if (number == size() && !substruct) { - return org.eclipse.golo.runtime.ArrayHelper.nullify(toArray(), toSkip); - } - if (number <= size() && substruct) { - Object[] d = new Object[number]; - Iterator it = this.iterator(); - for (int i = 0; i < number - 1; i++) { - if (Boolean.valueOf(true).equals(toSkip[i])) { - it.next(); - } else { - d[i] = it.next(); - } - } - if (Boolean.valueOf(false).equals(toSkip[number - 1])) { - d[number - 1] = newStartingFrom(it.next()); - } - return d; - } - if (number == size() + 1 && substruct) { - Object[] d = Arrays.copyOf(toArray(), number); - if (Boolean.valueOf(false).equals(toSkip[number - 1])) { - d[number - 1] = newStartingFrom(to()); - } - return org.eclipse.golo.runtime.ArrayHelper.nullify(d, toSkip); - } - throw InvalidDestructuringException.tooManyValues(number); - } - - /** - * Returns a copy of this range with a new starting value. - * - *

There is no check that the {@code newStart} value is compatible with the current start and increment. It is - * therefore possible that the new range yields different values than the original. - */ - public abstract Range newStartingFrom(T newStart); } diff --git a/src/main/java/gololang/HeadTail.java b/src/main/java/gololang/HeadTail.java index a7d1e2d15..c1cb4ba93 100644 --- a/src/main/java/gololang/HeadTail.java +++ b/src/main/java/gololang/HeadTail.java @@ -10,6 +10,8 @@ package gololang; +import org.eclipse.golo.runtime.InvalidDestructuringException; + /** * Structure having a head and a tail. *

@@ -76,4 +78,38 @@ public interface HeadTail extends Iterable { static Iterable toIterable(HeadTail headTail) { return () -> new HeadTailIterator<>(headTail); } + + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return an array containing the values to assign. + */ + default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { + Object[] destruct = new Object[number]; + HeadTail current = this; + for (int i = 0; i < number - 1; i++) { + if (current.isEmpty()) { + throw InvalidDestructuringException.tooManyValues(number); + } + if (Boolean.valueOf(false).equals(toSkip[i])) { + destruct[i] = current.head(); + } + current = current.tail(); + } + if (substruct && Boolean.valueOf(false).equals(toSkip[number - 1])) { + destruct[number - 1] = current; + } else if (current.isEmpty()) { + throw InvalidDestructuringException.tooManyValues(number); + } else if (!current.tail().isEmpty() && Boolean.valueOf(false).equals(toSkip[number - 1])) { + throw InvalidDestructuringException.notEnoughValues(number, substruct); + } else if (Boolean.valueOf(false).equals(toSkip[number - 1])) { + destruct[number - 1] = current.head(); + } + return destruct; + } } diff --git a/src/main/java/gololang/LazyList.java b/src/main/java/gololang/LazyList.java index 5898822e8..59002d428 100644 --- a/src/main/java/gololang/LazyList.java +++ b/src/main/java/gololang/LazyList.java @@ -246,40 +246,6 @@ public Tuple destruct() { return new Tuple(head(), tail()); } - /** - * New style destructuring helper. - * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. - * - * @param number number of variable that will be affected. - * @param substruct whether the destructuring is complete or should contains a sub structure. - * @return an array containing the values to assign. - */ - public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { - Object[] destruct = new Object[number]; - LazyList current = this; - for (int i = 0; i < number - 1; i++) { - if (current.isEmpty()) { - throw InvalidDestructuringException.tooManyValues(number); - } - if (Boolean.valueOf(false).equals(toSkip[i])) { - destruct[i] = current.head(); - } - current = current.tail(); - } - if (substruct && Boolean.valueOf(false).equals(toSkip[number - 1])) { - destruct[number - 1] = current; - } else if (current.isEmpty()) { - throw InvalidDestructuringException.tooManyValues(number); - } else if (!current.tail().isEmpty() && Boolean.valueOf(false).equals(toSkip[number - 1])) { - throw InvalidDestructuringException.notEnoughValues(number, substruct); - } else if (Boolean.valueOf(false).equals(toSkip[number - 1])) { - destruct[number - 1] = current.head(); - } - return destruct; - } - /** * Returns the element at the specified position in this list. *

diff --git a/src/main/java/gololang/Range.java b/src/main/java/gololang/Range.java index 993e25204..706d57fdd 100644 --- a/src/main/java/gololang/Range.java +++ b/src/main/java/gololang/Range.java @@ -11,6 +11,10 @@ package gololang; import java.util.Collection; +import java.util.Iterator; +import java.util.Arrays; + +import org.eclipse.golo.runtime.InvalidDestructuringException; /** * Represents a generic value range. @@ -81,4 +85,54 @@ public interface Range extends Collection, HeadTail { * {@code -increment()}. */ Range reversed(); + + /** + * New style destructuring helper. + * + * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of + * members of the structure. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @return an array containing the values to assign. + */ + default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { + if (number < size() && !substruct) { + throw InvalidDestructuringException.notEnoughValues(number, size(), substruct); + } + if (number == size() && !substruct) { + return org.eclipse.golo.runtime.ArrayHelper.nullify(toArray(), toSkip); + } + if (number <= size() && substruct) { + Object[] d = new Object[number]; + Iterator it = this.iterator(); + for (int i = 0; i < number - 1; i++) { + if (Boolean.valueOf(true).equals(toSkip[i])) { + it.next(); + } else { + d[i] = it.next(); + } + } + if (Boolean.valueOf(false).equals(toSkip[number - 1])) { + d[number - 1] = newStartingFrom(it.next()); + } + return d; + } + if (number == size() + 1 && substruct) { + Object[] d = Arrays.copyOf(toArray(), number); + if (Boolean.valueOf(false).equals(toSkip[number - 1])) { + d[number - 1] = newStartingFrom(to()); + } + return org.eclipse.golo.runtime.ArrayHelper.nullify(d, toSkip); + } + throw InvalidDestructuringException.tooManyValues(number); + } + + /** + * Returns a copy of this range with a new starting value. + * + *

There is no check that the {@code newStart} value is compatible with the current start and increment. It is + * therefore possible that the new range yields different values than the original. + */ + public Range newStartingFrom(T newStart); } diff --git a/src/main/java/gololang/ir/BinaryOperation.java b/src/main/java/gololang/ir/BinaryOperation.java index dd890b8bb..6086a6105 100644 --- a/src/main/java/gololang/ir/BinaryOperation.java +++ b/src/main/java/gololang/ir/BinaryOperation.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.List; +import org.eclipse.golo.runtime.InvalidDestructuringException; /** * Represents a binary operation. @@ -108,6 +109,13 @@ public boolean isMethodCall() { || this.getType() == OperatorType.ANON_CALL; } + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { + if (number == 2 && !substruct) { + return new Object[]{leftExpression, rightExpression}; + } + throw new InvalidDestructuringException("A BinaryOperation must destructure to exactly two values"); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/gololang/ir/FunctionContainer.java b/src/main/java/gololang/ir/FunctionContainer.java index 9014ab958..bc0106964 100644 --- a/src/main/java/gololang/ir/FunctionContainer.java +++ b/src/main/java/gololang/ir/FunctionContainer.java @@ -14,11 +14,17 @@ import java.util.List; import java.util.Collection; +import java.util.Iterator; /** * Interface for Golo elements that can contain functions (module, augmentations, ...). */ -public interface FunctionContainer { +public interface FunctionContainer extends Iterable { + + default Iterator iterator() { + return getFunctions().iterator(); + } + List getFunctions(); /** diff --git a/src/main/java/gololang/ir/NamedArgument.java b/src/main/java/gololang/ir/NamedArgument.java index 4ea023674..c7e402e33 100644 --- a/src/main/java/gololang/ir/NamedArgument.java +++ b/src/main/java/gololang/ir/NamedArgument.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.List; +import org.eclipse.golo.runtime.InvalidDestructuringException; import static gololang.Messages.message; @@ -59,6 +60,13 @@ private void setExpression(ExpressionStatement value) { this.expression = makeParentOf(value); } + public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { + if (number == 2 && !substruct) { + return new Object[]{name, expression}; + } + throw new InvalidDestructuringException("A NamedArgument must destructure to exactly two values"); + } + /** * {@inheritDoc} * From f7705a5e1f7e71b76d468e289de9b953d1f081b4 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Tue, 20 Oct 2020 12:31:29 +0200 Subject: [PATCH 18/18] Complete documentation and clean --- TODO.md | 33 ---------------- src/main/golo/standard-augmentations.golo | 38 ++++++++++++++----- src/main/java/gololang/GoloStruct.java | 5 ++- src/main/java/gololang/HeadTail.java | 6 +-- src/main/java/gololang/Range.java | 5 ++- src/main/java/gololang/Tuple.java | 4 +- src/main/java/gololang/Union.java | 5 ++- src/main/java/gololang/error/Result.java | 3 ++ .../java/gololang/ir/BinaryOperation.java | 11 ++++++ src/main/java/gololang/ir/NamedArgument.java | 11 ++++++ src/main/java/gololang/ir/WhenClause.java | 5 ++- 11 files changed, 70 insertions(+), 56 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4e30811e7..000000000 --- a/TODO.md +++ /dev/null @@ -1,33 +0,0 @@ -# Reimplementing destructuring - -branch: feat/destruct-reboot - -Trying to reimplement the destructuring feature better (see #524 for a full introduction to issues of current implementation). - -## TODO - -- [x] implement the new desugarization - - [x] allows to explicitly skip the rest (e.g. with special arg `_`) to avoid generating an unused (recursive) member -- [x] Adds a custom exception to signal invalid destructuring -- update the known `destruct()` method in the stdlib - - provides a new destructuring method - - [x] Tuple - - [x] struct - - [x] array - - [x] union - - [x] iter/collections - - [x] Result - - [x] map item - - [x] lazy lists - - [x] ranges - - [x] When IR - - [x] Binary operation IR - - [x] NamedArgument - - [x] mark the old one deprecated (may depend on PR #551 to annotate golo code) - - ~~use the real deprecated macro when #551 is merged~~ (not possible, compilation dependency) -- ~~provides augmentations to adapt both ways ?~~ (not needed) -- [x] Adds a feature flag using property / macro to switch old/new style -- update the doc - - [x] describe new style destruct - - [x] document feature flag - diff --git a/src/main/golo/standard-augmentations.golo b/src/main/golo/standard-augmentations.golo index 2102fcf01..7d21e6e13 100644 --- a/src/main/golo/standard-augmentations.golo +++ b/src/main/golo/standard-augmentations.golo @@ -292,6 +292,13 @@ augment java.lang.Iterable { ---- New style destructuring helper + + If a remainer is included, it will be an iterable reusing the underlying iterator. + + - *param* `number`: number of variable that will be affected. + - *param* `substruct`: whether the destructuring is complete or should contains a remainer. + - *param* `toSkip`: a boolean array indicating the elements to skip. + - *returns* an array containing the values to assign. ---- function __$$_destruct = |this, number, substruct, toSkip| { let it = this: iterator() @@ -328,14 +335,14 @@ augment java.util.Collection { } ---- - New style destructuring helper. + New style destructuring helper - New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - members of the structure. + If a remainer is included, it will be a collection of the same type. - *param* `number`: number of variable that will be affected. - - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. - - *return* an array containing the values to assign. + - *param* `substruct`: whether the destructuring is complete or should contains a remainer. + - *param* `toSkip`: a boolean array indicating the elements to skip. + - *returns* an array containing the values to assign. ---- function __$$_destruct = |this, number, substruct, toSkip| { if number < this: size() and not substruct { @@ -937,14 +944,14 @@ augment java.util.Map$Entry { } ---- - New style destructuring helper. + New style destructuring helper - New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - members of the structure. + The destructuring must be to exactly two values. No remainer syntax is allowed. - *param* `number`: number of variable that will be affected. - - *param* `substruct`: whether the destructuring is complete or should contains a sub structure. - - *return* a tuple containing the values to assign. + - *param* `substruct`: whether the destructuring is complete or should contains a remainer. + - *param* `toSkip`: a boolean array indicating the elements to skip. + - *returns* an array containing the values to assign. ---- function __$$_destruct = |this, number, substruct, toSkip| { if (number == 2 and not substruct) { @@ -1075,5 +1082,16 @@ local function destructIterator = |it, number, substruct, toSkip| { augment java.util.Iterator { + + ---- + New style destructuring helper + + If a remainer is included, it will contain the iterator itself. + + - *param* `number`: number of variable that will be affected. + - *param* `substruct`: whether the destructuring is complete or should contains a remainer. + - *param* `toSkip`: a boolean array indicating the elements to skip. + - *returns* an array containing the values to assign. + ---- function __$$_destruct = |this, number, substruct, toSkip| -> destructIterator(this, number, substruct, toSkip) } diff --git a/src/main/java/gololang/GoloStruct.java b/src/main/java/gololang/GoloStruct.java index 54a8f00d4..8600068a1 100644 --- a/src/main/java/gololang/GoloStruct.java +++ b/src/main/java/gololang/GoloStruct.java @@ -71,11 +71,12 @@ public Tuple destruct() { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + *

The number of variables to be affected must be the number of members. + * No remainer syntax is allowed. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/HeadTail.java b/src/main/java/gololang/HeadTail.java index c1cb4ba93..af9411718 100644 --- a/src/main/java/gololang/HeadTail.java +++ b/src/main/java/gololang/HeadTail.java @@ -82,11 +82,11 @@ static Iterable toIterable(HeadTail headTail) { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + *

If a remainer if included, it will be the tail of the last extracted value (even if skipped). * * @param number number of variable that will be affected. - * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param substruct whether the destructuring is complete or should contains a remainer. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/Range.java b/src/main/java/gololang/Range.java index 706d57fdd..1f1762743 100644 --- a/src/main/java/gololang/Range.java +++ b/src/main/java/gololang/Range.java @@ -89,11 +89,12 @@ public interface Range extends Collection, HeadTail { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + *

If a remainer if included, it will be a new range with same step and end values, starting to the next available + * value. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/Tuple.java b/src/main/java/gololang/Tuple.java index 9495d5dba..5e4296cfe 100644 --- a/src/main/java/gololang/Tuple.java +++ b/src/main/java/gololang/Tuple.java @@ -207,11 +207,11 @@ public Tuple tail() { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + *

If a remainer if included, it will be a new tuple of the remaining values. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/Union.java b/src/main/java/gololang/Union.java index 5dd18b6d6..472a78939 100644 --- a/src/main/java/gololang/Union.java +++ b/src/main/java/gololang/Union.java @@ -43,11 +43,12 @@ public Tuple destruct() { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + * The number of variables to be affected must be the number of members. + * No remainer syntax is allowed. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/error/Result.java b/src/main/java/gololang/error/Result.java index f983a6701..7c1265643 100644 --- a/src/main/java/gololang/error/Result.java +++ b/src/main/java/gololang/error/Result.java @@ -679,8 +679,11 @@ public Tuple destruct() { *

New style destructuring must be exact. The number of variables to be affected is thus checked against the number of * members of the structure. * + *

The destructuring must be to exactly two values. No remainer syntax is allowed. + * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { diff --git a/src/main/java/gololang/ir/BinaryOperation.java b/src/main/java/gololang/ir/BinaryOperation.java index 6086a6105..6294b3be4 100644 --- a/src/main/java/gololang/ir/BinaryOperation.java +++ b/src/main/java/gololang/ir/BinaryOperation.java @@ -109,6 +109,17 @@ public boolean isMethodCall() { || this.getType() == OperatorType.ANON_CALL; } + /** + * New style destructuring helper. + * + *

The destructuring must be to exactly two values. No remainer syntax is allowed. + *

The destructured values are the left and right expressions. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. + * @return an array containing the values to assign. + */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number == 2 && !substruct) { return new Object[]{leftExpression, rightExpression}; diff --git a/src/main/java/gololang/ir/NamedArgument.java b/src/main/java/gololang/ir/NamedArgument.java index c7e402e33..f02cfa85f 100644 --- a/src/main/java/gololang/ir/NamedArgument.java +++ b/src/main/java/gololang/ir/NamedArgument.java @@ -60,6 +60,17 @@ private void setExpression(ExpressionStatement value) { this.expression = makeParentOf(value); } + /** + * New style destructuring helper. + * + *

The destructuring must be to exactly two values. No remainer syntax is allowed. + *

The destructured values are the named and the expression. + * + * @param number number of variable that will be affected. + * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. + * @return an array containing the values to assign. + */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) { if (number == 2 && !substruct) { return new Object[]{name, expression}; diff --git a/src/main/java/gololang/ir/WhenClause.java b/src/main/java/gololang/ir/WhenClause.java index 9042711ae..e22c23e86 100644 --- a/src/main/java/gololang/ir/WhenClause.java +++ b/src/main/java/gololang/ir/WhenClause.java @@ -91,11 +91,12 @@ public Tuple destruct() { /** * New style destructuring helper. * - * New style destructuring must be exact. The number of variables to be affected is thus checked against the number of - * members of the structure. + *

The destructuring must be to exactly two values. No remainer syntax is allowed. + *

The destructured values are the condition and the action. * * @param number number of variable that will be affected. * @param substruct whether the destructuring is complete or should contains a sub structure. + * @param toSkip a boolean array indicating the elements to skip. * @return an array containing the values to assign. */ public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {