diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs index 5822db9f2f..7ccd64acd2 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs @@ -22,7 +22,7 @@ internal class BuiltinFunctionsCore // included into the BuiltinFunctionsLibrary library. Examples: Set, Join. internal static readonly IReadOnlyCollection OtherKnownFunctions = new HashSet() { - "Assert", "Back", "Choices", "ClearData", "Concurrent", "Confirm", "Copy", "DataSourceInfo", "Defaults", "Disable", "Distinct", "Download", "EditForm", "Enable", "Errors", "Exit", + "Assert", "Back", "Choices", "ClearData", "Concurrent", "Confirm", "Copy", "DataSourceInfo", "Defaults", "Disable", "Download", "EditForm", "Enable", "Errors", "Exit", "GroupBy", "HashTags", "IsMatch", "IsType", "Join", "JSON", "Launch", "LoadData", "Match", "MatchAll", "Navigate", "NewForm", "Notify", "PDF", "Param", "Pending", "Print", "ReadNFC", "RecordInfo", "Relate", "RemoveAll", "RemoveIf", "RequestHide", "Reset", "ResetForm", "Revert", "SaveData", "ScanBarcode", "Select", "SetFocus", "SetProperty", "ShowColumns", "State", "SubmitForm", "TraceValue", "Ungroup", "Unrelate", "Update", "UpdateContext", "UpdateIf", "User", "Validate", "ValidateRecord", "ViewForm", @@ -102,7 +102,8 @@ internal class BuiltinFunctionsCore public static readonly TexlFunction Dec2Hex = _library.Add(new Dec2HexFunction()); public static readonly TexlFunction Dec2HexT = _library.Add(new Dec2HexTFunction()); public static readonly TexlFunction Degrees = _library.Add(new DegreesFunction()); - public static readonly TexlFunction DegreesT = _library.Add(new DegreesTableFunction()); + public static readonly TexlFunction DegreesT = _library.Add(new DegreesTableFunction()); + public static readonly TexlFunction Distinct = _library.Add(new DistinctFunction()); public static readonly TexlFunction DropColumns = _library.Add(new DropColumnsFunction()); public static readonly TexlFunction EDate = _library.Add(new EDateFunction()); public static readonly TexlFunction EOMonth = _library.Add(new EOMonthFunction()); diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Distinct.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Distinct.cs index eb0ed27ff4..8d035a0550 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Distinct.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Distinct.cs @@ -8,6 +8,7 @@ using Microsoft.PowerFx.Core.Binding; using Microsoft.PowerFx.Core.Binding.BindInfo; using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core.Errors; using Microsoft.PowerFx.Core.Functions; using Microsoft.PowerFx.Core.Functions.Delegation; using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata; @@ -31,7 +32,7 @@ internal sealed class DistinctFunction : FunctionWithTableInput public DistinctFunction() : base("Distinct", TexlStrings.AboutDistinct, FunctionCategories.Table, DType.EmptyTable, 0x02, 2, 2, DType.EmptyTable) { - ScopeInfo = new FunctionScopeInfo(this); + ScopeInfo = new FunctionScopeInfo(this, usesAllFieldsInScope: false); } public override IEnumerable GetSignatures() @@ -53,7 +54,24 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp var exprType = argTypes[1]; - returnType = DType.CreateTable(new TypedName(exprType, GetOneColumnTableResultName(context.Features))); + // Restricted supported types + if (context.Features.PowerFxV1CompatibilityRules && !(exprType == DType.String || + exprType == DType.Number || + exprType == DType.Decimal || + exprType == DType.Boolean || + exprType == DType.Time || + exprType == DType.OptionSetValue || + exprType == DType.DateTime || + exprType == DType.Date || + exprType == DType.Guid)) + { + errors.EnsureError(DocumentErrorSeverity.Severe, args[1], TexlStrings.ErrNeedPrimitive); + fValid = false; + } + else + { + returnType = DType.CreateTable(new TypedName(exprType, GetOneColumnTableResultName(context.Features))); + } return fValid; } diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs index ab580f2dd0..0e93626281 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs @@ -22,12 +22,6 @@ namespace Microsoft.PowerFx.Functions { internal static partial class Library { - /// - /// This isn't part of since PA has different implementation of - /// Texl Instance of . - /// - public static readonly TexlFunction DistinctInterpreterFunction = new DistinctFunction(); - internal static readonly DateTime _epoch = new DateTime(1899, 12, 30, 0, 0, 0, 0); // Helper to get a service or fallback to a default if the service is missing. @@ -600,9 +594,9 @@ static Library() targetFunction: Decimal_UO) }, { - DistinctInterpreterFunction, + BuiltinFunctionsCore.Distinct, StandardErrorHandlingAsync( - DistinctInterpreterFunction.Name, + BuiltinFunctionsCore.Distinct.Name, expandArguments: NoArgExpansion, replaceBlankValues: DoNotReplaceBlank, checkRuntimeTypes: ExactSequence( diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt index 6de431c500..98db0873cf 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt @@ -90,13 +90,3 @@ Table({Value:1},{Value:2},{Value:3},{Value:4}) >> Distinct([GUID("c203b79b-b985-42f0-b523-c10eb64387c6"), GUID("c203b79b-b985-42f0-b523-c10eb64387c6")], Value) Table({Value:GUID("c203b79b-b985-42f0-b523-c10eb64387c6")}) - ->> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Blank()) -Table({Value:Blank()}) - -// Distinct only supports Primitive types. ->> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Table({test: a * 2})) -Error({Kind:ErrorKind.InvalidArgument}) - ->> Distinct(Table({x:1,y:2},{x:10,y:2}), ThisRecord) -Error({Kind:ErrorKind.InvalidArgument}) \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt index b6a94c5445..e0a4e43c85 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt @@ -7,3 +7,13 @@ Errors: Error 9-16: Invalid argument type.|Error 0-8: The function 'Distinct' ha // Untyped blanks are not allowed in the argument that defines the row scope >> Distinct(Error("error"), true) Errors: Error 9-23: Invalid argument type.|Error 0-8: The function 'Distinct' has some invalid arguments. + +>> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Blank()) +Errors: Error 47-54: Expected a text, numeric, boolean, or date/time value.|Error 0-8: The function 'Distinct' has some invalid arguments. + +// Distinct only supports Primitive types. +>> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Table({test: a * 2})) +Errors: Error 47-67: Expected a text, numeric, boolean, or date/time value.|Error 0-8: The function 'Distinct' has some invalid arguments. + +>> Distinct(Table({x:1,y:2},{x:10,y:2}), ThisRecord) +Errors: Error 38-48: Expected a text, numeric, boolean, or date/time value.|Error 0-8: The function 'Distinct' has some invalid arguments. diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt index 1cf35a6cd9..4ebe11c1c9 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt @@ -7,3 +7,13 @@ Blank() // Legacy behavior: untyped blanks are allowed in the argument that defines the row scope >> Distinct(Error("error"), true) Error({Kind:ErrorKind.Custom}) + +>> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Blank()) +Table({Value:Blank()}) + +// Distinct only supports Primitive types. +>> Distinct(Table({a:1,b:2},{a:3,b:4},{a:5,b:6}), Table({test: a * 2})) +Error({Kind:ErrorKind.InvalidArgument}) + +>> Distinct(Table({x:1,y:2},{x:10,y:2}), ThisRecord) +Error({Kind:ErrorKind.InvalidArgument}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs index 69196a9c9d..62fa8c4999 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs @@ -882,6 +882,45 @@ public void TexlFunctionTypeSemanticsFirstN() symbol); } + [Theory] + [InlineData("Distinct(Table, A)", "*[A:n, B:b, C:s]", "*[Value:n]")] + [InlineData("Distinct(Table, B)", "*[A:n, B:b, C:s]", "*[Value:b]")] + [InlineData("Distinct(Table, C)", "*[A:n, B:b, C:s]", "*[Value:s]")] + [InlineData("Distinct(Table, \"hello\")", "*[A:n, B:b, C:s]", "*[Value:s]")] + [InlineData("Distinct(Table, A * 2 / 3)", "*[A:n, B:b, C:s]", "*[Value:n]")] + [InlineData("Distinct(Table, REC)", "*[A:n, REC:![b:n], C:s]", "*[Value:![b:n]]", true)] + [InlineData("Distinct(Table, TBL)", "*[A:n, TBL:*[b:n], C:s]", "*[Value:*[b:n]]", true)] + [InlineData("Distinct(Table, Date)", "*[Date:D, DateTime:d, Time:T]", "*[Value:D]")] + [InlineData("Distinct(Table, DateTime)", "*[Date:D, DateTime:d, Time:T]", "*[Value:d]")] + [InlineData("Distinct(Table, Time)", "*[Date:D, DateTime:d, Time:T]", "*[Value:T]")] + [InlineData("Distinct(Table, G)", "*[G:g, OS:l]", "*[Value:g]")] + [InlineData("Distinct(Table, OS)", "*[G:g, OS:l]", "*[Value:l]")] + [InlineData("Distinct(Table, Untyped)", "*[Untyped:O]", "*[Value:O]", true)] + public void TexlFunctionTypeSemanticsDistinct(string expression, string tableType, string expectedResult, bool failsForPFxV1 = false) + { + foreach (var usePFxV1 in new[] { false, true }) + { + var symbolTable = new SymbolTable(); + symbolTable.AddVariable("Table", new TableType(TestUtils.DT(tableType))); + + var features = new Features + { + PowerFxV1CompatibilityRules = usePFxV1, + ConsistentOneColumnTableResult = true, + }; + + var expectedDType = TestUtils.DT(expectedResult); + if (!usePFxV1 || !failsForPFxV1) + { + TestSimpleBindingSuccess(expression, expectedDType, symbolTable: symbolTable, features: features); + } + else + { + TestBindingErrors(expression, TestUtils.DT("*[]"), symbolTable: symbolTable, features: features); + } + } + } + [Fact] public void TexlFunctionTypeSemanticsIf() { diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyTests.cs index 06d39006e5..db4ae901ee 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyTests.cs @@ -140,7 +140,6 @@ private ReadOnlySymbolTable GetSymbols() customSymbols.AddFunction(new SummarizeFunction()); customSymbols.AddFunction(new RecalcEngineSetFunction()); customSymbols.AddFunction(new RemoveFunction()); - customSymbols.AddFunction(Library.DistinctInterpreterFunction); customSymbols.AddVariable("local", localType, mutable: true); customSymbols.AddVariable("remote", remoteType, mutable: true); customSymbols.AddVariable("simple1", simple1Type, mutable: true); @@ -235,6 +234,7 @@ public void DepedencyScanFunctionTests() "Average", "Concat", "CountIf", + "Distinct", "Filter", "ForAll", "IfError", diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs index 87ec7f2f18..f1c923d55c 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs @@ -93,7 +93,7 @@ public void TestSuggestOptionSets(string expression, params string[] expectedSug // Test with display names. [Theory] - [InlineData("Dis|", "DisplayOpt", "DisplayRowScope")] // Match to row scope + [InlineData("Dis|", "DisplayOpt", "DisplayRowScope", "Distinct")] // Match to row scope public void TestSuggestOptionSetsDisplayName(string expression, params string[] expectedSuggestions) { var config = PowerFxConfig.BuildWithEnumStore(new EnumStoreBuilder(), new TexlFunctionSet());