|
6 | 6 | using System.Globalization; |
7 | 7 | using System.Linq; |
8 | 8 | using System.Linq.Expressions; |
| 9 | +using System.Numerics; |
9 | 10 | using System.Reflection.Metadata; |
| 11 | +using System.Text.RegularExpressions; |
10 | 12 | using Microsoft.CodeAnalysis; |
11 | 13 | using Microsoft.PowerFx.Core.App.Controls; |
12 | 14 | using Microsoft.PowerFx.Core.Binding; |
@@ -163,5 +165,90 @@ public void TestHasErrorsInTreeCallNode() |
163 | 165 |
|
164 | 166 | Assert.True(binding.ErrorContainer.HasErrorsInTree(binding.Top)); |
165 | 167 | } |
| 168 | + |
| 169 | + /// <summary> |
| 170 | + /// Tests that variable volatility propagates to the children of As nodes. |
| 171 | + /// </summary> |
| 172 | + [Fact] |
| 173 | + public void TestVolatileVariablesWithForAllAsKeyword() |
| 174 | + { |
| 175 | + var config = new PowerFxConfig(); |
| 176 | + config.SymbolTable.AddVariable( |
| 177 | + "volatileVariable", |
| 178 | + new KnownRecordType(TestUtils.DT("![DummyField:*[Value:n]]")), |
| 179 | + mutable: true); |
| 180 | + config.SymbolTable.AddVariable( |
| 181 | + "gblTable", |
| 182 | + new TableType(TestUtils.DT("*[Value:n]")), |
| 183 | + mutable: true); |
| 184 | + |
| 185 | + config.AddFunction(new FakeSetFunction()); |
| 186 | + |
| 187 | + var engine = new Engine(config); |
| 188 | + var parserOptions = new ParserOptions { AllowsSideEffects = true }; |
| 189 | + |
| 190 | + const string expression = @"With( |
| 191 | + {dummyWith: gblTable}, |
| 192 | + Set( |
| 193 | + volatileVariable, |
| 194 | + {DummyField: dummyWith} |
| 195 | + ); |
| 196 | + ForAll( |
| 197 | + volatileVariable.DummyField| As currentRecord, |
| 198 | + currentRecord.Value + 1 |
| 199 | + ) |
| 200 | + )"; |
| 201 | + |
| 202 | + var indices = Regex.Matches(expression, Regex.Escape("|")) |
| 203 | + .Select(m => m.Index) |
| 204 | + .ToArray(); |
| 205 | + |
| 206 | + string result = Regex.Replace(expression, Regex.Escape("|"), string.Empty); |
| 207 | + var checkResult = engine.Check(result, parserOptions); |
| 208 | + Assert.True(checkResult.IsSuccess); |
| 209 | + |
| 210 | + var binding = checkResult.Binding; |
| 211 | + |
| 212 | + // Navigate to the ForAll node |
| 213 | + foreach (var index in indices) |
| 214 | + { |
| 215 | + var node = FindNodeVisitor.Run(checkResult.Binding.Top, index); |
| 216 | + Assert.True(binding.IsUnliftable(node)); |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + private class FakeSetFunction : BuiltinFunction |
| 221 | + { |
| 222 | + public FakeSetFunction() |
| 223 | + : base( |
| 224 | + "Set", |
| 225 | + _ => "Mocks the set function", |
| 226 | + FunctionCategories.Behavior, |
| 227 | + DType.Boolean, |
| 228 | + 0, |
| 229 | + 2, |
| 230 | + 2) |
| 231 | + { |
| 232 | + } |
| 233 | + |
| 234 | + public override bool IsSelfContained => false; |
| 235 | + |
| 236 | + public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures() => |
| 237 | + Enumerable.Empty<TexlStrings.StringGetter[]>(); |
| 238 | + |
| 239 | + public override IEnumerable<Identifier> GetIdentifierOfModifiedValue( |
| 240 | + TexlNode[] args, |
| 241 | + out TexlNode identifierNode) |
| 242 | + { |
| 243 | + if (args.FirstOrDefault() is FirstNameNode { Ident: var ident } firstNameNode) |
| 244 | + { |
| 245 | + identifierNode = firstNameNode; |
| 246 | + return new List<Identifier> { ident }; |
| 247 | + } |
| 248 | + |
| 249 | + identifierNode = null; |
| 250 | + return null; |
| 251 | + } |
| 252 | + } |
166 | 253 | } |
167 | 254 | } |
0 commit comments