diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
index 10d9d8ea92..077f733a93 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
@@ -3389,6 +3389,14 @@ public override bool PreVisit(DottedNameNode node)
return true;
}
+ public override bool PreVisit(AsNode node)
+ {
+ Contracts.AssertValue(node);
+
+ _txb.AddVolatileVariables(node.Left, _txb.GetVolatileVariables(node));
+ return true;
+ }
+
public override void PostVisit(DottedNameNode node)
{
AssertValid();
diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs
index b5d4d2a785..09334e3a60 100644
--- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs
+++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs
@@ -6,7 +6,9 @@
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
+using System.Numerics;
using System.Reflection.Metadata;
+using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.PowerFx.Core.App.Controls;
using Microsoft.PowerFx.Core.Binding;
@@ -163,5 +165,90 @@ public void TestHasErrorsInTreeCallNode()
Assert.True(binding.ErrorContainer.HasErrorsInTree(binding.Top));
}
+
+ ///
+ /// Tests that variable volatility propagates to the children of As nodes.
+ ///
+ [Fact]
+ public void TestVolatileVariablesWithForAllAsKeyword()
+ {
+ var config = new PowerFxConfig();
+ config.SymbolTable.AddVariable(
+ "volatileVariable",
+ new KnownRecordType(TestUtils.DT("![DummyField:*[Value:n]]")),
+ mutable: true);
+ config.SymbolTable.AddVariable(
+ "gblTable",
+ new TableType(TestUtils.DT("*[Value:n]")),
+ mutable: true);
+
+ config.AddFunction(new FakeSetFunction());
+
+ var engine = new Engine(config);
+ var parserOptions = new ParserOptions { AllowsSideEffects = true };
+
+ const string expression = @"With(
+ {dummyWith: gblTable},
+ Set(
+ volatileVariable,
+ {DummyField: dummyWith}
+ );
+ ForAll(
+ volatileVariable.DummyField| As currentRecord,
+ currentRecord.Value + 1
+ )
+ )";
+
+ var indices = Regex.Matches(expression, Regex.Escape("|"))
+ .Select(m => m.Index)
+ .ToArray();
+
+ string result = Regex.Replace(expression, Regex.Escape("|"), string.Empty);
+ var checkResult = engine.Check(result, parserOptions);
+ Assert.True(checkResult.IsSuccess);
+
+ var binding = checkResult.Binding;
+
+ // Navigate to the ForAll node
+ foreach (var index in indices)
+ {
+ var node = FindNodeVisitor.Run(checkResult.Binding.Top, index);
+ Assert.True(binding.IsUnliftable(node));
+ }
+ }
+
+ private class FakeSetFunction : BuiltinFunction
+ {
+ public FakeSetFunction()
+ : base(
+ "Set",
+ _ => "Mocks the set function",
+ FunctionCategories.Behavior,
+ DType.Boolean,
+ 0,
+ 2,
+ 2)
+ {
+ }
+
+ public override bool IsSelfContained => false;
+
+ public override IEnumerable GetSignatures() =>
+ Enumerable.Empty();
+
+ public override IEnumerable GetIdentifierOfModifiedValue(
+ TexlNode[] args,
+ out TexlNode identifierNode)
+ {
+ if (args.FirstOrDefault() is FirstNameNode { Ident: var ident } firstNameNode)
+ {
+ identifierNode = firstNameNode;
+ return new List { ident };
+ }
+
+ identifierNode = null;
+ return null;
+ }
+ }
}
}