From dec124ed8649a395b4d3764525861feba20b6977 Mon Sep 17 00:00:00 2001 From: Carlos Figueira Date: Wed, 24 May 2023 15:22:20 -0700 Subject: [PATCH] Allow Void expressions to be used within ForAll (#1527) If a ForAll expression is used within a context where its result is not needed, then it should be able to use void expressions, like in the example below. This change allows it, making the entire result of the ForAll expression a void result. ForAll( Sequence(4), If( Mod(Value, 2) = 1, Patch( table, Index(table, Value), { IsOdd: true } ), // this returns a record, incompatible with tables Collect( table, { Value: Value, IsOdd: false }, { Value: Value + 1, IsOdd: true } // this returns a table ) ) --- .../Texl/Builtins/ForAll.cs | 14 +++++++++++-- .../Functions/Library.cs | 7 ++++++- .../Functions/LibraryUntypedObject.cs | 7 ++++++- .../ExpressionTestCases/ForAll.txt | 20 +++++++++++++++++++ .../Microsoft.PowerFx.Core.Tests/TexlTests.cs | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ForAll.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ForAll.cs index ac0669e560..f50c22724f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ForAll.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ForAll.cs @@ -46,7 +46,8 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp Contracts.Assert(args.Length == argTypes.Length); Contracts.AssertValue(errors); - var fArgsValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap); + nodeToCoercedTypeMap = null; + var fArgsValid = CheckType(context, args[0], argTypes[0], ParamTypes[0], errors, ref nodeToCoercedTypeMap); if (argTypes[1].IsRecord) { @@ -56,6 +57,10 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp { returnType = DType.CreateTable(new TypedName(argTypes[1], ColumnName_Value)); } + else if (argTypes[1].IsVoid) + { + returnType = argTypes[1]; + } else { returnType = DType.Error; @@ -104,7 +109,8 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp Contracts.Assert(args.Length == argTypes.Length); Contracts.AssertValue(errors); - var fArgsValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap); + nodeToCoercedTypeMap = null; + var fArgsValid = CheckType(context, args[0], argTypes[0], ParamTypes[0], errors, ref nodeToCoercedTypeMap); if (argTypes[1].IsRecord) { @@ -114,6 +120,10 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp { returnType = DType.CreateTable(new TypedName(argTypes[1], ColumnName_Value)); } + else if (argTypes[1].IsVoid) + { + returnType = argTypes[1]; + } else { returnType = DType.Error; diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs index d5b2dfa345..88cfe44832 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs @@ -2485,7 +2485,12 @@ public static async ValueTask ForAll(EvalVisitor runner, EvalVisit if (errorRows.Any()) { return ErrorValue.Combine(irContext, errorRows); - } + } + + if (irContext.ResultType is Types.Void) + { + return new VoidValue(irContext); + } return new InMemoryTableValue(irContext, StandardTableNodeRecords(irContext, rows.ToArray(), forceSingleColumn: false)); } diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs index 40027d25c3..f60140016a 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs @@ -555,7 +555,12 @@ public static async ValueTask ForAll_UO(EvalVisitor runner, EvalVi if (errorRows.Any()) { return ErrorValue.Combine(irContext, errorRows); - } + } + + if (irContext.ResultType is Types.Void) + { + return new VoidValue(irContext); + } return new InMemoryTableValue(irContext, StandardTableNodeRecords(irContext, rows.ToArray(), forceSingleColumn: false)); } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt b/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt index 86b4f18783..6980a2f2b8 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt @@ -89,3 +89,23 @@ Table({Value:true}) >> ForAll([Blank()],IsBlank(ThisRecord)) Table({Value:false}) +// ************ ForAll and Void expressions **************** + +>> ForAll([1,2], If(Value = 1, Value * 2, {Result: Value})) +If(true, {test:1}, "Mismatched args (result of the expression can't be used).") + +>> ForAll([1,2,3] As p, Switch(p.Value, 1, {a:1}, 2, [{a:2}], 3, "Hello")) +If(true, {test:1}, "Mismatched args (result of the expression can't be used).") + +>> ForAll(ParseJSON("[1,2]"), If(Value(ThisRecord) = 1, Value(ThisRecord) * 2, {Result: Value(ThisRecord)})) +If(true, {test:1}, "Mismatched args (result of the expression can't be used).") + +>> ForAll(ParseJSON("[1,2,3]"), Switch(Value(ThisRecord), 1, {a:1}, 2, [{a:2}], 3, "Hello")) +If(true, {test:1}, "Mismatched args (result of the expression can't be used).") + +// Errors are returned +>> ForAll([1,2], If(Value = 1, Sqrt(-Value), {Result: Value})) +Error({Kind:ErrorKind.Numeric}) + +>> ForAll(ParseJSON("[""1"",""a""]"), If(Text(ThisRecord) = "1", Value(ThisRecord) * 2, Value(ThisRecord))) +Error({Kind:ErrorKind.InvalidArgument}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs index 73089cf525..0fa01437b6 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs @@ -589,7 +589,7 @@ public void TexlFunctionTypeSemanticsIf() [InlineData("Hour(If(1 < 0, [1], 2))", "n", false)] // ForAll([1,2,3], V) - [InlineData("ForAll([1,2,3], If(1 < 0, [1], 2))", "e", false)] + [InlineData("ForAll([1,2,3], If(1 < 0, [1], 2))", "-", true)] public void TexlFunctionTypeSemanticsIfWithArgumentCoercion(string expression, string expectedType, bool checkSuccess) { var symbol = new SymbolTable();