diff --git a/doc/todo-list.txt b/doc/todo-list.txt index 6e7db2c16b..cf733f8128 100644 --- a/doc/todo-list.txt +++ b/doc/todo-list.txt @@ -35,11 +35,6 @@ - GG: consider allowing to debug an eval expression (debug eval) -- lambda capture of a non-effectively final var as a Ref or Var needs to be explicitly allowed - by the code; for example: "@AllowCapture String name = ..." - Alternatively, we could have @CaptureByValue that would be equivalent of creating a synthetic - effectively final value to capture. - - GG: consider changing Service.callLater() API to Future callLater(function Result doLater()); diff --git a/javatools/src/main/java/org/xvm/asm/ConstantPool.java b/javatools/src/main/java/org/xvm/asm/ConstantPool.java index 765b0f9f92..49c240ea7b 100755 --- a/javatools/src/main/java/org/xvm/asm/ConstantPool.java +++ b/javatools/src/main/java/org/xvm/asm/ConstantPool.java @@ -24,21 +24,16 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.Stack; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import org.xvm.asm.Constant.Format; -import org.xvm.asm.ast.RegisterAST; - import org.xvm.asm.constants.*; import org.xvm.asm.constants.TypeConstant.Relation; import org.xvm.asm.constants.TypeInfo.Progress; -import org.xvm.asm.ast.BinaryAST.ConstantResolver; - import org.xvm.compiler.Parser; import org.xvm.compiler.Source; @@ -2329,6 +2324,7 @@ public ByteConstant ensureNibbleConstant(int n) public ClassConstant clzTest() {ClassConstant c = m_clzTest; if (c == null) {m_clzTest = c = (ClassConstant) getImplicitlyImportedIdentity("Test" );} return c;} public ClassConstant clzTransient() {ClassConstant c = m_clzTransient; if (c == null) {m_clzTransient = c = (ClassConstant) getImplicitlyImportedIdentity("Transient" );} return c;} public ClassConstant clzUnassigned() {ClassConstant c = m_clzUnassigned; if (c == null) {m_clzUnassigned = c = (ClassConstant) getImplicitlyImportedIdentity("Unassigned" );} return c;} + public ClassConstant clzVolatile() {ClassConstant c = m_clzVolatile; if (c == null) {m_clzVolatile = c = (ClassConstant) getImplicitlyImportedIdentity("Volatile" );} return c;} public TypeConstant typeObject() {TypeConstant c = m_typeObject; if (c == null) {m_typeObject = c = ensureTerminalTypeConstant(clzObject() );} return c;} public TypeConstant typeInner() {TypeConstant c = m_typeInner; if (c == null) {m_typeInner = c = ensureVirtualChildTypeConstant(typeOuter(), "Inner" );} return c;} public TypeConstant typeOuter() {TypeConstant c = m_typeOuter; if (c == null) {m_typeOuter = c = ensureTerminalTypeConstant(clzOuter() );} return c;} @@ -3928,6 +3924,7 @@ public static Auto withPool(ConstantPool pool) { private transient ClassConstant m_clzTest; private transient ClassConstant m_clzTransient; private transient ClassConstant m_clzUnassigned; + private transient ClassConstant m_clzVolatile; private transient TypeConstant m_typeObject; private transient TypeConstant m_typeInner; private transient TypeConstant m_typeOuter; @@ -4072,6 +4069,7 @@ private void optimize() m_clzTest = null; m_clzTransient = null; m_clzUnassigned = null; + m_clzVolatile = null; m_typeObject = null; m_typeInner = null; m_typeOuter = null; diff --git a/javatools/src/main/java/org/xvm/compiler/Compiler.java b/javatools/src/main/java/org/xvm/compiler/Compiler.java index 38c04c5400..61b48441dc 100644 --- a/javatools/src/main/java/org/xvm/compiler/Compiler.java +++ b/javatools/src/main/java/org/xvm/compiler/Compiler.java @@ -1396,6 +1396,14 @@ public static Stage valueOf(int i) * A "try" must have either "catch" or "finally" block. */ public static final String TRY_WITHOUT_CATCH = "COMPILER-196"; + /** + * Attempt to mutate the captured variable {0}. (If intended, add the "@Volatile" annotation.) + */ + public static final String WRITEABLE_CAPTURE = "COMPILER-197"; + /** + * Invalid annotation combination: {0} and {1} are incompatible. + */ + public static final String INVALID_ANNOTATIONS_COMBO = "COMPILER-198"; /** * {0} is not yet implemented. */ diff --git a/javatools/src/main/java/org/xvm/compiler/ast/LambdaExpression.java b/javatools/src/main/java/org/xvm/compiler/ast/LambdaExpression.java index 282da79649..3a4b6b41f4 100644 --- a/javatools/src/main/java/org/xvm/compiler/ast/LambdaExpression.java +++ b/javatools/src/main/java/org/xvm/compiler/ast/LambdaExpression.java @@ -13,7 +13,6 @@ import org.xvm.asm.Argument; import org.xvm.asm.Component; -import org.xvm.asm.Constant; import org.xvm.asm.ConstantPool; import org.xvm.asm.ErrorListener; import org.xvm.asm.GenericTypeResolver; @@ -1180,9 +1179,16 @@ protected Argument[] calculateBindings(Context ctx, Code code, ErrorListener err } regCapture = regCapture.getOriginalRegister(); // remove any inferences + + boolean fAllowRefCapture = regCapture.isVar() && + regCapture.ensureRegType(true).isA(pool.clzVolatile().getType()); if (entry.getValue()) { // it's a read/write capture; capture the Var + if (!fAllowRefCapture) + { + log(errs, Severity.ERROR, Compiler.WRITEABLE_CAPTURE, sCapture); + } typeCapture = regCapture.isVar() ? regCapture.ensureRegType(true) : pool.ensureParameterizedTypeConstant(pool.typeVar(), typeCapture); @@ -1195,7 +1201,7 @@ protected Argument[] calculateBindings(Context ctx, Code code, ErrorListener err aAstBind[iParam] = new UnaryOpExprAST( regVal.getRegisterAST(), Operator.Var, typeCapture); } - else if (!regCapture.isEffectivelyFinal()) + else if (fAllowRefCapture && !regCapture.isEffectivelyFinal()) { // it's a read-only capture, but since we were unable to prove that the // register was effectively final, we need to capture the Ref diff --git a/javatools/src/main/java/org/xvm/compiler/ast/VariableDeclarationStatement.java b/javatools/src/main/java/org/xvm/compiler/ast/VariableDeclarationStatement.java index ae7cc3db68..fc62665634 100644 --- a/javatools/src/main/java/org/xvm/compiler/ast/VariableDeclarationStatement.java +++ b/javatools/src/main/java/org/xvm/compiler/ast/VariableDeclarationStatement.java @@ -24,8 +24,11 @@ import org.xvm.asm.op.Var_DN; import org.xvm.asm.op.Var_N; +import org.xvm.compiler.Compiler; import org.xvm.compiler.Token; +import org.xvm.util.Severity; + /** * A variable declaration statement specifies a type and a simple name for a variable. @@ -176,8 +179,8 @@ protected Statement validateImpl(Context ctx, ErrorListener errs) // create the register TypeConstant typeVar = exprNew.ensureTypeConstant(ctx, errs). removeAutoNarrowing().normalizeParameters(); - m_reg = ctx.createRegister(typeVar, getName()); - ctx.registerVar(name, m_reg, errs); + Register reg = m_reg = ctx.createRegister(typeVar, getName()); + ctx.registerVar(name, reg, errs); if (exprNew instanceof AnnotatedTypeExpression exprAnnoType) { @@ -190,7 +193,7 @@ protected Statement validateImpl(Context ctx, ErrorListener errs) if (exprAnnoType.isInjected()) { ctx.markVarWrite(name, false, errs); - m_reg.markEffectivelyFinal(); + reg.markEffectivelyFinal(); } boolean fVar = exprAnnoType.isVar(); @@ -199,6 +202,9 @@ protected Statement validateImpl(Context ctx, ErrorListener errs) boolean fInflate = false; TypeConstant typeReg = pool.ensureParameterizedTypeConstant( fVar ? pool.typeVar() : pool.typeRef(), typeVar); + int ixFinal = -1; // for error reporting only + int ixVolatile = -1; + for (int i = cRefAnnos - 1; i >= 0; --i) { AnnotationExpression exprAnno = listRefAnnos.get(i); @@ -209,8 +215,8 @@ protected Statement validateImpl(Context ctx, ErrorListener errs) // don't inflate @Final or @Unassigned if (clzAnno.equals(pool.clzFinal())) { - m_reg.markFinal(); - fInflate = true; + reg.markFinal(); + ixFinal = i; } else if (clzAnno.equals(pool.clzUnassigned())) { @@ -230,15 +236,26 @@ else if (clzAnno.equals(pool.clzFuture())) } typeReg = pool.ensureAnnotatedTypeConstant(typeReg, anno); fConst &= exprAnno.isConstant(); + + if (ixVolatile < 0 && typeReg.isA(pool.clzVolatile().getType())) + { + ixVolatile = i; + } } if (fUnassigned) { - m_reg.markAllowUnassigned(); + reg.markAllowUnassigned(); } if (fInflate) { - m_reg.specifyRegType(typeReg); + reg.specifyRegType(typeReg); + + if (ixFinal >= 0 && ixVolatile >= 0) + { + log(errs, Severity.ERROR, Compiler.INVALID_ANNOTATIONS_COMBO, + listRefAnnos.get(ixFinal), listRefAnnos.get(ixVolatile)); + } } m_fConstAnno = fConst; } diff --git a/javatools/src/main/resources/errors.properties b/javatools/src/main/resources/errors.properties index 1747a6b295..605c85ac20 100644 --- a/javatools/src/main/resources/errors.properties +++ b/javatools/src/main/resources/errors.properties @@ -240,6 +240,8 @@ COMPILER-193 = The "using" clause must specify an implementation of the Resource COMPILER-194 = The delegation target must be a property. COMPILER-195 = A super method indicated by @Override cannot be found. COMPILER-196 = A "try" must have either "catch" or "finally" block. +COMPILER-197 = Attempt to mutate the captured variable "{0}". (If intended, add the "@Volatile" annotation.) +COMPILER-198 = Invalid annotation combination: "{0}" and "{1}" are incompatible. COMPILER-NI = "{0}" is not yet implemented. VERIFY-01 = Unknown fatal verifier error: "{0}". diff --git a/javatools_bridge/src/main/x/_native/io/RTChannel.x b/javatools_bridge/src/main/x/_native/io/RTChannel.x index ed5a54d517..01e60e8493 100644 --- a/javatools_bridge/src/main/x/_native/io/RTChannel.x +++ b/javatools_bridge/src/main/x/_native/io/RTChannel.x @@ -243,8 +243,8 @@ service RTChannel(RawChannel rawChannel) // buffers to carry the data-to-write, so that it can bulk up the writes as much as // it can @Future Int pendingResult; - Int remain = size; - Int total = 0; + Int remain = size; + @Volatile Int total = 0; do { Byte[]|Int result = rawChannel.allocate(internal=True); if (result.is(Int)) { diff --git a/lib_ecstasy/src/main/resources/implicit.x b/lib_ecstasy/src/main/resources/implicit.x index 9893003e4c..51f459d118 100644 --- a/lib_ecstasy/src/main/resources/implicit.x +++ b/lib_ecstasy/src/main/resources/implicit.x @@ -60,6 +60,7 @@ import ecstasy.annotations.SoftVar as Soft; import ecstasy.annotations.Synchronized; import ecstasy.annotations.Test; import ecstasy.annotations.Transient; +import ecstasy.annotations.VolatileVar as Volatile; import ecstasy.annotations.WatchVar as Watch; import ecstasy.annotations.WeakVar as Weak; diff --git a/lib_ecstasy/src/main/x/ecstasy/Appender.x b/lib_ecstasy/src/main/x/ecstasy/Appender.x index 0906ae2a2c..9ae07a3478 100644 --- a/lib_ecstasy/src/main/x/ecstasy/Appender.x +++ b/lib_ecstasy/src/main/x/ecstasy/Appender.x @@ -39,7 +39,7 @@ interface Appender { */ @Concurrent Appender addAll(Iterator iter) { - var result = this; + @Volatile Appender result = this; iter.forEach(e -> {result = result.add(e);}); return result; } @@ -60,4 +60,4 @@ interface Appender { Appender ensureCapacity(Int count) { return this; } -} +} \ No newline at end of file diff --git a/lib_ecstasy/src/main/x/ecstasy/annotations/AtomicVar.x b/lib_ecstasy/src/main/x/ecstasy/annotations/AtomicVar.x index 49a46e6070..d6f03abbc7 100644 --- a/lib_ecstasy/src/main/x/ecstasy/annotations/AtomicVar.x +++ b/lib_ecstasy/src/main/x/ecstasy/annotations/AtomicVar.x @@ -60,7 +60,7 @@ * } */ mixin AtomicVar - into Var + extends VolatileVar incorporates conditional AtomicIntNumber { /** * Atomically replace the referent for this variable reference. @@ -109,4 +109,4 @@ mixin AtomicVar return True, curValue; } } -} +} \ No newline at end of file diff --git a/lib_ecstasy/src/main/x/ecstasy/annotations/FutureVar.x b/lib_ecstasy/src/main/x/ecstasy/annotations/FutureVar.x index c9d6f71e05..b9678b3216 100644 --- a/lib_ecstasy/src/main/x/ecstasy/annotations/FutureVar.x +++ b/lib_ecstasy/src/main/x/ecstasy/annotations/FutureVar.x @@ -46,7 +46,7 @@ * be modified once it is set. */ mixin FutureVar - into Var + extends VolatileVar implements Closeable { /** * Future completion status: @@ -263,7 +263,7 @@ mixin FutureVar * exception. */ FutureVar! orAny(FutureVar![] others = []) { - FutureVar result = this; + @Volatile FutureVar result = this; others.iterator().forEach(other -> {result = result.or(other);}); return result; } @@ -810,4 +810,4 @@ mixin FutureVar } } } -} +} \ No newline at end of file diff --git a/lib_ecstasy/src/main/x/ecstasy/annotations/LazyVar.x b/lib_ecstasy/src/main/x/ecstasy/annotations/LazyVar.x index 1912294287..8f743b3b7b 100644 --- a/lib_ecstasy/src/main/x/ecstasy/annotations/LazyVar.x +++ b/lib_ecstasy/src/main/x/ecstasy/annotations/LazyVar.x @@ -30,7 +30,7 @@ * provided.) */ mixin LazyVar(function Referent ()? calculate = Null) - into Var { + extends VolatileVar { private function Referent ()? calculate; diff --git a/lib_ecstasy/src/main/x/ecstasy/annotations/VolatileVar.x b/lib_ecstasy/src/main/x/ecstasy/annotations/VolatileVar.x new file mode 100644 index 0000000000..4f6ec20af0 --- /dev/null +++ b/lib_ecstasy/src/main/x/ecstasy/annotations/VolatileVar.x @@ -0,0 +1,6 @@ +/** + * TODO + */ +mixin VolatileVar + into Var { +} \ No newline at end of file diff --git a/lib_ecstasy/src/main/x/ecstasy/collections/Collection.x b/lib_ecstasy/src/main/x/ecstasy/collections/Collection.x index c27bd36db6..f73219e3d7 100644 --- a/lib_ecstasy/src/main/x/ecstasy/collections/Collection.x +++ b/lib_ecstasy/src/main/x/ecstasy/collections/Collection.x @@ -555,7 +555,7 @@ interface Collection @Concurrent Result reduce(Result initial, function Result(Result, Element) accumulate) { - Result result = initial; + @Volatile Result result = initial; forEach(e -> { result = accumulate(result, e); }); @@ -884,7 +884,7 @@ interface Collection @Override @Concurrent Collection addAll(Iterator iter) { - Collection collection = this; + @Volatile Collection collection = this; iter.forEach(value -> { collection = collection.add(value); }); @@ -950,7 +950,7 @@ interface Collection // this naive implementation is likely to be overridden in cases where optimizations can be // made with knowledge of either this collection and/or the passed in values, for example // if both are ordered; it must obviously be overridden for non-mutable collections - Collection result = this; + @Volatile Collection result = this; values.iterator().forEach(value -> { result = result.remove(value); }); @@ -997,7 +997,7 @@ interface Collection */ @Concurrent (Collection, Int) removeAll(function Boolean (Element) shouldRemove) { - Element[]? values = Null; + @Volatile Element[]? values = Null; forEach(value -> { if (shouldRemove(value)) { values = (values ?: new Element[]) + value; diff --git a/lib_ecstasy/src/main/x/ecstasy/collections/Map.x b/lib_ecstasy/src/main/x/ecstasy/collections/Map.x index 2ef5e263e8..e5d902a109 100644 --- a/lib_ecstasy/src/main/x/ecstasy/collections/Map.x +++ b/lib_ecstasy/src/main/x/ecstasy/collections/Map.x @@ -324,7 +324,7 @@ interface Map */ @Concurrent Map putAll(Map! that) { - Map result = this; + @Volatile Map result = this; that.entries.forEach(entry -> { result = result.put(entry.key, entry.value); }); @@ -426,7 +426,7 @@ interface Map Map clear() { // this method should be overridden by any class that has a more efficient implementation // available - Map result = this; + @Volatile Map result = this; if (inPlace) { keys.clear(); } else { diff --git a/lib_ecstasy/src/main/x/ecstasy/collections/deferred/DeferredCollection.x b/lib_ecstasy/src/main/x/ecstasy/collections/deferred/DeferredCollection.x index e429b516da..770de1ce0b 100644 --- a/lib_ecstasy/src/main/x/ecstasy/collections/deferred/DeferredCollection.x +++ b/lib_ecstasy/src/main/x/ecstasy/collections/deferred/DeferredCollection.x @@ -289,7 +289,7 @@ // this is the default implementation from Collection; if we super(), we would instead go // go via delegation to reified, and we are trying not to reify (at least not the first time // through) - Result result = initial; + @Volatile Result result = initial; forEach(e -> { result = accumulate(result, e); }); diff --git a/lib_json/src/main/x/json/Parser.x b/lib_json/src/main/x/json/Parser.x index 879cfdf84c..fca3c6d6dc 100644 --- a/lib_json/src/main/x/json/Parser.x +++ b/lib_json/src/main/x/json/Parser.x @@ -557,7 +557,7 @@ class Parser ListMap map = new ListMap(); expect(ObjectEnter); if (!match(ObjectExit)) { - Set? dups = Null; + @Volatile Set? dups = Null; do { String name = expect(StrVal).value.as(String); Token sep = expect(Colon); diff --git a/lib_net/src/main/x/net/UriTemplate.x b/lib_net/src/main/x/net/UriTemplate.x index 24ff6a3b55..412000ab6d 100644 --- a/lib_net/src/main/x/net/UriTemplate.x +++ b/lib_net/src/main/x/net/UriTemplate.x @@ -324,7 +324,7 @@ const UriTemplate { @Override Appender expand(Appender buf, Lookup values) { - Boolean first = True; + @Volatile Boolean first = True; function void() checkComma = () -> { // multiple defined variables are comma delimited (i.e. ignore undefined) if (first) { diff --git a/lib_xenia/src/main/x/xenia/Catalog.x b/lib_xenia/src/main/x/xenia/Catalog.x index 7b3d042a38..5da9c54b0b 100644 --- a/lib_xenia/src/main/x/xenia/Catalog.x +++ b/lib_xenia/src/main/x/xenia/Catalog.x @@ -309,8 +309,8 @@ const Catalog(WebApp webApp, String systemPath, WebServiceInfo[] services, Class classInfos += new ClassInfo(child.as(Class), path); // scan classes inside the WebService class - Collection childTypes = child.PrivateType.childTypes.values; - Class[] childClasses = new Class[]; + Collection childTypes = child.PrivateType.childTypes.values; + @Volatile Class[] childClasses = new Class[]; childTypes.forEach(t -> { if (Class c := t.fromClass()) { childClasses += c; diff --git a/manualTests/src/main/x/TestSimple.x b/manualTests/src/main/x/TestSimple.x index b236fbf590..12bb10fc6c 100644 --- a/manualTests/src/main/x/TestSimple.x +++ b/manualTests/src/main/x/TestSimple.x @@ -2,14 +2,24 @@ module TestSimple { @Inject Console console; void run() { - @Custom Int i = 0; // this used to blow up the run-time - while (i < 5) { - function Int() f = () -> ++i; - console.print("result=" + f()); + function void ()[] prints = []; + + { + Int i; + for (i = 0; i < 3; i++) { + prints += () -> console.print($"{i=}"); + } + } + + { + @Volatile Int i; + for (i = 0; i < 3; i++) { + prints += () -> console.print($"{i=}"); + } } - } - mixin Custom - into Var { + for (val print : prints) { + print(); + } } } \ No newline at end of file diff --git a/manualTests/src/main/x/lambda.x b/manualTests/src/main/x/lambda.x index 2ddf54f6cd..9b29d4bb12 100644 --- a/manualTests/src/main/x/lambda.x +++ b/manualTests/src/main/x/lambda.x @@ -59,15 +59,14 @@ module TestLambda { void testVarCapture() { console.print("\n** testVarCapture()"); - Int i = 0; + @Volatile Int i = 0; while (i < 5) { function Int() f = () -> ++i; console.print("result=" + f()); - // console.print("i=" + i + ", result=" + f() + ", i=" + i); } // test for the capture of an unassigned variable - Int j; + @Volatile Int j; function void() f2 = () -> {j = ++i;}; f2(); console.print("j=" + &j.get()); @@ -76,7 +75,7 @@ module TestLambda { void testComplexCapture() { console.print("\n** testComplexCapture()"); - Int i = 0; + @Volatile Int i = 0; while (i < 5) { function Int() f1 = () -> i; console.print("result=" + f1()); // initially would appear to be "Int", but must be "Ref" diff --git a/manualTests/src/main/x/literals.x b/manualTests/src/main/x/literals.x index 881d77b3d9..58cc4ce9cc 100644 --- a/manualTests/src/main/x/literals.x +++ b/manualTests/src/main/x/literals.x @@ -25,8 +25,8 @@ module TestLiterals { console.print("\n** testFactors()"); - String s = ""; - Int n = 0; + @Volatile String s = ""; + @Volatile Int n = 0; val show = () -> { console.print($"{s} == {n}"); diff --git a/manualTests/src/main/x/maps.x b/manualTests/src/main/x/maps.x index 67068f4b05..4d9866b736 100644 --- a/manualTests/src/main/x/maps.x +++ b/manualTests/src/main/x/maps.x @@ -447,7 +447,7 @@ module TestMaps { console.print($"\n** testConcurrentProcess({&map.actualClass.name})"); map.put(0, 0); - Int count = 0; + @Volatile Int count = 0; // run a long running blocking processor in the background map.process^(0, e -> { diff --git a/manualTests/src/main/x/misc.x b/manualTests/src/main/x/misc.x index cfc03aac28..69be732e89 100644 --- a/manualTests/src/main/x/misc.x +++ b/manualTests/src/main/x/misc.x @@ -625,14 +625,14 @@ module TestMisc { // tuple { - Int x = 3; + @Volatile Int x = 3; function Int() fn = () -> (x, ++x)[0]; assert fn() == 3 as "tuple side-effect"; } // invoke { - Int x = 3; + @Volatile Int x = 3; function Int() fn = () -> x.minOf(++x); assert fn() == 3 as "invoke side-effect"; } @@ -641,56 +641,56 @@ module TestMisc { { static const Point(Int x, Int y); - Int x = 3; + @Volatile Int x = 3; function Int() fn = () -> (new Point(x, ++x)).x; assert fn() == 3 as "new side-effect"; } // cmp { - Int x = 3; + @Volatile Int x = 3; function Boolean() fn = () -> (x < ++x); assert fn() as "cmp side-effect"; } // cmp2 { - Int x = 3; + @Volatile Int x = 3; function Boolean() fn = () -> (++x < ++x); assert fn() as "cmp2 side-effect"; } // cmp chain { - Int x = 3; + @Volatile Int x = 3; function Boolean() fn = () -> x <= 3 < ++x; assert fn() as "cmpChain side-effect"; } // relOp { - Int x = 3; + @Volatile Int x = 3; function Int() fn = () -> x + ++x; assert fn() == 7 as "relOp side-effect"; } // list { - Int x = 3; + @Volatile Int x = 3; function Int() fn = () -> [x, ++x, ++x][0]; assert fn() == 3 as "list side-effect"; } // map { - Int x = 3; + @Volatile Int x = 3; function Int?() fn = () -> Map:["a"=x, "b"=++x, "c"=++x].getOrNull("a"); assert fn() == 3 as "map side-effect"; } // unpack { - Int x = 3; + @Volatile Int x = 3; function (Int, Int)() fn = () -> (x, ++x); assert fn() == 3 as "unpacked side-effect"; } diff --git a/manualTests/src/main/x/numbers.x b/manualTests/src/main/x/numbers.x index 0131f77a4a..1f3d43b511 100644 --- a/manualTests/src/main/x/numbers.x +++ b/manualTests/src/main/x/numbers.x @@ -321,16 +321,16 @@ module TestNumbers { Partition[] partitions = new Partition[10](i -> new Partition(i)); - val finishSum = sum.finalAggregator.init(); - Int remainSum = partitions.size; - val finishAvg = avg.finalAggregator.init(); - Int remainAvg = partitions.size; - val finishMin = min.finalAggregator.init(); - Int remainMin = partitions.size; - val finishMax = max.finalAggregator.init(); - Int remainMax = partitions.size; - val finishMMx = mmx.finalAggregator.init(); - Int remainMMx = partitions.size; + @Volatile val finishSum = sum.finalAggregator.init(); + @Volatile Int remainSum = partitions.size; + @Volatile val finishAvg = avg.finalAggregator.init(); + @Volatile Int remainAvg = partitions.size; + @Volatile val finishMin = min.finalAggregator.init(); + @Volatile Int remainMin = partitions.size; + @Volatile val finishMax = max.finalAggregator.init(); + @Volatile Int remainMax = partitions.size; + @Volatile val finishMMx = mmx.finalAggregator.init(); + @Volatile Int remainMMx = partitions.size; Loop: for (Partition partition : partitions) { @Future sum.Partial pendingSum = partition.exec(sum); diff --git a/manualTests/src/main/x/services.x b/manualTests/src/main/x/services.x index 37685f42a9..f28b1d0fd1 100644 --- a/manualTests/src/main/x/services.x +++ b/manualTests/src/main/x/services.x @@ -65,8 +65,8 @@ module TestServices { } } catch (TimedOut e) {} - Int responded = 0; - Int count = 5; + @Volatile Int responded = 0; + Int count = 5; for (Int i : 0 ..< count) { console.print($"{tag()} calling service future-style: {i}"); @Future Int result = svc.calcSomethingBig(Duration.ofSeconds(i));